diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index b98e4e3b0..3a5c75926 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -83,10 +83,24 @@ class _ConfirmChangeNowSendViewState final coin = wallet.info.coin; final sendProgressController = ProgressAndSuccessController(); + var isSendingDialogOpen = false; + void closeSendingDialog() { + if (!context.mounted || !isSendingDialogOpen) { + return; + } + final rootNavigator = Navigator.of(context, rootNavigator: true); + if (rootNavigator.canPop()) { + rootNavigator.pop(); + } + isSendingDialogOpen = false; + } + + isSendingDialogOpen = true; unawaited( showDialog( context: context, + useRootNavigator: true, useSafeArea: false, barrierDismissible: false, builder: (context) { @@ -95,7 +109,7 @@ class _ConfirmChangeNowSendViewState controller: sendProgressController, ); }, - ), + ).whenComplete(() => isSendingDialogOpen = false), ); final time = Future.delayed(const Duration(milliseconds: 2500)); @@ -141,10 +155,8 @@ class _ConfirmChangeNowSendViewState // pop back to wallet if (context.mounted) { + closeSendingDialog(); if (Util.isDesktop) { - // pop sending dialog - Navigator.of(context, rootNavigator: true).pop(); - // one day we'll do routing right Navigator.of(context, rootNavigator: true).pop(); if (widget.fromDesktopStep4) { @@ -162,7 +174,7 @@ class _ConfirmChangeNowSendViewState ); // pop sending dialog - Navigator.of(context).pop(); + closeSendingDialog(); await showDialog( context: context, diff --git a/lib/pages/masternodes/create_masternode_view.dart b/lib/pages/masternodes/create_masternode_view.dart index 369272496..9916c04dc 100644 --- a/lib/pages/masternodes/create_masternode_view.dart +++ b/lib/pages/masternodes/create_masternode_view.dart @@ -54,7 +54,11 @@ class _CreateMasternodeDialogState extends ConsumerState { style: STextStyles.desktopH3(context), ), ), - const DesktopDialogCloseButton(), + DesktopDialogCloseButton( + onPressedOverride: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + ), ], ), Flexible( diff --git a/lib/pages/masternodes/masternodes_home_view.dart b/lib/pages/masternodes/masternodes_home_view.dart index 040505697..e50fd1035 100644 --- a/lib/pages/masternodes/masternodes_home_view.dart +++ b/lib/pages/masternodes/masternodes_home_view.dart @@ -4,7 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar_community/isar.dart'; +import 'package:tuple/tuple.dart'; +import '../../models/send_view_auto_fill_data.dart'; import '../../models/isar/models/blockchain_data/utxo.dart'; import '../../providers/global/wallets_provider.dart'; import '../../themes/stack_colors.dart'; @@ -22,6 +24,7 @@ import '../../widgets/desktop/primary_button.dart'; import '../../widgets/dialogs/s_dialog.dart'; import '../../widgets/loading_indicator.dart'; import '../../widgets/stack_dialog.dart'; +import '../send_view/send_view.dart'; import 'create_masternode_view.dart'; import 'sub_widgets/masternodes_list.dart'; import 'sub_widgets/masternodes_table_desktop.dart'; @@ -39,6 +42,11 @@ class MasternodesHomeView extends ConsumerStatefulWidget { } class _MasternodesHomeViewState extends ConsumerState { + static final BigInt _masternodeCollateralRaw = Amount.fromDecimal( + kMasterNodeValue, + fractionDigits: 8, + ).raw; + late Future> _masternodesFuture; bool _hasPromptedForCollateral = false; bool _isCheckingForCollateral = false; @@ -95,27 +103,153 @@ class _MasternodesHomeViewState extends ConsumerState { return null; } + Future< + ({String txid, int vout, String address, int confirmations, int required})? + > + _findPendingCollateralUtxo() async { + final wallet = ref.read(pWallets).getWallet(widget.walletId) as FiroWallet; + final List utxos = await wallet.mainDB + .getUTXOs(widget.walletId) + .findAll(); + final currentChainHeight = await wallet.chainHeight; + final requiredConfirms = wallet.cryptoCurrency.minConfirms; + final masternodeRaw = Amount.fromDecimal( + kMasterNodeValue, + fractionDigits: wallet.cryptoCurrency.fractionDigits, + ).raw.toInt(); + + ({String txid, int vout, String address, int confirmations, int required})? + bestPending; + + for (final utxo in utxos) { + if (utxo.value != masternodeRaw || + utxo.isBlocked || + utxo.used == true || + utxo.address == null) { + continue; + } + + final confirmations = utxo.getConfirmations(currentChainHeight); + final isConfirmed = utxo.isConfirmed( + currentChainHeight, + wallet.cryptoCurrency.minConfirms, + wallet.cryptoCurrency.minCoinbaseConfirms, + ); + + if (isConfirmed) { + continue; + } + + final candidate = ( + txid: utxo.txid, + vout: utxo.vout, + address: utxo.address!, + confirmations: confirmations, + required: requiredConfirms, + ); + + if (bestPending == null || + candidate.confirmations > bestPending.confirmations) { + bestPending = candidate; + } + } + + return bestPending; + } + Future _createMasternode() async { + final wallet = ref.read(pWallets).getWallet(widget.walletId) as FiroWallet; final collateral = await _findCollateralUtxo(); if (!mounted) { return; } if (collateral == null) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "No collateral found", - message: - "A masternode needs one confirmed, unblocked transparent " - "UTXO of exactly 1000 FIRO.\n\n" - "Total balance above 1000 FIRO is not enough if no single " - "1000 output exists. Also ensure fee is not subtracted from " - "the recipient amount when sending to yourself.", - desktopPopRootNavigator: Util.isDesktop, - maxWidth: Util.isDesktop ? 400 : null, - ), - ); + final pendingCollateral = await _findPendingCollateralUtxo(); + if (!mounted) { + return; + } + if (pendingCollateral != null) { + final message = + "Your 1000 FIRO collateral is on its way.\n\n" + "Waiting for confirmations...\n" + "Once confirmed, click Create Masternode again to continue."; + await showDialog( + context: context, + builder: (ctx) => StackOkDialog( + title: "Waiting for collateral confirmation", + message: message, + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 420 : null, + ), + ); + return; + } + + final spendableBalance = wallet.info.cachedBalance.spendable.raw; + if (spendableBalance < _masternodeCollateralRaw) { + await showDialog( + context: context, + builder: (ctx) => StackOkDialog( + title: "Not enough FIRO to create the collateral", + message: + "A masternode collateral is exactly 1000 FIRO on your transparent balance, plus a " + "small network fee to send it. Your spendable transparent balance is " + "below this amount.\n\n" + "Add more FIRO to your wallet, then click Create " + "Masternode again to continue.", + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 420 : null, + ), + ); + return; + } + + if (Util.isDesktop) { + final shouldOpenSend = await showDialog( + context: context, + builder: (ctx) => StackDialog( + title: "Set up your 1000 FIRO masternode collateral?", + message: + "Registering a masternode requires a 1000 FIRO collateral: " + "a single confirmed amount sitting in your wallet. We didn't " + "find one, but you have enough FIRO to create it.\n\n" + "We can help by opening the Send window with a new address " + "you own pre-filled, ready for you to send 1000 FIRO to it. " + "This consolidates your smaller amounts into the single 1000 " + "FIRO collateral you need. The network fee is paid from your " + "remaining balance.\n\n" + "Once you have sent it, wait for the transaction to confirm, " + "then click Create Masternode again to continue.", + leftButton: TextButton( + style: Theme.of( + ctx, + ).extension()!.getSecondaryEnabledButtonStyle(ctx), + onPressed: () => Navigator.of(ctx).pop(), + child: Text( + "Cancel", + style: STextStyles.button(ctx).copyWith( + color: Theme.of( + ctx, + ).extension()!.accentColorDark, + ), + ), + ), + rightButton: TextButton( + style: Theme.of( + ctx, + ).extension()!.getPrimaryEnabledButtonStyle(ctx), + onPressed: () => Navigator.of(ctx).pop(true), + child: Text("Open Send", style: STextStyles.button(ctx)), + ), + ), + ); + if (shouldOpenSend == true && mounted) { + await _openCreateCollateralSendFlow(wallet); + } + } else { + await _openCreateCollateralSendFlow(wallet); + } return; } @@ -147,6 +281,31 @@ class _MasternodesHomeViewState extends ConsumerState { } } + Future _openCreateCollateralSendFlow(FiroWallet wallet) async { + var selfAddress = await wallet.getCurrentReceivingAddress(); + if (selfAddress == null) { + await wallet.generateNewReceivingAddress(); + selfAddress = await wallet.getCurrentReceivingAddress(); + } + if (!mounted || selfAddress == null) { + return; + } + + await Navigator.of(context).pushNamed( + SendView.routeName, + arguments: Tuple3( + widget.walletId, + wallet.cryptoCurrency, + SendViewAutoFillData( + address: selfAddress.value, + contactLabel: "My FIRO address", + amount: kMasterNodeValue, + note: "Masternode collateral prep (1000 FIRO self-send).", + ), + ), + ); + } + Future _maybePromptForExistingCollateral() async { if (_hasPromptedForCollateral || _isCheckingForCollateral || !mounted) { return; diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 8f7eab592..732008dfb 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -24,8 +24,8 @@ import '../../models/isar/models/transaction_note.dart'; import '../../models/isar/ordinal.dart'; import '../../notifications/show_flush_bar.dart'; import '../../pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; +import '../../pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; -import '../../providers/global/global_nav_key_provider.dart'; import '../../providers/providers.dart'; import '../../providers/wallet/public_private_balance_state_provider.dart'; import '../../route_generator.dart'; @@ -57,7 +57,6 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/desktop/desktop_dialog.dart'; import '../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../widgets/desktop/primary_button.dart'; -import '../../widgets/dialogs/s_dialog.dart'; import '../../widgets/icon_widgets/x_icon.dart'; import '../../widgets/rounded_container.dart'; import '../../widgets/rounded_white_container.dart'; @@ -65,7 +64,6 @@ import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_text_field.dart'; import '../../widgets/textfield_icon_button.dart'; import '../../wl_gen/interfaces/libepiccash_interface.dart'; -import '../masternodes/create_masternode_view.dart'; import '../pinpad_views/lock_screen_view.dart'; import '../wallet_view/wallet_view.dart'; import 'sub_widgets/epic_slatepack_dialog.dart'; @@ -78,7 +76,7 @@ class ConfirmTransactionView extends ConsumerStatefulWidget { required this.txData, required this.walletId, required this.onSuccess, - this.routeOnSuccessName = WalletView.routeName, + this.routeOnSuccessName, this.isTradeTransaction = false, this.isPaynymTransaction = false, this.isPaynymNotificationTransaction = false, @@ -90,7 +88,7 @@ class ConfirmTransactionView extends ConsumerStatefulWidget { final TxData txData; final String walletId; - final String routeOnSuccessName; + final String? routeOnSuccessName; final bool isTradeTransaction; final bool isPaynymTransaction; final bool isPaynymNotificationTransaction; @@ -312,106 +310,28 @@ class _ConfirmTransactionViewState } } - Future _resolveFiroCollateralVout({ - required FiroWallet wallet, - required String txid, - required String recipientAddress, - required Amount amount, - }) async { - try { - final tx = await wallet.electrumXClient.getTransaction(txHash: txid); - final outputs = tx['vout']; - if (outputs is! List) { - return null; - } - - for (final output in outputs) { - if (output is! Map) { - continue; - } - final outputMap = Map.from(output); - final n = outputMap['n']; - final outputIndex = switch (n) { - int value => value, - String value => int.tryParse(value), - _ => null, - }; - if (outputIndex == null) { - continue; - } - - final valueDecimal = Decimal.tryParse(outputMap['value'].toString()); - if (valueDecimal == null) { - continue; - } - final outputAmount = Amount.fromDecimal( - valueDecimal, - fractionDigits: wallet.cryptoCurrency.fractionDigits, - ); - if (outputAmount != amount) { - continue; - } - - final scriptPubKey = outputMap['scriptPubKey']; - if (scriptPubKey is! Map) { - continue; - } - - final recipientAddresses = {}; - final addresses = scriptPubKey['addresses']; - if (addresses is List) { - recipientAddresses.addAll(addresses.whereType()); - } - - final address = scriptPubKey['address']; - if (address is String) { - recipientAddresses.add(address); - } - - if (recipientAddresses.contains(recipientAddress)) { - return outputIndex; - } - } - } catch (e, s) { - Logging.instance.w( - "Failed to resolve collateral vout for txid=$txid: $e", - error: e, - stackTrace: s, - ); - } - - return null; - } - - void _showMasternodeSubmittedDialog(BuildContext? rootContext, String txid) { - if (rootContext == null) { - return; - } - unawaited( - showDialog( - context: rootContext, - builder: (_) => StackOkDialog( - title: "Masternode Registration Submitted", - message: - "Masternode registration submitted, your masternode will " - "appear in the list after the tx is confirmed.\n\nTransaction " - "ID: $txid", - desktopPopRootNavigator: Util.isDesktop, - maxWidth: Util.isDesktop ? 400 : null, - ), - ), - ); - } - Future _attemptSend(BuildContext context) async { final wallet = ref.read(pWallets).getWallet(walletId); final coin = wallet.info.coin; final sendProgressController = ProgressAndSuccessController(); + var isSendingDialogOpen = true; + + void closeSendingDialog() { + if (!context.mounted || !isSendingDialogOpen) { + return; + } + final navigator = Navigator.of(context, rootNavigator: true); + if (navigator.canPop()) { + navigator.pop(); + } + isSendingDialogOpen = false; + } unawaited( showDialog( context: context, + useRootNavigator: true, useSafeArea: false, barrierDismissible: false, builder: (context) { @@ -420,7 +340,7 @@ class _ConfirmTransactionViewState controller: sendProgressController, ); }, - ), + ).whenComplete(() => isSendingDialogOpen = false), ); final time = Future.delayed(const Duration(milliseconds: 2500)); @@ -481,6 +401,7 @@ class _ConfirmTransactionViewState context, wallet as MimblewimblecoinWallet, ); + closeSendingDialog(); return; // Exit early, don't continue with normal transaction flow. } else { // Handle MWCMQS or HTTP transactions normally. @@ -504,6 +425,7 @@ class _ConfirmTransactionViewState context, wallet as EpiccashWallet, ); + closeSendingDialog(); return; // Exit early, don't continue with normal transaction flow. } else { // Handle Epicbox transactions normally. @@ -553,6 +475,8 @@ class _ConfirmTransactionViewState unawaited(wallet.refresh()); } + closeSendingDialog(); + widget.onSuccess.call(); // Check for 1000 FIRO transparent self-send → prompt MN registration @@ -583,97 +507,28 @@ class _ConfirmTransactionViewState .findFirst(); if (ownAddress != null && context.mounted) { - final collateralVout = await _resolveFiroCollateralVout( - wallet: wallet, - txid: confirmedTx.txid!, - recipientAddress: mnRecipient.address, - amount: masternodeAmount, + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Collateral transaction sent", + message: + "Your 1000 FIRO collateral transaction was sent " + "successfully. Once it confirms, open Masternodes and " + "click Create Masternode to continue.", + desktopPopRootNavigator: Util.isDesktop, + maxWidth: Util.isDesktop ? 420 : null, + ), ); - if (!context.mounted) { - return; - } - if (collateralVout == null) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Unable to determine collateral output index " - "automatically. Open Masternodes and select your " - "1000 FIRO UTXO manually.", - context: context, - ), - ); - } else { - navigatedToMN = true; - final rootContext = ref.read(pNavKey).currentContext; - - void completeMnParentNavigation() { - if (widget.onSuccessInsteadOfRouteOnSuccess == null) { - if (isDesktop) { - Navigator.of( - context, - ).popUntil(ModalRoute.withName(routeOnSuccessName)); - } else { - final navigator = Navigator.of(context); - navigator.popUntil( - ModalRoute.withName(routeOnSuccessName), - ); - } - } else { - widget.onSuccessInsteadOfRouteOnSuccess!.call(); - } - } - - completeMnParentNavigation(); - - if (isDesktop) { - if (rootContext != null && rootContext.mounted) { - unawaited( - showDialog( - context: rootContext, - barrierDismissible: true, - builder: (_) => SDialog( - child: CreateMasternodeView( - firoWalletId: walletId, - collateralTxid: confirmedTx.txid!, - collateralVout: collateralVout, - collateralAddress: mnRecipient.address, - ), - ), - ).then((result) { - if (result is String) { - _showMasternodeSubmittedDialog(rootContext, result); - } - }), - ); - } - } else { - final navContext = - (rootContext != null && rootContext.mounted) - ? rootContext - : context; - if (!navContext.mounted) { - return; - } - unawaited( - Navigator.of(navContext) - .pushNamed( - CreateMasternodeView.routeName, - arguments: { - 'walletId': walletId, - 'collateralTxid': confirmedTx.txid!, - 'collateralVout': collateralVout, - 'collateralAddress': mnRecipient.address, - }, - ) - .then((result) { - if (result is String) { - _showMasternodeSubmittedDialog(rootContext, result); - } - }), - ); + if (context.mounted) { + // Pop confirm + send; returns to the screen that opened send + // (e.g. Masternodes or desktop wallet) without relying on + // popUntil matching a route name in the nested navigator. + final navigator = Navigator.of(context); + for (var i = 0; i < 2 && navigator.canPop(); i++) { + navigator.pop(); } + navigatedToMN = true; } } } else if (mnRecipient != null && @@ -747,7 +602,7 @@ class _ConfirmTransactionViewState } on BadHttpAddressException catch (_) { if (context.mounted) { // pop building dialog - Navigator.of(context).pop(); + closeSendingDialog(); unawaited( showFloatingFlushBar( type: FlushBarType.warning, @@ -763,7 +618,7 @@ class _ConfirmTransactionViewState Logging.instance.e(message, error: e, stackTrace: s); // pop sending dialog if (context.mounted) { - Navigator.of(context).pop(); + closeSendingDialog(); await showDialog( context: context, @@ -840,7 +695,9 @@ class _ConfirmTransactionViewState isDesktop = Util.isDesktop; walletId = widget.walletId; - routeOnSuccessName = widget.routeOnSuccessName; + routeOnSuccessName = + widget.routeOnSuccessName ?? + (Util.isDesktop ? DesktopWalletView.routeName : WalletView.routeName); _noteFocusNode = FocusNode(); noteController = TextEditingController(); noteController.text = widget.txData.note ?? ""; @@ -981,8 +838,7 @@ class _ConfirmTransactionViewState AppBarBackButton( size: 40, iconSize: 24, - onPressed: () => - Navigator.of(context, rootNavigator: true).pop(), + onPressed: () => Navigator.of(context).pop(), ), Text( "Confirm $unit transaction", diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 94b5663c8..db5b2926d 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -16,6 +16,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:isar_community/isar.dart'; import 'package:tuple/tuple.dart'; import '../../models/epic_slatepack_models.dart'; @@ -81,6 +82,7 @@ import '../../widgets/stack_text_field.dart'; import '../../widgets/textfield_icon_button.dart'; import '../address_book_views/address_book_view.dart'; import '../coin_control/coin_control_view.dart'; +import '../masternodes/masternodes_home_view.dart'; import 'confirm_transaction_view.dart'; import 'sub_widgets/building_transaction_dialog.dart'; import 'sub_widgets/dual_balance_selection_sheet.dart'; @@ -142,6 +144,7 @@ class _SendViewState extends ConsumerState { late final bool hasOptionalMemo; late final bool isFiro; late final bool isEth; + late final bool _isMasternodeCollateralSelfSend; Amount? _cachedAmountToSend; String? _address; @@ -270,6 +273,82 @@ class _SendViewState extends ConsumerState { } } + Future _pickMyAddressForMasternodeCollateral() async { + final wallet = ref.read(pWallets).getWallet(walletId); + if (wallet is! FiroWallet) { + return; + } + + var currentAddress = await wallet.getCurrentReceivingAddress(); + if (currentAddress == null) { + await wallet.generateNewReceivingAddress(); + currentAddress = await wallet.getCurrentReceivingAddress(); + } + + final allWalletAddresses = await wallet.mainDB.isar.addresses + .where() + .walletIdEqualTo(walletId) + .findAll(); + + final transparentAddresses = allWalletAddresses + .where((e) => e.type != AddressType.spark) + .map((e) => e.value) + .where((String e) => e.isNotEmpty) + .toSet(); + + if (currentAddress != null && + wallet.cryptoCurrency.getAddressType(currentAddress.value) != + AddressType.spark) { + transparentAddresses.add(currentAddress.value); + } + + final addresses = {...transparentAddresses}.toList()..sort(); + + if (!mounted || addresses.isEmpty) { + return; + } + + final selectedAddress = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text("Choose your address"), + content: SizedBox( + width: 520, + child: ListView.builder( + shrinkWrap: true, + itemCount: addresses.length, + itemBuilder: (_, index) => ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + addresses[index], + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + onTap: () => Navigator.of(ctx).pop(addresses[index]), + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: const Text("Cancel"), + ), + ], + ), + ); + + if (selectedAddress == null) { + return; + } + + _address = selectedAddress; + sendToController.text = selectedAddress; + _setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = true; + }); + } + Future _scanQr() async { try { // ref @@ -867,13 +946,15 @@ class _SendViewState extends ConsumerState { } } + final shouldShowBuildingDialog = mounted && !Util.isDesktop; try { bool wasCancelled = false; - if (mounted) { + if (shouldShowBuildingDialog) { unawaited( showDialog( context: context, + useRootNavigator: false, useSafeArea: false, barrierDismissible: false, builder: (context) { @@ -885,8 +966,6 @@ class _SendViewState extends ConsumerState { BalanceType.private, onCancel: () { wasCancelled = true; - - Navigator.of(context).pop(); }, ); }, @@ -1062,8 +1141,10 @@ class _SendViewState extends ConsumerState { txData = txData.copyWith(noteOnChain: onChainNoteController.text); } - // pop building dialog - Navigator.of(context).pop(); + if (shouldShowBuildingDialog) { + // pop building dialog + Navigator.of(context, rootNavigator: false).pop(); + } unawaited( Navigator.of(context).push( @@ -1073,7 +1154,11 @@ class _SendViewState extends ConsumerState { txData: txData, walletId: walletId, isPaynymTransaction: isPaynymSend, - onSuccess: clearSendForm, + onSuccess: () { + if (mounted) { + clearSendForm(); + } + }, ), settings: const RouteSettings( name: ConfirmTransactionView.routeName, @@ -1085,8 +1170,10 @@ class _SendViewState extends ConsumerState { } catch (e, s) { Logging.instance.e("$e\n$s", error: e, stackTrace: s); if (mounted) { - // pop building dialog - Navigator.of(context).pop(); + if (shouldShowBuildingDialog) { + // pop building dialog + Navigator.of(context, rootNavigator: false).pop(); + } unawaited( showDialog( @@ -1266,8 +1353,14 @@ class _SendViewState extends ConsumerState { _data = widget.autoFillData; walletId = widget.walletId; clipboard = widget.clipboard; + _isMasternodeCollateralSelfSend = + (_data?.note.contains("Masternode collateral prep") ?? false) && isFiro; WidgetsBinding.instance.addPostFrameCallback((_) { + if (_isMasternodeCollateralSelfSend) { + ref.read(publicPrivateBalanceStateProvider.state).state = + BalanceType.public; + } ref.refresh(feeSheetSessionCacheProvider); ref.refresh(pIsExchangeAddress); }); @@ -1476,12 +1569,10 @@ class _SendViewState extends ConsumerState { backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 50)); - } - if (context.mounted) { + onPressed: () { + if (_isMasternodeCollateralSelfSend) { + Navigator.of(context).pop(); + } else { Navigator.of(context).pop(); } }, @@ -1830,6 +1921,18 @@ class _SendViewState extends ConsumerState { child: const AddressBookIcon(), ), + if (_isMasternodeCollateralSelfSend) + TextFieldIconButton( + semanticsLabel: + "My addresses button. Opens your wallet addresses for collateral self-send.", + key: const Key( + "sendViewMyAddressesButtonKey", + ), + onTap: + _pickMyAddressForMasternodeCollateral, + child: + const AddressBookIcon(), + ), if (sendToController .text .isEmpty) diff --git a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart index f7dc832f8..095bd9f0e 100644 --- a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart +++ b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart @@ -50,49 +50,29 @@ class _RestoringDialogState extends ConsumerState { @override Widget build(BuildContext context) { - final assetPath = ref.watch( - coinImageSecondaryProvider( - widget.coin, - ), - ); + final assetPath = ref.watch(coinImageSecondaryProvider(widget.coin)); if (Util.isDesktop) { return Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - "Generating transaction", - style: STextStyles.desktopH3(context), - ), - if (widget.isSpark) - const SizedBox( - height: 16, - ), + Text("Generating transaction", style: STextStyles.desktopH3(context)), + if (widget.isSpark) const SizedBox(height: 16), if (widget.isSpark) Text( "This may take a few minutes...", style: STextStyles.desktopSubtitleH2(context), ), - const SizedBox( - height: 40, - ), + const SizedBox(height: 40), assetPath.endsWith(".gif") - ? Image.file( - File( - assetPath, - ), - ) - : const RotatingArrows( - width: 40, - height: 40, - ), - const SizedBox( - height: 40, - ), + ? Image.file(File(assetPath)) + : const RotatingArrows(width: 40, height: 40), + const SizedBox(height: 40), SecondaryButton( buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { + Navigator.of(context).pop(); onCancel.call(); }, ), @@ -109,29 +89,20 @@ class _RestoringDialogState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ - Image.file( - File( - assetPath, - ), - ), + Image.file(File(assetPath)), Text( "Generating transaction", textAlign: TextAlign.center, style: STextStyles.pageTitleH2(context), ), - if (widget.isSpark) - const SizedBox( - height: 12, - ), + if (widget.isSpark) const SizedBox(height: 12), if (widget.isSpark) Text( "This may take a few minutes...", textAlign: TextAlign.center, style: STextStyles.w500_16(context), ), - const SizedBox( - height: 32, - ), + const SizedBox(height: 32), Row( children: [ const Spacer(), @@ -157,12 +128,10 @@ class _RestoringDialogState extends ConsumerState { ) : StackDialog( title: "Generating transaction", - message: - widget.isSpark ? "This may take a few minutes..." : null, - icon: const RotatingArrows( - width: 24, - height: 24, - ), + message: widget.isSpark + ? "This may take a few minutes..." + : null, + icon: const RotatingArrows(width: 24, height: 24), rightButton: TextButton( style: Theme.of(context) .extension()! diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart index 65bc9c4c7..7f4b84c05 100644 --- a/lib/wallets/wallet/impl/particl_wallet.dart +++ b/lib/wallets/wallet/impl/particl_wallet.dart @@ -512,7 +512,6 @@ class ParticlWallet ), witnessValue: insAndKeys[i].utxo.value, redeemScript: extraData[i].redeem, - isParticl: true, overridePrefix: cryptoCurrency.networkParams.bech32Hrp, ); } diff --git a/lib/widgets/stack_dialog.dart b/lib/widgets/stack_dialog.dart index 2c56aa7c0..3af1a0015 100644 --- a/lib/widgets/stack_dialog.dart +++ b/lib/widgets/stack_dialog.dart @@ -37,10 +37,9 @@ class StackDialogBase extends StatelessWidget { bottom: 16 + keyboardPaddingAmount, ), child: Column( - mainAxisAlignment: - !Util.isDesktop - ? MainAxisAlignment.end - : MainAxisAlignment.center, + mainAxisAlignment: !Util.isDesktop + ? MainAxisAlignment.end + : MainAxisAlignment.center, children: [ Flexible( child: SingleChildScrollView( @@ -48,8 +47,9 @@ class StackDialogBase extends StatelessWidget { borderRadius: BorderRadius.circular(20), child: Container( decoration: BoxDecoration( - color: - Theme.of(context).extension()!.popupBG, + color: Theme.of( + context, + ).extension()!.popupBG, borderRadius: BorderRadius.circular(20), ), child: Padding(padding: padding, child: child), @@ -199,30 +199,26 @@ class StackOkDialog extends StatelessWidget { const SizedBox(width: 8), Expanded( child: TextButton( - onPressed: - !Util.isDesktop - ? () { - Navigator.of(context).pop(); - onOkPressed?.call("OK"); + onPressed: !Util.isDesktop + ? () { + Navigator.of(context).pop(); + onOkPressed?.call("OK"); + } + : () { + if (desktopPopRootNavigator) { + Navigator.of(context, rootNavigator: true).pop(); + } else { + int count = 0; + Navigator.of( + context, + ).popUntil((_) => count++ >= 2); + // onOkPressed?.call("OK"); } - : () { - if (desktopPopRootNavigator) { - Navigator.of( - context, - rootNavigator: true, - ).pop(); - } else { - int count = 0; - Navigator.of( - context, - ).popUntil((_) => count++ >= 2); - // onOkPressed?.call("OK"); - } - }, + }, style: Theme.of(context) .extension()! .getPrimaryEnabledButtonStyle(context), - child: Text("Ok", style: STextStyles.button(context)), + child: Text("OK", style: STextStyles.button(context)), ), ), ],