/* * 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:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar/isar.dart'; import 'package:tuple/tuple.dart'; import '../../app_config.dart'; import '../../frost_route_generator.dart'; import '../../models/isar/exchange_cache/currency.dart'; import '../../notifications/show_flush_bar.dart'; import '../../providers/global/active_wallet_provider.dart'; import '../../providers/global/auto_swb_service_provider.dart'; import '../../providers/global/paynym_api_provider.dart'; import '../../providers/providers.dart'; import '../../providers/ui/transaction_filter_provider.dart'; import '../../providers/ui/unread_notifications_provider.dart'; import '../../providers/wallet/my_paynym_account_state_provider.dart'; import '../../services/event_bus/events/global/node_connection_status_changed_event.dart'; import '../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import '../../services/event_bus/global_event_bus.dart'; import '../../services/exchange/exchange_data_loading_service.dart'; import '../../themes/coin_icon_provider.dart'; import '../../themes/stack_colors.dart'; import '../../themes/theme_providers.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/assets.dart'; import '../../utilities/clipboard_interface.dart'; import '../../utilities/constants.dart'; import '../../utilities/enums/backup_frequency_type.dart'; import '../../utilities/enums/sync_type_enum.dart'; import '../../utilities/logger.dart'; import '../../utilities/show_loading.dart'; import '../../utilities/text_styles.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; import '../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../wallets/wallet/impl/firo_wallet.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.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/custom_loading_overlay.dart'; import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/frost_scaffold.dart'; import '../../widgets/loading_indicator.dart'; import '../../widgets/small_tor_icon.dart'; import '../../widgets/stack_dialog.dart'; import '../../widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/frost_sign_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/fusion_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/ordinals_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/receive_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/send_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/wallet_navigation_bar_item.dart'; import '../../widgets/wallet_navigation_bar/wallet_navigation_bar.dart'; import '../buy_view/buy_in_wallet_view.dart'; import '../cashfusion/cashfusion_view.dart'; import '../coin_control/coin_control_view.dart'; import '../exchange_view/wallet_initiated_exchange_view.dart'; import '../home_view/home_view.dart'; import '../monkey/monkey_view.dart'; import '../notification_views/notifications_view.dart'; import '../ordinals/ordinals_view.dart'; import '../paynym/paynym_claim_view.dart'; import '../paynym/paynym_home_view.dart'; import '../receive_view/receive_view.dart'; import '../send_view/frost_ms/frost_send_view.dart'; import '../send_view/send_view.dart'; import '../settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart'; import '../settings_views/wallet_settings_view/wallet_settings_view.dart'; import '../special/firo_rescan_recovery_error_dialog.dart'; import '../token_view/my_tokens_view.dart'; import 'sub_widgets/transactions_list.dart'; import 'sub_widgets/wallet_summary.dart'; import 'transaction_views/all_transactions_view.dart'; import 'transaction_views/tx_v2/all_transactions_v2_view.dart'; import 'transaction_views/tx_v2/transaction_v2_list.dart'; /// [eventBus] should only be set during testing class WalletView extends ConsumerStatefulWidget { const WalletView({ super.key, required this.walletId, this.eventBus, this.clipboardInterface = const ClipboardWrapper(), }); static const String routeName = "/wallet"; static const double navBarHeight = 65.0; final String walletId; final EventBus? eventBus; final ClipboardInterface clipboardInterface; @override ConsumerState createState() => _WalletViewState(); } class _WalletViewState extends ConsumerState { late final EventBus eventBus; late final String walletId; late final CryptoCurrency coin; late final bool isSparkWallet; // late final bool _shouldDisableAutoSyncOnLogOut; late WalletSyncStatus _currentSyncStatus; late NodeConnectionStatus _currentNodeStatus; late StreamSubscription _syncStatusSubscription; late StreamSubscription _nodeStatusSubscription; bool _rescanningOnOpen = false; bool _lelantusRescanRecovery = false; Future _firoRescanRecovery() async { final success = await (ref.read(pWallets).getWallet(walletId) as FiroWallet) .firoRescanRecovery(); if (success) { // go into wallet WidgetsBinding.instance.addPostFrameCallback( (_) => setState(() { _rescanningOnOpen = false; _lelantusRescanRecovery = false; }), ); } else { // show error message dialog w/ options if (mounted) { final shouldRetry = await Navigator.of(context).pushNamed( FiroRescanRecoveryErrorView.routeName, arguments: walletId, ); if (shouldRetry is bool && shouldRetry) { await _firoRescanRecovery(); } } else { return await _firoRescanRecovery(); } } } @override void initState() { walletId = widget.walletId; final wallet = ref.read(pWallets).getWallet(walletId); coin = wallet.info.coin; WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(currentWalletIdProvider.notifier).state = wallet.walletId; }); if (!wallet.shouldAutoSync) { // enable auto sync if it wasn't enabled when loading wallet wallet.shouldAutoSync = true; // _shouldDisableAutoSyncOnLogOut = true; // } else { // _shouldDisableAutoSyncOnLogOut = false; } isSparkWallet = wallet is SparkInterface; if (coin is Firo && (wallet as FiroWallet).lelantusCoinIsarRescanRequired) { _rescanningOnOpen = true; _lelantusRescanRecovery = true; _firoRescanRecovery(); } else { wallet.refresh(); } if (wallet.refreshMutex.isLocked) { _currentSyncStatus = WalletSyncStatus.syncing; _currentNodeStatus = NodeConnectionStatus.connected; } else { _currentSyncStatus = WalletSyncStatus.synced; if (wallet.isConnected) { _currentNodeStatus = NodeConnectionStatus.connected; } else { _currentNodeStatus = NodeConnectionStatus.disconnected; _currentSyncStatus = WalletSyncStatus.unableToSync; } } eventBus = widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance; _syncStatusSubscription = eventBus.on().listen( (event) async { if (event.walletId == widget.walletId) { // switch (event.newStatus) { // case WalletSyncStatus.unableToSync: // break; // case WalletSyncStatus.synced: // break; // case WalletSyncStatus.syncing: // break; // } setState(() { _currentSyncStatus = event.newStatus; }); } }, ); _nodeStatusSubscription = eventBus.on().listen( (event) async { if (event.walletId == widget.walletId) { // switch (event.newStatus) { // case NodeConnectionStatus.disconnected: // break; // case NodeConnectionStatus.connected: // break; // } setState(() { _currentNodeStatus = event.newStatus; }); } }, ); super.initState(); } @override void dispose() { _nodeStatusSubscription.cancel(); _syncStatusSubscription.cancel(); super.dispose(); } DateTime? _cachedTime; Future _onWillPop() async { if (_rescanningOnOpen || _lelantusRescanRecovery) { return false; } final now = DateTime.now(); const timeout = Duration(milliseconds: 1500); if (_cachedTime == null || now.difference(_cachedTime!) > timeout) { _cachedTime = now; unawaited( showDialog( context: context, barrierDismissible: false, builder: (_) => WillPopScope( onWillPop: () async { Navigator.of(context).popUntil( ModalRoute.withName(HomeView.routeName), ); _logout(); return false; }, child: const StackDialog(title: "Tap back again to exit wallet"), ), ).timeout( timeout, onTimeout: () => Navigator.of(context).popUntil( ModalRoute.withName(WalletView.routeName), ), ), ); } return false; } void _logout() async { // if (_shouldDisableAutoSyncOnLogOut) { // // disable auto sync if it was enabled only when loading wallet ref.read(pWallets).getWallet(walletId).shouldAutoSync = false; // } ref.read(currentWalletIdProvider.notifier).state = null; ref.read(transactionFilterProvider.state).state = null; if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled && ref.read(prefsChangeNotifierProvider).backupFrequencyType == BackupFrequencyType.afterClosingAWallet) { unawaited(ref.read(autoSWBServiceProvider).doBackup()); } // Close the wallet according to syncing preferences. switch (ref.read(prefsChangeNotifierProvider).syncType) { case SyncingType.currentWalletOnly: // Close the wallet. unawaited(ref.watch(pWallets).getWallet(walletId).exit()); // unawaited so we don't lag the UI. case SyncingType.selectedWalletsAtStartup: // Close if this wallet is not in the list to be synced. if (!ref .read(prefsChangeNotifierProvider) .walletIdsSyncOnStartup .contains(widget.walletId)) { unawaited(ref.watch(pWallets).getWallet(walletId).exit()); // unawaited so we don't lag the UI. } case SyncingType.allWalletsOnStartup: // Do nothing. break; } } Widget _buildNetworkIcon(WalletSyncStatus status) { switch (status) { case WalletSyncStatus.unableToSync: return SvgPicture.asset( Assets.svg.radioProblem, color: Theme.of(context).extension()!.accentColorRed, width: 20, height: 20, ); case WalletSyncStatus.synced: return SvgPicture.asset( Assets.svg.radio, color: Theme.of(context).extension()!.accentColorGreen, width: 20, height: 20, ); case WalletSyncStatus.syncing: return SvgPicture.asset( Assets.svg.radioSyncing, color: Theme.of(context).extension()!.accentColorYellow, width: 20, height: 20, ); } } Future _onFrostSignPressed(BuildContext context) async { final wallet = ref.read(pWallets).getWallet(walletId) as BitcoinFrostWallet; ref.read(pFrostScaffoldArgs.state).state = ( info: ( walletName: wallet.info.name, frostCurrency: wallet.cryptoCurrency, ), walletId: walletId, stepRoutes: FrostRouteGenerator.signFrostTxStepRoutes, parentNav: Navigator.of(context), frostInterruptionDialogType: FrostInterruptionDialogType.transactionCreation, callerRouteName: WalletView.routeName, ); await Navigator.of(context).pushNamed( FrostStepScaffold.routeName, ); } Future _onExchangePressed(BuildContext context) async { final CryptoCurrency coin = ref.read(pWalletCoin(walletId)); if (coin.network.isTestNet) { await showDialog( context: context, builder: (_) => const StackOkDialog( title: "Exchange not available for test net coins", ), ); } else { Future _future; try { _future = ExchangeDataLoadingService.instance.isar.currencies .where() .tickerEqualToAnyExchangeNameName(coin.ticker) .findFirst(); } catch (_) { _future = ExchangeDataLoadingService.instance.loadAll().then( (_) => ExchangeDataLoadingService.instance.isar.currencies .where() .tickerEqualToAnyExchangeNameName(coin.ticker) .findFirst(), ); } final currency = await showLoading( whileFuture: _future, context: context, message: "Loading...", ); if (context.mounted) { unawaited( Navigator.of(context).pushNamed( WalletInitiatedExchangeView.routeName, arguments: Tuple2( walletId, currency == null ? Bitcoin(CryptoCurrencyNetwork.main) : coin, ), ), ); } } } Future _onBuyPressed(BuildContext context) async { final CryptoCurrency coin = ref.read(pWalletCoin(walletId)); if (coin.network.isTestNet) { await showDialog( context: context, builder: (_) => const StackOkDialog( title: "Buy not available for test net coins", ), ); } else { if (mounted) { unawaited( Navigator.of(context).pushNamed( BuyInWalletView.routeName, arguments: coin.hasBuySupport ? coin : Bitcoin(CryptoCurrencyNetwork.main), ), ); } } } Future attemptAnonymize() async { bool shouldPop = false; unawaited( showDialog( context: context, builder: (context) => WillPopScope( child: const CustomLoadingOverlay( message: "Anonymizing balance", eventBus: null, ), onWillPop: () async => shouldPop, ), ), ); final firoWallet = ref.read(pWallets).getWallet(walletId) as FiroWallet; final Amount publicBalance = firoWallet.info.cachedBalance.spendable; if (publicBalance <= Amount.zero) { shouldPop = true; if (mounted) { Navigator.of(context).popUntil( ModalRoute.withName(WalletView.routeName), ); unawaited( showFloatingFlushBar( type: FlushBarType.info, message: "No funds available to anonymize!", context: context, ), ); } return; } try { // await firoWallet.anonymizeAllLelantus(); await firoWallet.anonymizeAllSpark(); shouldPop = true; if (mounted) { Navigator.of(context).popUntil( ModalRoute.withName(WalletView.routeName), ); unawaited( showFloatingFlushBar( type: FlushBarType.success, message: "Anonymize transaction submitted", context: context, ), ); } } catch (e) { shouldPop = true; if (mounted) { Navigator.of(context).popUntil( ModalRoute.withName(WalletView.routeName), ); await showDialog( context: context, builder: (_) => StackOkDialog( title: "Anonymize all failed", message: "Reason: $e", ), ); } } } @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); final coin = ref.watch(pWalletCoin(walletId)); return ConditionalParent( condition: _rescanningOnOpen, builder: (child) { return WillPopScope( onWillPop: () async => !_rescanningOnOpen, child: Stack( children: [ child, Background( child: CustomLoadingOverlay( message: "Migration in progress\nThis could take a while\nPlease don't leave this screen", subMessage: "This only needs to run once per wallet", eventBus: null, textColor: Theme.of(context).extension()!.textDark, actionButton: _lelantusRescanRecovery ? null : SecondaryButton( label: "Cancel", onPressed: () async { await showDialog( context: context, builder: (context) => StackDialog( title: "Warning!", message: "Skipping this process can completely" " break your wallet. It is only meant to be done in" " emergency situations where the migration fails" " and will not let you continue. Still skip?", leftButton: SecondaryButton( label: "Cancel", onPressed: Navigator.of(context, rootNavigator: true) .pop, ), rightButton: SecondaryButton( label: "Ok", onPressed: () { Navigator.of(context, rootNavigator: true) .pop(); setState(() => _rescanningOnOpen = false); }, ), ), ); }, ), ), ), ], ), ); }, child: WillPopScope( onWillPop: _onWillPop, child: Background( child: Stack( children: [ Scaffold( backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( leading: AppBarBackButton( onPressed: () { _logout(); Navigator.of(context).pop(); }, ), titleSpacing: 0, title: Row( children: [ SvgPicture.file( File( ref.watch(coinIconProvider(coin)), ), width: 24, height: 24, ), const SizedBox( width: 16, ), Expanded( child: Text( ref.watch(pWalletName(walletId)), style: STextStyles.navBarTitle(context), overflow: TextOverflow.ellipsis, ), ), ], ), actions: [ const Padding( padding: EdgeInsets.only( top: 10, bottom: 10, right: 10, ), child: AspectRatio( aspectRatio: 1, child: SmallTorIcon(), ), ), Padding( padding: const EdgeInsets.only( top: 10, bottom: 10, right: 10, ), child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( semanticsLabel: "Network Button. Takes To Network Status Page.", key: const Key("walletViewRadioButton"), size: 36, shadows: const [], color: Theme.of(context) .extension()! .background, icon: _buildNetworkIcon(_currentSyncStatus), onPressed: () { Navigator.of(context).pushNamed( WalletNetworkSettingsView.routeName, arguments: Tuple3( walletId, _currentSyncStatus, _currentNodeStatus, ), ); }, ), ), ), Padding( padding: const EdgeInsets.only( top: 10, bottom: 10, right: 10, ), child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( semanticsLabel: "Notifications Button. Takes To Notifications Page.", key: const Key("walletViewAlertsButton"), size: 36, shadows: const [], color: Theme.of(context) .extension()! .background, icon: ref.watch( notificationsProvider.select( (value) => value.hasUnreadNotificationsFor(walletId), ), ) ? SvgPicture.file( File( ref.watch( themeProvider.select( (value) => value.assets.bellNew, ), ), ), width: 20, height: 20, color: ref.watch( notificationsProvider.select( (value) => value.hasUnreadNotificationsFor( walletId, ), ), ) ? null : Theme.of(context) .extension()! .topNavIconPrimary, ) : SvgPicture.asset( Assets.svg.bell, width: 20, height: 20, color: ref.watch( notificationsProvider.select( (value) => value.hasUnreadNotificationsFor( walletId, ), ), ) ? null : Theme.of(context) .extension()! .topNavIconPrimary, ), onPressed: () { // reset unread state ref.refresh(unreadNotificationsStateProvider); Navigator.of(context) .pushNamed( NotificationsView.routeName, arguments: walletId, ) .then((_) { final Set unreadNotificationIds = ref .read(unreadNotificationsStateProvider.state) .state; if (unreadNotificationIds.isEmpty) return; final List> futures = []; for (int i = 0; i < unreadNotificationIds.length - 1; i++) { futures.add( ref.read(notificationsProvider).markAsRead( unreadNotificationIds.elementAt(i), false, ), ); } // wait for multiple to update if any Future.wait(futures).then((_) { // only notify listeners once ref.read(notificationsProvider).markAsRead( unreadNotificationIds.last, true, ); }); }); }, ), ), ), Padding( padding: const EdgeInsets.only( top: 10, bottom: 10, right: 10, ), child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( semanticsLabel: "Settings Button. Takes To Wallet Settings Page.", key: const Key("walletViewSettingsButton"), size: 36, shadows: const [], color: Theme.of(context) .extension()! .background, icon: SvgPicture.asset( Assets.svg.bars, color: Theme.of(context) .extension()! .accentColorDark, width: 20, height: 20, ), onPressed: () { //todo: check if print needed // debugPrint("wallet view settings tapped"); Navigator.of(context).pushNamed( WalletSettingsView.routeName, arguments: Tuple4( walletId, coin, _currentSyncStatus, _currentNodeStatus, ), ); }, ), ), ), ], ), body: SafeArea( child: Container( color: Theme.of(context).extension()!.background, child: Column( children: [ const SizedBox( height: 10, ), Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: WalletSummary( walletId: walletId, aspectRatio: 1.75, initialSyncStatus: ref .watch(pWallets) .getWallet(walletId) .refreshMutex .isLocked ? WalletSyncStatus.syncing : WalletSyncStatus.synced, ), ), ), if (isSparkWallet) const SizedBox( height: 10, ), if (isSparkWallet) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Expanded( child: TextButton( style: Theme.of(context) .extension()! .getSecondaryEnabledButtonStyle( context, ), onPressed: () async { await showDialog( context: context, builder: (context) => StackDialog( title: "Attention!", message: "You're about to anonymize all of your public funds.", leftButton: TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text( "Cancel", style: STextStyles.button(context) .copyWith( color: Theme.of(context) .extension()! .accentColorDark, ), ), ), rightButton: TextButton( onPressed: () async { Navigator.of(context).pop(); unawaited(attemptAnonymize()); }, style: Theme.of(context) .extension()! .getPrimaryEnabledButtonStyle( context, ), child: Text( "Continue", style: STextStyles.button(context), ), ), ), ); }, child: Text( "Anonymize funds", style: STextStyles.button(context).copyWith( color: Theme.of(context) .extension()! .buttonTextSecondary, ), ), ), ), ], ), ), const SizedBox( height: 20, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Transactions", style: STextStyles.itemSubtitle(context).copyWith( color: Theme.of(context) .extension()! .textDark3, ), ), CustomTextButton( text: "See all", onTap: () { Navigator.of(context).pushNamed( ref .read(pWallets) .getWallet(widget.walletId) .isarTransactionVersion == 2 ? AllTransactionsV2View.routeName : AllTransactionsView.routeName, arguments: walletId, ); }, ), ], ), ), const SizedBox( height: 12, ), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: ClipRRect( borderRadius: BorderRadius.vertical( top: Radius.circular( Constants.size.circularBorderRadius, ), bottom: Radius.circular( // WalletView.navBarHeight / 2.0, Constants.size.circularBorderRadius, ), ), child: ShaderMask( blendMode: BlendMode.dstOut, shaderCallback: (Rect bounds) { return const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.transparent, Colors.white, ], stops: [ 0.0, 0.8, 1.0, ], ).createShader(bounds); }, child: Container( decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: ref .read(pWallets) .getWallet(widget.walletId) .isarTransactionVersion == 2 ? TransactionsV2List( walletId: widget.walletId, ) : TransactionsList( walletId: walletId, ), ), ], ), ), ), ), ), ), ], ), ), ), ), WalletNavigationBar( items: [ WalletNavigationBarItemData( label: "Receive", icon: const ReceiveNavIcon(), onTap: () { if (mounted) { unawaited( Navigator.of(context).pushNamed( ReceiveView.routeName, arguments: walletId, ), ); } }, ), if (ref.watch(pWalletCoin(walletId)) is FrostCurrency) WalletNavigationBarItemData( label: "Sign", icon: const FrostSignNavIcon(), onTap: () => _onFrostSignPressed(context), ), WalletNavigationBarItemData( label: "Send", icon: const SendNavIcon(), onTap: () { // not sure what this is supposed to accomplish? // switch (ref // .read(walletBalanceToggleStateProvider.state) // .state) { // case WalletBalanceToggleState.full: // ref // .read(publicPrivateBalanceStateProvider.state) // .state = "Public"; // break; // case WalletBalanceToggleState.available: // ref // .read(publicPrivateBalanceStateProvider.state) // .state = "Private"; // break; // } Navigator.of(context).pushNamed( ref.read(pWallets).getWallet(walletId) is BitcoinFrostWallet ? FrostSendView.routeName : SendView.routeName, arguments: ( walletId: walletId, coin: coin, ), ); }, ), if (Constants.enableExchange && ref.watch(pWalletCoin(walletId)) is! FrostCurrency && AppConfig.hasFeature(AppFeature.swap)) WalletNavigationBarItemData( label: "Swap", icon: const ExchangeNavIcon(), onTap: () => _onExchangePressed(context), ), if (Constants.enableExchange && ref.watch(pWalletCoin(walletId)) is! FrostCurrency && AppConfig.hasFeature(AppFeature.buy)) WalletNavigationBarItemData( label: "Buy", icon: const BuyNavIcon(), onTap: () => _onBuyPressed(context), ), ], moreItems: [ if (ref.watch( pWallets.select( (value) => value .getWallet(widget.walletId) .cryptoCurrency .hasTokenSupport, ), )) WalletNavigationBarItemData( label: "Tokens", icon: const CoinControlNavIcon(), onTap: () { Navigator.of(context).pushNamed( MyTokensView.routeName, arguments: walletId, ); }, ), if (coin is Banano) WalletNavigationBarItemData( icon: SvgPicture.asset( Assets.svg.monkey, height: 20, width: 20, color: Theme.of(context) .extension()! .bottomNavIconIcon, ), label: "MonKey", onTap: () { Navigator.of(context).pushNamed( MonkeyView.routeName, arguments: widget.walletId, ); }, ), if (ref.watch( pWallets.select( (value) => value.getWallet(widget.walletId) is CoinControlInterface, ), ) && ref.watch( prefsChangeNotifierProvider.select( (value) => value.enableCoinControl, ), )) WalletNavigationBarItemData( label: "Coin control", icon: const CoinControlNavIcon(), onTap: () { Navigator.of(context).pushNamed( CoinControlView.routeName, arguments: Tuple2( widget.walletId, CoinControlViewType.manage, ), ); }, ), if (ref.watch( pWallets.select( (value) => value.getWallet(widget.walletId) is PaynymInterface, ), )) WalletNavigationBarItemData( label: "PayNym", icon: const PaynymNavIcon(), onTap: () async { unawaited( showDialog( context: context, builder: (context) => const LoadingIndicator( width: 100, ), ), ); final wallet = ref.read(pWallets).getWallet(widget.walletId); final paynymInterface = wallet as PaynymInterface; final code = await paynymInterface.getPaymentCode( isSegwit: false, ); final account = await ref .read(paynymAPIProvider) .nym(code.toString()); Logging.instance.log( "my nym account: $account", level: LogLevel.Info, ); if (context.mounted) { Navigator.of(context).pop(); // check if account exists and for matching code to see if claimed if (account.value != null && account.value!.nonSegwitPaymentCode.claimed && account.value!.segwit) { ref.read(myPaynymAccountStateProvider.state).state = account.value!; await Navigator.of(context).pushNamed( PaynymHomeView.routeName, arguments: widget.walletId, ); } else { await Navigator.of(context).pushNamed( PaynymClaimView.routeName, arguments: widget.walletId, ); } } }, ), if (ref.watch( pWallets.select( (value) => value.getWallet(widget.walletId) is OrdinalsInterface, ), )) WalletNavigationBarItemData( label: "Ordinals", icon: const OrdinalsNavIcon(), onTap: () { Navigator.of(context).pushNamed( OrdinalsView.routeName, arguments: widget.walletId, ); }, ), if (ref.watch( pWallets.select( (value) => value.getWallet(widget.walletId) is CashFusionInterface, ), )) WalletNavigationBarItemData( label: "Fusion", icon: const FusionNavIcon(), onTap: () { Navigator.of(context).pushNamed( CashFusionView.routeName, arguments: walletId, ); }, ), ], ), ], ), ), ), ); } }