From 36307473b7b9d1851858d91fcf60faf378b3e393 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sun, 1 Mar 2026 19:36:23 -0600 Subject: [PATCH 1/3] feat: show/hide balances toggle closes #168 --- .../appearance_settings_view.dart | 57 ++++++++++++ .../token_view/sub_widgets/token_summary.dart | 49 +++++++---- .../sub_widgets/wallet_summary_info.dart | 45 ++++++---- .../sub_widgets/favorite_card.dart | 52 +++++++---- .../sub_widgets/desktop_wallet_summary.dart | 33 ++++--- .../firo_desktop_wallet_summary.dart | 29 ++++--- .../mweb_desktop_wallet_summary.dart | 29 ++++--- .../appearance_settings.dart | 40 +++++++++ lib/utilities/prefs.dart | 28 ++++++ lib/widgets/hideable_balance.dart | 86 +++++++++++++++++++ 10 files changed, 364 insertions(+), 84 deletions(-) create mode 100644 lib/widgets/hideable_balance.dart diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart index aa10724a7f..3e781f8061 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart @@ -113,6 +113,63 @@ class AppearanceSettingsView extends ConsumerWidget { ), ), const SizedBox(height: 10), + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + splashColor: + Theme.of( + context, + ).extension()!.highlight, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: null, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Hide balances", + style: STextStyles.titleBold12( + context, + ), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.hideBalances, + ), + ), + onValueChanged: (newValue) { + ref + .read( + prefsChangeNotifierProvider, + ) + .hideBalances = newValue; + }, + ), + ), + ], + ), + ), + ); + }, + ), + ), + const SizedBox(height: 10), RoundedWhiteContainer( child: Column( children: [ diff --git a/lib/pages/token_view/sub_widgets/token_summary.dart b/lib/pages/token_view/sub_widgets/token_summary.dart index 0f1bd177d4..4b057c309e 100644 --- a/lib/pages/token_view/sub_widgets/token_summary.dart +++ b/lib/pages/token_view/sub_widgets/token_summary.dart @@ -36,6 +36,7 @@ import '../../../wallets/isar/providers/eth/token_balance_provider.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../widgets/coin_ticker_tag.dart'; import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/hideable_balance.dart'; import '../../../widgets/rounded_container.dart'; import '../../buy_view/buy_in_wallet_view.dart'; import '../../exchange_view/wallet_initiated_exchange_view.dart'; @@ -104,18 +105,24 @@ class TokenSummary extends ConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - ref - .watch( - pAmountFormatter( - Ethereum(CryptoCurrencyNetwork.main), - ), - ) - .format(balance.total, tokenContract: token), - style: STextStyles.pageTitleH1(context).copyWith( - color: Theme.of( - context, - ).extension()!.tokenSummaryTextPrimary, + HideableBalance( + iconColor: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + iconSize: 24, + child: Text( + ref + .watch( + pAmountFormatter( + Ethereum(CryptoCurrencyNetwork.main), + ), + ) + .format(balance.total, tokenContract: token), + style: STextStyles.pageTitleH1(context).copyWith( + color: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + ), ), ), const SizedBox(width: 10), @@ -128,12 +135,18 @@ class TokenSummary extends ConsumerWidget { ), if (price != null) const SizedBox(height: 6), if (price != null) - Text( - "${(balance.total.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.subtitle500(context).copyWith( - color: Theme.of( - context, - ).extension()!.tokenSummaryTextPrimary, + HideableBalance( + iconColor: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + iconSize: 16, + child: Text( + "${(balance.total.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.subtitle500(context).copyWith( + color: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + ), ), ), const SizedBox(height: 20), diff --git a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart index 68e0123bc8..4e118360b7 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart @@ -33,6 +33,7 @@ import '../../../wallets/crypto_currency/coins/firo.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/wallet/impl/banano_wallet.dart'; import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/hideable_balance.dart'; import 'wallet_balance_toggle_sheet.dart'; import 'wallet_refresh_button.dart'; @@ -206,25 +207,39 @@ class WalletSummaryInfo extends ConsumerWidget { const Spacer(), FittedBox( fit: BoxFit.scaleDown, - child: SelectableText( - ref.watch(pAmountFormatter(coin)).format(balanceToShow), - style: STextStyles.pageTitleH1(context).copyWith( - fontSize: 24, - color: - Theme.of( - context, - ).extension()!.textFavoriteCard, + child: HideableBalance( + iconColor: + Theme.of( + context, + ).extension()!.textFavoriteCard, + iconSize: 24, + child: SelectableText( + ref.watch(pAmountFormatter(coin)).format(balanceToShow), + style: STextStyles.pageTitleH1(context).copyWith( + fontSize: 24, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, + ), ), ), ), if (price != null) - Text( - "${(price.value * balanceToShow.decimal).toAmount(fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", - style: STextStyles.subtitle500(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textFavoriteCard, + HideableBalance( + iconColor: + Theme.of( + context, + ).extension()!.textFavoriteCard, + iconSize: 16, + child: Text( + "${(price.value * balanceToShow.decimal).toAmount(fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", + style: STextStyles.subtitle500(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, + ), ), ), ], diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index aad2dc5dbb..3ec1357d8b 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -31,6 +31,7 @@ import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/wallet/intermediate/external_wallet.dart'; import '../../../widgets/coin_card.dart'; import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/hideable_balance.dart'; import '../../wallet_view/wallet_view.dart'; class FavoriteCard extends ConsumerStatefulWidget { @@ -241,30 +242,45 @@ class _FavoriteCardState extends ConsumerState { children: [ FittedBox( fit: BoxFit.scaleDown, - child: Text( - ref.watch(pAmountFormatter(coin)).format(total), - style: STextStyles.titleBold12(context).copyWith( - fontSize: 16, - color: - Theme.of(context) - .extension()! - .textFavoriteCard, + child: HideableBalance( + iconColor: + Theme.of(context) + .extension()! + .textFavoriteCard, + iconSize: 16, + child: Text( + ref.watch(pAmountFormatter(coin)).format(total), + style: + STextStyles.titleBold12(context).copyWith( + fontSize: 16, + color: + Theme.of(context) + .extension()! + .textFavoriteCard, + ), ), ), ), if (externalCalls && price != null) const SizedBox(height: 4), if (externalCalls && price != null) - Text( - "${fiatTotal.fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.itemSubtitle12( - context, - ).copyWith( - fontSize: 10, - color: - Theme.of(context) - .extension()! - .textFavoriteCard, + HideableBalance( + iconColor: + Theme.of(context) + .extension()! + .textFavoriteCard, + iconSize: 10, + child: Text( + "${fiatTotal.fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.itemSubtitle12( + context, + ).copyWith( + fontSize: 10, + color: + Theme.of(context) + .extension()! + .textFavoriteCard, + ), ), ), ], diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index ac01104ff3..89ea899573 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -33,6 +33,7 @@ import '../../../../wallets/isar/providers/eth/token_balance_provider.dart'; import '../../../../wallets/isar/providers/solana/current_sol_token_wallet_provider.dart'; import '../../../../wallets/isar/providers/solana/sol_token_balance_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../widgets/hideable_balance.dart'; import 'desktop_balance_toggle_button.dart'; class DesktopWalletSummary extends ConsumerStatefulWidget { @@ -171,20 +172,30 @@ class _WDesktopWalletSummaryState extends ConsumerState { children: [ FittedBox( fit: BoxFit.scaleDown, - child: SelectableText( - ref - .watch(pAmountFormatter(coin)) - .format(balanceToShow, tokenContract: tokenContract), - style: STextStyles.desktopH3(context), + child: HideableBalance( + iconSize: 24, + child: SelectableText( + ref + .watch(pAmountFormatter(coin)) + .format(balanceToShow, tokenContract: tokenContract), + style: STextStyles.desktopH3(context), + ), ), ), if (externalCalls && price != null) - SelectableText( - "${Amount.fromDecimal(price.value * balanceToShow.decimal, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of( - context, - ).extension()!.textSubtitle1, + HideableBalance( + iconColor: Theme.of( + context, + ).extension()!.textSubtitle1, + iconSize: 16, + child: SelectableText( + "${Amount.fromDecimal(price.value * balanceToShow.decimal, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), ), ), // if (coin is Firo) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart index e193ab380c..68b5804864 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart @@ -27,6 +27,7 @@ import '../../../../utilities/extensions/extensions.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/crypto_currency/coins/firo.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../widgets/hideable_balance.dart'; import 'desktop_balance_toggle_button.dart'; class FiroDesktopWalletSummary extends ConsumerStatefulWidget { @@ -194,10 +195,13 @@ class _Balance extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return SelectableText( - ref.watch(pAmountFormatter(coin)).format(amount, tokenContract: null), - style: STextStyles.desktopH3(context), - textAlign: TextAlign.end, + return HideableBalance( + iconSize: 24, + child: SelectableText( + ref.watch(pAmountFormatter(coin)).format(amount, tokenContract: null), + style: STextStyles.desktopH3(context), + textAlign: TextAlign.end, + ), ); } } @@ -218,14 +222,17 @@ class _Price extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Padding( padding: const EdgeInsets.only(left: 16), - child: SelectableText( - "${Amount.fromDecimal(price * amount.decimal, fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} " - "${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textSubtitle1, + child: HideableBalance( + iconColor: Theme.of(context).extension()!.textSubtitle1, + iconSize: 16, + child: SelectableText( + "${Amount.fromDecimal(price * amount.decimal, fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} " + "${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ), + textAlign: TextAlign.end, ), - - textAlign: TextAlign.end, ), ); } diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/mweb_desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/mweb_desktop_wallet_summary.dart index 6b025f0dd7..e4c672bc14 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/mweb_desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/mweb_desktop_wallet_summary.dart @@ -23,6 +23,7 @@ import '../../../../utilities/enums/wallet_balance_toggle_state.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../widgets/hideable_balance.dart'; import 'desktop_balance_toggle_button.dart'; class MwebDesktopWalletSummary extends ConsumerStatefulWidget { @@ -169,10 +170,13 @@ class _Balance extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return SelectableText( - ref.watch(pAmountFormatter(coin)).format(amount, tokenContract: null), - style: STextStyles.desktopH3(context), - textAlign: TextAlign.end, + return HideableBalance( + iconSize: 24, + child: SelectableText( + ref.watch(pAmountFormatter(coin)).format(amount, tokenContract: null), + style: STextStyles.desktopH3(context), + textAlign: TextAlign.end, + ), ); } } @@ -193,14 +197,17 @@ class _Price extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Padding( padding: const EdgeInsets.only(left: 16), - child: SelectableText( - "${Amount.fromDecimal(price * amount.decimal, fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} " - "${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textSubtitle1, + child: HideableBalance( + iconColor: Theme.of(context).extension()!.textSubtitle1, + iconSize: 16, + child: SelectableText( + "${Amount.fromDecimal(price * amount.decimal, fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} " + "${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ), + textAlign: TextAlign.end, ), - - textAlign: TextAlign.end, ), ); } diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart index 8fa05124b1..d8e1e2fc89 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart @@ -140,6 +140,46 @@ class _AppearanceOptionSettings thickness: 0.5, ), ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Hide balances", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.hideBalances, + ), + ), + onValueChanged: (newValue) { + ref + .read(prefsChangeNotifierProvider) + .hideBalances = newValue; + }, + ), + ), + ], + ), + ), + const Padding( + padding: EdgeInsets.all(10.0), + child: Divider( + thickness: 0.5, + ), + ), Padding( padding: const EdgeInsets.all(10.0), child: Row( diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 20308539ea..771e7076e9 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -83,6 +83,7 @@ class Prefs extends ChangeNotifier { _autoLockInfo = await _getAutoLockInfo(); _privacyScreen = await _getPrivacyScreen(); _disableScreenShots = await _getDisableScreenShots(); + _hideBalances = await _getHideBalances(); _initialized = true; } @@ -1433,4 +1434,31 @@ class Prefs extends ChangeNotifier { as bool? ?? false; } + + // hide balances + + bool _hideBalances = false; + + bool get hideBalances => _hideBalances; + + set hideBalances(bool hideBalances) { + if (_hideBalances != hideBalances) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "hideBalances", + value: hideBalances, + ); + _hideBalances = hideBalances; + notifyListeners(); + } + } + + Future _getHideBalances() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "hideBalances", + ) + as bool? ?? + false; + } } diff --git a/lib/widgets/hideable_balance.dart b/lib/widgets/hideable_balance.dart new file mode 100644 index 0000000000..803b0491c3 --- /dev/null +++ b/lib/widgets/hideable_balance.dart @@ -0,0 +1,86 @@ +/* + * 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 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../providers/global/prefs_provider.dart'; +import '../utilities/assets.dart'; + +/// Wraps a balance value widget so that, when the hide balances preference is +/// enabled, the value is replaced by an eye mask icon. Tapping the icon reveals +/// the underlying value for a few seconds before it is masked again. The +/// persisted preference is never modified by the tap to reveal behaviour. +class HideableBalance extends ConsumerStatefulWidget { + const HideableBalance({ + super.key, + required this.child, + this.iconColor, + this.iconSize = 20, + }); + + final Widget child; + final Color? iconColor; + final double iconSize; + + static const Duration revealDuration = Duration(seconds: 3); + + @override + ConsumerState createState() => _HideableBalanceState(); +} + +class _HideableBalanceState extends ConsumerState { + Timer? _revealTimer; + bool _revealed = false; + + void _reveal() { + _revealTimer?.cancel(); + setState(() { + _revealed = true; + }); + _revealTimer = Timer(HideableBalance.revealDuration, () { + if (mounted) { + setState(() { + _revealed = false; + }); + } + }); + } + + @override + void dispose() { + _revealTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final hideBalances = ref.watch( + prefsChangeNotifierProvider.select((value) => value.hideBalances), + ); + + if (!hideBalances || _revealed) { + return widget.child; + } + + return GestureDetector( + onTap: _reveal, + child: SvgPicture.asset( + Assets.svg.eyeSlash, + color: widget.iconColor, + width: widget.iconSize, + height: widget.iconSize, + ), + ); + } +} From 7818f14698d77351bfa51b62205240b4ff15f92d Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 28 May 2026 11:30:30 -0500 Subject: [PATCH 2/3] chore: dart format --- .../appearance_settings_view.dart | 49 +++--- .../sub_widgets/wallet_summary_info.dart | 81 +++++----- .../sub_widgets/favorite_card.dart | 149 +++++++++--------- .../sub_widgets/desktop_wallet_summary.dart | 12 +- .../appearance_settings.dart | 139 ++++++++-------- 5 files changed, 207 insertions(+), 223 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart index 3e781f8061..0e45fd8094 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart @@ -59,10 +59,9 @@ class AppearanceSettingsView extends ConsumerWidget { child: Consumer( builder: (_, ref, __) { return RawMaterialButton( - splashColor: - Theme.of( - context, - ).extension()!.highlight, + splashColor: Theme.of( + context, + ).extension()!.highlight, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( @@ -91,17 +90,19 @@ class AppearanceSettingsView extends ConsumerWidget { width: 40, child: DraggableSwitchButton( isOn: ref.watch( - prefsChangeNotifierProvider.select( - (value) => - value.showFavoriteWallets, - ), + prefsChangeNotifierProvider + .select( + (value) => value + .showFavoriteWallets, + ), ), onValueChanged: (newValue) { ref - .read( - prefsChangeNotifierProvider, - ) - .showFavoriteWallets = newValue; + .read( + prefsChangeNotifierProvider, + ) + .showFavoriteWallets = + newValue; }, ), ), @@ -117,10 +118,9 @@ class AppearanceSettingsView extends ConsumerWidget { child: Consumer( builder: (_, ref, __) { return RawMaterialButton( - splashColor: - Theme.of( - context, - ).extension()!.highlight, + splashColor: Theme.of( + context, + ).extension()!.highlight, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( @@ -149,16 +149,19 @@ class AppearanceSettingsView extends ConsumerWidget { width: 40, child: DraggableSwitchButton( isOn: ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.hideBalances, - ), + prefsChangeNotifierProvider + .select( + (value) => + value.hideBalances, + ), ), onValueChanged: (newValue) { ref - .read( - prefsChangeNotifierProvider, - ) - .hideBalances = newValue; + .read( + prefsChangeNotifierProvider, + ) + .hideBalances = + newValue; }, ), ), diff --git a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart index 4e118360b7..4d09bbef20 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart @@ -102,10 +102,9 @@ class WalletSummaryInfo extends ConsumerWidget { "${_showAvailable ? "Available" : "Full"} ${type.name.capitalize()} balance"; switch (type) { case BalanceType.private: - final balance = - coin is Firo - ? ref.watch(pWalletBalanceTertiary(walletId)) - : ref.watch(pWalletBalanceSecondary(walletId)); + final balance = coin is Firo + ? ref.watch(pWalletBalanceTertiary(walletId)) + : ref.watch(pWalletBalanceSecondary(walletId)); balanceToShow = _showAvailable ? balance.spendable : balance.total; break; @@ -123,23 +122,21 @@ class WalletSummaryInfo extends ConsumerWidget { List? imageBytes; if (coin is Banano) { - imageBytes = - (ref.watch(pWallets).getWallet(walletId) as BananoWallet) - .getMonkeyImageBytes(); + imageBytes = (ref.watch(pWallets).getWallet(walletId) as BananoWallet) + .getMonkeyImageBytes(); } return ConditionalParent( condition: imageBytes != null, - builder: - (child) => Stack( - children: [ - Positioned.fill( - left: 150.0, - child: SvgPicture.memory(Uint8List.fromList(imageBytes!)), - ), - child, - ], + builder: (child) => Stack( + children: [ + Positioned.fill( + left: 150.0, + child: SvgPicture.memory(Uint8List.fromList(imageBytes!)), ), + child, + ], + ), child: Row( children: [ Expanded( @@ -152,12 +149,14 @@ class WalletSummaryInfo extends ConsumerWidget { if (ref.read(walletBalanceToggleStateProvider) == WalletBalanceToggleState.available) { ref - .read(walletBalanceToggleStateProvider.notifier) - .state = WalletBalanceToggleState.full; + .read(walletBalanceToggleStateProvider.notifier) + .state = + WalletBalanceToggleState.full; } else { ref - .read(walletBalanceToggleStateProvider.notifier) - .state = WalletBalanceToggleState.available; + .read(walletBalanceToggleStateProvider.notifier) + .state = + WalletBalanceToggleState.available; } } else { showSheet(context); @@ -168,20 +167,18 @@ class WalletSummaryInfo extends ConsumerWidget { Text( title, style: STextStyles.subtitle500(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textFavoriteCard, + color: Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), if (!toggleBalance) ...[ const SizedBox(width: 4), SvgPicture.asset( Assets.svg.chevronDown, - color: - Theme.of( - context, - ).extension()!.textFavoriteCard, + color: Theme.of( + context, + ).extension()!.textFavoriteCard, width: 8, height: 4, ), @@ -208,37 +205,33 @@ class WalletSummaryInfo extends ConsumerWidget { FittedBox( fit: BoxFit.scaleDown, child: HideableBalance( - iconColor: - Theme.of( - context, - ).extension()!.textFavoriteCard, + iconColor: Theme.of( + context, + ).extension()!.textFavoriteCard, iconSize: 24, child: SelectableText( ref.watch(pAmountFormatter(coin)).format(balanceToShow), style: STextStyles.pageTitleH1(context).copyWith( fontSize: 24, - color: - Theme.of( - context, - ).extension()!.textFavoriteCard, + color: Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), ), ), if (price != null) HideableBalance( - iconColor: - Theme.of( - context, - ).extension()!.textFavoriteCard, + iconColor: Theme.of( + context, + ).extension()!.textFavoriteCard, iconSize: 16, child: Text( "${(price.value * balanceToShow.decimal).toAmount(fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.subtitle500(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textFavoriteCard, + color: Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), ), diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 3ec1357d8b..7f321bc561 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -79,53 +79,51 @@ class _FavoriteCardState extends ConsumerState { } return ConditionalParent( condition: Util.isDesktop, - builder: - (child) => MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - _hovering = true; - }); - }, - onExit: (_) { - setState(() { - _hovering = false; - }); - }, - child: AnimatedScale( - duration: const Duration(milliseconds: 200), - scale: _hovering ? 1.05 : 1, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: - _hovering - ? BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - boxShadow: [ - Theme.of( - context, - ).extension()!.standardBoxShadow, - Theme.of( - context, - ).extension()!.standardBoxShadow, - Theme.of( - context, - ).extension()!.standardBoxShadow, - ], - ) - : BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: child, - ), - ), + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: _hovering ? 1.05 : 1, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: _hovering + ? BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + boxShadow: [ + Theme.of( + context, + ).extension()!.standardBoxShadow, + Theme.of( + context, + ).extension()!.standardBoxShadow, + Theme.of( + context, + ).extension()!.standardBoxShadow, + ], + ) + : BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: child, ), + ), + ), child: GestureDetector( onTap: () async { final wallet = ref.read(pWallets).getWallet(walletId); @@ -194,10 +192,9 @@ class _FavoriteCardState extends ConsumerState { child: Text( ref.watch(pWalletName(walletId)), style: STextStyles.itemSubtitle12(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textFavoriteCard, + color: Theme.of( + context, + ).extension()!.textFavoriteCard, ), overflow: TextOverflow.fade, ), @@ -216,15 +213,18 @@ class _FavoriteCardState extends ConsumerState { Amount total = balance.total; if (coin is Firo) { - total += - ref.watch(pWalletBalanceSecondary(walletId)).total; - total += - ref.watch(pWalletBalanceTertiary(walletId)).total; + total += ref + .watch(pWalletBalanceSecondary(walletId)) + .total; + total += ref + .watch(pWalletBalanceTertiary(walletId)) + .total; } else if (ref.watch( pWalletInfo(walletId).select((s) => s.isMwebEnabled), )) { - total += - ref.watch(pWalletBalanceSecondary(walletId)).total; + total += ref + .watch(pWalletBalanceSecondary(walletId)) + .total; } Amount fiatTotal = Amount.zero; @@ -243,21 +243,19 @@ class _FavoriteCardState extends ConsumerState { FittedBox( fit: BoxFit.scaleDown, child: HideableBalance( - iconColor: - Theme.of(context) - .extension()! - .textFavoriteCard, + iconColor: Theme.of( + context, + ).extension()!.textFavoriteCard, iconSize: 16, child: Text( ref.watch(pAmountFormatter(coin)).format(total), - style: - STextStyles.titleBold12(context).copyWith( - fontSize: 16, - color: - Theme.of(context) + style: STextStyles.titleBold12(context) + .copyWith( + fontSize: 16, + color: Theme.of(context) .extension()! .textFavoriteCard, - ), + ), ), ), ), @@ -265,22 +263,19 @@ class _FavoriteCardState extends ConsumerState { const SizedBox(height: 4), if (externalCalls && price != null) HideableBalance( - iconColor: - Theme.of(context) - .extension()! - .textFavoriteCard, + iconColor: Theme.of( + context, + ).extension()!.textFavoriteCard, iconSize: 10, child: Text( "${fiatTotal.fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.itemSubtitle12( - context, - ).copyWith( - fontSize: 10, - color: - Theme.of(context) + style: STextStyles.itemSubtitle12(context) + .copyWith( + fontSize: 10, + color: Theme.of(context) .extension()! .textFavoriteCard, - ), + ), ), ), ], diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index 89ea899573..48c45f9a7a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -190,12 +190,12 @@ class _WDesktopWalletSummaryState extends ConsumerState { iconSize: 16, child: SelectableText( "${Amount.fromDecimal(price.value * balanceToShow.decimal, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of( - context, - ).extension()!.textSubtitle1, - ), + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), ), ), // if (coin is Firo) diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart index d8e1e2fc89..72325c22a3 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart @@ -48,9 +48,7 @@ class _AppearanceOptionSettings child: Column( children: [ Padding( - padding: const EdgeInsets.only( - right: 30, - ), + padding: const EdgeInsets.only(right: 30), child: RoundedWhiteContainer( radiusMultiplier: 2, child: Wrap( @@ -77,16 +75,17 @@ class _AppearanceOptionSettings children: [ TextSpan( text: "Appearances", - style: - STextStyles.desktopTextSmall(context), + style: STextStyles.desktopTextSmall( + context, + ), ), TextSpan( text: "\n\nCustomize how your ${AppConfig.appName} looks according to your preferences.", style: STextStyles.desktopTextExtraExtraSmall( - context, - ), + context, + ), ), ], ), @@ -96,9 +95,7 @@ class _AppearanceOptionSettings ), const Padding( padding: EdgeInsets.all(10.0), - child: Divider( - thickness: 0.5, - ), + child: Divider(thickness: 0.5), ), Padding( padding: const EdgeInsets.all(10.0), @@ -109,10 +106,10 @@ class _AppearanceOptionSettings "Display favorite wallets", style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + color: Theme.of( + context, + ).extension()!.textDark, + ), textAlign: TextAlign.left, ), SizedBox( @@ -126,8 +123,9 @@ class _AppearanceOptionSettings ), onValueChanged: (newValue) { ref - .read(prefsChangeNotifierProvider) - .showFavoriteWallets = newValue; + .read(prefsChangeNotifierProvider) + .showFavoriteWallets = + newValue; }, ), ), @@ -136,9 +134,7 @@ class _AppearanceOptionSettings ), const Padding( padding: EdgeInsets.all(10.0), - child: Divider( - thickness: 0.5, - ), + child: Divider(thickness: 0.5), ), Padding( padding: const EdgeInsets.all(10.0), @@ -149,10 +145,10 @@ class _AppearanceOptionSettings "Hide balances", style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + color: Theme.of( + context, + ).extension()!.textDark, + ), textAlign: TextAlign.left, ), SizedBox( @@ -166,8 +162,9 @@ class _AppearanceOptionSettings ), onValueChanged: (newValue) { ref - .read(prefsChangeNotifierProvider) - .hideBalances = newValue; + .read(prefsChangeNotifierProvider) + .hideBalances = + newValue; }, ), ), @@ -176,9 +173,7 @@ class _AppearanceOptionSettings ), const Padding( padding: EdgeInsets.all(10.0), - child: Divider( - thickness: 0.5, - ), + child: Divider(thickness: 0.5), ), Padding( padding: const EdgeInsets.all(10.0), @@ -189,10 +184,10 @@ class _AppearanceOptionSettings "Choose theme", style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + color: Theme.of( + context, + ).extension()!.textDark, + ), textAlign: TextAlign.left, ), ], @@ -215,9 +210,7 @@ class _AppearanceOptionSettings } class ThemeToggle extends ConsumerStatefulWidget { - const ThemeToggle({ - super.key, - }); + const ThemeToggle({super.key}); @override ConsumerState createState() => _ThemeToggle(); @@ -259,8 +252,9 @@ class _ThemeToggle extends ConsumerState { } // apply theme - ref.read(themeProvider.notifier).state = - ref.read(pThemeService).getTheme(themeId: themeId)!; + ref.read(themeProvider.notifier).state = ref + .read(pThemeService) + .getTheme(themeId: themeId)!; // Assets.precache(context); } else { @@ -279,8 +273,9 @@ class _ThemeToggle extends ConsumerState { ref.read(prefsChangeNotifierProvider.notifier).themeId = themeId; // apply theme - ref.read(themeProvider.notifier).state = - ref.read(pThemeService).getTheme(themeId: themeId)!; + ref.read(themeProvider.notifier).state = ref + .read(pThemeService) + .getTheme(themeId: themeId)!; // Assets.precache(context); } @@ -318,16 +313,20 @@ class _ThemeToggle extends ConsumerState { void initState() { _updateInstalledList(); - _subscription = - ref.read(mainDBProvider).isar.stackThemes.watchLazy().listen((_) { - if (mounted) { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _updateInstalledList(); - }); + _subscription = ref + .read(mainDBProvider) + .isar + .stackThemes + .watchLazy() + .listen((_) { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _updateInstalledList(); + }); + }); + } }); - } - }); super.initState(); } @@ -367,28 +366,24 @@ class _ThemeToggle extends ConsumerState { border: Border.all( width: 2.5, color: _current == i - ? Theme.of(context) - .extension()! - .infoItemIcons - : Theme.of(context) - .extension()! - .popupBG, + ? Theme.of( + context, + ).extension()!.infoItemIcons + : Theme.of( + context, + ).extension()!.popupBG, ), borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), ), child: SvgPicture.file( - File( - installedThemeIdNames[i].item3, - ), + File(installedThemeIdNames[i].item3), height: 160, width: 200, ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Row( children: [ SizedBox( @@ -407,17 +402,15 @@ class _ThemeToggle extends ConsumerState { }, ), ), - const SizedBox( - width: 14, - ), + const SizedBox(width: 14), Text( installedThemeIdNames[i].item2, style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + color: Theme.of( + context, + ).extension()!.textDark, + ), ), ], ), @@ -449,9 +442,9 @@ class _ThemeToggle extends ConsumerState { focusElevation: 0, hoverElevation: 0, highlightElevation: 0, - fillColor: Theme.of(context) - .extension()! - .textFieldActiveBG, + fillColor: Theme.of( + context, + ).extension()!.textFieldActiveBG, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -464,9 +457,9 @@ class _ThemeToggle extends ConsumerState { child: Center( child: SvgPicture.asset( Assets.svg.circlePlusFilled, - color: Theme.of(context) - .extension()! - .textSubtitle2, + color: Theme.of( + context, + ).extension()!.textSubtitle2, width: 20, height: 20, ), From 5b33ccdb82604ebcc2054b97fba66538cf499003 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 21 Aug 2026 15:47:46 -0500 Subject: [PATCH 3/3] fix: harden hidden balances --- .../appearance_settings_view.dart | 49 +++--- .../sub_widgets/token_summary_sol.dart | 40 +++-- .../sub_widgets/wallet_summary_info.dart | 81 +++++----- .../sub_widgets/favorite_card.dart | 149 +++++++++--------- .../sub_widgets/desktop_wallet_summary.dart | 12 +- .../appearance_settings.dart | 139 ++++++++-------- lib/providers/global/prefs_provider.dart | 16 +- lib/widgets/hideable_balance.dart | 33 ++-- test/widget_tests/hideable_balance_test.dart | 96 +++++++++++ 9 files changed, 374 insertions(+), 241 deletions(-) create mode 100644 test/widget_tests/hideable_balance_test.dart diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart index 0e45fd8094..3e781f8061 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart @@ -59,9 +59,10 @@ class AppearanceSettingsView extends ConsumerWidget { child: Consumer( builder: (_, ref, __) { return RawMaterialButton( - splashColor: Theme.of( - context, - ).extension()!.highlight, + splashColor: + Theme.of( + context, + ).extension()!.highlight, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( @@ -90,19 +91,17 @@ class AppearanceSettingsView extends ConsumerWidget { width: 40, child: DraggableSwitchButton( isOn: ref.watch( - prefsChangeNotifierProvider - .select( - (value) => value - .showFavoriteWallets, - ), + prefsChangeNotifierProvider.select( + (value) => + value.showFavoriteWallets, + ), ), onValueChanged: (newValue) { ref - .read( - prefsChangeNotifierProvider, - ) - .showFavoriteWallets = - newValue; + .read( + prefsChangeNotifierProvider, + ) + .showFavoriteWallets = newValue; }, ), ), @@ -118,9 +117,10 @@ class AppearanceSettingsView extends ConsumerWidget { child: Consumer( builder: (_, ref, __) { return RawMaterialButton( - splashColor: Theme.of( - context, - ).extension()!.highlight, + splashColor: + Theme.of( + context, + ).extension()!.highlight, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( @@ -149,19 +149,16 @@ class AppearanceSettingsView extends ConsumerWidget { width: 40, child: DraggableSwitchButton( isOn: ref.watch( - prefsChangeNotifierProvider - .select( - (value) => - value.hideBalances, - ), + prefsChangeNotifierProvider.select( + (value) => value.hideBalances, + ), ), onValueChanged: (newValue) { ref - .read( - prefsChangeNotifierProvider, - ) - .hideBalances = - newValue; + .read( + prefsChangeNotifierProvider, + ) + .hideBalances = newValue; }, ), ), diff --git a/lib/pages/token_view/sub_widgets/token_summary_sol.dart b/lib/pages/token_view/sub_widgets/token_summary_sol.dart index 9d627c1f58..1b1f23463c 100644 --- a/lib/pages/token_view/sub_widgets/token_summary_sol.dart +++ b/lib/pages/token_view/sub_widgets/token_summary_sol.dart @@ -23,12 +23,12 @@ import '../../../utilities/amount/amount.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/constants.dart'; import '../../../utilities/text_styles.dart'; -import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../wallets/isar/providers/solana/current_sol_token_wallet_provider.dart'; import '../../../wallets/isar/providers/solana/sol_token_balance_provider.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../widgets/coin_ticker_tag.dart'; import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/hideable_balance.dart'; import '../../../widgets/rounded_container.dart'; import '../../receive_view/sol_token_receive_view.dart'; import '../../send_view/sol_token_send_view.dart'; @@ -124,12 +124,20 @@ class SolanaTokenSummary extends ConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - balance.total.decimal.toStringAsFixed(tokenWallet.tokenDecimals), - style: STextStyles.pageTitleH1(context).copyWith( - color: Theme.of( - context, - ).extension()!.tokenSummaryTextPrimary, + HideableBalance( + iconColor: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + iconSize: 24, + child: Text( + balance.total.decimal.toStringAsFixed( + tokenWallet.tokenDecimals, + ), + style: STextStyles.pageTitleH1(context).copyWith( + color: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + ), ), ), const SizedBox(width: 10), @@ -140,12 +148,18 @@ class SolanaTokenSummary extends ConsumerWidget { ), if (price != null) const SizedBox(height: 6), if (price != null) - Text( - "${(balance.total.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.subtitle500(context).copyWith( - color: Theme.of( - context, - ).extension()!.tokenSummaryTextPrimary, + HideableBalance( + iconColor: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + iconSize: 16, + child: Text( + "${(balance.total.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.subtitle500(context).copyWith( + color: Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, + ), ), ), const SizedBox(height: 20), diff --git a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart index 4d09bbef20..4e118360b7 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart @@ -102,9 +102,10 @@ class WalletSummaryInfo extends ConsumerWidget { "${_showAvailable ? "Available" : "Full"} ${type.name.capitalize()} balance"; switch (type) { case BalanceType.private: - final balance = coin is Firo - ? ref.watch(pWalletBalanceTertiary(walletId)) - : ref.watch(pWalletBalanceSecondary(walletId)); + final balance = + coin is Firo + ? ref.watch(pWalletBalanceTertiary(walletId)) + : ref.watch(pWalletBalanceSecondary(walletId)); balanceToShow = _showAvailable ? balance.spendable : balance.total; break; @@ -122,21 +123,23 @@ class WalletSummaryInfo extends ConsumerWidget { List? imageBytes; if (coin is Banano) { - imageBytes = (ref.watch(pWallets).getWallet(walletId) as BananoWallet) - .getMonkeyImageBytes(); + imageBytes = + (ref.watch(pWallets).getWallet(walletId) as BananoWallet) + .getMonkeyImageBytes(); } return ConditionalParent( condition: imageBytes != null, - builder: (child) => Stack( - children: [ - Positioned.fill( - left: 150.0, - child: SvgPicture.memory(Uint8List.fromList(imageBytes!)), + builder: + (child) => Stack( + children: [ + Positioned.fill( + left: 150.0, + child: SvgPicture.memory(Uint8List.fromList(imageBytes!)), + ), + child, + ], ), - child, - ], - ), child: Row( children: [ Expanded( @@ -149,14 +152,12 @@ class WalletSummaryInfo extends ConsumerWidget { if (ref.read(walletBalanceToggleStateProvider) == WalletBalanceToggleState.available) { ref - .read(walletBalanceToggleStateProvider.notifier) - .state = - WalletBalanceToggleState.full; + .read(walletBalanceToggleStateProvider.notifier) + .state = WalletBalanceToggleState.full; } else { ref - .read(walletBalanceToggleStateProvider.notifier) - .state = - WalletBalanceToggleState.available; + .read(walletBalanceToggleStateProvider.notifier) + .state = WalletBalanceToggleState.available; } } else { showSheet(context); @@ -167,18 +168,20 @@ class WalletSummaryInfo extends ConsumerWidget { Text( title, style: STextStyles.subtitle500(context).copyWith( - color: Theme.of( - context, - ).extension()!.textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), if (!toggleBalance) ...[ const SizedBox(width: 4), SvgPicture.asset( Assets.svg.chevronDown, - color: Theme.of( - context, - ).extension()!.textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, width: 8, height: 4, ), @@ -205,33 +208,37 @@ class WalletSummaryInfo extends ConsumerWidget { FittedBox( fit: BoxFit.scaleDown, child: HideableBalance( - iconColor: Theme.of( - context, - ).extension()!.textFavoriteCard, + iconColor: + Theme.of( + context, + ).extension()!.textFavoriteCard, iconSize: 24, child: SelectableText( ref.watch(pAmountFormatter(coin)).format(balanceToShow), style: STextStyles.pageTitleH1(context).copyWith( fontSize: 24, - color: Theme.of( - context, - ).extension()!.textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), ), ), if (price != null) HideableBalance( - iconColor: Theme.of( - context, - ).extension()!.textFavoriteCard, + iconColor: + Theme.of( + context, + ).extension()!.textFavoriteCard, iconSize: 16, child: Text( "${(price.value * balanceToShow.decimal).toAmount(fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.subtitle500(context).copyWith( - color: Theme.of( - context, - ).extension()!.textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), ), diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 7f321bc561..3ec1357d8b 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -79,51 +79,53 @@ class _FavoriteCardState extends ConsumerState { } return ConditionalParent( condition: Util.isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - _hovering = true; - }); - }, - onExit: (_) { - setState(() { - _hovering = false; - }); - }, - child: AnimatedScale( - duration: const Duration(milliseconds: 200), - scale: _hovering ? 1.05 : 1, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: _hovering - ? BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - boxShadow: [ - Theme.of( - context, - ).extension()!.standardBoxShadow, - Theme.of( - context, - ).extension()!.standardBoxShadow, - Theme.of( - context, - ).extension()!.standardBoxShadow, - ], - ) - : BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: child, + builder: + (child) => MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: _hovering ? 1.05 : 1, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: + _hovering + ? BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + boxShadow: [ + Theme.of( + context, + ).extension()!.standardBoxShadow, + Theme.of( + context, + ).extension()!.standardBoxShadow, + Theme.of( + context, + ).extension()!.standardBoxShadow, + ], + ) + : BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: child, + ), + ), ), - ), - ), child: GestureDetector( onTap: () async { final wallet = ref.read(pWallets).getWallet(walletId); @@ -192,9 +194,10 @@ class _FavoriteCardState extends ConsumerState { child: Text( ref.watch(pWalletName(walletId)), style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of( - context, - ).extension()!.textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), overflow: TextOverflow.fade, ), @@ -213,18 +216,15 @@ class _FavoriteCardState extends ConsumerState { Amount total = balance.total; if (coin is Firo) { - total += ref - .watch(pWalletBalanceSecondary(walletId)) - .total; - total += ref - .watch(pWalletBalanceTertiary(walletId)) - .total; + total += + ref.watch(pWalletBalanceSecondary(walletId)).total; + total += + ref.watch(pWalletBalanceTertiary(walletId)).total; } else if (ref.watch( pWalletInfo(walletId).select((s) => s.isMwebEnabled), )) { - total += ref - .watch(pWalletBalanceSecondary(walletId)) - .total; + total += + ref.watch(pWalletBalanceSecondary(walletId)).total; } Amount fiatTotal = Amount.zero; @@ -243,19 +243,21 @@ class _FavoriteCardState extends ConsumerState { FittedBox( fit: BoxFit.scaleDown, child: HideableBalance( - iconColor: Theme.of( - context, - ).extension()!.textFavoriteCard, + iconColor: + Theme.of(context) + .extension()! + .textFavoriteCard, iconSize: 16, child: Text( ref.watch(pAmountFormatter(coin)).format(total), - style: STextStyles.titleBold12(context) - .copyWith( - fontSize: 16, - color: Theme.of(context) + style: + STextStyles.titleBold12(context).copyWith( + fontSize: 16, + color: + Theme.of(context) .extension()! .textFavoriteCard, - ), + ), ), ), ), @@ -263,19 +265,22 @@ class _FavoriteCardState extends ConsumerState { const SizedBox(height: 4), if (externalCalls && price != null) HideableBalance( - iconColor: Theme.of( - context, - ).extension()!.textFavoriteCard, + iconColor: + Theme.of(context) + .extension()! + .textFavoriteCard, iconSize: 10, child: Text( "${fiatTotal.fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - fontSize: 10, - color: Theme.of(context) + style: STextStyles.itemSubtitle12( + context, + ).copyWith( + fontSize: 10, + color: + Theme.of(context) .extension()! .textFavoriteCard, - ), + ), ), ), ], diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index 48c45f9a7a..89ea899573 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -190,12 +190,12 @@ class _WDesktopWalletSummaryState extends ConsumerState { iconSize: 16, child: SelectableText( "${Amount.fromDecimal(price.value * balanceToShow.decimal, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of( - context, - ).extension()!.textSubtitle1, - ), + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), ), ), // if (coin is Firo) diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart index 72325c22a3..d8e1e2fc89 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart @@ -48,7 +48,9 @@ class _AppearanceOptionSettings child: Column( children: [ Padding( - padding: const EdgeInsets.only(right: 30), + padding: const EdgeInsets.only( + right: 30, + ), child: RoundedWhiteContainer( radiusMultiplier: 2, child: Wrap( @@ -75,17 +77,16 @@ class _AppearanceOptionSettings children: [ TextSpan( text: "Appearances", - style: STextStyles.desktopTextSmall( - context, - ), + style: + STextStyles.desktopTextSmall(context), ), TextSpan( text: "\n\nCustomize how your ${AppConfig.appName} looks according to your preferences.", style: STextStyles.desktopTextExtraExtraSmall( - context, - ), + context, + ), ), ], ), @@ -95,7 +96,9 @@ class _AppearanceOptionSettings ), const Padding( padding: EdgeInsets.all(10.0), - child: Divider(thickness: 0.5), + child: Divider( + thickness: 0.5, + ), ), Padding( padding: const EdgeInsets.all(10.0), @@ -106,10 +109,10 @@ class _AppearanceOptionSettings "Display favorite wallets", style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of( - context, - ).extension()!.textDark, - ), + color: Theme.of(context) + .extension()! + .textDark, + ), textAlign: TextAlign.left, ), SizedBox( @@ -123,9 +126,8 @@ class _AppearanceOptionSettings ), onValueChanged: (newValue) { ref - .read(prefsChangeNotifierProvider) - .showFavoriteWallets = - newValue; + .read(prefsChangeNotifierProvider) + .showFavoriteWallets = newValue; }, ), ), @@ -134,7 +136,9 @@ class _AppearanceOptionSettings ), const Padding( padding: EdgeInsets.all(10.0), - child: Divider(thickness: 0.5), + child: Divider( + thickness: 0.5, + ), ), Padding( padding: const EdgeInsets.all(10.0), @@ -145,10 +149,10 @@ class _AppearanceOptionSettings "Hide balances", style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of( - context, - ).extension()!.textDark, - ), + color: Theme.of(context) + .extension()! + .textDark, + ), textAlign: TextAlign.left, ), SizedBox( @@ -162,9 +166,8 @@ class _AppearanceOptionSettings ), onValueChanged: (newValue) { ref - .read(prefsChangeNotifierProvider) - .hideBalances = - newValue; + .read(prefsChangeNotifierProvider) + .hideBalances = newValue; }, ), ), @@ -173,7 +176,9 @@ class _AppearanceOptionSettings ), const Padding( padding: EdgeInsets.all(10.0), - child: Divider(thickness: 0.5), + child: Divider( + thickness: 0.5, + ), ), Padding( padding: const EdgeInsets.all(10.0), @@ -184,10 +189,10 @@ class _AppearanceOptionSettings "Choose theme", style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of( - context, - ).extension()!.textDark, - ), + color: Theme.of(context) + .extension()! + .textDark, + ), textAlign: TextAlign.left, ), ], @@ -210,7 +215,9 @@ class _AppearanceOptionSettings } class ThemeToggle extends ConsumerStatefulWidget { - const ThemeToggle({super.key}); + const ThemeToggle({ + super.key, + }); @override ConsumerState createState() => _ThemeToggle(); @@ -252,9 +259,8 @@ class _ThemeToggle extends ConsumerState { } // apply theme - ref.read(themeProvider.notifier).state = ref - .read(pThemeService) - .getTheme(themeId: themeId)!; + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; // Assets.precache(context); } else { @@ -273,9 +279,8 @@ class _ThemeToggle extends ConsumerState { ref.read(prefsChangeNotifierProvider.notifier).themeId = themeId; // apply theme - ref.read(themeProvider.notifier).state = ref - .read(pThemeService) - .getTheme(themeId: themeId)!; + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; // Assets.precache(context); } @@ -313,20 +318,16 @@ class _ThemeToggle extends ConsumerState { void initState() { _updateInstalledList(); - _subscription = ref - .read(mainDBProvider) - .isar - .stackThemes - .watchLazy() - .listen((_) { - if (mounted) { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _updateInstalledList(); - }); - }); - } + _subscription = + ref.read(mainDBProvider).isar.stackThemes.watchLazy().listen((_) { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _updateInstalledList(); + }); }); + } + }); super.initState(); } @@ -366,24 +367,28 @@ class _ThemeToggle extends ConsumerState { border: Border.all( width: 2.5, color: _current == i - ? Theme.of( - context, - ).extension()!.infoItemIcons - : Theme.of( - context, - ).extension()!.popupBG, + ? Theme.of(context) + .extension()! + .infoItemIcons + : Theme.of(context) + .extension()! + .popupBG, ), borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), ), child: SvgPicture.file( - File(installedThemeIdNames[i].item3), + File( + installedThemeIdNames[i].item3, + ), height: 160, width: 200, ), ), - const SizedBox(height: 12), + const SizedBox( + height: 12, + ), Row( children: [ SizedBox( @@ -402,15 +407,17 @@ class _ThemeToggle extends ConsumerState { }, ), ), - const SizedBox(width: 14), + const SizedBox( + width: 14, + ), Text( installedThemeIdNames[i].item2, style: STextStyles.desktopTextExtraSmall(context) .copyWith( - color: Theme.of( - context, - ).extension()!.textDark, - ), + color: Theme.of(context) + .extension()! + .textDark, + ), ), ], ), @@ -442,9 +449,9 @@ class _ThemeToggle extends ConsumerState { focusElevation: 0, hoverElevation: 0, highlightElevation: 0, - fillColor: Theme.of( - context, - ).extension()!.textFieldActiveBG, + fillColor: Theme.of(context) + .extension()! + .textFieldActiveBG, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -457,9 +464,9 @@ class _ThemeToggle extends ConsumerState { child: Center( child: SvgPicture.asset( Assets.svg.circlePlusFilled, - color: Theme.of( - context, - ).extension()!.textSubtitle2, + color: Theme.of(context) + .extension()! + .textSubtitle2, width: 20, height: 20, ), diff --git a/lib/providers/global/prefs_provider.dart b/lib/providers/global/prefs_provider.dart index abe6653d20..1db3def282 100644 --- a/lib/providers/global/prefs_provider.dart +++ b/lib/providers/global/prefs_provider.dart @@ -8,15 +8,15 @@ * */ -import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../utilities/prefs.dart'; -int _count = 0; -final prefsChangeNotifierProvider = ChangeNotifierProvider((_) { - if (kDebugMode) { - _count++; - } +final prefsChangeNotifierProvider = ChangeNotifierProvider( + (_) => Prefs.instance, +); - return Prefs.instance; -}); +final hideBalancesProvider = Provider( + (ref) => ref.watch( + prefsChangeNotifierProvider.select((value) => value.hideBalances), + ), +); diff --git a/lib/widgets/hideable_balance.dart b/lib/widgets/hideable_balance.dart index 803b0491c3..4adf4925bf 100644 --- a/lib/widgets/hideable_balance.dart +++ b/lib/widgets/hideable_balance.dart @@ -17,10 +17,7 @@ import 'package:flutter_svg/svg.dart'; import '../providers/global/prefs_provider.dart'; import '../utilities/assets.dart'; -/// Wraps a balance value widget so that, when the hide balances preference is -/// enabled, the value is replaced by an eye mask icon. Tapping the icon reveals -/// the underlying value for a few seconds before it is masked again. The -/// persisted preference is never modified by the tap to reveal behaviour. +/// Masks a balance and temporarily reveals it without changing the preference. class HideableBalance extends ConsumerStatefulWidget { const HideableBalance({ super.key, @@ -65,21 +62,31 @@ class _HideableBalanceState extends ConsumerState { @override Widget build(BuildContext context) { - final hideBalances = ref.watch( - prefsChangeNotifierProvider.select((value) => value.hideBalances), - ); + final hideBalances = ref.watch(hideBalancesProvider); if (!hideBalances || _revealed) { return widget.child; } - return GestureDetector( + return Semantics( + label: "Show balance temporarily", + button: true, onTap: _reveal, - child: SvgPicture.asset( - Assets.svg.eyeSlash, - color: widget.iconColor, - width: widget.iconSize, - height: widget.iconSize, + excludeSemantics: true, + child: IconButton( + tooltip: "Show balance temporarily", + onPressed: _reveal, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 24, minHeight: 24), + visualDensity: VisualDensity.compact, + icon: SvgPicture.asset( + Assets.svg.eyeSlash, + colorFilter: widget.iconColor == null + ? null + : ColorFilter.mode(widget.iconColor!, BlendMode.srcIn), + width: widget.iconSize, + height: widget.iconSize, + ), ), ); } diff --git a/test/widget_tests/hideable_balance_test.dart b/test/widget_tests/hideable_balance_test.dart new file mode 100644 index 0000000000..c3d16851f6 --- /dev/null +++ b/test/widget_tests/hideable_balance_test.dart @@ -0,0 +1,96 @@ +import 'dart:ui' show SemanticsAction; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/widgets/hideable_balance.dart'; + +void main() { + Widget testApp({required bool hidden, required Widget child}) { + return ProviderScope( + overrides: [hideBalancesProvider.overrideWithValue(hidden)], + child: MaterialApp(home: Scaffold(body: child)), + ); + } + + testWidgets("shows the balance when masking is disabled", (tester) async { + await tester.pumpWidget( + testApp( + hidden: false, + child: const HideableBalance(child: Text("12.34 BTC")), + ), + ); + + expect(find.text("12.34 BTC"), findsOneWidget); + expect(find.byType(IconButton), findsNothing); + }); + + testWidgets("exposes a labeled keyboard-accessible reveal control", ( + tester, + ) async { + final semantics = tester.ensureSemantics(); + + await tester.pumpWidget( + testApp( + hidden: true, + child: const HideableBalance(child: Text("12.34 BTC")), + ), + ); + + final control = find.byType(IconButton); + expect(find.text("12.34 BTC"), findsNothing); + expect( + tester.widget(control).tooltip, + "Show balance temporarily", + ); + expect(tester.getSize(control).shortestSide, greaterThanOrEqualTo(24)); + + final semanticsNode = tester.getSemantics(control); + expect(semanticsNode.label, contains("Show balance temporarily")); + expect(semanticsNode.flagsCollection.isButton, isTrue); + expect( + semanticsNode.getSemanticsData().hasAction(SemanticsAction.tap), + isTrue, + ); + + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pump(); + + expect(find.text("12.34 BTC"), findsOneWidget); + expect(control, findsNothing); + + await tester.pump(HideableBalance.revealDuration); + expect(find.text("12.34 BTC"), findsNothing); + expect(find.byType(IconButton), findsOneWidget); + semantics.dispose(); + }); + + testWidgets("reveals only the selected balance", (tester) async { + await tester.pumpWidget( + testApp( + hidden: true, + child: const Column( + children: [ + HideableBalance(child: Text("1 BTC")), + HideableBalance(child: Text(r"$2")), + ], + ), + ), + ); + + await tester.tap(find.byType(IconButton).first); + await tester.pump(); + + expect(find.text("1 BTC"), findsOneWidget); + expect(find.text(r"$2"), findsNothing); + expect(find.byType(IconButton), findsOneWidget); + + await tester.pumpWidget(const SizedBox()); + await tester.pump(HideableBalance.revealDuration); + expect(tester.takeException(), isNull); + }); +}