Cw 1117 prevent sending a transaction twice by mistake (#2567)

* Revert "Prevent double send actions in SendPage (#2542)" This reverts commit dd4c941d2f356d7c4f2d39e9744ab43bbe1962c7. * disable send buttons when ExecutedSuccessfully * return value on bottom sheet pop [skip ci]

Serhii committed Oct 9, 2025 at 15:35 UTC f7d29c9f389499e74acebaa5fda392961a805b71
4 files changed +16 -19
lib/cake_pay/src/cards/cake_pay_buy_card_page.dart
+1 -1
@@ -371,7 +371,7 @@ class CakePayBuyCardPage extends BasePage {
371 },
372 text: S.of(context).purchase_gift_card,
373 isDisabled: !cakePayBuyCardViewModel.isAmountSufficient ||
374 - cakePayBuyCardViewModel.isPurchasing,
374 + cakePayBuyCardViewModel.isPurchasing || _sendViewModel.state is ExecutedSuccessfullyState,
375 isLoading: _sendViewModel.state is IsExecutingState ||
376 cakePayBuyCardViewModel.isPurchasing,
377 color: Theme.of(context).colorScheme.primary,
lib/src/screens/exchange_trade/exchange_trade_page.dart
+8 -4
@@ -194,7 +194,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
194 !(sendingState is TransactionCommitted)),
195 child: LoadingPrimaryButton(
196 key: ValueKey('exchange_trade_page_send_from_cake_button_key'),
197 - isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty,
197 + isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty ||
198 + sendingState is ExecutedSuccessfullyState,
199 isLoading: sendingState is IsExecutingState,
200 onPressed: () => widget.exchangeTradeViewModel.confirmSending(),
201 text: S.current.send_from_cake_wallet,
@@ -272,9 +273,9 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
273 }
274
275 if (state is ExecutedSuccessfullyState) {
275 - WidgetsBinding.instance.addPostFrameCallback((_) {
276 + WidgetsBinding.instance.addPostFrameCallback((_) async {
277 if (context.mounted) {
277 - showModalBottomSheet<void>(
278 + final result = await showModalBottomSheet<bool>(
279 context: context,
280 isDismissible: false,
281 isScrollControlled: true,
@@ -304,13 +305,16 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
305 outputs: widget.exchangeTradeViewModel.sendViewModel.outputs,
306 onSlideActionComplete: () async {
307 if (bottomSheetContext.mounted) {
307 - Navigator.of(bottomSheetContext).pop();
308 + Navigator.of(bottomSheetContext).pop(true);
309 }
310 widget.exchangeTradeViewModel.sendViewModel.commitTransaction(context);
311 },
312 );
313 },
314 );
315 +
316 + if (result == null) widget.exchangeTradeViewModel.sendViewModel.dismissTransaction();
317 +
318 }
319 });
320 }
lib/src/screens/send/send_page.dart
+1 -12
@@ -64,7 +64,6 @@ class SendPage extends BasePage {
64 final PaymentRequest? initialPaymentRequest;
65
66 bool _effectsInstalled = false;
67 - bool _sendInProgress = false;
67 ContactRecord? newContactAddress;
68
69 @override
@@ -413,9 +412,6 @@ class SendPage extends BasePage {
412 return LoadingPrimaryButton(
413 key: ValueKey('send_page_send_button_key'),
414 onPressed: () async {
416 - // Prevent double taps
417 - if (_sendInProgress) return;
418 -
415 //Request dummy node to get the focus out of the text fields
416 FocusScope.of(context).requestFocus(FocusNode());
417
@@ -474,8 +470,6 @@ class SendPage extends BasePage {
470 }
471 }
472
477 - _sendInProgress = true;
478 -
473 final check = sendViewModel.shouldDisplayTotp();
474 authService.authenticateAction(
475 context,
@@ -483,8 +477,6 @@ class SendPage extends BasePage {
477 onAuthSuccess: (value) async {
478 if (value) {
479 await sendViewModel.createTransaction();
486 - } else {
487 - _sendInProgress = false;
480 }
481 },
482 );
@@ -496,7 +488,7 @@ class SendPage extends BasePage {
488 sendViewModel.state is TransactionCommitting ||
489 sendViewModel.state is IsAwaitingDeviceResponseState ||
490 sendViewModel.state is LoadingTemplateExecutingState,
499 - isDisabled: !sendViewModel.isReadyForSend,
491 + isDisabled: !sendViewModel.isReadyForSend || sendViewModel.state is ExecutedSuccessfullyState,
492 );
493 },
494 )
@@ -533,7 +525,6 @@ class SendPage extends BasePage {
525 }
526
527 if (state is FailureState) {
536 - _sendInProgress = false;
528 WidgetsBinding.instance.addPostFrameCallback(
529 (_) {
530 showPopUp<void>(
@@ -614,13 +605,11 @@ class SendPage extends BasePage {
605 );
606
607 if (result == null) sendViewModel.dismissTransaction();
617 - _sendInProgress = false;
608 }
609 });
610 }
611
612 if (state is TransactionCommitted) {
623 - _sendInProgress = false;
613 WidgetsBinding.instance.addPostFrameCallback((_) async {
614 if (!context.mounted) {
615 return;
lib/src/screens/transaction_details/rbf_details_page.dart
+6 -2
@@ -105,6 +105,7 @@ class RBFDetailsPage extends BasePage {
105 text: S.of(context).send,
106 isLoading:
107 transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
108 + isDisabled: transactionDetailsViewModel.sendViewModel.state is ExecutedSuccessfullyState,
109 color: Theme.of(context).colorScheme.primary,
110 textColor: Theme.of(context).colorScheme.onPrimary,
111 ))),
@@ -180,9 +181,9 @@ class RBFDetailsPage extends BasePage {
181 }
182
183 if (state is ExecutedSuccessfullyState) {
183 - WidgetsBinding.instance.addPostFrameCallback((_) {
184 + WidgetsBinding.instance.addPostFrameCallback((_) async {
185 if (context.mounted) {
185 - showModalBottomSheet<void>(
186 + final result = await showModalBottomSheet<bool>(
187 context: context,
188 isDismissible: false,
189 isScrollControlled: true,
@@ -214,6 +215,9 @@ class RBFDetailsPage extends BasePage {
215 );
216 },
217 );
218 + if (result == null) {
219 + transactionDetailsViewModel.sendViewModel.dismissTransaction();
220 + }
221 }
222 });
223 }