/* * 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/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../app_config.dart'; import '../../models/exchange/change_now/exchange_transaction_status.dart'; import '../../models/isar/models/blockchain_data/transaction.dart'; import '../../models/isar/stack_theme.dart'; import '../../notifications/show_flush_bar.dart'; import '../../providers/global/trades_service_provider.dart'; import '../../providers/providers.dart'; import '../../route_generator.dart'; import '../../services/exchange/change_now/change_now_exchange.dart'; import '../../services/exchange/exchange.dart'; import '../../services/exchange/majestic_bank/majestic_bank_exchange.dart'; import '../../services/exchange/nanswap/nanswap_exchange.dart'; import '../../services/exchange/simpleswap/simpleswap_exchange.dart'; import '../../services/exchange/trocador/trocador_exchange.dart'; import '../../themes/stack_colors.dart'; import '../../themes/theme_providers.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/amount/amount_formatter.dart'; import '../../utilities/assets.dart'; import '../../utilities/clipboard_interface.dart'; import '../../utilities/constants.dart'; import '../../utilities/format.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../widgets/background.dart'; import '../../widgets/conditional_parent.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; import '../../widgets/desktop/desktop_dialog.dart'; import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/qr.dart'; import '../../widgets/rounded_container.dart'; import '../../widgets/rounded_white_container.dart'; import '../../widgets/stack_dialog.dart'; import '../wallet_view/transaction_views/edit_note_view.dart'; import '../wallet_view/transaction_views/transaction_details_view.dart'; import 'edit_trade_note_view.dart'; import 'send_from_view.dart'; class TradeDetailsView extends ConsumerStatefulWidget { const TradeDetailsView({ super.key, required this.tradeId, required this.transactionIfSentFromStack, required this.walletId, required this.walletName, this.clipboard = const ClipboardWrapper(), }); static const String routeName = "/tradeDetails"; final String tradeId; final ClipboardInterface clipboard; final Transaction? transactionIfSentFromStack; final String? walletId; final String? walletName; @override ConsumerState createState() => _TradeDetailsViewState(); } class _TradeDetailsViewState extends ConsumerState { late final String tradeId; late final ClipboardInterface clipboard; late final Transaction? transactionIfSentFromStack; late final String? walletId; @override initState() { tradeId = widget.tradeId; clipboard = widget.clipboard; transactionIfSentFromStack = widget.transactionIfSentFromStack; walletId = widget.walletId; if (ref.read(prefsChangeNotifierProvider).externalCalls) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { final trade = ref .read(tradesServiceProvider) .trades .firstWhere((e) => e.tradeId == tradeId); if (mounted) { final exchange = Exchange.fromName(trade.exchangeName); final response = await exchange.updateTrade(trade); if (mounted && response.value != null) { await ref .read(tradesServiceProvider) .edit(trade: response.value!, shouldNotifyListeners: true); } } }); } super.initState(); } String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) { ChangeNowTransactionStatus? status; try { if (statusString.toLowerCase().startsWith("waiting")) { statusString = "Waiting"; } status = changeNowTransactionStatusFromStringIgnoreCase(statusString); } on ArgumentError catch (_) { switch (statusString.toLowerCase()) { case "funds confirming": case "processing payment": return assets.txExchangePending; case "completed": return assets.txExchange; default: status = ChangeNowTransactionStatus.Failed; } } switch (status) { case ChangeNowTransactionStatus.New: case ChangeNowTransactionStatus.Waiting: case ChangeNowTransactionStatus.Confirming: case ChangeNowTransactionStatus.Exchanging: case ChangeNowTransactionStatus.Sending: case ChangeNowTransactionStatus.Refunded: case ChangeNowTransactionStatus.Verifying: return assets.txExchangePending; case ChangeNowTransactionStatus.Finished: return assets.txExchange; case ChangeNowTransactionStatus.Failed: return assets.txExchangeFailed; } } @override Widget build(BuildContext context) { final bool sentFromStack = transactionIfSentFromStack != null && walletId != null; final trade = ref.watch( tradesServiceProvider.select( (value) => value.trades.firstWhere((e) => e.tradeId == tradeId), ), ); final bool hasTx = sentFromStack || !(trade.status == "New" || trade.status == "new" || trade.status == "Waiting" || trade.status == "waiting" || trade.status == "Refunded" || trade.status == "refunded" || trade.status == "Closed" || trade.status == "closed" || trade.status == "Expired" || trade.status == "expired" || trade.status == "Failed" || trade.status == "failed"); //todo: check if print needed // debugPrint("walletId: $walletId"); // debugPrint("transactionIfSentFromStack: $transactionIfSentFromStack"); // debugPrint("sentFromStack: $sentFromStack"); // debugPrint("hasTx: $hasTx"); // debugPrint("trade: ${trade.toString()}"); final sendAmount = Decimal.tryParse(trade.payInAmount) ?? Decimal.parse("-1"); final isDesktop = Util.isDesktop; final showSendFromStackButton = !hasTx && !["xmr", "monero", "wow", "wownero"] .contains(trade.payInCurrency.toLowerCase()) && AppConfig.isStackCoin(trade.payInCurrency) && (trade.status == "New" || trade.status == "new" || trade.status == "waiting" || trade.status == "Waiting"); 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 { Navigator.of(context).pop(); }, ), title: Text( "Trade details", style: STextStyles.navBarTitle(context), ), ), body: Padding( padding: const EdgeInsets.all(12), child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(4), child: child, ), ), ), ), ), child: Padding( padding: isDesktop ? const EdgeInsets.only(left: 32) : const EdgeInsets.all(0), child: BranchedParent( condition: isDesktop, conditionBranchBuilder: (children) => Padding( padding: const EdgeInsets.only( right: 20, ), child: Padding( padding: const EdgeInsets.only( right: 12, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ RoundedWhiteContainer( borderColor: Theme.of(context) .extension()! .backgroundAppBar, padding: const EdgeInsets.all(0), child: ListView( primary: false, shrinkWrap: true, children: children, ), ), if (showSendFromStackButton) const SizedBox( height: 32, ), if (showSendFromStackButton) SecondaryButton( label: "Send from ${AppConfig.prefix}", buttonHeight: ButtonHeight.l, onPressed: () { CryptoCurrency coin; try { coin = AppConfig.getCryptoCurrencyForTicker( trade.payInCurrency, )!; } catch (_) { coin = AppConfig.getCryptoCurrencyByPrettyName( trade.payInCurrency, ); } final amount = Amount.fromDecimal( sendAmount, fractionDigits: coin.fractionDigits, ); final address = trade.payInAddress; Navigator.of(context).pushNamed( SendFromView.routeName, arguments: Tuple4( coin, amount, address, trade, ), ); }, ), const SizedBox( height: 32, ), ], ), ), ), otherBranchBuilder: (children) => Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, children: children, ), children: [ RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12), child: Container( decoration: isDesktop ? BoxDecoration( color: Theme.of(context) .extension()! .backgroundAppBar, borderRadius: BorderRadius.vertical( top: Radius.circular( Constants.size.circularBorderRadius, ), ), ) : null, child: Padding( padding: isDesktop ? const EdgeInsets.all(12) : const EdgeInsets.all(0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (isDesktop) Row( children: [ SvgPicture.file( File( _fetchIconAssetForStatus( trade.status, ref.watch(themeAssetsProvider), ), ), width: 32, height: 32, ), const SizedBox( width: 16, ), SelectableText( "Swap service", style: STextStyles.desktopTextMedium(context), ), ], ), Column( crossAxisAlignment: isDesktop ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ SelectableText( "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", style: STextStyles.titleBold12(context), ), const SizedBox( height: 4, ), Builder( builder: (context) { String text; try { final coin = AppConfig.getCryptoCurrencyForTicker( trade.payInCurrency, )!; final amount = sendAmount.toAmount( fractionDigits: coin.fractionDigits, ); text = ref .watch(pAmountFormatter(coin)) .format(amount); } catch (_) { text = sendAmount.toStringAsFixed( trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, ); } return SelectableText( "-$text ${trade.payInCurrency.toUpperCase()}", style: STextStyles.itemSubtitle(context), ); }, ), ], ), if (!isDesktop) Container( width: 32, height: 32, decoration: BoxDecoration( borderRadius: BorderRadius.circular(32), ), child: Center( child: SvgPicture.file( File( _fetchIconAssetForStatus( trade.status, ref.watch(themeAssetsProvider), ), ), width: 32, height: 32, ), ), ), ], ), ), ), ), isDesktop ? const _Divider() : const SizedBox( height: 12, ), RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Status", style: STextStyles.itemSubtitle(context), ), if (trade.exchangeName == MajesticBankExchange.exchangeName && trade.status == "Completed") Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( onTap: () { showDialog( context: context, builder: (context) => const StackOkDialog( title: "Trade Info", message: "Majestic Bank does not store order data indefinitely", ), ); }, child: SvgPicture.asset( Assets.svg.circleInfo, height: 20, width: 20, color: Theme.of(context) .extension()! .infoItemIcons, ), ), ], ), ], ), const SizedBox( height: 4, ), SelectableText( trade.status, style: STextStyles.itemSubtitle(context).copyWith( color: Theme.of(context) .extension()! .colorForStatus(trade.status), ), ), ], ), ), if (!sentFromStack && !hasTx) isDesktop ? const _Divider() : const SizedBox( height: 12, ), if (!sentFromStack && !hasTx) RoundedContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), color: isDesktop ? Theme.of(context).extension()!.popupBG : Theme.of(context) .extension()! .warningBackground, child: ConditionalParent( condition: isDesktop, builder: (child) => Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Amount", style: STextStyles.desktopTextExtraExtraSmall( context, ), ), const SizedBox( height: 2, ), Text( "${trade.payInAmount} ${trade.payInCurrency.toUpperCase()}", style: STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( color: Theme.of(context) .extension()! .textDark, ), ), ], ), IconCopyButton( data: trade.payInAmount, ), ], ), const SizedBox( height: 6, ), child, ], ), child: RichText( text: TextSpan( text: "You must send at least ${sendAmount.toStringAsFixed( trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, )} ${trade.payInCurrency.toUpperCase()}. ", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) .extension()! .accentColorRed, ) : STextStyles.label(context).copyWith( color: Theme.of(context) .extension()! .warningForeground, ), children: [ TextSpan( text: "If you send less than ${sendAmount.toStringAsFixed( trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( color: Theme.of(context) .extension()! .accentColorRed, ) : STextStyles.label(context).copyWith( color: Theme.of(context) .extension()! .warningForeground, ), ), ], ), ), ), ), if (sentFromStack) isDesktop ? const _Divider() : const SizedBox( height: 12, ), if (sentFromStack) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Sent from", style: STextStyles.itemSubtitle(context), ), const SizedBox( height: 4, ), SelectableText( widget.walletName!, style: STextStyles.itemSubtitle12(context), ), const SizedBox( height: 10, ), CustomTextButton( text: "View transaction", onTap: () { final coin = AppConfig.getCryptoCurrencyForTicker( trade.payInCurrency, )!; if (isDesktop) { Navigator.of(context).push( FadePageRoute( DesktopDialog( maxHeight: MediaQuery.of(context).size.height - 64, maxWidth: 580, child: TransactionDetailsView( coin: coin, transaction: transactionIfSentFromStack!, walletId: walletId!, ), ), const RouteSettings( name: TransactionDetailsView.routeName, ), ), ); } else { Navigator.of(context).pushNamed( TransactionDetailsView.routeName, arguments: Tuple3( transactionIfSentFromStack!, coin, walletId!, ), ); } }, ), ], ), ), if (sentFromStack) isDesktop ? const _Divider() : const SizedBox( height: 12, ), if (sentFromStack) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${trade.exchangeName} address", style: STextStyles.itemSubtitle(context), ), const SizedBox( height: 4, ), Row( children: [ Flexible( child: SelectableText( trade.payInAddress, style: STextStyles.itemSubtitle12(context), ), ), ], ), ], ), ), if (isDesktop) IconCopyButton( data: trade.payInAddress, ), ], ), ), if (!sentFromStack && !hasTx) isDesktop ? const _Divider() : const SizedBox( height: 12, ), if (!sentFromStack && !hasTx) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Send ${trade.payInCurrency.toUpperCase()} to this address", style: STextStyles.itemSubtitle(context), ), isDesktop ? IconCopyButton( data: trade.payInAddress, ) : GestureDetector( onTap: () async { final address = trade.payInAddress; await Clipboard.setData( ClipboardData( text: address, ), ); if (context.mounted) { unawaited( showFloatingFlushBar( type: FlushBarType.info, message: "Copied to clipboard", context: context, ), ); } }, child: Row( children: [ SvgPicture.asset( Assets.svg.copy, width: 12, height: 12, color: Theme.of(context) .extension()! .infoItemIcons, ), const SizedBox( width: 4, ), Text( "Copy", style: STextStyles.link2(context), ), ], ), ), ], ), const SizedBox( height: 4, ), Row( children: [ Expanded( child: SelectableText( trade.payInAddress, style: STextStyles.itemSubtitle12(context), ), ), ], ), const SizedBox( height: 10, ), GestureDetector( onTap: () { showDialog( context: context, useSafeArea: false, barrierDismissible: true, builder: (_) { final width = MediaQuery.of(context).size.width / 2; return StackDialogBase( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Text( "Send ${trade.payInCurrency.toUpperCase()} to this address", style: STextStyles.pageTitleH2(context), ), ), const SizedBox( height: 12, ), Center( child: RepaintBoundary( // key: _qrKey, child: SizedBox( width: width + 20, height: width + 20, child: QR( data: trade.payInAddress, size: width, ), ), ), ), const SizedBox( height: 12, ), Center( child: SizedBox( width: width, child: TextButton( onPressed: () async { // await _capturePng(true); Navigator.of(context).pop(); }, style: Theme.of(context) .extension()! .getSecondaryEnabledButtonStyle( context, ), child: Text( "Cancel", style: STextStyles.button(context) .copyWith( color: Theme.of(context) .extension()! .accentColorDark, ), ), ), ), ), ], ), ); }, ); }, child: Row( children: [ SvgPicture.asset( Assets.svg.qrcode, width: 12, height: 12, color: Theme.of(context) .extension()! .infoItemIcons, ), const SizedBox( width: 4, ), Text( "Show QR code", style: STextStyles.link2(context), ), ], ), ), ], ), ), isDesktop ? const _Divider() : const SizedBox( height: 12, ), if (trade.payInExtraId.isNotEmpty && !sentFromStack && !hasTx) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Memo", style: STextStyles.itemSubtitle(context), ), isDesktop ? IconCopyButton( data: trade.payInExtraId, ) : GestureDetector( onTap: () async { final address = trade.payInExtraId; await Clipboard.setData( ClipboardData( text: address, ), ); if (context.mounted) { unawaited( showFloatingFlushBar( type: FlushBarType.info, message: "Copied to clipboard", context: context, ), ); } }, child: Row( children: [ SvgPicture.asset( Assets.svg.copy, width: 12, height: 12, color: Theme.of(context) .extension()! .infoItemIcons, ), const SizedBox( width: 4, ), Text( "Copy", style: STextStyles.link2(context), ), ], ), ), ], ), const SizedBox( height: 4, ), SelectableText( trade.payInExtraId, style: STextStyles.itemSubtitle12(context), ), ], ), ), if (trade.payInExtraId.isNotEmpty && !sentFromStack && !hasTx) isDesktop ? const _Divider() : const SizedBox( height: 12, ), RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Trade note", style: STextStyles.itemSubtitle(context), ), isDesktop ? IconPencilButton( onPressed: () { showDialog( context: context, builder: (context) { return DesktopDialog( maxWidth: 580, maxHeight: 360, child: EditTradeNoteView( tradeId: tradeId, note: ref .read(tradeNoteServiceProvider) .getNote(tradeId: tradeId), ), ); }, ); }, ) : GestureDetector( onTap: () { Navigator.of(context).pushNamed( EditTradeNoteView.routeName, arguments: Tuple2( tradeId, ref .read(tradeNoteServiceProvider) .getNote(tradeId: tradeId), ), ); }, child: Row( children: [ SvgPicture.asset( Assets.svg.pencil, width: 10, height: 10, color: Theme.of(context) .extension()! .infoItemIcons, ), const SizedBox( width: 4, ), Text( "Edit", style: STextStyles.link2(context), ), ], ), ), ], ), const SizedBox( height: 4, ), SelectableText( ref.watch( tradeNoteServiceProvider .select((value) => value.getNote(tradeId: tradeId)), ), style: STextStyles.itemSubtitle12(context), ), ], ), ), if (sentFromStack) isDesktop ? const _Divider() : const SizedBox( height: 12, ), if (sentFromStack) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Transaction note", style: STextStyles.itemSubtitle(context), ), isDesktop ? IconPencilButton( onPressed: () { showDialog( context: context, builder: (context) { return DesktopDialog( maxWidth: 580, maxHeight: 360, child: EditNoteView( txid: transactionIfSentFromStack!.txid, walletId: walletId!, ), ); }, ); }, ) : GestureDetector( onTap: () { Navigator.of(context).pushNamed( EditNoteView.routeName, arguments: Tuple2( transactionIfSentFromStack!.txid, walletId, ), ); }, child: Row( children: [ SvgPicture.asset( Assets.svg.pencil, width: 10, height: 10, color: Theme.of(context) .extension()! .infoItemIcons, ), const SizedBox( width: 4, ), Text( "Edit", style: STextStyles.link2(context), ), ], ), ), ], ), const SizedBox( height: 4, ), SelectableText( ref .watch( pTransactionNote( ( txid: transactionIfSentFromStack!.txid, walletId: walletId!, ), ), ) ?.value ?? "", style: STextStyles.itemSubtitle12(context), ), ], ), ), isDesktop ? const _Divider() : const SizedBox( height: 12, ), RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Date", style: STextStyles.itemSubtitle(context), ), if (isDesktop) const SizedBox( height: 2, ), if (isDesktop) SelectableText( Format.extractDateFrom( trade.timestamp.millisecondsSinceEpoch ~/ 1000, ), style: STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) .extension()! .textDark, ), ), ], ), if (!isDesktop) SelectableText( Format.extractDateFrom( trade.timestamp.millisecondsSinceEpoch ~/ 1000, ), style: STextStyles.itemSubtitle12(context), ), if (isDesktop) IconCopyButton( data: Format.extractDateFrom( trade.timestamp.millisecondsSinceEpoch ~/ 1000, ), ), ], ), ), isDesktop ? const _Divider() : const SizedBox( height: 12, ), RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Swap service", style: STextStyles.itemSubtitle(context), ), if (isDesktop) const SizedBox( height: 2, ), if (isDesktop) SelectableText( trade.exchangeName, style: STextStyles.itemSubtitle12(context), ), ], ), if (isDesktop) IconCopyButton( data: trade.exchangeName, ), if (!isDesktop) SelectableText( trade.exchangeName, style: STextStyles.itemSubtitle12(context), ), ], ), ), isDesktop ? const _Divider() : const SizedBox( height: 12, ), RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Trade ID", style: STextStyles.itemSubtitle(context), ), if (isDesktop) const SizedBox( height: 2, ), if (isDesktop) Text( trade.tradeId, style: STextStyles.itemSubtitle12(context), ), ], ), if (isDesktop) IconCopyButton( data: trade.tradeId, ), if (!isDesktop) Row( children: [ Text( trade.tradeId, style: STextStyles.itemSubtitle12(context), ), const SizedBox( width: 10, ), GestureDetector( onTap: () async { final data = ClipboardData(text: trade.tradeId); await clipboard.setData(data); if (context.mounted) { unawaited( showFloatingFlushBar( type: FlushBarType.info, message: "Copied to clipboard", context: context, ), ); } }, child: SvgPicture.asset( Assets.svg.copy, color: Theme.of(context) .extension()! .infoItemIcons, width: 12, ), ), ], ), ], ), ), isDesktop ? const _Divider() : const SizedBox( height: 12, ), RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Tracking", style: STextStyles.itemSubtitle(context), ), const SizedBox( height: 4, ), Builder( builder: (context) { late final String url; switch (trade.exchangeName) { case ChangeNowExchange.exchangeName: url = "https://changenow.io/exchange/txs/${trade.tradeId}"; break; case SimpleSwapExchange.exchangeName: url = "https://simpleswap.io/exchange?id=${trade.tradeId}"; break; case MajesticBankExchange.exchangeName: url = "https://majesticbank.sc/track?trx=${trade.tradeId}"; break; case NanswapExchange.exchangeName: url = "https://nanswap.com/transaction/${trade.tradeId}"; break; default: if (trade.exchangeName .startsWith(TrocadorExchange.exchangeName)) { url = "https://trocador.app/en/checkout/${trade.tradeId}"; } } return ConditionalParent( condition: isDesktop, builder: (child) => MouseRegion( cursor: SystemMouseCursors.click, child: child, ), child: GestureDetector( onTap: () { launchUrl( Uri.parse(url), mode: LaunchMode.externalApplication, ); }, child: Text( url, style: STextStyles.link2(context), ), ), ); }, ), ], ), ), if (!isDesktop) const SizedBox( height: 12, ), if (!isDesktop && showSendFromStackButton) SecondaryButton( label: "Send from ${AppConfig.prefix}", onPressed: () { CryptoCurrency coin; try { coin = AppConfig.getCryptoCurrencyForTicker( trade.payInCurrency, )!; } catch (_) { coin = AppConfig.getCryptoCurrencyByPrettyName( trade.payInCurrency, ); } final amount = Amount.fromDecimal( sendAmount, fractionDigits: coin.fractionDigits, ); final address = trade.payInAddress; Navigator.of(context).pushNamed( SendFromView.routeName, arguments: Tuple4( coin, amount, address, trade, ), ); }, ), ], ), ), ); } } class _Divider extends StatelessWidget { const _Divider({super.key}); @override Widget build(BuildContext context) { return Container( height: 1, color: Theme.of(context).extension()!.backgroundAppBar, ); } }