Merge pull request #1469 from Cyrix126/building_dialog_cancel
What changed, and why it matters
This commit fixes a mobile UI bug where pressing Cancel on a 'building transaction' dialog accidentally closed one too many screens. The fix moves the responsibility for closing the dialog into each screen's cancel handler, and prevents error dialogs from appearing after the user has already cancelled. It is a user-experience bug fix, not a security vulnerability.
No security action required; treat as a normal UX bug fix. Reviewers may optionally verify that cancellation no longer pops the underlying send/exchange view on mobile.
Security signals we found
No security-relevant signals in the diff
UI-only navigation and error-dialog suppression changes
No cryptographic, authentication, authorization, or data-handling changes
Evidence from the diff
The patch refactors how the BuildingTransactionDialog handles cancellation. Previously the dialog itself called Navigator.pop() before invoking onCancel, which could pop the wrong route on mobile. Now the dialog only calls onCancel, and each caller’s onCancel handler explicitly pops the dialog. Additionally, the wasCancelled flag is moved outside the try block so it remains in scope for the catch handler, which now suppresses the error dialog when the user cancelled. The changes are consistent across send, exchange, token send, Namecoin, and Salvium flows.
Changed components
lib/pages/send_view/send_view.dartlib/pages/send_view/sol_token_send_view.dartlib/pages/send_view/token_send_view.dartlib/pages/send_view/sub_widgets/building_transaction_dialog.dartlib/pages/exchange_view/exchange_step_views/step_4_view.dartlib/pages/exchange_view/send_from_view.dartlib/pages/namecoin_names/sub_widgets/transfer_option_widget.dartlib/pages/namecoin_names/sub_widgets/update_option_widget.dartlib/pages/salvium_stake/salvium_create_stake_view.dartlib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dartlib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_sol_token_send.dartlib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dartInspect captured patch +68 / −84
### lib/pages/exchange_view/exchange_step_views/step_4_view.dart
@@ -286,6 +286,7 @@ class _Step4ViewState extends ConsumerState<Step4View> {
isSpark: wallet is FiroWallet && !firoPublicSend,
onCancel: () {
wasCancelled = true;
+ Navigator.of(context).pop();
},
);
},
### lib/pages/exchange_view/send_from_view.dart
@@ -106,9 +106,9 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
builder: (child) {
return Background(
child: Scaffold(
- backgroundColor: Theme.of(
- context,
- ).extension<StackColors>()!.background,
+ backgroundColor: Theme.of(context)
+ .extension<StackColors>()!
+ .background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
@@ -225,9 +225,8 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
Future<void> _send({bool? shouldSendPublicFiroFunds}) async {
final coin = ref.read(pWalletCoin(walletId));
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
final wallet = ref.read(pWallets).getWallet(walletId);
unawaited(
@@ -362,7 +361,7 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
}
} catch (e, s) {
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context, rootNavigator: Util.isDesktop).pop();
@@ -381,9 +380,9 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
child: Text(
"Ok",
style: STextStyles.button(context).copyWith(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.buttonTextSecondary,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .buttonTextSecondary,
),
),
onPressed: () {
@@ -427,9 +426,9 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
children: [
if (!trade.exchangeName.startsWith(TrocadorExchange.exchangeName))
MaterialButton(
- splashColor: Theme.of(
- context,
- ).extension<StackColors>()!.highlight,
+ splashColor: Theme.of(context)
+ .extension<StackColors>()!
+ .highlight,
key: Key("walletsSheetItemButtonFiroPrivateKey_$walletId"),
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
@@ -481,19 +480,19 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
Assets.svg.chevronRight,
height: 14,
width: 7,
- color: Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .infoItemLabel,
),
],
),
),
),
),
MaterialButton(
- splashColor: Theme.of(
- context,
- ).extension<StackColors>()!.highlight,
+ splashColor: Theme.of(context)
+ .extension<StackColors>()!
+ .highlight,
key: Key("walletsSheetItemButtonFiroPublicKey_$walletId"),
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
@@ -543,9 +542,9 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
Assets.svg.chevronRight,
height: 14,
width: 7,
- color: Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .infoItemLabel,
),
],
),
### lib/pages/namecoin_names/sub_widgets/transfer_option_widget.dart
@@ -71,11 +71,10 @@ class _TransferOptionWidgetState extends ConsumerState<TransferOptionWidget> {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 100));
+ bool wasCancelled = false;
try {
final wallet = ref.read(pWallets).getWallet(walletId) as NamecoinWallet;
- bool wasCancelled = false;
-
if (mounted) {
if (Util.isDesktop) {
unawaited(
@@ -194,7 +193,7 @@ class _TransferOptionWidgetState extends ConsumerState<TransferOptionWidget> {
stackTrace: s,
);
- if (mounted) {
+ if (mounted && !wasCancelled) {
String err = e.toString();
if (err.startsWith("Exception: ")) {
err = err.replaceFirst("Exception: ", "");
@@ -361,8 +360,7 @@ class _TransferOptionWidgetState extends ConsumerState<TransferOptionWidget> {
children: [
_addressController.text.isNotEmpty
? TextFieldIconButton(
- semanticsLabel:
- "Clear Button. Clears The Address Field Input.",
+ semanticsLabel: "Clear Button. Clears The Address Field Input.",
key: const Key(
"nameTransferClearAddressFieldButtonKey",
),
@@ -375,8 +373,7 @@ class _TransferOptionWidgetState extends ConsumerState<TransferOptionWidget> {
child: const XIcon(),
)
: TextFieldIconButton(
- semanticsLabel:
- "Paste Button. Pastes From Clipboard To Address Field Input.",
+ semanticsLabel: "Paste Button. Pastes From Clipboard To Address Field Input.",
key: const Key(
"nameTransferPasteAddressFieldButtonKey",
),
@@ -405,8 +402,7 @@ class _TransferOptionWidgetState extends ConsumerState<TransferOptionWidget> {
),
if (_addressController.text.isEmpty)
TextFieldIconButton(
- semanticsLabel:
- "Address Book Button. Opens Address Book For Address Field.",
+ semanticsLabel: "Address Book Button. Opens Address Book For Address Field.",
key: const Key(
"nameTransferAddressBookButtonKey",
),
@@ -420,8 +416,7 @@ class _TransferOptionWidgetState extends ConsumerState<TransferOptionWidget> {
),
if (_addressController.text.isEmpty)
TextFieldIconButton(
- semanticsLabel:
- "Scan QR Button. Opens Camera For Scanning QR Code.",
+ semanticsLabel: "Scan QR Button. Opens Camera For Scanning QR Code.",
key: const Key("nameTransferScanQrButtonKey"),
onTap: _scanQr,
child: const QrCodeIcon(),
### lib/pages/namecoin_names/sub_widgets/update_option_widget.dart
@@ -73,6 +73,7 @@ class _BuyDomainWidgetState extends ConsumerState<UpdateOptionWidget> {
Future<void> _previewUpdate() async {
if (_previewLock) return;
_previewLock = true;
+ bool wasCancelled = false;
try {
final newValue = _getNewValue();
if (newValue == _currentValue) {
@@ -86,8 +87,6 @@ class _BuyDomainWidgetState extends ConsumerState<UpdateOptionWidget> {
final wallet =
ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet;
- bool wasCancelled = false;
-
if (mounted) {
if (Util.isDesktop) {
unawaited(
@@ -183,16 +182,15 @@ class _BuyDomainWidgetState extends ConsumerState<UpdateOptionWidget> {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
- builder:
- (context) => SDialog(
- child: SizedBox(
- width: 580,
- child: ConfirmNameTransactionView(
- txData: txData,
- walletId: widget.walletId,
- ),
- ),
+ builder: (context) => SDialog(
+ child: SizedBox(
+ width: 580,
+ child: ConfirmNameTransactionView(
+ txData: txData,
+ walletId: widget.walletId,
),
+ ),
+ ),
);
} else {
await Navigator.of(context).pushNamed(
@@ -214,16 +212,15 @@ class _BuyDomainWidgetState extends ConsumerState<UpdateOptionWidget> {
err = "Contains invalid characters";
}
- if (mounted) {
+ if (mounted && !wasCancelled) {
await showDialog<void>(
context: context,
- builder:
- (_) => StackOkDialog(
- title: "Update failed",
- message: err,
- desktopPopRootNavigator: Util.isDesktop,
- maxWidth: Util.isDesktop ? 600 : null,
- ),
+ builder: (_) => StackOkDialog(
+ title: "Update failed",
+ message: err,
+ desktopPopRootNavigator: Util.isDesktop,
+ maxWidth: Util.isDesktop ? 600 : null,
+ ),
);
}
} finally {
@@ -260,10 +257,9 @@ class _BuyDomainWidgetState extends ConsumerState<UpdateOptionWidget> {
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
- crossAxisAlignment:
- Util.isDesktop
- ? CrossAxisAlignment.start
- : CrossAxisAlignment.stretch,
+ crossAxisAlignment: Util.isDesktop
+ ? CrossAxisAlignment.start
+ : CrossAxisAlignment.stretch,
children: [
Text("Edit value", style: STextStyles.label(context)),
const SizedBox(height: 6),
@@ -293,10 +289,9 @@ class _BuyDomainWidgetState extends ConsumerState<UpdateOptionWidget> {
return Text(
"$length/$valueMaxLength",
style: STextStyles.w500_10(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textSubtitle2,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textSubtitle2,
),
);
},
@@ -311,8 +306,10 @@ class _BuyDomainWidgetState extends ConsumerState<UpdateOptionWidget> {
child: SecondaryButton(
label: "Cancel",
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
- onPressed:
- Navigator.of(context, rootNavigator: Util.isDesktop).pop,
+ onPressed: Navigator.of(
+ context,
+ rootNavigator: Util.isDesktop,
+ ).pop,
),
),
const SizedBox(width: 16),
### lib/pages/salvium_stake/salvium_create_stake_view.dart
@@ -74,9 +74,8 @@ class _SalviumCreateStakeViewState
if (_lock) return;
_lock = true;
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
unawaited(
showDialog<dynamic>(
context: context,
@@ -170,7 +169,7 @@ class _SalviumCreateStakeViewState
} catch (e, s) {
Logging.instance.e("Salvium stake preview: ", error: e, stackTrace: s);
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context, rootNavigator: Util.isDesktop).pop();
### lib/pages/send_view/send_view.dart
@@ -935,9 +935,8 @@ class _SendViewState extends ConsumerState<SendView> {
}
}
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
if (mounted) {
unawaited(
showDialog<void>(
@@ -1167,7 +1166,7 @@ class _SendViewState extends ConsumerState<SendView> {
}
} catch (e, s) {
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context, rootNavigator: true).pop();
### lib/pages/send_view/sol_token_send_view.dart
@@ -464,9 +464,8 @@ class _SolTokenSendViewState extends ConsumerState<SolTokenSendView> {
final wallet = ref.read(pWallets).getWallet(walletId);
final Amount amount = _amountToSend!;
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
if (mounted) {
unawaited(
showDialog<void>(
@@ -543,7 +542,7 @@ class _SolTokenSendViewState extends ConsumerState<SolTokenSendView> {
}
} catch (e, s) {
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context).pop();
### lib/pages/send_view/sub_widgets/building_transaction_dialog.dart
@@ -12,6 +12,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+
import '../../../themes/coin_image_provider.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/text_styles.dart';
@@ -115,7 +116,6 @@ class _RestoringDialogState extends ConsumerState<BuildingTransactionDialog> {
style: STextStyles.itemSubtitle12(context),
),
onPressed: () {
- Navigator.of(context).pop();
onCancel.call();
},
),
@@ -140,7 +140,6 @@ class _RestoringDialogState extends ConsumerState<BuildingTransactionDialog> {
style: STextStyles.itemSubtitle12(context),
),
onPressed: () {
- Navigator.of(context).pop();
onCancel.call();
},
),
### lib/pages/send_view/token_send_view.dart
@@ -463,9 +463,8 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
// }
// }
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
if (mounted) {
unawaited(
showDialog<void>(
@@ -538,7 +537,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
}
} catch (e, s) {
Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context).pop();
### lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart
@@ -549,9 +549,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
}
}
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
if (mounted) {
unawaited(
showDialog<dynamic>(
@@ -793,7 +792,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
}
} catch (e, s) {
Logging.instance.e("Desktop send: ", error: e, stackTrace: s);
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context, rootNavigator: true).pop();
### lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_sol_token_send.dart
@@ -226,9 +226,8 @@ class _DesktopSolTokenSendState extends ConsumerState<DesktopSolTokenSend> {
}
}
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
if (mounted) {
unawaited(
showDialog<dynamic>(
@@ -311,7 +310,7 @@ class _DesktopSolTokenSendState extends ConsumerState<DesktopSolTokenSend> {
);
}
} catch (e) {
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context, rootNavigator: true).pop();
### lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart
@@ -208,9 +208,8 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
}
}
+ bool wasCancelled = false;
try {
- bool wasCancelled = false;
-
if (mounted) {
unawaited(
showDialog<dynamic>(
@@ -290,7 +289,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
);
}
} catch (e) {
- if (mounted) {
+ if (mounted && !wasCancelled) {
// pop building dialog
Navigator.of(context, rootNavigator: true).pop();
Why this scored 24/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.