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/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 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/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/utilities/prefs.dart b/lib/utilities/prefs.dart index 56013d64d7..550bf87cbf 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; } @@ -1440,4 +1441,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..4adf4925bf --- /dev/null +++ b/lib/widgets/hideable_balance.dart @@ -0,0 +1,93 @@ +/* + * 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'; + +/// Masks a balance and temporarily reveals it without changing the preference. +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(hideBalancesProvider); + + if (!hideBalances || _revealed) { + return widget.child; + } + + return Semantics( + label: "Show balance temporarily", + button: true, + onTap: _reveal, + 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); + }); +}