/* * This file is part of Stack Wallet. * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. * Generated by Cypher Stack on 2023-05-26 * */ import 'dart:async'; import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/lib.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/isar/models/transaction_note.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount_formatter.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_provider.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; class ConfirmTransactionView extends ConsumerStatefulWidget { const ConfirmTransactionView({ Key? key, required this.txData, required this.walletId, required this.onSuccess, this.routeOnSuccessName = WalletView.routeName, this.isTradeTransaction = false, this.isPaynymTransaction = false, this.isPaynymNotificationTransaction = false, this.isTokenTx = false, this.onSuccessInsteadOfRouteOnSuccess, }) : super(key: key); static const String routeName = "/confirmTransactionView"; final TxData txData; final String walletId; final String routeOnSuccessName; final bool isTradeTransaction; final bool isPaynymTransaction; final bool isPaynymNotificationTransaction; final bool isTokenTx; final VoidCallback? onSuccessInsteadOfRouteOnSuccess; final VoidCallback onSuccess; @override ConsumerState createState() => _ConfirmTransactionViewState(); } class _ConfirmTransactionViewState extends ConsumerState { late final String walletId; late final String routeOnSuccessName; late final bool isDesktop; late final FocusNode _noteFocusNode; late final TextEditingController noteController; late final FocusNode _onChainNoteFocusNode; late final TextEditingController onChainNoteController; Future _attemptSend(BuildContext context) async { final wallet = ref.read(pWallets).getWallet(walletId); final coin = wallet.info.coin; final sendProgressController = ProgressAndSuccessController(); unawaited( showDialog( context: context, useSafeArea: false, barrierDismissible: false, builder: (context) { return SendingTransactionDialog( coin: coin, controller: sendProgressController, ); }, ), ); final time = Future.delayed( const Duration( milliseconds: 2500, ), ); final List txids = []; Future txDataFuture; final note = noteController.text; try { if (widget.isTokenTx) { txDataFuture = ref.read(pCurrentTokenWallet)!.confirmSend(txData: widget.txData); } else if (widget.isPaynymNotificationTransaction) { txDataFuture = (wallet as PaynymInterface) .broadcastNotificationTx(txData: widget.txData); } else if (widget.isPaynymTransaction) { txDataFuture = wallet.confirmSend(txData: widget.txData); } else { if (wallet is FiroWallet) { switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.public: if (widget.txData.sparkMints == null) { txDataFuture = wallet.confirmSend(txData: widget.txData); } else { txDataFuture = wallet.confirmSparkMintTransactions(txData: widget.txData); } break; case FiroType.lelantus: txDataFuture = wallet.confirmSendLelantus(txData: widget.txData); break; case FiroType.spark: txDataFuture = wallet.confirmSendSpark(txData: widget.txData); break; } } else { if (coin == Coin.epicCash) { txDataFuture = wallet.confirmSend( txData: widget.txData.copyWith( noteOnChain: onChainNoteController.text, ), ); } else { txDataFuture = wallet.confirmSend(txData: widget.txData); } } } final results = await Future.wait([ txDataFuture, time, ]); sendProgressController.triggerSuccess?.call(); await Future.delayed(const Duration(seconds: 5)); if (wallet is FiroWallet && (results.first as TxData).sparkMints != null) { txids.addAll((results.first as TxData).sparkMints!.map((e) => e.txid!)); } else { txids.add((results.first as TxData).txid!); } ref.refresh(desktopUseUTXOs); // save note for (final txid in txids) { await ref.read(mainDBProvider).putTransactionNote( TransactionNote( walletId: walletId, txid: txid, value: note, ), ); } if (widget.isTokenTx) { unawaited(ref.read(pCurrentTokenWallet)!.refresh()); } else { unawaited(wallet.refresh()); } widget.onSuccess.call(); // pop back to wallet if (mounted) { if (widget.onSuccessInsteadOfRouteOnSuccess == null) { Navigator.of(context) .popUntil(ModalRoute.withName(routeOnSuccessName)); } else { widget.onSuccessInsteadOfRouteOnSuccess!.call(); } } } on BadEpicHttpAddressException catch (_) { if (mounted) { // pop building dialog Navigator.of(context).pop(); unawaited( showFloatingFlushBar( type: FlushBarType.warning, message: "Connection failed. Please check the address and try again.", context: context, ), ); return; } } catch (e, s) { //todo: comeback to this debugPrint("$e\n$s"); // pop sending dialog Navigator.of(context).pop(); await showDialog( context: context, useSafeArea: false, barrierDismissible: true, builder: (context) { if (isDesktop) { return DesktopDialog( maxWidth: 450, child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Broadcast transaction failed", style: STextStyles.desktopH3(context), ), const SizedBox( height: 24, ), Flexible( child: SingleChildScrollView( child: SelectableText( e.toString(), style: STextStyles.smallMed14(context), ), ), ), const SizedBox( height: 56, ), Row( children: [ const Spacer(), Expanded( child: PrimaryButton( buttonHeight: ButtonHeight.l, label: "Ok", onPressed: Navigator.of(context).pop, ), ), ], ) ], ), ), ); } else { return StackDialog( title: "Broadcast transaction failed", message: e.toString(), rightButton: TextButton( style: Theme.of(context) .extension()! .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.button(context).copyWith( color: Theme.of(context) .extension()! .accentColorDark), ), onPressed: () { Navigator.of(context).pop(); }, ), ); } }, ); } } @override void initState() { isDesktop = Util.isDesktop; walletId = widget.walletId; routeOnSuccessName = widget.routeOnSuccessName; _noteFocusNode = FocusNode(); noteController = TextEditingController(); noteController.text = widget.txData.note ?? ""; _onChainNoteFocusNode = FocusNode(); onChainNoteController = TextEditingController(); onChainNoteController.text = widget.txData.noteOnChain ?? ""; super.initState(); } @override void dispose() { noteController.dispose(); onChainNoteController.dispose(); _noteFocusNode.dispose(); _onChainNoteFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final coin = ref.watch(pWalletCoin(walletId)); final String unit; if (widget.isTokenTx) { unit = ref.watch( pCurrentTokenWallet.select((value) => value!.tokenContract.symbol)); } else { unit = coin.ticker; } final Amount? fee; final Amount amountWithoutChange; final wallet = ref.watch(pWallets).getWallet(walletId); if (wallet is FiroWallet) { switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.public: if (widget.txData.sparkMints != null) { fee = widget.txData.sparkMints! .map((e) => e.fee!) .reduce((value, element) => value += element); amountWithoutChange = widget.txData.sparkMints! .map((e) => e.amountSpark!) .reduce((value, element) => value += element); } else { fee = widget.txData.fee; amountWithoutChange = widget.txData.amountWithoutChange!; } break; case FiroType.lelantus: fee = widget.txData.fee; amountWithoutChange = widget.txData.amountWithoutChange!; break; case FiroType.spark: fee = widget.txData.fee; amountWithoutChange = (widget.txData.amountWithoutChange ?? Amount.zeroWith( fractionDigits: wallet.cryptoCurrency.fractionDigits)) + (widget.txData.amountSparkWithoutChange ?? Amount.zeroWith( fractionDigits: wallet.cryptoCurrency.fractionDigits)); break; } } else { fee = widget.txData.fee; amountWithoutChange = widget.txData.amountWithoutChange!; } return ConditionalParent( condition: !isDesktop, builder: (child) => Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( backgroundColor: Theme.of(context).extension()!.background, leading: AppBarBackButton( onPressed: () async { // if (FocusScope.of(context).hasFocus) { // FocusScope.of(context).unfocus(); // await Future.delayed(Duration(milliseconds: 50)); // } Navigator.of(context).pop(); }, ), title: Text( "Confirm transaction", style: STextStyles.navBarTitle(context), ), ), body: LayoutBuilder( builder: (builderContext, constraints) { return Padding( padding: const EdgeInsets.only( left: 12, top: 12, right: 12, ), child: SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints( minHeight: constraints.maxHeight - 24, ), child: IntrinsicHeight( child: Padding( padding: const EdgeInsets.all(4), child: child, ), ), ), ), ); }, ), ), ), child: ConditionalParent( condition: isDesktop, builder: (child) => Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ AppBarBackButton( size: 40, iconSize: 24, onPressed: () => Navigator.of( context, rootNavigator: true, ).pop(), ), Text( "Confirm $unit transaction", style: STextStyles.desktopH3(context), ), ], ), Flexible( child: SingleChildScrollView( child: child, ), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, children: [ if (!isDesktop) Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( "Send $unit", style: STextStyles.pageTitleH1(context), ), const SizedBox( height: 12, ), RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( widget.isPaynymTransaction ? "PayNym recipient" : "Recipient", style: STextStyles.smallMed12(context), ), const SizedBox( height: 4, ), Text( widget.isPaynymTransaction ? widget.txData.paynymAccountLite!.nymName : widget.txData.recipients?.first.address ?? widget.txData.sparkRecipients!.first.address, style: STextStyles.itemSubtitle12(context), ), ], ), ), const SizedBox( height: 12, ), RoundedWhiteContainer( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Amount", style: STextStyles.smallMed12(context), ), SelectableText( ref.watch(pAmountFormatter(coin)).format( amountWithoutChange, ethContract: ref .watch(pCurrentTokenWallet) ?.tokenContract, ), style: STextStyles.itemSubtitle12(context), textAlign: TextAlign.right, ), ], ), ), if (coin != Coin.banano && coin != Coin.nano) const SizedBox( height: 12, ), if (coin != Coin.banano && coin != Coin.nano) RoundedWhiteContainer( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Transaction fee", style: STextStyles.smallMed12(context), ), SelectableText( ref.watch(pAmountFormatter(coin)).format(fee!), style: STextStyles.itemSubtitle12(context), textAlign: TextAlign.right, ), ], ), ), if (widget.txData.fee != null && widget.txData.vSize != null) const SizedBox( height: 12, ), if (widget.txData.fee != null && widget.txData.vSize != null) RoundedWhiteContainer( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "sats/vByte", style: STextStyles.smallMed12(context), ), const SizedBox( height: 4, ), SelectableText( "~${fee!.raw.toInt() ~/ widget.txData.vSize!}", style: STextStyles.itemSubtitle12(context), ), ], ), ), if (coin == Coin.epicCash && widget.txData.noteOnChain!.isNotEmpty) const SizedBox( height: 12, ), if (coin == Coin.epicCash && widget.txData.noteOnChain!.isNotEmpty) RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( "On chain note", style: STextStyles.smallMed12(context), ), const SizedBox( height: 4, ), SelectableText( widget.txData.noteOnChain!, style: STextStyles.itemSubtitle12(context), ), ], ), ), if (widget.txData.note!.isNotEmpty) const SizedBox( height: 12, ), if (widget.txData.note!.isNotEmpty) RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( (coin == Coin.epicCash) ? "Local Note" : "Note", style: STextStyles.smallMed12(context), ), const SizedBox( height: 4, ), SelectableText( widget.txData.note!, style: STextStyles.itemSubtitle12(context), ), ], ), ), ], ), if (isDesktop) Padding( padding: const EdgeInsets.only( top: 16, left: 32, right: 32, bottom: 50, ), child: RoundedWhiteContainer( padding: const EdgeInsets.all(0), borderColor: Theme.of(context).extension()!.background, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( decoration: BoxDecoration( color: Theme.of(context) .extension()! .background, borderRadius: BorderRadius.only( topLeft: Radius.circular( Constants.size.circularBorderRadius, ), topRight: Radius.circular( Constants.size.circularBorderRadius, ), ), ), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 22, ), child: Row( children: [ SvgPicture.file( File( ref.watch( themeProvider.select( (value) => value.assets.send, ), ), ), width: 32, height: 32, ), const SizedBox( width: 16, ), Text( "Send $unit", style: STextStyles.desktopTextMedium(context), ), ], ), ), ), Padding( padding: const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Amount", style: STextStyles.desktopTextExtraExtraSmall( context), ), const SizedBox( height: 2, ), Builder( builder: (context) { final externalCalls = ref.watch( prefsChangeNotifierProvider.select( (value) => value.externalCalls)); String fiatAmount = "N/A"; if (externalCalls) { final price = widget.isTokenTx ? ref .read( priceAnd24hChangeNotifierProvider) .getTokenPrice( ref .read(pCurrentTokenWallet)! .tokenContract .address, ) .item1 : ref .read( priceAnd24hChangeNotifierProvider) .getPrice(coin) .item1; if (price > Decimal.zero) { fiatAmount = (amountWithoutChange.decimal * price) .toAmount(fractionDigits: 2) .fiatString( locale: ref .read( localeServiceChangeNotifierProvider) .locale, ); } } return Row( children: [ SelectableText( ref.watch(pAmountFormatter(coin)).format( amountWithoutChange, ethContract: ref .read(pCurrentTokenWallet) ?.tokenContract), style: STextStyles .desktopTextExtraExtraSmall( context) .copyWith( color: Theme.of(context) .extension()! .textDark, ), ), if (externalCalls) Text( " | ", style: STextStyles .desktopTextExtraExtraSmall( context), ), if (externalCalls) SelectableText( "~$fiatAmount ${ref.watch(prefsChangeNotifierProvider.select( (value) => value.currency, ))}", style: STextStyles .desktopTextExtraExtraSmall( context), ), ], ); }, ), ], ), ), Container( height: 1, color: Theme.of(context) .extension()! .background, ), Padding( padding: const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.isPaynymTransaction ? "PayNym recipient" : "Send to", style: STextStyles.desktopTextExtraExtraSmall( context), ), const SizedBox( height: 2, ), SelectableText( // TODO: [prio=med] spark transaction specifics - better handling widget.isPaynymTransaction ? widget.txData.paynymAccountLite!.nymName : widget.txData.recipients?.first.address ?? widget.txData.sparkRecipients!.first .address, style: STextStyles.desktopTextExtraExtraSmall( context) .copyWith( color: Theme.of(context) .extension()! .textDark, ), ) ], ), ), if (widget.isPaynymTransaction) Container( height: 1, color: Theme.of(context) .extension()! .background, ), if (widget.isPaynymTransaction) Padding( padding: const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Transaction fee", style: STextStyles.desktopTextExtraExtraSmall( context), ), const SizedBox( height: 2, ), SelectableText( ref.watch(pAmountFormatter(coin)).format(fee!), style: STextStyles.desktopTextExtraExtraSmall( context) .copyWith( color: Theme.of(context) .extension()! .textDark, ), ), ], ), ), // Container( // height: 1, // color: Theme.of(context) // .extension()! // .background, // ), // Padding( // padding: const EdgeInsets.all(12), // child: Column( // mainAxisSize: MainAxisSize.min, // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Text( // "Note", // style: STextStyles.desktopTextExtraExtraSmall( // context), // ), // const SizedBox( // height: 2, // ), // Text( // transactionInfo["note"] as String, // style: STextStyles.desktopTextExtraExtraSmall( // context) // .copyWith( // color: Theme.of(context) // .extension()! // .textDark, // ), // ) // ], // ), // ), ], ), ), ), if (isDesktop) Padding( padding: const EdgeInsets.only( left: 32, right: 32, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (coin == Coin.epicCash) Text( "On chain Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), if (coin == Coin.epicCash) const SizedBox( height: 8, ), if (coin == Coin.epicCash) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), child: TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, maxLength: 256, controller: onChainNoteController, focusNode: _onChainNoteFocusNode, style: STextStyles.field(context), onChanged: (_) => setState(() {}), decoration: standardInputDecoration( "Type something...", _onChainNoteFocusNode, context, ).copyWith( suffixIcon: onChainNoteController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( children: [ TextFieldIconButton( child: const XIcon(), onTap: () async { setState(() { onChainNoteController.text = ""; }); }, ), ], ), ), ) : null, ), ), ), if (coin == Coin.epicCash) const SizedBox( height: 12, ), SelectableText( (coin == Coin.epicCash) ? "Local Note (optional)" : "Note (optional)", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! .textFieldActiveSearchIconRight, ), textAlign: TextAlign.left, ), const SizedBox( height: 10, ), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), child: TextField( minLines: 1, maxLines: 5, autocorrect: isDesktop ? false : true, enableSuggestions: isDesktop ? false : true, controller: noteController, focusNode: _noteFocusNode, style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! .textFieldActiveText, height: 1.8, ), onChanged: (_) => setState(() {}), decoration: standardInputDecoration( "Type something...", _noteFocusNode, context, desktopMed: true, ).copyWith( contentPadding: const EdgeInsets.only( left: 16, top: 11, bottom: 12, right: 5, ), suffixIcon: noteController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( children: [ TextFieldIconButton( child: const XIcon(), onTap: () async { setState( () => noteController.text = "", ); }, ), ], ), ), ) : null, ), ), ), const SizedBox( height: 20, ) ], ), ), if (isDesktop && !widget.isPaynymTransaction) Padding( padding: const EdgeInsets.only( left: 32, ), child: Text( "Transaction fee", style: STextStyles.desktopTextExtraExtraSmall(context), ), ), if (isDesktop && !widget.isPaynymTransaction) Padding( padding: const EdgeInsets.only( top: 10, left: 32, right: 32, ), child: RoundedContainer( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ), color: Theme.of(context) .extension()! .textFieldDefaultBG, child: SelectableText( ref.watch(pAmountFormatter(coin)).format(fee!), style: STextStyles.itemSubtitle(context), ), ), ), if (isDesktop && !widget.isPaynymTransaction && widget.txData.fee != null && widget.txData.vSize != null) Padding( padding: const EdgeInsets.only( left: 32, ), child: Text( "sats/vByte", style: STextStyles.desktopTextExtraExtraSmall(context), ), ), if (isDesktop && !widget.isPaynymTransaction && widget.txData.fee != null && widget.txData.vSize != null) Padding( padding: const EdgeInsets.only( top: 10, left: 32, right: 32, ), child: RoundedContainer( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ), color: Theme.of(context) .extension()! .textFieldDefaultBG, child: SelectableText( "~${fee!.raw.toInt() ~/ widget.txData.vSize!}", style: STextStyles.itemSubtitle(context), ), ), ), if (!isDesktop) const Spacer(), SizedBox( height: isDesktop ? 23 : 12, ), if (!widget.isTokenTx) Padding( padding: isDesktop ? const EdgeInsets.symmetric( horizontal: 32, ) : const EdgeInsets.all(0), child: RoundedContainer( padding: isDesktop ? const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ) : const EdgeInsets.all(12), color: Theme.of(context) .extension()! .snackBarBackSuccess, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( isDesktop ? "Total amount to send" : "Total amount", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) .extension()! .textConfirmTotalAmount, ) : STextStyles.titleBold12(context).copyWith( color: Theme.of(context) .extension()! .textConfirmTotalAmount, ), ), SelectableText( ref .watch(pAmountFormatter(coin)) .format(amountWithoutChange + fee!), style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) .extension()! .textConfirmTotalAmount, ) : STextStyles.itemSubtitle12(context).copyWith( color: Theme.of(context) .extension()! .textConfirmTotalAmount, ), textAlign: TextAlign.right, ), ], ), ), ), SizedBox( height: isDesktop ? 28 : 16, ), Padding( padding: isDesktop ? const EdgeInsets.symmetric( horizontal: 32, ) : const EdgeInsets.all(0), child: PrimaryButton( label: "Send", buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: () async { final dynamic unlocked; if (isDesktop) { unlocked = await showDialog( context: context, builder: (context) => DesktopDialog( maxWidth: 580, maxHeight: double.infinity, child: Column( mainAxisSize: MainAxisSize.min, children: [ const Row( mainAxisAlignment: MainAxisAlignment.end, children: [ DesktopDialogCloseButton(), ], ), Padding( padding: const EdgeInsets.only( left: 32, right: 32, bottom: 32, ), child: DesktopAuthSend( coin: coin, ), ), ], ), ), ); } else { unlocked = await Navigator.push( context, RouteGenerator.getRoute( shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, builder: (_) => const LockscreenView( showBackButton: true, popOnSuccess: true, routeOnSuccessArguments: true, routeOnSuccess: "", biometricsCancelButtonString: "CANCEL", biometricsLocalizedReason: "Authenticate to send transaction", biometricsAuthenticationTitle: "Confirm Transaction", ), settings: const RouteSettings(name: "/confirmsendlockscreen"), ), ); } if (mounted) { if (unlocked == true) { unawaited(_attemptSend(context)); } else { unawaited( showFloatingFlushBar( type: FlushBarType.warning, message: Util.isDesktop ? "Invalid passphrase" : "Invalid PIN", context: context), ); } } }, ), ), if (isDesktop) const SizedBox( height: 32, ), ], ), ), ); } }