From c72339a413dba295ddfa692e401657918df5b390 Mon Sep 17 00:00:00 2001
From: vjrj <vjrj@comunes.org>
Date: Wed, 3 May 2023 01:46:41 +0200
Subject: [PATCH] Refactor and Transactions page bloc

---
 .../models}/bottom_nav_cubit.dart             |   0
 lib/data/models/pending_transaction.dart      |  33 +++++
 lib/data/models/pending_transaction.g.dart    | 123 ++++++++++++++++++
 lib/{cubit => data/models}/theme_cubit.dart   |   0
 lib/{cubit => data/models}/theme_state.dart   |   0
 lib/data/models/transactions_bloc.dart        | 103 +++++++++++++++
 lib/data/models/transactions_state.dart       |  13 ++
 lib/ui/screens/fifth_screen.dart              |   4 +-
 lib/ui/screens/first_screen.dart              |   2 +-
 lib/ui/screens/fourth_screen.dart             |   2 +-
 lib/ui/screens/second_screen.dart             |   2 +-
 lib/ui/screens/skeleton_screen.dart           |   2 +-
 lib/ui/screens/third_screen.dart              |   8 +-
 lib/ui/widgets/bottom_nav_bar.dart            |   2 +-
 lib/ui/widgets/first_screen/pay_form.dart     |   4 +-
 lib/ui/widgets/first_screen/theme_card.dart   |   2 +-
 .../fourth_screen/transaction_page.dart       |  33 ++++-
 .../widgets/third_screen/contacts_page.dart   |   2 +-
 pubspec.lock                                  |   2 +-
 pubspec.yaml                                  |   1 +
 20 files changed, 315 insertions(+), 23 deletions(-)
 rename lib/{cubit => data/models}/bottom_nav_cubit.dart (100%)
 create mode 100644 lib/data/models/pending_transaction.dart
 create mode 100644 lib/data/models/pending_transaction.g.dart
 rename lib/{cubit => data/models}/theme_cubit.dart (100%)
 rename lib/{cubit => data/models}/theme_state.dart (100%)
 create mode 100644 lib/data/models/transactions_bloc.dart
 create mode 100644 lib/data/models/transactions_state.dart

diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/data/models/bottom_nav_cubit.dart
similarity index 100%
rename from lib/cubit/bottom_nav_cubit.dart
rename to lib/data/models/bottom_nav_cubit.dart
diff --git a/lib/data/models/pending_transaction.dart b/lib/data/models/pending_transaction.dart
new file mode 100644
index 00000000..648cef9a
--- /dev/null
+++ b/lib/data/models/pending_transaction.dart
@@ -0,0 +1,33 @@
+import 'package:copy_with_extension/copy_with_extension.dart';
+import 'package:equatable/equatable.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+import 'contact.dart';
+
+part 'pending_transaction.g.dart';
+
+@JsonSerializable()
+@CopyWith()
+class PendingTransaction extends Equatable {
+  const PendingTransaction({
+    required this.amount,
+    required this.comment,
+    required this.time,
+    required this.from,
+    required this.to,
+  });
+
+  factory PendingTransaction.fromJson(Map<String, dynamic> json) =>
+      _$PendingTransactionFromJson(json);
+
+  final Contact from;
+  final Contact to;
+  final double amount;
+  final String comment;
+  final DateTime time;
+
+  Map<String, dynamic> toJson() => _$PendingTransactionToJson(this);
+
+  @override
+  List<Object?> get props => <dynamic>[from, to, amount, comment, time];
+}
diff --git a/lib/data/models/pending_transaction.g.dart b/lib/data/models/pending_transaction.g.dart
new file mode 100644
index 00000000..56b16107
--- /dev/null
+++ b/lib/data/models/pending_transaction.g.dart
@@ -0,0 +1,123 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'pending_transaction.dart';
+
+// **************************************************************************
+// CopyWithGenerator
+// **************************************************************************
+
+abstract class _$PendingTransactionCWProxy {
+  PendingTransaction amount(double amount);
+
+  PendingTransaction comment(String comment);
+
+  PendingTransaction time(DateTime time);
+
+  PendingTransaction from(Contact from);
+
+  PendingTransaction to(Contact to);
+
+  /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PendingTransaction(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
+  ///
+  /// Usage
+  /// ```dart
+  /// PendingTransaction(...).copyWith(id: 12, name: "My name")
+  /// ````
+  PendingTransaction call({
+    double? amount,
+    String? comment,
+    DateTime? time,
+    Contact? from,
+    Contact? to,
+  });
+}
+
+/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfPendingTransaction.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfPendingTransaction.copyWith.fieldName(...)`
+class _$PendingTransactionCWProxyImpl implements _$PendingTransactionCWProxy {
+  const _$PendingTransactionCWProxyImpl(this._value);
+
+  final PendingTransaction _value;
+
+  @override
+  PendingTransaction amount(double amount) => this(amount: amount);
+
+  @override
+  PendingTransaction comment(String comment) => this(comment: comment);
+
+  @override
+  PendingTransaction time(DateTime time) => this(time: time);
+
+  @override
+  PendingTransaction from(Contact from) => this(from: from);
+
+  @override
+  PendingTransaction to(Contact to) => this(to: to);
+
+  @override
+
+  /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PendingTransaction(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
+  ///
+  /// Usage
+  /// ```dart
+  /// PendingTransaction(...).copyWith(id: 12, name: "My name")
+  /// ````
+  PendingTransaction call({
+    Object? amount = const $CopyWithPlaceholder(),
+    Object? comment = const $CopyWithPlaceholder(),
+    Object? time = const $CopyWithPlaceholder(),
+    Object? from = const $CopyWithPlaceholder(),
+    Object? to = const $CopyWithPlaceholder(),
+  }) {
+    return PendingTransaction(
+      amount: amount == const $CopyWithPlaceholder() || amount == null
+          ? _value.amount
+          // ignore: cast_nullable_to_non_nullable
+          : amount as double,
+      comment: comment == const $CopyWithPlaceholder() || comment == null
+          ? _value.comment
+          // ignore: cast_nullable_to_non_nullable
+          : comment as String,
+      time: time == const $CopyWithPlaceholder() || time == null
+          ? _value.time
+          // ignore: cast_nullable_to_non_nullable
+          : time as DateTime,
+      from: from == const $CopyWithPlaceholder() || from == null
+          ? _value.from
+          // ignore: cast_nullable_to_non_nullable
+          : from as Contact,
+      to: to == const $CopyWithPlaceholder() || to == null
+          ? _value.to
+          // ignore: cast_nullable_to_non_nullable
+          : to as Contact,
+    );
+  }
+}
+
+extension $PendingTransactionCopyWith on PendingTransaction {
+  /// Returns a callable class that can be used as follows: `instanceOfPendingTransaction.copyWith(...)` or like so:`instanceOfPendingTransaction.copyWith.fieldName(...)`.
+  // ignore: library_private_types_in_public_api
+  _$PendingTransactionCWProxy get copyWith =>
+      _$PendingTransactionCWProxyImpl(this);
+}
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+PendingTransaction _$PendingTransactionFromJson(Map<String, dynamic> json) =>
+    PendingTransaction(
+      amount: (json['amount'] as num).toDouble(),
+      comment: json['comment'] as String,
+      time: DateTime.parse(json['time'] as String),
+      from: Contact.fromJson(json['from'] as Map<String, dynamic>),
+      to: Contact.fromJson(json['to'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$PendingTransactionToJson(PendingTransaction instance) =>
+    <String, dynamic>{
+      'from': instance.from,
+      'to': instance.to,
+      'amount': instance.amount,
+      'comment': instance.comment,
+      'time': instance.time.toIso8601String(),
+    };
diff --git a/lib/cubit/theme_cubit.dart b/lib/data/models/theme_cubit.dart
similarity index 100%
rename from lib/cubit/theme_cubit.dart
rename to lib/data/models/theme_cubit.dart
diff --git a/lib/cubit/theme_state.dart b/lib/data/models/theme_state.dart
similarity index 100%
rename from lib/cubit/theme_state.dart
rename to lib/data/models/theme_state.dart
diff --git a/lib/data/models/transactions_bloc.dart b/lib/data/models/transactions_bloc.dart
new file mode 100644
index 00000000..ae01dd3b
--- /dev/null
+++ b/lib/data/models/transactions_bloc.dart
@@ -0,0 +1,103 @@
+import 'dart:async';
+
+import 'package:rxdart/rxdart.dart';
+
+import 'node_list_cubit.dart';
+import 'transaction.dart';
+import 'transaction_cubit.dart';
+
+part 'transactions_state.dart';
+
+class TransactionsBloc {
+  TransactionsBloc() {
+    _onPageRequest.stream
+        .flatMap(_fetchCharacterSummaryList)
+        .listen(_onNewListingStateController.add)
+        .addTo(_subscriptions);
+
+    _onSearchInputChangedSubject.stream
+        .flatMap((_) => _resetSearch())
+        .listen(_onNewListingStateController.add)
+        .addTo(_subscriptions);
+  }
+
+  late NodeListCubit nodeListCubit;
+  late TransactionsCubit transCubit;
+
+  static const int _pageSize = 20;
+
+  final CompositeSubscription _subscriptions = CompositeSubscription();
+
+  final BehaviorSubject<TransactionsState> _onNewListingStateController =
+  BehaviorSubject<TransactionsState>.seeded(
+    TransactionsState(),
+  );
+
+  Stream<TransactionsState> get onNewListingState =>
+      _onNewListingStateController.stream;
+
+  final StreamController<String?> _onPageRequest = StreamController<String?>();
+
+  Sink<String?> get onPageRequestSink => _onPageRequest.sink;
+
+  final BehaviorSubject<String?> _onSearchInputChangedSubject =
+  BehaviorSubject<String?>.seeded(null);
+
+  Sink<String?> get onSearchInputChangedSink =>
+      _onSearchInputChangedSubject.sink;
+
+  // String? get _searchInputValue => _onSearchInputChangedSubject.value;
+
+  Stream<TransactionsState> _resetSearch() async* {
+    yield TransactionsState();
+    yield* _fetchCharacterSummaryList(null);
+  }
+
+  void init(TransactionsCubit transCubit, NodeListCubit nodeListCubit) {
+    this.transCubit = transCubit;
+    this.nodeListCubit = nodeListCubit;
+  }
+
+  Stream<TransactionsState> _fetchCharacterSummaryList(String? pageKey) async* {
+    final TransactionsState lastListingState =
+        _onNewListingStateController.value;
+    try {
+      /*    final newItems = await RemoteApi.getCharacterList(
+        pageKey,
+        _pageSize,
+        searchTerm: _searchInputValue,
+      );
+*/
+      final List<Transaction> newItems = await transCubit.fetchTransactions(
+          nodeListCubit,
+          cursor: pageKey,
+          pageSize: _pageSize);
+
+      final bool isLastPage = newItems.length < _pageSize;
+      final String? nextPageKey = isLastPage ? null : transCubit.state
+          .endCursor;
+
+      yield TransactionsState(
+        // error: null,
+        nextPageKey: nextPageKey,
+        itemList: <Transaction>[
+          ...lastListingState.itemList ?? <Transaction>[],
+          ...newItems
+        ],
+      );
+    } catch (e) {
+      yield TransactionsState(
+        error: e,
+        nextPageKey: lastListingState.nextPageKey,
+        itemList: lastListingState.itemList,
+      );
+    }
+  }
+
+  void dispose() {
+    _onSearchInputChangedSubject.close();
+    _onNewListingStateController.close();
+    _subscriptions.dispose();
+    _onPageRequest.close();
+  }
+}
diff --git a/lib/data/models/transactions_state.dart b/lib/data/models/transactions_state.dart
new file mode 100644
index 00000000..beb14783
--- /dev/null
+++ b/lib/data/models/transactions_state.dart
@@ -0,0 +1,13 @@
+part of 'transactions_bloc.dart';
+
+class TransactionsState {
+  TransactionsState({
+    this.itemList,
+    this.error,
+    this.nextPageKey,
+  });
+
+  final List<Transaction>? itemList;
+  final dynamic error;
+  final String? nextPageKey;
+}
diff --git a/lib/ui/screens/fifth_screen.dart b/lib/ui/screens/fifth_screen.dart
index a4702622..3d401fc4 100644
--- a/lib/ui/screens/fifth_screen.dart
+++ b/lib/ui/screens/fifth_screen.dart
@@ -4,10 +4,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:pwa_install/pwa_install.dart';
 import 'package:share_plus/share_plus.dart';
 
-import '../../cubit/bottom_nav_cubit.dart';
-import '../../cubit/theme_cubit.dart';
 import '../../data/models/app_cubit.dart';
 import '../../data/models/app_state.dart';
+import '../../data/models/bottom_nav_cubit.dart';
+import '../../data/models/theme_cubit.dart';
 import '../../shared_prefs.dart';
 import '../notification_controller.dart';
 import '../tutorial.dart';
diff --git a/lib/ui/screens/first_screen.dart b/lib/ui/screens/first_screen.dart
index 7a05603f..ea4135e3 100644
--- a/lib/ui/screens/first_screen.dart
+++ b/lib/ui/screens/first_screen.dart
@@ -4,9 +4,9 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:web_browser_detect/web_browser_detect.dart';
 
-import '../../cubit/bottom_nav_cubit.dart';
 import '../../data/models/app_cubit.dart';
 import '../../data/models/app_state.dart';
+import '../../data/models/bottom_nav_cubit.dart';
 import '../../data/models/payment_cubit.dart';
 import '../../data/models/payment_state.dart';
 import '../../data/models/transaction_cubit.dart';
diff --git a/lib/ui/screens/fourth_screen.dart b/lib/ui/screens/fourth_screen.dart
index ee985635..b3eb58d2 100644
--- a/lib/ui/screens/fourth_screen.dart
+++ b/lib/ui/screens/fourth_screen.dart
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../cubit/bottom_nav_cubit.dart';
+import '../../data/models/bottom_nav_cubit.dart';
 import '../tutorial.dart';
 import '../widgets/fourth_screen/fourth_tutorial.dart';
 import '../widgets/fourth_screen/transaction_page.dart';
diff --git a/lib/ui/screens/second_screen.dart b/lib/ui/screens/second_screen.dart
index ebf78b28..6bd36762 100644
--- a/lib/ui/screens/second_screen.dart
+++ b/lib/ui/screens/second_screen.dart
@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../cubit/bottom_nav_cubit.dart';
+import '../../data/models/bottom_nav_cubit.dart';
 import '../tutorial.dart';
 import '../widgets/card_drawer.dart';
 import '../widgets/second_screen/card_terminal.dart';
diff --git a/lib/ui/screens/skeleton_screen.dart b/lib/ui/screens/skeleton_screen.dart
index 8b55b9fb..817ac515 100644
--- a/lib/ui/screens/skeleton_screen.dart
+++ b/lib/ui/screens/skeleton_screen.dart
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../cubit/bottom_nav_cubit.dart';
+import '../../data/models/bottom_nav_cubit.dart';
 import '../widgets/app_bar_gone.dart';
 import '../widgets/bottom_nav_bar.dart';
 import 'fifth_screen.dart';
diff --git a/lib/ui/screens/third_screen.dart b/lib/ui/screens/third_screen.dart
index 04c00866..bb287ffe 100644
--- a/lib/ui/screens/third_screen.dart
+++ b/lib/ui/screens/third_screen.dart
@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../cubit/bottom_nav_cubit.dart';
+import '../../data/models/bottom_nav_cubit.dart';
 import '../../data/models/contact.dart';
 import '../../data/models/contact_cubit.dart';
 import '../../g1/g1_helper.dart';
@@ -27,9 +27,7 @@ class _ThirdScreenState extends State<ThirdScreen> {
   @override
   void initState() {
     tutorial = ThirdTutorial(context);
-    if (context
-        .read<BottomNavCubit>()
-        .state == 2) {
+    if (context.read<BottomNavCubit>().state == 2) {
       Future<void>.delayed(Duration.zero, () => tutorial.showTutorial());
     }
     super.initState();
@@ -46,7 +44,7 @@ class _ThirdScreenState extends State<ThirdScreen> {
               final String? pubKey = await QrManager.qrScan(context);
               if (pubKey != null && validateKey(pubKey)) {
                 final Contact contact =
-                await ContactsCache().getContact(pubKey);
+                    await ContactsCache().getContact(pubKey);
                 if (!mounted) {
                   return;
                 }
diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart
index d7a598b6..f742dcb0 100644
--- a/lib/ui/widgets/bottom_nav_bar.dart
+++ b/lib/ui/widgets/bottom_nav_bar.dart
@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../cubit/bottom_nav_cubit.dart';
+import '../../data/models/bottom_nav_cubit.dart';
 import '../tutorial_keys.dart';
 
 class BottomNavBar extends StatefulWidget {
diff --git a/lib/ui/widgets/first_screen/pay_form.dart b/lib/ui/widgets/first_screen/pay_form.dart
index b4ecf679..8322a62b 100644
--- a/lib/ui/widgets/first_screen/pay_form.dart
+++ b/lib/ui/widgets/first_screen/pay_form.dart
@@ -62,8 +62,8 @@ class _PayFormState extends State<PayForm> {
           fontSize: 16,
         ),
       );
-      final Widget payBtnText = Text(tr('g1_form_pay_send') +
-          (!kReleaseMode ? ' ${state.amount} ${state.comment}' : ''));
+      final Widget payBtnText = Text(
+          tr('g1_form_pay_send') + (!kReleaseMode ? ' ${state.status}' : ''));
       return Form(
         key: _formKey,
         child: Column(
diff --git a/lib/ui/widgets/first_screen/theme_card.dart b/lib/ui/widgets/first_screen/theme_card.dart
index 678f0a9f..16c44e48 100644
--- a/lib/ui/widgets/first_screen/theme_card.dart
+++ b/lib/ui/widgets/first_screen/theme_card.dart
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import '../../../cubit/theme_cubit.dart';
+import '../../../data/models/theme_cubit.dart';
 
 class ThemeCard extends StatelessWidget {
   const ThemeCard({
diff --git a/lib/ui/widgets/fourth_screen/transaction_page.dart b/lib/ui/widgets/fourth_screen/transaction_page.dart
index 05fce950..82c56a41 100644
--- a/lib/ui/widgets/fourth_screen/transaction_page.dart
+++ b/lib/ui/widgets/fourth_screen/transaction_page.dart
@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:backdrop/backdrop.dart';
 import 'package:easy_debounce/easy_throttle.dart';
 import 'package:easy_localization/easy_localization.dart';
@@ -10,8 +12,8 @@ import '../../../data/models/node_list_cubit.dart';
 import '../../../data/models/transaction.dart';
 import '../../../data/models/transaction_balance_state.dart';
 import '../../../data/models/transaction_cubit.dart';
+import '../../../data/models/transactions_bloc.dart';
 import '../../../shared_prefs.dart';
-import '../../logger.dart';
 import '../../tutorial_keys.dart';
 import '../../ui_helpers.dart';
 import 'transaction_chart.dart';
@@ -29,11 +31,11 @@ class _TransactionsAndBalanceWidgetState
     extends State<TransactionsAndBalanceWidget>
     with SingleTickerProviderStateMixin {
   final ScrollController _transScrollController = ScrollController();
-
+  final TransactionsBloc _bloc = TransactionsBloc();
+  late StreamSubscription<TransactionsState> _blocListingStateSubscription;
   late NodeListCubit nodeListCubit;
   late TransactionsCubit transCubit;
   bool isLoading = false;
-  static const int _pageSize = 20;
 
   final PagingController<String?, Transaction> _pagingController =
       PagingController<String?, Transaction>(firstPageKey: null);
@@ -43,13 +45,31 @@ class _TransactionsAndBalanceWidgetState
     // Remove in the future
     transCubit = context.read<TransactionsCubit>();
     nodeListCubit = context.read<NodeListCubit>();
+    _bloc.init(transCubit, nodeListCubit);
+    _pagingController.addPageRequestListener((String? cursor) {
+      _bloc.onPageRequestSink.add(cursor);
+    });
+    // We could've used StreamBuilder, but that would unnecessarily recreate
+    // the entire [PagedSliverGrid] every time the state changes.
+    // Instead, handling the subscription ourselves and updating only the
+    // _pagingController is more efficient.
+    _blocListingStateSubscription =
+        _bloc.onNewListingState.listen((TransactionsState listingState) {
+      _pagingController.value = PagingState<String?, Transaction>(
+        nextPageKey: listingState.nextPageKey,
+        error: listingState.error,
+        itemList: listingState.itemList,
+      );
+    });
+
+    /*
     _pagingController.addPageRequestListener((String? cursor) {
       EasyThrottle.throttle('my-throttler-$cursor', const Duration(seconds: 1),
           () => _fetchPage(cursor),
           onAfter:
               () {} // <-- Optional callback, called after the duration has passed
           );
-    });
+    }); */
     _pagingController.addStatusListener((PagingStatus status) {
       if (status == PagingStatus.subsequentPageError) {
         ScaffoldMessenger.of(context).showSnackBar(
@@ -67,7 +87,7 @@ class _TransactionsAndBalanceWidgetState
     super.initState();
   }
 
-  Future<void> _fetchPage(String? cursor) async {
+/*  Future<void> _fetchPage(String? cursor) async {
     logger('Fetching from transaction page with cursor $cursor');
     try {
       final List<Transaction> newItems = await transCubit.fetchTransactions(
@@ -85,12 +105,13 @@ class _TransactionsAndBalanceWidgetState
     } catch (error) {
       _pagingController.error = error;
     }
-  }
+  }*/
 
   @override
   void dispose() {
     _transScrollController.dispose();
     _pagingController.dispose();
+    _blocListingStateSubscription.cancel();
     super.dispose();
   }
 
diff --git a/lib/ui/widgets/third_screen/contacts_page.dart b/lib/ui/widgets/third_screen/contacts_page.dart
index b23c2362..be4eb126 100644
--- a/lib/ui/widgets/third_screen/contacts_page.dart
+++ b/lib/ui/widgets/third_screen/contacts_page.dart
@@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_slidable/flutter_slidable.dart';
 import 'package:share_plus/share_plus.dart';
 
-import '../../../cubit/bottom_nav_cubit.dart';
+import '../../../data/models/bottom_nav_cubit.dart';
 import '../../../data/models/contact.dart';
 import '../../../data/models/contact_cubit.dart';
 import '../../../data/models/payment_cubit.dart';
diff --git a/pubspec.lock b/pubspec.lock
index ad07e2eb..c5f143a4 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1159,7 +1159,7 @@ packages:
     source: hosted
     version: "0.2.0"
   rxdart:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: rxdart
       sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
diff --git a/pubspec.yaml b/pubspec.yaml
index 24745e3a..2680b08d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -85,6 +85,7 @@ dependencies:
   feedback_sentry: ^2.4.0
   feedback_gitlab: ^2.2.0
   connectivity_wrapper: ^1.1.3
+  rxdart: ^0.27.7
 
 dev_dependencies:
   flutter_test:
-- 
GitLab