diff --git a/lib/data/models/multi_wallet_transaction_cubit.dart b/lib/data/models/multi_wallet_transaction_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..838dfc9d2728a2309a2438f16a87a1d3f59cf031 --- /dev/null +++ b/lib/data/models/multi_wallet_transaction_cubit.dart @@ -0,0 +1,307 @@ +import 'dart:collection'; + +import 'package:flutter/foundation.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:tuple/tuple.dart'; + +import '../../g1/api.dart'; +import '../../g1/currency.dart'; +import '../../g1/g1_helper.dart'; +import '../../g1/transaction_parser.dart'; +import '../../shared_prefs_helper.dart'; +import '../../ui/logger.dart'; +import '../../ui/notification_controller.dart'; +import '../../ui/pay_helper.dart'; +import 'app_cubit.dart'; +import 'contact.dart'; +import 'multi_wallet_transaction_state.dart'; +import 'node.dart'; +import 'node_list_cubit.dart'; +import 'node_type.dart'; +import 'transaction.dart'; +import 'transaction_state.dart'; +import 'transaction_type.dart'; +import 'transactions_bloc.dart'; + +class MultiWalletTransactionCubit + extends HydratedCubit<MultiWalletTransactionState> { + MultiWalletTransactionCubit() + : super(const MultiWalletTransactionState(<String, TransactionState>{})); + + @override + String get storagePrefix => + kIsWeb ? 'MultiWalletTransactionsCubit' : super.storagePrefix; + + @override + MultiWalletTransactionState fromJson(Map<String, dynamic> json) => + MultiWalletTransactionState.fromJson(json); + + @override + Map<String, dynamic> toJson(MultiWalletTransactionState state) => + state.toJson(); + + void addPendingTransaction(Transaction pendingTransaction, {String? key}) { + key = _defKey(key); + final TransactionState currentState = _getStateOfWallet(key); + final List<Transaction> newPendingTransactions = + List<Transaction>.of(currentState.pendingTransactions) + ..add(pendingTransaction); + final TransactionState newState = + currentState.copyWith(pendingTransactions: newPendingTransactions); + _emitState(key, newState); + } + + void _emitState(String key, TransactionState newState) { + final Map<String, TransactionState> newStates = + Map<String, TransactionState>.of(state.map)..[key] = newState; + emit(MultiWalletTransactionState(newStates)); + } + + String _defKey(String? key) { + key = key ?? SharedPreferencesHelper().getPubKey(); + return key; + } + + TransactionState _getStateOfWallet(String key) { + final TransactionState currentState = + state.map[key] ?? TransactionState.emptyState; + return currentState; + } + + void removePendingTransaction(Transaction pendingTransaction, {String? key}) { + key = _defKey(key); + final TransactionState currentState = _getStateOfWallet(key); + final List<Transaction> newPendingTransactions = + List<Transaction>.of(currentState.pendingTransactions) + ..remove(pendingTransaction); + final TransactionState newState = + currentState.copyWith(pendingTransactions: newPendingTransactions); + _emitState(key, newState); + } + + void updatePendingTransaction(Transaction tx, {String? key}) { + key = _defKey(key); + final TransactionState currentState = _getStateOfWallet(key); + final List<Transaction> newPendingTransactions = <Transaction>[]; + for (final Transaction t in currentState.pendingTransactions) { + if (tx.from == t.from && + tx.to == t.to && + tx.amount == t.amount && + tx.comment == t.comment) { + newPendingTransactions + .add(t.copyWith(time: DateTime.now(), type: tx.type)); + } else { + newPendingTransactions.add(t); + } + } + final TransactionState newState = + currentState.copyWith(pendingTransactions: newPendingTransactions); + _emitState(key, newState); + } + + void insertPendingTransaction(Transaction tx, {String? key}) { + key = _defKey(key); + final TransactionState currentState = _getStateOfWallet(key); + final List<Transaction> newPendingTransactions = + currentState.pendingTransactions; + newPendingTransactions.insert(0, tx); + final TransactionState newState = + currentState.copyWith(pendingTransactions: newPendingTransactions); + _emitState(key, newState); + } + + List<Transaction> get transactions => currentWalletState().transactions; + + TransactionState currentWalletState() => _getStateOfWallet(_defKey(null)); + + double get balance => currentWalletState().balance; + + DateTime get lastChecked => currentWalletState().lastChecked; + + String _getTxKey(Transaction t) => '${t.to.pubKey}-${t.comment}-${t.amount}'; + + Future<List<Transaction>> fetchTransactions( + NodeListCubit cubit, AppCubit appCubit, + {int retries = 5, + int? pageSize, + String? cursor, + String? myPubKey}) async { + myPubKey = _defKey(myPubKey); + final TransactionState currentState = _getStateOfWallet(myPubKey); + Tuple2<Map<String, dynamic>?, Node> txDataResult; + bool success = false; + final bool isG1 = appCubit.currency == Currency.G1; + + for (int attempt = 0; attempt < retries; attempt++) { + txDataResult = await gvaHistoryAndBalance(myPubKey, pageSize, cursor); + final Node node = txDataResult.item2; + logger( + 'Loading transactions using $node (pageSize: $pageSize, cursor: $cursor) --------------------'); + + if (txDataResult.item1 == null) { + logger( + 'Failed to get transactions, attempt ${attempt + 1} of $retries'); + await Future<void>.delayed(const Duration(seconds: 1)); + increaseNodeErrors(NodeType.gva, node); + continue; + } + + final Map<String, dynamic> txData = txDataResult.item1!; + TransactionState newState = + await transactionsGvaParser(txData, currentState); + + if (newState.balance < 0) { + logger('Warning: Negative balance in node ${txDataResult.item2}'); + increaseNodeErrors(NodeType.gva, node); + continue; + } + success = true; + + if (newState.currentUd != null) { + appCubit.setUd(newState.currentUd!); + } + + logger( + 'Last received notification: ${newState.latestReceivedNotification.toIso8601String()})}'); + logger( + 'Last sent notification: ${newState.latestSentNotification.toIso8601String()})}'); + + // Check pending transactions + if (cursor == null) { + // First page, so let's check pending transactions + final LinkedHashSet<Transaction> newPendingTransactions = + LinkedHashSet<Transaction>(); + final List<Transaction> newTransactions = <Transaction>[]; + + // Index transactions by key + final Map<String, Transaction> txMap = <String, Transaction>{}; + final Map<String, Transaction> pendingMap = <String, Transaction>{}; + + // or maybe it doesn't merit the effort + for (final Transaction t in newState.transactions) { + txMap[_getTxKey(t)] = t; + } + // Get a range of tx in 1h + TransactionsBloc().lastTx().forEach((Transaction t) { + txMap[_getTxKey(t)] = t; + }); + for (final Transaction t in newState.pendingTransactions) { + pendingMap[_getTxKey(t)] = t; + } + + // Adjust pending transactions + for (final Transaction pend in newState.pendingTransactions) { + if (pend.type == TransactionType.waitingNetwork) { + newPendingTransactions.add(pend); + continue; + } + if (txMap[_getTxKey(pend)] != null) { + // Found a match + // VER SI SENT o que + final Transaction t = txMap[_getTxKey(pend)]!; + if (t.type == TransactionType.sent) { + loggerDev( + '@@@@@ Found a sent match for pending transaction ${pend.toStringSmall(myPubKey)}'); + // Add later the tx, but don't add the pending + } else { + if (t.type == TransactionType.sending) { + loggerDev( + '@@@@@ Found a sending match for pending transaction ${pend.toStringSmall(myPubKey)}'); + // Re-add as pending + // The tx will not be add as sending (as some nodes will show it and others will not, + // we use better the pending) + // FIXME: if this is old, probably is stuck, so maybe we should cancel->retry + newPendingTransactions.add(pend.copyWith( + debugInfo: + pend.debugInfo ?? 'Node where see it: ${node.url}')); + } else { + loggerDev( + '@@@@@ WARNING: Found a ${t.type} match for pending transaction ${pend.toStringSmall(myPubKey)}'); + } + } + } else { + // Not found a match + if (areDatesClose(DateTime.now(), pend.time, paymentTimeRange)) { + loggerDev( + '@@@@@ Not found yet pending transaction ${pend.toStringSmall(myPubKey)}'); + newPendingTransactions.add(pend); + } else { + // Old pending transaction, warn user + loggerDev( + '@@@@@ Warn user: Not found an old pending transaction ${pend.toStringSmall(myPubKey)}'); + // Add it but with missing type + newPendingTransactions + .add(pend.copyWith(type: TransactionType.failed)); + } + } + } + + for (final Transaction tx in newState.transactions) { + if (pendingMap[_getTxKey(tx)] != null && + (tx.type == TransactionType.sending || + tx.type == TransactionType.sent)) { + // Found a match + if (tx.type == TransactionType.sent) { + // Ok add it, but not as pending + newTransactions.add(tx); + } else { + // It's sending so should be added before as pending + } + } else { + // Does not match + if (tx.type == TransactionType.sending) { + // Not found, maybe we are in other client, so add as pending + newPendingTransactions + .add(tx.copyWith(type: TransactionType.pending)); + } else { + // the rest + newTransactions.add(tx); + } + } + } + + newState = newState.copyWith( + transactions: newTransactions, + pendingTransactions: newPendingTransactions.toList()); + } + _emitState(myPubKey, newState); + + for (final Transaction tx in newState.transactions.reversed) { + if (tx.type == TransactionType.received && + newState.latestReceivedNotification.isBefore(tx.time)) { + // Future + final Contact from = tx.from; + NotificationController.createNewNotification( + tx.time.millisecondsSinceEpoch.toString(), + amount: tx.amount, + currentUd: appCubit.currentUd, + from: from.title, + isG1: isG1); + final TransactionState notifState = + newState.copyWith(latestReceivedNotification: tx.time); + _emitState(myPubKey, notifState); + } + if (tx.type == TransactionType.sent && + newState.latestSentNotification.isBefore(tx.time)) { + // Future + final Contact to = tx.to; + NotificationController.createNewNotification( + tx.time.millisecondsSinceEpoch.toString(), + amount: -tx.amount, + currentUd: appCubit.currentUd, + to: to.title, + isG1: isG1); + final TransactionState notifState = + newState.copyWith(latestSentNotification: tx.time); + _emitState(myPubKey, notifState); + } + } + return newState.transactions; + } + if (!success) { + throw Exception('Failed to get transactions after $retries attempts'); + } + // This should not be executed + return <Transaction>[]; + } +} diff --git a/lib/data/models/multi_wallet_transaction_state.dart b/lib/data/models/multi_wallet_transaction_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..c9d6b738e187057890f3557d3b94c2e5a7399535 --- /dev/null +++ b/lib/data/models/multi_wallet_transaction_state.dart @@ -0,0 +1,23 @@ +import 'package:copy_with_extension/copy_with_extension.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import 'transaction_state.dart'; + +part 'multi_wallet_transaction_state.g.dart'; + +@JsonSerializable() +@CopyWith() +class MultiWalletTransactionState extends Equatable { + const MultiWalletTransactionState(this.map); + + factory MultiWalletTransactionState.fromJson(Map<String, dynamic> json) => + _$MultiWalletTransactionStateFromJson(json); + + final Map<String, TransactionState> map; + + Map<String, dynamic> toJson() => _$MultiWalletTransactionStateToJson(this); + + @override + List<Object?> get props => <dynamic>[map]; +} diff --git a/lib/data/models/multi_wallet_transaction_state.g.dart b/lib/data/models/multi_wallet_transaction_state.g.dart new file mode 100644 index 0000000000000000000000000000000000000000..e10b4eb759d6cd002a9ef29750fb4064a9bf3344 --- /dev/null +++ b/lib/data/models/multi_wallet_transaction_state.g.dart @@ -0,0 +1,78 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'multi_wallet_transaction_state.dart'; + +// ************************************************************************** +// CopyWithGenerator +// ************************************************************************** + +abstract class _$MultiWalletTransactionStateCWProxy { + MultiWalletTransactionState map(Map<String, TransactionState> map); + + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `MultiWalletTransactionState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. + /// + /// Usage + /// ```dart + /// MultiWalletTransactionState(...).copyWith(id: 12, name: "My name") + /// ```` + MultiWalletTransactionState call({ + Map<String, TransactionState>? map, + }); +} + +/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfMultiWalletTransactionState.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfMultiWalletTransactionState.copyWith.fieldName(...)` +class _$MultiWalletTransactionStateCWProxyImpl + implements _$MultiWalletTransactionStateCWProxy { + const _$MultiWalletTransactionStateCWProxyImpl(this._value); + + final MultiWalletTransactionState _value; + + @override + MultiWalletTransactionState map(Map<String, TransactionState> map) => + this(map: map); + + @override + + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `MultiWalletTransactionState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. + /// + /// Usage + /// ```dart + /// MultiWalletTransactionState(...).copyWith(id: 12, name: "My name") + /// ```` + MultiWalletTransactionState call({ + Object? map = const $CopyWithPlaceholder(), + }) { + return MultiWalletTransactionState( + map == const $CopyWithPlaceholder() || map == null + ? _value.map + // ignore: cast_nullable_to_non_nullable + : map as Map<String, TransactionState>, + ); + } +} + +extension $MultiWalletTransactionStateCopyWith on MultiWalletTransactionState { + /// Returns a callable class that can be used as follows: `instanceOfMultiWalletTransactionState.copyWith(...)` or like so:`instanceOfMultiWalletTransactionState.copyWith.fieldName(...)`. + // ignore: library_private_types_in_public_api + _$MultiWalletTransactionStateCWProxy get copyWith => + _$MultiWalletTransactionStateCWProxyImpl(this); +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MultiWalletTransactionState _$MultiWalletTransactionStateFromJson( + Map<String, dynamic> json) => + MultiWalletTransactionState( + (json['map'] as Map<String, dynamic>).map( + (k, e) => + MapEntry(k, TransactionState.fromJson(e as Map<String, dynamic>)), + ), + ); + +Map<String, dynamic> _$MultiWalletTransactionStateToJson( + MultiWalletTransactionState instance) => + <String, dynamic>{ + 'map': instance.map, + }; diff --git a/lib/data/models/transaction_cubit.dart b/lib/data/models/transaction_cubit.dart index 9a376360dd6c7b6178e91c11fa7245c6654f9de3..0c01395edad17505e55f5edbb6f6a1985c0b01ad 100644 --- a/lib/data/models/transaction_cubit.dart +++ b/lib/data/models/transaction_cubit.dart @@ -22,8 +22,8 @@ import 'transaction_state.dart'; import 'transaction_type.dart'; import 'transactions_bloc.dart'; -class TransactionCubit extends HydratedCubit<TransactionState> { - TransactionCubit() +class TransactionCubitRemove extends HydratedCubit<TransactionState> { + TransactionCubitRemove() : super(TransactionState( transactions: const <Transaction>[], pendingTransactions: const <Transaction>[], @@ -34,6 +34,7 @@ class TransactionCubit extends HydratedCubit<TransactionState> { String get storagePrefix => kIsWeb ? 'TransactionsCubit' : super.storagePrefix; + @Deprecated('Use MultiWalletTransactionCubit instead.') void addPendingTransaction(Transaction pendingTransaction) { final TransactionState currentState = state; final List<Transaction> newPendingTransactions = @@ -104,14 +105,14 @@ class TransactionCubit extends HydratedCubit<TransactionState> { // or maybe it doesn't merit the effort for (final Transaction t in newState.transactions) { - txMap[getTxKey(t)] = t; + txMap[_getTxKey(t)] = t; } // Get a range of tx in 1h TransactionsBloc().lastTx().forEach((Transaction t) { - txMap[getTxKey(t)] = t; + txMap[_getTxKey(t)] = t; }); for (final Transaction t in newState.pendingTransactions) { - pendingMap[getTxKey(t)] = t; + pendingMap[_getTxKey(t)] = t; } // Adjust pending transactions @@ -120,10 +121,10 @@ class TransactionCubit extends HydratedCubit<TransactionState> { newPendingTransactions.add(pend); continue; } - if (txMap[getTxKey(pend)] != null) { + if (txMap[_getTxKey(pend)] != null) { // Found a match // VER SI SENT o que - final Transaction t = txMap[getTxKey(pend)]!; + final Transaction t = txMap[_getTxKey(pend)]!; if (t.type == TransactionType.sent) { loggerDev( '@@@@@ Found a sent match for pending transaction ${pend.toStringSmall(myPubKey)}'); @@ -162,7 +163,7 @@ class TransactionCubit extends HydratedCubit<TransactionState> { } for (final Transaction tx in newState.transactions) { - if (pendingMap[getTxKey(tx)] != null && + if (pendingMap[_getTxKey(tx)] != null && (tx.type == TransactionType.sending || tx.type == TransactionType.sent)) { // Found a match @@ -226,7 +227,7 @@ class TransactionCubit extends HydratedCubit<TransactionState> { return <Transaction>[]; } - String getTxKey(Transaction t) => '${t.to.pubKey}-${t.comment}-${t.amount}'; + String _getTxKey(Transaction t) => '${t.to.pubKey}-${t.comment}-${t.amount}'; @override TransactionState fromJson(Map<String, dynamic> json) => diff --git a/lib/data/models/transaction_state.dart b/lib/data/models/transaction_state.dart index d772dd39d4de05d08301084a194f377401591a56..bb6e377b92542756f355d852f905a99e2654961f 100644 --- a/lib/data/models/transaction_state.dart +++ b/lib/data/models/transaction_state.dart @@ -25,6 +25,12 @@ class TransactionState extends Equatable { factory TransactionState.fromJson(Map<String, dynamic> json) => _$TransactionStateFromJson(json); + static final TransactionState emptyState = TransactionState( + transactions: const <Transaction>[], + pendingTransactions: const <Transaction>[], + balance: 0, + lastChecked: DateTime.now()); + final List<Transaction> transactions; final List<Transaction> pendingTransactions; final double balance; diff --git a/lib/data/models/transactions_bloc.dart b/lib/data/models/transactions_bloc.dart index 868daa14c17ae03293b0fdde85cdbcbaec330fe0..db268e2652a04c36a4454b578b3687878de5e399 100644 --- a/lib/data/models/transactions_bloc.dart +++ b/lib/data/models/transactions_bloc.dart @@ -7,9 +7,9 @@ import '../../ui/logger.dart'; import '../../ui/pay_helper.dart'; import '../../ui/widgets/connectivity_widget_wrapper_wrapper.dart'; import 'app_cubit.dart'; +import 'multi_wallet_transaction_cubit.dart'; import 'node_list_cubit.dart'; import 'transaction.dart'; -import 'transaction_cubit.dart'; part 'transactions_state.dart'; @@ -28,7 +28,7 @@ class TransactionsBloc { late AppCubit appCubit; late NodeListCubit nodeListCubit; - late TransactionCubit transCubit; + late MultiWalletTransactionCubit transCubit; static const int _pageSize = 20; @@ -70,7 +70,7 @@ class TransactionsBloc { yield* _fetchTransactionsList(null); } - void init(TransactionCubit transCubit, NodeListCubit nodeListCubit, + void init(MultiWalletTransactionCubit transCubit, NodeListCubit nodeListCubit, AppCubit appCubit) { this.appCubit = appCubit; this.transCubit = transCubit; @@ -104,7 +104,7 @@ class TransactionsBloc { final bool isLastPage = fetchedItems.length < _pageSize; final String? nextPageKey = - isLastPage ? null : transCubit.state.endCursor; + isLastPage ? null : transCubit.currentWalletState().endCursor; yield TransactionsState( // error: null, diff --git a/lib/main.dart b/lib/main.dart index d324663752be31fa90b502d8f3ba17ef634a9b4f..f1123e8b5930415c52d59b1893367289558bca71 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,6 +31,7 @@ import 'data/models/app_cubit.dart'; import 'data/models/app_state.dart'; import 'data/models/bottom_nav_cubit.dart'; import 'data/models/contact_cubit.dart'; +import 'data/models/multi_wallet_transaction_cubit.dart'; import 'data/models/node_list_cubit.dart'; import 'data/models/node_list_state.dart'; import 'data/models/node_manager.dart'; @@ -142,8 +143,12 @@ void main() async { create: (BuildContext context) => NodeListCubit()), BlocProvider<ContactsCubit>( create: (BuildContext context) => ContactsCubit()), - BlocProvider<TransactionCubit>( - create: (BuildContext context) => TransactionCubit()), + // TODO: Remove when clean the state of this + BlocProvider<TransactionCubitRemove>( + create: (BuildContext context) => TransactionCubitRemove()), + BlocProvider<MultiWalletTransactionCubit>( + create: (BuildContext context) => + MultiWalletTransactionCubit()), BlocProvider<ThemeCubit>( create: (BuildContext context) => ThemeCubit()), // Add other BlocProviders here if needed @@ -314,6 +319,8 @@ class _GinkgoAppState extends State<GinkgoApp> { NodeManager().loadFromCubit(context.read<NodeListCubit>()); // Only after at least the action method is set, the notification events are delivered NotificationController.startListeningNotificationEvents(); + // Wipe Old Transactions Cubit + context.read<TransactionCubitRemove>().close(); Once.runHourly('load_nodes', callback: () async { final bool isConnected = await ConnectivityWidgetWrapperWrapper.isConnected; diff --git a/lib/ui/pay_helper.dart b/lib/ui/pay_helper.dart index 93b0e7d9e23187fa7db9242801cd69feb69fc91e..58ba46b83aa67649db5e82a26eaab4b4efc4b826 100644 --- a/lib/ui/pay_helper.dart +++ b/lib/ui/pay_helper.dart @@ -9,12 +9,12 @@ import '../../../data/models/node_list_cubit.dart'; import '../../../data/models/node_type.dart'; import '../../../data/models/payment_cubit.dart'; import '../../../data/models/transaction.dart'; -import '../../../data/models/transaction_cubit.dart'; import '../../../data/models/transaction_type.dart'; import '../../../g1/api.dart'; import '../../../shared_prefs_helper.dart'; import '../data/models/app_cubit.dart'; import '../data/models/bottom_nav_cubit.dart'; +import '../data/models/multi_wallet_transaction_cubit.dart'; import '../data/models/payment_state.dart'; import '../g1/currency.dart'; import '../g1/g1_helper.dart'; @@ -36,7 +36,8 @@ Future<void> payWithRetry( assert(amount > 0); - final TransactionCubit txCubit = context.read<TransactionCubit>(); + final MultiWalletTransactionCubit txCubit = + context.read<MultiWalletTransactionCubit>(); final PaymentCubit paymentCubit = context.read<PaymentCubit>(); final AppCubit appCubit = context.read<AppCubit>(); paymentCubit.sending(); @@ -134,7 +135,7 @@ bool weHaveBalance(BuildContext context, double amount) { } double getBalance(BuildContext context) => - context.read<TransactionCubit>().balance; + context.read<MultiWalletTransactionCubit>().balance; Future<bool?> _confirmSend(BuildContext context, String amount, String to, bool isRetry, Currency currency) async { diff --git a/lib/ui/screens/first_screen.dart b/lib/ui/screens/first_screen.dart index b898a6d1810f63ae938b29323f851f3f350b7b5f..9d47573d418956155d73451f6c77edfc08b970ac 100644 --- a/lib/ui/screens/first_screen.dart +++ b/lib/ui/screens/first_screen.dart @@ -8,9 +8,9 @@ import 'package:web_browser_detect/web_browser_detect.dart'; import '../../data/models/app_cubit.dart'; import '../../data/models/app_state.dart'; import '../../data/models/bottom_nav_cubit.dart'; +import '../../data/models/multi_wallet_transaction_cubit.dart'; import '../../data/models/payment_cubit.dart'; import '../../data/models/payment_state.dart'; -import '../../data/models/transaction_cubit.dart'; import '../../shared_prefs_helper.dart'; import '../tutorial.dart'; import '../tutorial_keys.dart'; @@ -38,7 +38,7 @@ class _FirstScreenState extends State<FirstScreen> { context, context.read<AppCubit>().wasTutorialShown('first_screen')); super.initState(); if (context.read<BottomNavCubit>().state == 0 && - context.read<TransactionCubit>().balance == 0) { + context.read<MultiWalletTransactionCubit>().balance == 0) { Future<void>.delayed(Duration.zero, () => tutorial.showTutorial()); } } diff --git a/lib/ui/ui_helpers.dart b/lib/ui/ui_helpers.dart index ae563ba318682a8ca6b63eec9b7269d5832b9f88..689c49be98224c7fac1d1b3213e4fddb521549a2 100644 --- a/lib/ui/ui_helpers.dart +++ b/lib/ui/ui_helpers.dart @@ -14,8 +14,8 @@ import 'package:url_launcher/url_launcher.dart'; import '../data/models/app_cubit.dart'; import '../data/models/contact.dart'; +import '../data/models/multi_wallet_transaction_cubit.dart'; import '../data/models/node_list_cubit.dart'; -import '../data/models/transaction_cubit.dart'; import '../g1/api.dart'; import '../g1/currency.dart'; import '../shared_prefs_helper.dart'; @@ -244,7 +244,8 @@ String cleanComment(String? comment) { void fetchTransactions(BuildContext context) { final AppCubit appCubit = context.read<AppCubit>(); - final TransactionCubit transCubit = context.read<TransactionCubit>(); + final MultiWalletTransactionCubit transCubit = + context.read<MultiWalletTransactionCubit>(); final NodeListCubit nodeListCubit = context.read<NodeListCubit>(); transCubit.fetchTransactions(nodeListCubit, appCubit); } diff --git a/lib/ui/widgets/fifth_screen/import_dialog.dart b/lib/ui/widgets/fifth_screen/import_dialog.dart index 190ab59731c9e12c8b0f96ca69647e7a645acc79..02024d9de28575dff418e8d81ec9d1599d7fb5d1 100644 --- a/lib/ui/widgets/fifth_screen/import_dialog.dart +++ b/lib/ui/widgets/fifth_screen/import_dialog.dart @@ -11,7 +11,7 @@ import 'package:pattern_lock/pattern_lock.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:universal_html/html.dart' as html; -import '../../../data/models/transaction_cubit.dart'; +import '../../../data/models/multi_wallet_transaction_cubit.dart'; import '../../../g1/g1_helper.dart'; import '../../../shared_prefs_helper.dart'; import '../../logger.dart'; @@ -250,7 +250,8 @@ class _ImportDialogState extends State<ImportDialog> { } Future<bool?> confirmImport(BuildContext context) async { - final bool hasBalance = context.read<TransactionCubit>().balance > 0; + final bool hasBalance = + context.read<MultiWalletTransactionCubit>().balance > 0; return showDialog<bool>( context: context, builder: (BuildContext context) { diff --git a/lib/ui/widgets/first_screen/pay_form.dart b/lib/ui/widgets/first_screen/pay_form.dart index 740574705d95196e9a391fc32092fd19960bc8cd..73eb41ea33afe2d10b80145f9cd37aa1478696b8 100644 --- a/lib/ui/widgets/first_screen/pay_form.dart +++ b/lib/ui/widgets/first_screen/pay_form.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../data/models/app_cubit.dart'; +import '../../../data/models/multi_wallet_transaction_cubit.dart'; import '../../../data/models/payment_cubit.dart'; import '../../../data/models/payment_state.dart'; import '../../../data/models/theme_cubit.dart'; -import '../../../data/models/transaction_cubit.dart'; import '../../../g1/currency.dart'; import '../../../shared_prefs_helper.dart'; import '../../logger.dart'; @@ -223,7 +223,7 @@ class _PayFormState extends State<PayForm> { } double getBalance(BuildContext context) => - context.read<TransactionCubit>().balance; + context.read<MultiWalletTransactionCubit>().balance; } class RetryException implements Exception { diff --git a/lib/ui/widgets/fourth_screen/transaction_item.dart b/lib/ui/widgets/fourth_screen/transaction_item.dart index 62e16d86e4fefed6ddf2d52a3d1b9c5b1debd3d0..034a9bb9891f1168ac341661a684b45bfce56b00 100644 --- a/lib/ui/widgets/fourth_screen/transaction_item.dart +++ b/lib/ui/widgets/fourth_screen/transaction_item.dart @@ -6,10 +6,10 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../data/models/bottom_nav_cubit.dart'; import '../../../data/models/contact.dart'; import '../../../data/models/contact_cubit.dart'; +import '../../../data/models/multi_wallet_transaction_cubit.dart'; +import '../../../data/models/multi_wallet_transaction_state.dart'; import '../../../data/models/payment_cubit.dart'; import '../../../data/models/transaction.dart'; -import '../../../data/models/transaction_cubit.dart'; -import '../../../data/models/transaction_state.dart'; import '../../../data/models/transaction_type.dart'; import '../../../g1/g1_helper.dart'; import '../../../shared_prefs_helper.dart'; @@ -46,8 +46,10 @@ class TransactionListItem extends StatelessWidget { @override Widget build(BuildContext context) { // logger('TransactionListItem build'); - return BlocBuilder<TransactionCubit, TransactionState>( - builder: (BuildContext context, TransactionState transBalanceState) => + return BlocBuilder<MultiWalletTransactionCubit, + MultiWalletTransactionState>( + builder: (BuildContext context, + MultiWalletTransactionState transBalanceState) => _buildTransactionItem(context, transaction)); } @@ -111,7 +113,7 @@ class TransactionListItem extends StatelessWidget { SlidableAction( onPressed: (BuildContext c) { context - .read<TransactionCubit>() + .read<MultiWalletTransactionCubit>() .removePendingTransaction(transaction); ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/lib/ui/widgets/fourth_screen/transaction_page.dart b/lib/ui/widgets/fourth_screen/transaction_page.dart index 696be8d86c39f0495e891cf9a33b4c32bcfc586e..cf190bd65291e46b2a97e7e26895082b22d4697d 100644 --- a/lib/ui/widgets/fourth_screen/transaction_page.dart +++ b/lib/ui/widgets/fourth_screen/transaction_page.dart @@ -10,11 +10,11 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:we_slide/we_slide.dart'; import '../../../data/models/app_cubit.dart'; +import '../../../data/models/multi_wallet_transaction_cubit.dart'; +import '../../../data/models/multi_wallet_transaction_state.dart'; import '../../../data/models/node_list_cubit.dart'; import '../../../data/models/theme_cubit.dart'; import '../../../data/models/transaction.dart'; -import '../../../data/models/transaction_cubit.dart'; -import '../../../data/models/transaction_state.dart'; import '../../../data/models/transactions_bloc.dart'; import '../../../g1/currency.dart'; import '../../../shared_prefs_helper.dart'; @@ -42,7 +42,7 @@ class _TransactionsAndBalanceWidgetState late StreamSubscription<TransactionsState> _blocListingStateSubscription; late AppCubit appCubit; late NodeListCubit nodeListCubit; - late TransactionCubit transCubit; + late MultiWalletTransactionCubit transCubit; final PagingController<String?, Transaction> _pagingController = PagingController<String?, Transaction>(firstPageKey: null); @@ -60,7 +60,7 @@ class _TransactionsAndBalanceWidgetState void initState() { // Remove in the future appCubit = context.read<AppCubit>(); - transCubit = context.read<TransactionCubit>(); + transCubit = context.read<MultiWalletTransactionCubit>(); nodeListCubit = context.read<NodeListCubit>(); _bloc.init(transCubit, nodeListCubit, appCubit); _pagingController.addPageRequestListener((String? cursor) { @@ -165,11 +165,14 @@ class _TransactionsAndBalanceWidgetState useSymbol: true, isG1: isG1, locale: currentLocale(context)); final bool isCurrencyBefore = isSymbolPlacementBefore(currentNumber.symbols.CURRENCY_PATTERN); - return BlocBuilder<TransactionCubit, TransactionState>( - builder: (BuildContext context, TransactionState transBalanceState) { + return BlocBuilder<MultiWalletTransactionCubit, + MultiWalletTransactionState>( + builder: (BuildContext context, + MultiWalletTransactionState transBalanceState) { // final List<Transaction> transactions = transBalanceState.transactions; - final double balance = transBalanceState.balance; final String myPubKey = SharedPreferencesHelper().getPubKey(); + final double balance = getBalance(context); + return Scaffold( drawer: const CardDrawer(), onDrawerChanged: (bool isOpened) { @@ -401,11 +404,14 @@ class _TransactionsAndBalanceWidgetState Future<void> _fetchPending(int pageKey) async { try { final bool shouldPaginate = - transCubit.state.pendingTransactions.length > _pendingPageSize; + transCubit.currentWalletState().pendingTransactions.length > + _pendingPageSize; final List<Transaction> newItems = shouldPaginate - ? transCubit.state.pendingTransactions + ? transCubit + .currentWalletState() + .pendingTransactions .sublist(pageKey, _pendingPageSize) - : transCubit.state.pendingTransactions; + : transCubit.currentWalletState().pendingTransactions; final bool isLastPage = newItems.length < _pendingPageSize; if (isLastPage) { _pendingController.appendLastPage(newItems); @@ -417,4 +423,7 @@ class _TransactionsAndBalanceWidgetState _pendingController.error = error; } } + + double getBalance(BuildContext context) => + context.read<MultiWalletTransactionCubit>().balance; } diff --git a/test/transactions_test.dart b/test/transactions_test.dart index ad0a4291f711e928ed355ee5e88ecebe8f635f07..c1df53f50282ea9987d5547662fb542c2277bbbd 100644 --- a/test/transactions_test.dart +++ b/test/transactions_test.dart @@ -9,12 +9,6 @@ import 'package:ginkgo/g1/transaction_parser.dart'; import 'package:ginkgo/ui/contacts_cache.dart'; void main() { - final TransactionState emptyState = TransactionState( - transactions: const <Transaction>[], - pendingTransactions: const <Transaction>[], - balance: 0, - lastChecked: DateTime(1970)); - setUpAll(() { // ContactsCache().init(); }); @@ -54,7 +48,7 @@ void main() { final TransactionState result = await transactionsGvaParser( (jsonDecode(txData) as Map<String, dynamic>)['data'] as Map<String, dynamic>, - emptyState); + TransactionState.emptyState); expect(result.balance, equals(3)); final List<Transaction> txs = result.transactions; for (final Transaction tx in txs) { @@ -108,7 +102,7 @@ void main() { final TransactionState emptyResult = await transactionsGvaParser( (jsonDecode(emptyTx) as Map<String, dynamic>)['data'] as Map<String, dynamic>, - emptyState); + TransactionState.emptyState); expect(emptyResult.balance, equals(0)); final List<Transaction> emptyTxs = emptyResult.transactions; expect(emptyTxs.length, equals(0));