Use snack status for non-blocking wallet operations
This commit is contained in:
+42
-4
@@ -42,6 +42,8 @@ class _PeyaAppState extends ConsumerState<PeyaApp> with WindowListener {
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||
String? _lastErrorMessage;
|
||||
String? _lastStatusMessage;
|
||||
bool _statusSnackVisible = false;
|
||||
bool _desktopCloseHookInitialized = false;
|
||||
bool _handlingWindowClose = false;
|
||||
|
||||
@@ -70,6 +72,7 @@ class _PeyaAppState extends ConsumerState<PeyaApp> with WindowListener {
|
||||
});
|
||||
_walletSubscription = ref
|
||||
.listenManual<WalletState>(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<PeyaApp> 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...',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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<SubaddressInfo> 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<SubaddressInfo>? 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<WalletState> {
|
||||
} finally {
|
||||
await _cacheRecovery.clearOpenAttempt();
|
||||
}
|
||||
});
|
||||
}, message: 'Creating wallet...');
|
||||
}
|
||||
|
||||
Future<String> prepareWalletSeedDraft({
|
||||
@@ -155,7 +169,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
await tempDir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, message: 'Preparing seed backup...');
|
||||
}
|
||||
|
||||
Future<void> restoreWalletFromSeed({
|
||||
@@ -179,7 +193,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
} finally {
|
||||
await _cacheRecovery.clearOpenAttempt();
|
||||
}
|
||||
});
|
||||
}, message: 'Restoring wallet...');
|
||||
}
|
||||
|
||||
Future<void> openWallet({
|
||||
@@ -196,7 +210,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
} finally {
|
||||
await _cacheRecovery.clearOpenAttempt();
|
||||
}
|
||||
});
|
||||
}, message: 'Opening wallet...');
|
||||
}
|
||||
|
||||
Future<bool> verifyWalletPassword({
|
||||
@@ -217,7 +231,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
Future<void> connectNode(NodeConfig nodeConfig) async {
|
||||
await _runWithLoading(() async {
|
||||
await _repository.connectNode(nodeConfig);
|
||||
});
|
||||
}, blocking: false, message: 'Connecting to node...');
|
||||
}
|
||||
|
||||
Future<void> switchWallet() async {
|
||||
@@ -237,7 +251,11 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
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<void> setSubaddressLabel({
|
||||
@@ -254,7 +272,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
addressIndex: addressIndex,
|
||||
label: label,
|
||||
);
|
||||
});
|
||||
}, blocking: false, message: 'Updating subaddress...');
|
||||
await refreshSubaddresses();
|
||||
}
|
||||
|
||||
@@ -270,7 +288,11 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
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<SendResult> commitPreparedSend() async {
|
||||
@@ -281,7 +303,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
final result = await _repository.commitPreparedSend();
|
||||
await _refreshSnapshot();
|
||||
return result;
|
||||
});
|
||||
}, blocking: false, message: 'Submitting transaction...');
|
||||
}
|
||||
|
||||
Future<void> discardPreparedSend() async {
|
||||
@@ -300,7 +322,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
amountAtomic: amountAtomic,
|
||||
accountIndex: accountIndex,
|
||||
);
|
||||
});
|
||||
}, blocking: false, message: 'Preparing stake...');
|
||||
}
|
||||
|
||||
Future<Map<String, StakeYieldInfo>> getActiveStakeYields({
|
||||
@@ -324,7 +346,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
final result = await _repository.commitPreparedStake();
|
||||
await _refreshSnapshot();
|
||||
return result;
|
||||
});
|
||||
}, blocking: false, message: 'Submitting stake...');
|
||||
}
|
||||
|
||||
Future<void> discardPreparedStake() async {
|
||||
@@ -384,6 +406,7 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
syncStatus: SyncStatus.initial(),
|
||||
transactions: const [],
|
||||
subaddresses: subaddresses,
|
||||
operationMessage: null,
|
||||
error: null,
|
||||
);
|
||||
}
|
||||
@@ -414,8 +437,17 @@ class WalletController extends StateNotifier<WalletState> {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
Future<T> _runWithLoading<T>(Future<T> Function() action) async {
|
||||
state = state.copyWith(isLoading: true, error: null);
|
||||
Future<T> _runWithLoading<T>(
|
||||
Future<T> 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<WalletState> {
|
||||
state = state.copyWith(error: error.toString());
|
||||
rethrow;
|
||||
} finally {
|
||||
state = state.copyWith(isLoading: false);
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
isBlockingOperation: false,
|
||||
operationMessage: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user