From aa5d4542b7ad65cef3bb7a24ed59fefb99a4f47a Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Mon, 20 Apr 2026 01:24:06 +0200 Subject: [PATCH] Use snack status for non-blocking wallet operations --- lib/app.dart | 46 +++++++++++++++++++++-- lib/state/wallet_controller.dart | 64 +++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 9d4556d..f90fff2 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -42,6 +42,8 @@ class _PeyaAppState extends ConsumerState with WindowListener { GlobalKey(); final GlobalKey _navigatorKey = GlobalKey(); String? _lastErrorMessage; + String? _lastStatusMessage; + bool _statusSnackVisible = false; bool _desktopCloseHookInitialized = false; bool _handlingWindowClose = false; @@ -70,6 +72,7 @@ class _PeyaAppState extends ConsumerState with WindowListener { }); _walletSubscription = ref .listenManual(walletControllerProvider, (previous, next) { + _handleOperationStatusChange(previous, next); if (previous?.walletInfo == null && next.walletInfo != null) { final config = ref.read(appConfigControllerProvider); _connectNodeIfPossible(config); @@ -86,6 +89,40 @@ class _PeyaAppState extends ConsumerState with WindowListener { }); } + void _handleOperationStatusChange(WalletState? previous, WalletState next) { + final messenger = _scaffoldMessengerKey.currentState; + if (messenger == null) { + return; + } + final shouldShowStatus = next.isLoading && + !next.isBlockingOperation && + next.operationMessage != null && + next.operationMessage!.isNotEmpty; + + if (shouldShowStatus) { + final message = next.operationMessage!; + if (_statusSnackVisible && _lastStatusMessage == message) { + return; + } + messenger.hideCurrentSnackBar(); + messenger.showSnackBar( + SnackBar( + content: Text(message), + duration: const Duration(days: 1), + ), + ); + _statusSnackVisible = true; + _lastStatusMessage = message; + return; + } + + if (_statusSnackVisible) { + messenger.hideCurrentSnackBar(); + _statusSnackVisible = false; + _lastStatusMessage = null; + } + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -653,7 +690,7 @@ class _RootRouter extends ConsumerWidget { return Stack( children: [ child, - if (state.isLoading) + if (state.isLoading && state.isBlockingOperation) Positioned.fill( child: ColoredBox( color: const Color(0x66000000), @@ -670,9 +707,10 @@ class _RootRouter extends ConsumerWidget { const CircularProgressIndicator(), const SizedBox(height: 16), Text( - AppLocalizations.of(context) - ?.walletOperationInProgress ?? - 'Working...', + state.operationMessage ?? + AppLocalizations.of(context) + ?.walletOperationInProgress ?? + 'Working...', ), ], ), diff --git a/lib/state/wallet_controller.dart b/lib/state/wallet_controller.dart index 6b7e5ad..06dcfdd 100644 --- a/lib/state/wallet_controller.dart +++ b/lib/state/wallet_controller.dart @@ -13,6 +13,8 @@ import '../domain/transactions.dart'; import '../services/wallet_cache_recovery.dart'; class WalletState { + static const Object _sentinel = Object(); + const WalletState({ required this.walletInfo, required this.balanceAtomic, @@ -21,6 +23,8 @@ class WalletState { required this.subaddresses, required this.syncStatus, required this.isLoading, + required this.isBlockingOperation, + required this.operationMessage, required this.isSyncing, required this.error, }); @@ -32,6 +36,8 @@ class WalletState { final List subaddresses; final SyncStatus syncStatus; final bool isLoading; + final bool isBlockingOperation; + final String? operationMessage; final bool isSyncing; final String? error; @@ -43,6 +49,8 @@ class WalletState { List? subaddresses, SyncStatus? syncStatus, bool? isLoading, + bool? isBlockingOperation, + Object? operationMessage = _sentinel, bool? isSyncing, String? error, }) { @@ -54,6 +62,10 @@ class WalletState { subaddresses: subaddresses ?? this.subaddresses, syncStatus: syncStatus ?? this.syncStatus, isLoading: isLoading ?? this.isLoading, + isBlockingOperation: isBlockingOperation ?? this.isBlockingOperation, + operationMessage: identical(operationMessage, _sentinel) + ? this.operationMessage + : operationMessage as String?, isSyncing: isSyncing ?? this.isSyncing, error: error, ); @@ -68,6 +80,8 @@ class WalletState { subaddresses: const [], syncStatus: SyncStatus.initial(), isLoading: false, + isBlockingOperation: false, + operationMessage: null, isSyncing: false, error: null, ); @@ -129,7 +143,7 @@ class WalletController extends StateNotifier { } finally { await _cacheRecovery.clearOpenAttempt(); } - }); + }, message: 'Creating wallet...'); } Future prepareWalletSeedDraft({ @@ -155,7 +169,7 @@ class WalletController extends StateNotifier { await tempDir.delete(recursive: true); } } - }); + }, message: 'Preparing seed backup...'); } Future restoreWalletFromSeed({ @@ -179,7 +193,7 @@ class WalletController extends StateNotifier { } finally { await _cacheRecovery.clearOpenAttempt(); } - }); + }, message: 'Restoring wallet...'); } Future openWallet({ @@ -196,7 +210,7 @@ class WalletController extends StateNotifier { } finally { await _cacheRecovery.clearOpenAttempt(); } - }); + }, message: 'Opening wallet...'); } Future verifyWalletPassword({ @@ -217,7 +231,7 @@ class WalletController extends StateNotifier { Future connectNode(NodeConfig nodeConfig) async { await _runWithLoading(() async { await _repository.connectNode(nodeConfig); - }); + }, blocking: false, message: 'Connecting to node...'); } Future switchWallet() async { @@ -237,7 +251,11 @@ class WalletController extends StateNotifier { if (state.walletInfo == null) { throw StateError('No wallet loaded'); } - return _runWithLoading(() => _repository.createSubaddress(label: label)); + return _runWithLoading( + () => _repository.createSubaddress(label: label), + blocking: false, + message: 'Creating subaddress...', + ); } Future setSubaddressLabel({ @@ -254,7 +272,7 @@ class WalletController extends StateNotifier { addressIndex: addressIndex, label: label, ); - }); + }, blocking: false, message: 'Updating subaddress...'); await refreshSubaddresses(); } @@ -270,7 +288,11 @@ class WalletController extends StateNotifier { if (state.walletInfo == null) { throw StateError('No wallet loaded'); } - return _runWithLoading(() => _repository.prepareSend(request)); + return _runWithLoading( + () => _repository.prepareSend(request), + blocking: false, + message: 'Preparing transaction...', + ); } Future commitPreparedSend() async { @@ -281,7 +303,7 @@ class WalletController extends StateNotifier { final result = await _repository.commitPreparedSend(); await _refreshSnapshot(); return result; - }); + }, blocking: false, message: 'Submitting transaction...'); } Future discardPreparedSend() async { @@ -300,7 +322,7 @@ class WalletController extends StateNotifier { amountAtomic: amountAtomic, accountIndex: accountIndex, ); - }); + }, blocking: false, message: 'Preparing stake...'); } Future> getActiveStakeYields({ @@ -324,7 +346,7 @@ class WalletController extends StateNotifier { final result = await _repository.commitPreparedStake(); await _refreshSnapshot(); return result; - }); + }, blocking: false, message: 'Submitting stake...'); } Future discardPreparedStake() async { @@ -384,6 +406,7 @@ class WalletController extends StateNotifier { syncStatus: SyncStatus.initial(), transactions: const [], subaddresses: subaddresses, + operationMessage: null, error: null, ); } @@ -414,8 +437,17 @@ class WalletController extends StateNotifier { return snapshot; } - Future _runWithLoading(Future Function() action) async { - state = state.copyWith(isLoading: true, error: null); + Future _runWithLoading( + Future Function() action, { + bool blocking = true, + String? message, + }) async { + state = state.copyWith( + isLoading: true, + isBlockingOperation: blocking, + operationMessage: message, + error: null, + ); try { await SchedulerBinding.instance.endOfFrame; return await action(); @@ -424,7 +456,11 @@ class WalletController extends StateNotifier { state = state.copyWith(error: error.toString()); rethrow; } finally { - state = state.copyWith(isLoading: false); + state = state.copyWith( + isLoading: false, + isBlockingOperation: false, + operationMessage: null, + ); } }