stack_wallet/lib/pages/wallet_view/wallet_view.dart

1245 lines
49 KiB
Dart
Raw Normal View History

2023-05-26 21:21:16 +00:00
/*
* 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
*
*/
2022-08-26 08:11:35 +00:00
import 'dart:async';
import 'dart:io';
2022-08-26 08:11:35 +00:00
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';
2023-03-10 22:48:44 +00:00
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';
2024-11-07 22:09:54 +00:00
import '../../wallets/wallet/intermediate/lib_monero_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';
2024-11-12 15:29:12 +00:00
import '../../wallets/wallet/wallet_mixin_interfaces/view_only_option_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';
2024-11-07 22:09:54 +00:00
import '../../widgets/wallet_navigation_bar/components/icons/churn_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';
2024-11-07 22:09:54 +00:00
import '../churning/churning_view.dart';
import '../coin_control/coin_control_view.dart';
import '../exchange_view/wallet_initiated_exchange_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';
2022-08-26 08:11:35 +00:00
/// [eventBus] should only be set during testing
class WalletView extends ConsumerStatefulWidget {
const WalletView({
2024-05-14 16:26:03 +00:00
super.key,
2022-08-26 08:11:35 +00:00
required this.walletId,
this.eventBus,
this.clipboardInterface = const ClipboardWrapper(),
2024-05-14 16:26:03 +00:00
});
2022-08-26 08:11:35 +00:00
static const String routeName = "/wallet";
static const double navBarHeight = 65.0;
final String walletId;
final EventBus? eventBus;
final ClipboardInterface clipboardInterface;
2022-08-26 08:11:35 +00:00
@override
ConsumerState<WalletView> createState() => _WalletViewState();
}
class _WalletViewState extends ConsumerState<WalletView> {
late final EventBus eventBus;
late final String walletId;
2024-05-15 21:20:45 +00:00
late final CryptoCurrency coin;
2022-08-26 08:11:35 +00:00
late final bool isSparkWallet;
// late final bool _shouldDisableAutoSyncOnLogOut;
2022-08-26 08:11:35 +00:00
late WalletSyncStatus _currentSyncStatus;
late NodeConnectionStatus _currentNodeStatus;
late StreamSubscription<dynamic> _syncStatusSubscription;
late StreamSubscription<dynamic> _nodeStatusSubscription;
2023-02-08 13:29:27 +00:00
bool _rescanningOnOpen = false;
2023-07-28 16:55:04 +00:00
bool _lelantusRescanRecovery = false;
Future<void> _firoRescanRecovery() async {
final success = await (ref.read(pWallets).getWallet(walletId) as FiroWallet)
2023-07-28 16:55:04 +00:00
.firoRescanRecovery();
if (success) {
// go into wallet
WidgetsBinding.instance.addPostFrameCallback(
(_) => setState(() {
_rescanningOnOpen = false;
_lelantusRescanRecovery = false;
}),
);
} else {
// show error message dialog w/ options
if (mounted) {
2023-07-28 19:10:57 +00:00
final shouldRetry = await Navigator.of(context).pushNamed(
FiroRescanRecoveryErrorView.routeName,
arguments: walletId,
2023-07-28 16:55:04 +00:00
);
2023-07-28 19:10:57 +00:00
if (shouldRetry is bool && shouldRetry) {
await _firoRescanRecovery();
2023-07-28 16:55:04 +00:00
}
} else {
return await _firoRescanRecovery();
}
}
}
2023-02-08 13:29:27 +00:00
2022-08-26 08:11:35 +00:00
@override
void initState() {
walletId = widget.walletId;
final wallet = ref.read(pWallets).getWallet(walletId);
coin = wallet.info.coin;
2022-08-26 08:11:35 +00:00
2024-05-08 16:58:14 +00:00
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(currentWalletIdProvider.notifier).state = wallet.walletId;
});
if (!wallet.shouldAutoSync) {
2022-08-26 08:11:35 +00:00
// enable auto sync if it wasn't enabled when loading wallet
wallet.shouldAutoSync = true;
// _shouldDisableAutoSyncOnLogOut = true;
// } else {
// _shouldDisableAutoSyncOnLogOut = false;
2022-08-26 08:11:35 +00:00
}
isSparkWallet = wallet is SparkInterface;
2024-05-15 21:20:45 +00:00
if (coin is Firo && (wallet as FiroWallet).lelantusCoinIsarRescanRequired) {
2023-07-28 16:55:04 +00:00
_rescanningOnOpen = true;
_lelantusRescanRecovery = true;
2023-07-28 19:10:57 +00:00
_firoRescanRecovery();
2023-02-08 13:29:27 +00:00
} else {
wallet.refresh();
2023-02-08 13:29:27 +00:00
}
2022-08-26 08:11:35 +00:00
if (wallet.refreshMutex.isLocked) {
2022-08-26 08:11:35 +00:00
_currentSyncStatus = WalletSyncStatus.syncing;
_currentNodeStatus = NodeConnectionStatus.connected;
} else {
_currentSyncStatus = WalletSyncStatus.synced;
if (wallet.isConnected) {
2022-08-26 08:11:35 +00:00
_currentNodeStatus = NodeConnectionStatus.connected;
} else {
_currentNodeStatus = NodeConnectionStatus.disconnected;
_currentSyncStatus = WalletSyncStatus.unableToSync;
}
}
eventBus =
widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance;
_syncStatusSubscription =
eventBus.on<WalletSyncStatusChangedEvent>().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<NodeConnectionStatusChangedEvent>().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;
2022-08-26 08:11:35 +00:00
Future<bool> _onWillPop() async {
2023-07-28 16:55:04 +00:00
if (_rescanningOnOpen || _lelantusRescanRecovery) {
return false;
}
_logout();
return true;
// final now = DateTime.now();
// const timeout = Duration(milliseconds: 1500);
// if (_cachedTime == null || now.difference(_cachedTime!) > timeout) {
// _cachedTime = now;
// unawaited(
// showDialog<dynamic>(
// 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;
2022-08-26 08:11:35 +00:00
}
2022-09-16 13:33:22 +00:00
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;
2022-08-26 08:11:35 +00:00
ref.read(transactionFilterProvider.state).state = null;
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled &&
ref.read(prefsChangeNotifierProvider).backupFrequencyType ==
BackupFrequencyType.afterClosingAWallet) {
2022-09-16 13:33:22 +00:00
unawaited(ref.read(autoSWBServiceProvider).doBackup());
2022-08-26 08:11:35 +00:00
}
// 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;
}
2022-08-26 08:11:35 +00:00
}
Widget _buildNetworkIcon(WalletSyncStatus status) {
switch (status) {
case WalletSyncStatus.unableToSync:
return SvgPicture.asset(
Assets.svg.radioProblem,
color: Theme.of(context).extension<StackColors>()!.accentColorRed,
2022-08-26 08:11:35 +00:00
width: 20,
height: 20,
);
case WalletSyncStatus.synced:
return SvgPicture.asset(
Assets.svg.radio,
color: Theme.of(context).extension<StackColors>()!.accentColorGreen,
2022-08-26 08:11:35 +00:00
width: 20,
height: 20,
);
case WalletSyncStatus.syncing:
return SvgPicture.asset(
Assets.svg.radioSyncing,
color: Theme.of(context).extension<StackColors>()!.accentColorYellow,
2022-08-26 08:11:35 +00:00
width: 20,
height: 20,
);
}
}
Future<void> _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,
2024-05-03 22:38:12 +00:00
parentNav: Navigator.of(context),
2024-05-01 20:50:12 +00:00
frostInterruptionDialogType:
FrostInterruptionDialogType.transactionCreation,
callerRouteName: WalletView.routeName,
);
2024-05-01 19:08:59 +00:00
await Navigator.of(context).pushNamed(
FrostStepScaffold.routeName,
);
}
Future<void> _onExchangePressed(BuildContext context) async {
2024-05-15 21:20:45 +00:00
final CryptoCurrency coin = ref.read(pWalletCoin(walletId));
2022-08-26 08:11:35 +00:00
2024-06-19 15:15:49 +00:00
if (coin.network.isTestNet) {
await showDialog<void>(
2022-08-26 08:11:35 +00:00
context: context,
builder: (_) => const StackOkDialog(
title: "Exchange not available for test net coins",
2022-08-26 08:11:35 +00:00
),
);
} else {
Future<Currency?> _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,
2023-02-08 22:51:13 +00:00
arguments: Tuple2(
walletId,
2024-05-15 21:20:45 +00:00
currency == null ? Bitcoin(CryptoCurrencyNetwork.main) : coin,
),
),
);
}
2022-08-26 08:11:35 +00:00
}
}
Future<void> _onBuyPressed(BuildContext context) async {
2024-05-15 21:20:45 +00:00
final CryptoCurrency coin = ref.read(pWalletCoin(walletId));
2023-03-10 22:48:44 +00:00
2024-06-19 15:15:49 +00:00
if (coin.network.isTestNet) {
2023-03-10 22:48:44 +00:00
await showDialog<void>(
context: context,
builder: (_) => const StackOkDialog(
title: "Buy not available for test net coins",
),
);
} else {
if (mounted) {
unawaited(
Navigator.of(context).pushNamed(
BuyInWalletView.routeName,
2024-05-15 21:20:45 +00:00
arguments:
coin.hasBuySupport ? coin : Bitcoin(CryptoCurrencyNetwork.main),
2023-03-10 22:48:44 +00:00
),
);
}
}
}
2022-09-06 23:27:14 +00:00
Future<void> 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;
2022-09-06 23:27:14 +00:00
2023-11-20 15:15:36 +00:00
final Amount publicBalance = firoWallet.info.cachedBalance.spendable;
2023-04-05 22:06:31 +00:00
if (publicBalance <= Amount.zero) {
2022-09-06 23:27:14 +00:00
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 {
2023-12-23 00:15:44 +00:00
// await firoWallet.anonymizeAllLelantus();
await firoWallet.anonymizeAllSpark();
2022-09-06 23:27:14 +00:00
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<dynamic>(
context: context,
builder: (_) => StackOkDialog(
title: "Anonymize all failed",
message: "Reason: $e",
),
);
}
}
}
2022-08-26 08:11:35 +00:00
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final coin = ref.watch(pWalletCoin(walletId));
2022-09-06 23:27:14 +00:00
final prefs = ref.watch(prefsChangeNotifierProvider);
final showExchange = prefs.enableExchange;
2024-11-12 15:29:12 +00:00
final wallet = ref.watch(pWallets).getWallet(walletId);
final viewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly;
2023-02-08 13:29:27 +00:00
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<StackColors>()!.textDark,
2023-07-28 16:55:04 +00:00
actionButton: _lelantusRescanRecovery
? null
: SecondaryButton(
label: "Cancel",
onPressed: () async {
await showDialog<void>(
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);
},
),
),
);
},
),
),
),
2023-02-08 13:29:27 +00:00
],
),
);
},
child: WillPopScope(
onWillPop: _onWillPop,
child: Background(
2023-03-10 18:42:45 +00:00
child: Stack(
children: [
Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
_logout();
Navigator.of(context).pop();
},
),
2023-03-10 18:42:45 +00:00
titleSpacing: 0,
title: Row(
children: [
SvgPicture.file(
File(
ref.watch(coinIconProvider(coin)),
),
2023-03-10 18:42:45 +00:00
width: 24,
height: 24,
),
const SizedBox(
width: 16,
),
Expanded(
child: Text(
ref.watch(pWalletName(walletId)),
2023-03-10 18:42:45 +00:00
style: STextStyles.navBarTitle(context),
overflow: TextOverflow.ellipsis,
),
),
2023-03-10 18:42:45 +00:00
],
2022-08-26 08:11:35 +00:00
),
2023-03-10 18:42:45 +00:00
actions: [
2023-09-13 16:11:14 +00:00
const Padding(
padding: EdgeInsets.only(
2023-09-12 21:45:57 +00:00
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
2023-09-13 16:11:14 +00:00
child: SmallTorIcon(),
2023-09-12 21:45:57 +00:00
),
),
2023-03-10 18:42:45 +00:00
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.",
2023-03-10 18:42:45 +00:00
key: const Key("walletViewRadioButton"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.background,
icon: _buildNetworkIcon(_currentSyncStatus),
onPressed: () {
Navigator.of(context).pushNamed(
WalletNetworkSettingsView.routeName,
arguments: Tuple3(
walletId,
_currentSyncStatus,
_currentNodeStatus,
),
);
},
),
),
2023-02-08 13:29:27 +00:00
),
2023-03-10 18:42:45 +00:00
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
semanticsLabel:
"Notifications Button. Takes To Notifications Page.",
2023-03-10 18:42:45 +00:00
key: const Key("walletViewAlertsButton"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.background,
icon: ref.watch(
notificationsProvider.select(
(value) =>
value.hasUnreadNotificationsFor(walletId),
),
)
? SvgPicture.file(
File(
ref.watch(
themeProvider.select(
(value) => value.assets.bellNew,
),
2023-04-24 14:38:17 +00:00
),
),
width: 20,
height: 20,
color: ref.watch(
notificationsProvider.select(
(value) =>
value.hasUnreadNotificationsFor(
walletId,
),
),
)
? null
: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
)
: SvgPicture.asset(
Assets.svg.bell,
width: 20,
height: 20,
color: ref.watch(
notificationsProvider.select(
(value) =>
value.hasUnreadNotificationsFor(
walletId,
),
),
)
? null
: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
2023-03-10 18:42:45 +00:00
onPressed: () {
// reset unread state
ref.refresh(unreadNotificationsStateProvider);
Navigator.of(context)
.pushNamed(
NotificationsView.routeName,
arguments: walletId,
)
.then((_) {
final Set<int> unreadNotificationIds = ref
.read(unreadNotificationsStateProvider.state)
.state;
if (unreadNotificationIds.isEmpty) return;
final List<Future<dynamic>> futures = [];
2023-03-10 18:42:45 +00:00
for (int i = 0;
i < unreadNotificationIds.length - 1;
i++) {
futures.add(
ref.read(notificationsProvider).markAsRead(
2023-03-10 18:42:45 +00:00
unreadNotificationIds.elementAt(i),
false,
),
);
2023-03-10 18:42:45 +00:00
}
// wait for multiple to update if any
Future.wait(futures).then((_) {
// only notify listeners once
ref.read(notificationsProvider).markAsRead(
unreadNotificationIds.last,
true,
);
2023-03-10 18:42:45 +00:00
});
});
},
),
2023-02-08 13:29:27 +00:00
),
2022-08-26 08:11:35 +00:00
),
2023-03-10 18:42:45 +00:00
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
2023-03-10 18:42:45 +00:00
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
semanticsLabel:
"Settings Button. Takes To Wallet Settings Page.",
2023-03-10 18:42:45 +00:00
key: const Key("walletViewSettingsButton"),
size: 36,
shadows: const [],
color: Theme.of(context)
.extension<StackColors>()!
.background,
icon: SvgPicture.asset(
Assets.svg.bars,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
2023-02-08 13:29:27 +00:00
),
2023-03-10 18:42:45 +00:00
onPressed: () {
//todo: check if print needed
// debugPrint("wallet view settings tapped");
Navigator.of(context).pushNamed(
WalletSettingsView.routeName,
arguments: Tuple4(
walletId,
coin,
2023-03-10 18:42:45 +00:00
_currentSyncStatus,
_currentNodeStatus,
),
);
},
2023-02-08 13:29:27 +00:00
),
),
),
2023-03-10 18:42:45 +00:00
],
),
body: SafeArea(
child: Container(
color:
Theme.of(context).extension<StackColors>()!.background,
child: Column(
children: [
const SizedBox(
height: 10,
),
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: WalletSummary(
walletId: walletId,
2023-06-15 23:47:58 +00:00
aspectRatio: 1.75,
initialSyncStatus: ref
.watch(pWallets)
.getWallet(walletId)
.refreshMutex
.isLocked
2023-03-10 18:42:45 +00:00
? WalletSyncStatus.syncing
: WalletSyncStatus.synced,
),
),
),
if (isSparkWallet)
2023-03-10 18:42:45 +00:00
const SizedBox(
height: 10,
),
if (isSparkWallet)
2023-03-10 18:42:45 +00:00
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Expanded(
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(
context,
),
2023-03-10 18:42:45 +00:00
onPressed: () async {
await showDialog<void>(
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<StackColors>()!
.accentColorDark,
),
),
),
rightButton: TextButton(
onPressed: () async {
Navigator.of(context).pop();
unawaited(attemptAnonymize());
},
style: Theme.of(context)
2023-02-08 13:29:27 +00:00
.extension<StackColors>()!
2023-03-10 18:42:45 +00:00
.getPrimaryEnabledButtonStyle(
context,
),
2023-03-10 18:42:45 +00:00
child: Text(
"Continue",
style:
STextStyles.button(context),
),
2023-02-08 13:29:27 +00:00
),
),
2023-03-10 18:42:45 +00:00
);
},
child: Text(
"Anonymize funds",
style:
STextStyles.button(context).copyWith(
color: Theme.of(context)
2023-02-08 13:29:27 +00:00
.extension<StackColors>()!
2023-03-10 18:42:45 +00:00
.buttonTextSecondary,
),
2022-09-06 23:27:14 +00:00
),
),
),
2023-03-10 18:42:45 +00:00
],
2023-02-08 13:29:27 +00:00
),
),
2023-03-10 18:42:45 +00:00
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<StackColors>()!
.textDark3,
),
),
CustomTextButton(
text: "See all",
onTap: () {
Navigator.of(context).pushNamed(
2023-11-14 23:21:55 +00:00
ref
.read(pWallets)
.getWallet(widget.walletId)
.isarTransactionVersion ==
2
2023-10-23 21:05:50 +00:00
? AllTransactionsV2View.routeName
: AllTransactionsView.routeName,
2023-03-10 18:42:45 +00:00
arguments: walletId,
);
},
),
],
2022-09-06 23:27:14 +00:00
),
2023-03-10 18:42:45 +00:00
),
const SizedBox(
height: 12,
),
Expanded(
child: Padding(
2023-02-08 13:29:27 +00:00
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,
2023-10-23 21:05:50 +00:00
],
).createShader(bounds);
},
2023-02-08 13:29:27 +00:00
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
2023-02-08 13:29:27 +00:00
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
Expanded(
child: ref
.read(pWallets)
.getWallet(widget.walletId)
.isarTransactionVersion ==
2
2023-10-23 21:05:50 +00:00
? TransactionsV2List(
walletId: widget.walletId,
)
: TransactionsList(
walletId: walletId,
),
2023-02-08 13:29:27 +00:00
),
],
),
),
2022-08-26 08:11:35 +00:00
),
),
),
2023-03-10 18:42:45 +00:00
),
],
),
2023-03-10 18:42:45 +00:00
),
2023-02-08 13:29:27 +00:00
),
),
2023-03-10 18:42:45 +00:00
WalletNavigationBar(
items: [
WalletNavigationBarItemData(
label: "Receive",
icon: const ReceiveNavIcon(),
onTap: () {
if (mounted) {
unawaited(
Navigator.of(context).pushNamed(
ReceiveView.routeName,
arguments: walletId,
2023-03-10 18:42:45 +00:00
),
);
}
},
),
2024-05-15 21:20:45 +00:00
if (ref.watch(pWalletCoin(walletId)) is FrostCurrency)
WalletNavigationBarItemData(
label: "Sign",
icon: const FrostSignNavIcon(),
onTap: () => _onFrostSignPressed(context),
),
2024-11-12 15:29:12 +00:00
if (!viewOnly)
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(
wallet is BitcoinFrostWallet
? FrostSendView.routeName
: SendView.routeName,
arguments: (
walletId: walletId,
coin: coin,
),
);
},
),
if (!viewOnly &&
Constants.enableExchange &&
ref.watch(pWalletCoin(walletId)) is! FrostCurrency &&
AppConfig.hasFeature(AppFeature.swap) &&
showExchange)
2023-03-10 22:48:44 +00:00
WalletNavigationBarItemData(
label: "Swap",
icon: const ExchangeNavIcon(),
onTap: () => _onExchangePressed(context),
),
if (Constants.enableExchange &&
ref.watch(pWalletCoin(walletId)) is! FrostCurrency &&
AppConfig.hasFeature(AppFeature.buy) &&
showExchange)
2023-03-10 22:48:44 +00:00
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,
);
},
),
2024-05-15 21:20:45 +00:00
if (coin is Banano)
WalletNavigationBarItemData(
icon: SvgPicture.asset(
Assets.svg.monkey,
height: 20,
width: 20,
color: Theme.of(context)
.extension<StackColors>()!
.bottomNavIconIcon,
),
label: "MonKey",
onTap: () {
Navigator.of(context).pushNamed(
MonkeyView.routeName,
arguments: widget.walletId,
);
},
),
2024-11-12 15:29:12 +00:00
if (wallet is CoinControlInterface &&
2023-03-10 18:42:45 +00:00
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,
),
);
},
),
2024-11-14 20:56:17 +00:00
if (!viewOnly && wallet is PaynymInterface)
2023-03-10 18:42:45 +00:00
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);
2023-03-10 18:42:45 +00:00
2023-11-16 16:54:57 +00:00
final paynymInterface = wallet as PaynymInterface;
2023-03-10 18:42:45 +00:00
final code = await paynymInterface.getPaymentCode(
2023-04-25 21:06:12 +00:00
isSegwit: false,
);
2023-03-10 18:42:45 +00:00
final account = await ref
.read(paynymAPIProvider)
.nym(code.toString());
Logging.instance.log(
"my nym account: $account",
level: LogLevel.Info,
);
if (context.mounted) {
2023-03-10 18:42:45 +00:00
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
) {
2023-03-10 18:42:45 +00:00
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,
);
}
}
},
),
2023-07-18 16:15:05 +00:00
if (ref.watch(
pWallets.select(
2023-07-18 16:15:05 +00:00
(value) =>
value.getWallet(widget.walletId) is OrdinalsInterface,
2023-07-18 16:15:05 +00:00
),
))
WalletNavigationBarItemData(
label: "Ordinals",
2023-07-18 17:51:57 +00:00
icon: const OrdinalsNavIcon(),
2023-07-18 16:15:05 +00:00
onTap: () {
Navigator.of(context).pushNamed(
OrdinalsView.routeName,
arguments: widget.walletId,
);
},
),
2024-11-12 15:29:12 +00:00
if (wallet is CashFusionInterface && !viewOnly)
WalletNavigationBarItemData(
label: "Fusion",
icon: const FusionNavIcon(),
onTap: () {
Navigator.of(context).pushNamed(
CashFusionView.routeName,
arguments: walletId,
);
},
),
2024-11-12 15:29:12 +00:00
if (wallet is LibMoneroWallet && !viewOnly)
2024-11-07 22:09:54 +00:00
WalletNavigationBarItemData(
label: "Churn",
icon: const ChurnNavIcon(),
onTap: () {
Navigator.of(context).pushNamed(
ChurningView.routeName,
arguments: walletId,
);
},
),
2023-03-10 18:42:45 +00:00
],
),
],
2022-08-26 08:11:35 +00:00
),
),
),
);
}
}