From ae32b14f3026d063b04072f04321ddbd62bcdab8 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Tue, 25 Nov 2025 11:19:57 +0100 Subject: [PATCH 1/8] wip: new wallets page w balance display --- lib/di.dart | 5 + lib/new-ui/new_dashboard.dart | 6 +- lib/new-ui/pages/wallets_page.dart | 25 +++ .../assets_history => }/asset_tile.dart | 23 ++- .../assets_history/assets_section.dart | 10 +- .../screens/wallet_list/wallet_list_page.dart | 166 ++++++++++-------- lib/utils/feature_flag.dart | 2 +- .../wallet_list/wallet_list_item.dart | 2 + .../wallet_list/wallet_list_view_model.dart | 17 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + 10 files changed, 158 insertions(+), 99 deletions(-) create mode 100644 lib/new-ui/pages/wallets_page.dart rename lib/new-ui/widgets/{coins_page/assets_history => }/asset_tile.dart (82%) diff --git a/lib/di.dart b/lib/di.dart index 7b056ea679..ed93b65587 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -13,6 +13,7 @@ import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/new-ui/new_dashboard.dart'; import 'package:cake_wallet/new-ui/pages/home_page.dart'; +import 'package:cake_wallet/new-ui/pages/wallets_page.dart'; import 'package:cake_wallet/order/order.dart'; import 'package:cake_wallet/core/backup_service_v3.dart'; import 'package:cake_wallet/core/new_wallet_arguments.dart'; @@ -894,6 +895,10 @@ Future setup({ onWalletLoaded: onWalletLoaded as Future Function(BuildContext)?, )); + getIt.registerFactory(() => NewWalletListPage( + walletListViewModel: getIt.get(), + )); + getIt.registerFactoryParam( (WalletListViewModel walletListViewModel, _) => WalletEditViewModel( walletListViewModel, diff --git a/lib/new-ui/new_dashboard.dart b/lib/new-ui/new_dashboard.dart index 7bb008fb7e..728b2fd25e 100644 --- a/lib/new-ui/new_dashboard.dart +++ b/lib/new-ui/new_dashboard.dart @@ -1,9 +1,11 @@ import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/new-ui/pages/home_page.dart'; +import 'package:cake_wallet/new-ui/pages/wallets_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/new_main_navbar_widget.dart'; import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:flutter/material.dart'; import '../view_model/dashboard/dashboard_view_model.dart'; @@ -14,7 +16,7 @@ class NewDashboard extends StatefulWidget { final List dashboardPageWidgets = [ getIt.get(), - getIt.get(), + FeatureFlag.hasNewUiExtraPages ? getIt.get() : getIt.get(), getIt.get(), getIt.get(), Placeholder(), @@ -32,7 +34,7 @@ class _NewDashboardState extends State { return Scaffold( body: Stack( children: [ - widget.dashboardPageWidgets[_selectedPage], + widget.dashboardPageWidgets[_selectedPage], NewMainNavBar( dashboardViewModel: widget.dashboardViewModel, selectedIndex: _selectedPage, diff --git a/lib/new-ui/pages/wallets_page.dart b/lib/new-ui/pages/wallets_page.dart new file mode 100644 index 0000000000..36b56e6cbb --- /dev/null +++ b/lib/new-ui/pages/wallets_page.dart @@ -0,0 +1,25 @@ +import 'package:cake_wallet/new-ui/widgets/asset_tile.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; +import 'package:flutter/material.dart'; + +class NewWalletListPage extends StatelessWidget { + const NewWalletListPage({super.key, required this.walletListViewModel}); + + final WalletListViewModel walletListViewModel; + + + @override + Widget build(BuildContext context) { + return SafeArea( + child: ListView.builder( + itemCount: walletListViewModel.wallets.length, + itemBuilder: (context, index){ + final wallet = walletListViewModel.wallets[index]; + + return AssetTile(iconPath: walletTypeToCryptoCurrency(wallet.type).iconPath!, name: wallet.name, amount: "123 BTC", amountFiat: "123 USD"); + }, + ) + ); + } +} diff --git a/lib/new-ui/widgets/coins_page/assets_history/asset_tile.dart b/lib/new-ui/widgets/asset_tile.dart similarity index 82% rename from lib/new-ui/widgets/coins_page/assets_history/asset_tile.dart rename to lib/new-ui/widgets/asset_tile.dart index b811604fb6..914a2d8bca 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/asset_tile.dart +++ b/lib/new-ui/widgets/asset_tile.dart @@ -2,9 +2,17 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; class AssetTile extends StatelessWidget { - const AssetTile({super.key, required this.dashboardViewModel}); + const AssetTile( + {super.key, + required this.iconPath, + required this.name, + required this.amount, + required this.amountFiat}); - final DashboardViewModel dashboardViewModel; + final String iconPath; + final String name; + final String amount; + final String amountFiat; @override Widget build(BuildContext context) { @@ -37,19 +45,19 @@ class AssetTile extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - Container(width: 45, height: 45, child: Image.asset("assets/images/crypto/tether.webp")), - SizedBox(width: 8.0), + Container(width: 36, height: 36, child: Image.asset(iconPath)), + SizedBox(width: 12.0), Column( spacing: 4.0, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "DummyCoin", + name, style: TextStyle(fontWeight: FontWeight.bold), ), Text( - "0.000 DMC", + amount, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -58,9 +66,8 @@ class AssetTile extends StatelessWidget { ), ], ), - Text( - "\$0.00", + amountFiat, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, ), diff --git a/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart b/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart index 44eff375b9..cabe08986f 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart @@ -1,8 +1,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; - -import 'asset_tile.dart'; +import '../../asset_tile.dart'; class AssetsSection extends StatelessWidget { const AssetsSection({super.key, required this.dashboardViewModel}); @@ -16,7 +15,12 @@ class AssetsSection extends StatelessWidget { physics: NeverScrollableScrollPhysics(), itemCount: 1, itemBuilder: (context, index) { - return AssetTile(dashboardViewModel: dashboardViewModel,); + return AssetTile( + iconPath: "assets/images/crypto/tether.webp", + name: "DummyCoin", + amount: "0.000 DMC", + amountFiat: "\$ 0.00", + ); }, ); } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index bf882a4e6f..48a81b6e8c 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -49,20 +49,19 @@ class WalletListPage extends BasePage { String get title => S.current.wallets; @override - Widget body(BuildContext context) => Observer( - builder: (_) { - if (walletListViewModel.singleWalletsList.isEmpty && walletListViewModel.multiWalletGroups.isEmpty) { - return Center( - child: CircularProgressIndicator(), + Widget body(BuildContext context) => Observer(builder: (_) { + if (walletListViewModel.singleWalletsList.isEmpty && + walletListViewModel.multiWalletGroups.isEmpty) { + return Center( + child: CircularProgressIndicator(), + ); + } + return WalletListBody( + walletListViewModel: walletListViewModel, + authService: authService, + onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(), ); - } - return WalletListBody( - walletListViewModel: walletListViewModel, - authService: authService, - onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(), - ); - } - ); + }); @override Widget trailing(BuildContext context) { @@ -166,76 +165,91 @@ class WalletListBodyState extends State { padding: EdgeInsets.only(left: 20, right: 20), child: Observer( builder: (_) => FilteredList( - shrinkWrap: true, - list: widget.walletListViewModel.multiWalletGroups, - updateFunction: widget.walletListViewModel.reorderAccordingToWalletList, - itemBuilder: (context, index) { - final group = widget.walletListViewModel.multiWalletGroups[index]; - final groupName = - group.groupName ?? '${S.current.wallet_group} ${index + 1}'; + shrinkWrap: true, + list: widget.walletListViewModel.multiWalletGroups, + updateFunction: widget.walletListViewModel.reorderAccordingToWalletList, + itemBuilder: (context, index) { + final group = widget.walletListViewModel.multiWalletGroups[index]; + final groupName = + group.groupName ?? '${S.current.wallet_group} ${index + 1}'; - widget.walletListViewModel.updateTileState( - index, - widget.walletListViewModel.expansionTileStateTrack[index] ?? false, - ); + widget.walletListViewModel.updateTileState( + index, + widget.walletListViewModel.expansionTileStateTrack[index] ?? false, + ); - return GroupedWalletExpansionTile( - onExpansionChanged: (value) { - widget.walletListViewModel.updateTileState(index, value); - setState(() {}); - }, - shouldShowCurrentWalletPointer: true, - borderRadius: BorderRadius.all(Radius.circular(16)), - title: groupName, - tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'), - leadingWidget: Icon( - Icons.account_balance_wallet_outlined, - size: 28, - ), - trailingWidget: EditWalletButtonWidget( - width: 88, - isGroup: true, - isExpanded: - widget.walletListViewModel.expansionTileStateTrack[index]!, - onTap: () { - final wallet = widget.walletListViewModel - .convertWalletInfoToWalletListItem(group.wallets.first); - Navigator.of(context).pushNamed( - Routes.walletEdit, - arguments: WalletEditPageArguments( - walletListViewModel: widget.walletListViewModel, - editingWallet: wallet, - isWalletGroup: true, - groupName: groupName, - walletGroupKey: group.groupKey, + return FutureBuilder>( + future: Future.wait( + group.wallets.map((w) { + return widget.walletListViewModel + .convertWalletInfoToWalletListItem(w); + }), + ), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SizedBox( + height: 60, + child: Center(child: CircularProgressIndicator()), + ); + } + + final childWallets = snapshot.data!; + + return GroupedWalletExpansionTile( + onExpansionChanged: (value) { + widget.walletListViewModel.updateTileState(index, value); + setState(() {}); + }, + shouldShowCurrentWalletPointer: true, + borderRadius: BorderRadius.all(Radius.circular(16)), + title: groupName, + tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'), + leadingWidget: Icon( + Icons.account_balance_wallet_outlined, + size: 28, ), - ); - }, - ), - childWallets: group.wallets.map((walletInfo) { - return widget.walletListViewModel - .convertWalletInfoToWalletListItem(walletInfo); - }).toList(), - isSelected: false, - onChildItemTapped: (wallet) => - wallet.isCurrent ? null : _loadWallet(wallet), - childTrailingWidget: (item) { - return item.isCurrent - ? SizedBox.shrink() - : EditWalletButtonWidget( - width: 60, - onTap: () => Navigator.of(context).pushNamed( + trailingWidget: EditWalletButtonWidget( + width: 88, + isGroup: true, + isExpanded: widget + .walletListViewModel.expansionTileStateTrack[index]!, + onTap: () async { + final wallet = await widget.walletListViewModel + .convertWalletInfoToWalletListItem(group.wallets.first); + Navigator.of(context).pushNamed( Routes.walletEdit, arguments: WalletEditPageArguments( walletListViewModel: widget.walletListViewModel, - editingWallet: item, + editingWallet: wallet, + isWalletGroup: true, + groupName: groupName, + walletGroupKey: group.groupKey, ), - ), - ); - }, - ); - }, - ), + ); + }, + ), + childWallets: childWallets, + isSelected: false, + onChildItemTapped: (wallet) => + wallet.isCurrent ? null : _loadWallet(wallet), + childTrailingWidget: (item) { + return item.isCurrent + ? SizedBox.shrink() + : EditWalletButtonWidget( + width: 60, + onTap: () => Navigator.of(context).pushNamed( + Routes.walletEdit, + arguments: WalletEditPageArguments( + walletListViewModel: widget.walletListViewModel, + editingWallet: item, + ), + ), + ); + }, + ); + }, + ); + }), ), ), ), diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index 6b3e78fc0c..2f3374aa75 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -15,5 +15,5 @@ class FeatureFlag { static const bool hasBitcoinViewOnly = true; static const bool customBackgroundEnabled = false; static const bool hasNewUi = true; - static const bool hasNewUiExtraPages = false; + static const bool hasNewUiExtraPages = true; } diff --git a/lib/view_model/wallet_list/wallet_list_item.dart b/lib/view_model/wallet_list/wallet_list_item.dart index 8f8c58ea9f..46e814f073 100644 --- a/lib/view_model/wallet_list/wallet_list_item.dart +++ b/lib/view_model/wallet_list/wallet_list_item.dart @@ -6,6 +6,7 @@ class WalletListItem { required this.type, required this.key, required this.isHardware, + this.balance, this.isCurrent = false, this.isEnabled = true, this.isTestnet = false, @@ -15,6 +16,7 @@ class WalletListItem { final WalletType type; final bool isCurrent; final dynamic key; + final String? balance; final bool isEnabled; final bool isTestnet; final bool isHardware; diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 46d6d38c47..1915842403 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -66,8 +66,7 @@ abstract class WalletListViewModelBase with Store { WalletType get currentWalletType => _appStore.wallet!.type; Future requireHardwareWalletConnection(WalletListItem walletItem) async => - _walletLoadingService.requireHardwareWalletConnection( - walletItem.type, walletItem.name); + _walletLoadingService.requireHardwareWalletConnection(walletItem.type, walletItem.name); @action Future loadWallet(WalletListItem walletItem) async { @@ -84,8 +83,8 @@ abstract class WalletListViewModelBase with Store { bool get ascending => _appStore.settingsStore.walletListAscending; - bool isUpdating = false; + @action Future updateList() async { if (isUpdating) { @@ -100,7 +99,7 @@ abstract class WalletListViewModelBase with Store { final list = await WalletInfo.getAll(); for (var info in list) { - wallets.add(convertWalletInfoToWalletListItem(info)); + wallets.add(await convertWalletInfoToWalletListItem(info)); } //========== Split into shared seed groups and single wallets list @@ -110,7 +109,7 @@ abstract class WalletListViewModelBase with Store { for (var group in walletGroupsFromManager) { if (group.wallets.length == 1) { - singleWalletsList.add(convertWalletInfoToWalletListItem(group.wallets.first)); + singleWalletsList.add(await convertWalletInfoToWalletListItem(group.wallets.first)); continue; } @@ -150,7 +149,7 @@ abstract class WalletListViewModelBase with Store { for (WalletInfo walletInfo in group.wallets) { for (int i = 0; i < wiList.length; i++) { if (wiList[i].name == walletInfo.name) { - wiList[i].sortOrder = i+oldI; + wiList[i].sortOrder = i + oldI; await wiList[i].save(); wiList.removeAt(i); break; @@ -234,13 +233,13 @@ abstract class WalletListViewModelBase with Store { } } - WalletListItem convertWalletInfoToWalletListItem(WalletInfo info) { + Future convertWalletInfoToWalletListItem(WalletInfo info) async { return WalletListItem( name: info.name, type: info.type, key: info.id, - isCurrent: info.name == _appStore.wallet?.name && - info.type == _appStore.wallet?.type, + balance: derivationInfoItem.balance, + isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type, isEnabled: availableWalletTypes.contains(info.type), isTestnet: info.network?.toLowerCase().contains('testnet') ?? false, isHardware: info.isHardwareWallet, diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index f28097b88c..e5dd4c50fe 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -48,6 +48,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> From d78ddc7182d1b0f0b384df285dbc34c57611f6a3 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Sat, 29 Nov 2025 13:47:36 +0100 Subject: [PATCH 2/8] feat: cached balance for wallet list page --- cw_bitcoin/lib/electrum_wallet.dart | 16 ++++++ cw_core/lib/wallet_info.dart | 54 +++++++++++++++--- cw_evm/lib/evm_chain_wallet.dart | 24 +++++++- cw_monero/lib/monero_wallet.dart | 16 ++++++ cw_nano/lib/nano_wallet.dart | 16 ++++++ cw_solana/lib/solana_wallet.dart | 16 ++++++ cw_tron/lib/tron_wallet.dart | 20 ++++++- cw_wownero/lib/wownero_wallet.dart | 18 ++++++ cw_zano/lib/zano_wallet.dart | 16 ++++++ lib/di.dart | 2 + lib/entities/default_settings_migration.dart | 13 +++++ lib/main.dart | 2 +- lib/new-ui/pages/wallets_page.dart | 24 +++++--- .../wallet_list/wallet_list_item.dart | 2 - .../wallet_list/wallet_list_view_model.dart | 56 ++++++++++++++++++- 15 files changed, 268 insertions(+), 27 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index a38879a8bf..33e1c04ed4 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -114,6 +114,19 @@ abstract class ElectrumWalletBase reaction((_) => syncStatus, _syncStatusReaction); sharedPrefs.complete(SharedPreferences.getInstance()); + + _onBalanceChangeReaction = reaction( + (_) => balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); } static Bip32Slip10Secp256k1 getAccountHDWallet( @@ -184,6 +197,8 @@ abstract class ElectrumWalletBase final EncryptionFileUtils encryptionFileUtils; + late final ReactionDisposer _onBalanceChangeReaction; + @override final String? passphrase; @@ -1583,6 +1598,7 @@ abstract class ElectrumWalletBase } catch (_) {} _autoSaveTimer?.cancel(); _updateFeeRateTimer?.cancel(); + _onBalanceChangeReaction.reaction.dispose(); } @action diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index 2fd18016b1..7101b3ee51 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -70,7 +70,7 @@ class WalletInfoAddressInfo { String address; String label; - static String get tableName => 'walletInfoAddressInfo'; + static String get tableName => 'walletInfoAddressInfo'; static String get selfIdColumn => "${tableName}Id"; static Future> selectList(int walletInfoId) async { @@ -134,8 +134,8 @@ class WalletInfoAddressMap { String addressKey; String addressValue; - static String get tableName => 'walletInfoAddressMap'; - static String get selfIdColumn => "${tableName}Id"; + static String get tableName => 'walletInfoAddressMap'; + static String get selfIdColumn => "${tableName}Id"; static Future> selectList(int walletInfoId) async { final query = await db.query(tableName, where: 'walletInfoId = ?', whereArgs: [walletInfoId]); @@ -184,7 +184,7 @@ class WalletInfoAddress { WalletInfoAddressType type; String address; - static String get tableName => 'walletInfoAddress'; + static String get tableName => 'walletInfoAddress'; static String get selfIdColumn => "${tableName}Id"; static Future> selectList(int walletInfoId, WalletInfoAddressType type) async { @@ -245,7 +245,7 @@ class DerivationInfo { int id; - static String get tableName => 'walletInfoDerivationInfo'; + static String get tableName => 'walletInfoDerivationInfo'; static String get selfIdColumn => "${tableName}Id"; String address; @@ -262,7 +262,7 @@ class DerivationInfo { columns: [ selfIdColumn, 'address', - 'balance', + 'balance', 'transactionsCount', 'derivationType', 'derivationPath', @@ -377,7 +377,7 @@ class WalletInfo { ); } - static String get tableName => 'walletInfo'; + static String get tableName => 'walletInfo'; static String get selfIdColumn => "${tableName}Id"; int internalId; @@ -495,7 +495,7 @@ class WalletInfo { String? parentAddress; String? hashedWalletIdentifier; bool isNonSeedWallet; - + int sortOrder; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; @@ -615,3 +615,41 @@ class WalletInfo { await save(); } } + +class BalanceCache { + final String title; + final String tag; + final int walletInfoId; + final String cachedBalance; + + static String get tableName => "BalanceCache"; + + const BalanceCache(this.title, this.tag, this.walletInfoId, this.cachedBalance); + + BalanceCache.fromJson(Map json) + : title = json['title'] as String, + tag = json['tag'] as String, + walletInfoId = json['walletInfoId'] as int, + cachedBalance = json['cachedBalance'] as String; + + Map toJson() => { + 'title': title, + 'tag': tag, + 'walletInfoId': walletInfoId, + 'cachedBalance': cachedBalance, + }; + + static Future> fromWalletId(int walletInfoId) async { + final list = await db.query( + tableName, + where: 'walletInfoId = ?', + whereArgs: [walletInfoId], + ); + return List.generate(list.length, (index) => BalanceCache.fromJson(list[index])); + } + + Future save() async { + final json = toJson(); + await db.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace); + } +} diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 0db8781c7a..8ea2f84b74 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -98,6 +98,19 @@ abstract class EVMChainWalletBase } sharedPrefs.complete(SharedPreferences.getInstance()); + + _onBalanceChangeReaction = reaction( + (_) => balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); } final String? _mnemonic; @@ -105,6 +118,8 @@ abstract class EVMChainWalletBase final String _password; final EncryptionFileUtils encryptionFileUtils; + late final ReactionDisposer _onBalanceChangeReaction; + late final Box erc20TokensBox; late final Box evmChainErc20TokensBox; @@ -334,7 +349,8 @@ abstract class EVMChainWalletBase final gasUnits = await _client.getEstimatedGasUnitsForTransaction( senderAddress: evmChainPrivateKey.address, toAddress: evmChainPrivateKey.address, - contractAddress: _getUSDCContractAddress(), // Using USDC for default estimation + contractAddress: _getUSDCContractAddress(), + // Using USDC for default estimation gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice), value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.from(0.0000000001)), ); @@ -413,6 +429,7 @@ abstract class EVMChainWalletBase Future close({bool shouldCleanup = false}) async { _client.stop(); _transactionsUpdateTimer?.cancel(); + _onBalanceChangeReaction.reaction.dispose(); } @action @@ -609,7 +626,8 @@ abstract class EVMChainWalletBase ) async { // Estimate gas with the SAME call (sender, to, value, data) final gas = await calculateActualEstimatedFeeForCreateTransaction( - amount: valueWei, // native value (usually 0 for ERC20 transfer) + amount: valueWei, + // native value (usually 0 for ERC20 transfer) receivingAddressHex: to, priority: priority, contractAddress: null, @@ -806,7 +824,6 @@ abstract class EVMChainWalletBase Future _updateBalance() async { balance[currency] = await _fetchEVMChainBalance(); - await _fetchErc20Balances(); await save(); } @@ -882,6 +899,7 @@ abstract class EVMChainWalletBase @override Future? updateBalance() async => await _updateBalance(); + @override Future updateTransactionsHistory() async => await _updateTransactions(); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 9f5986e267..db8bd490d2 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -96,6 +96,19 @@ abstract class MoneroWalletBase extends WalletBase transactionHistory, (__) { _updateSubAddress(isEnabledAutoGenerateSubaddress, account: walletAddresses.account); }); + + _onBalanceChangeReaction = reaction( + (_) => balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); } static const int _autoSaveInterval = 30; @@ -147,6 +160,8 @@ abstract class MoneroWalletBase extends WalletBase balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); } String _mnemonic; @@ -75,6 +88,8 @@ abstract class NanoWalletBase final EncryptionFileUtils _encryptionFileUtils; + late final ReactionDisposer _onBalanceChangeReaction; + String? _privateKey; String? _publicAddress; String? _hexSeed; @@ -154,6 +169,7 @@ abstract class NanoWalletBase Future close({bool shouldCleanup = false}) async { _client.stop(); _receiveTimer?.cancel(); + _onBalanceChangeReaction.reaction.dispose(); } @action diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 49af6deca0..366b538f7d 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -70,6 +70,19 @@ abstract class SolanaWalletBase CakeHive.registerAdapter(SPLTokenAdapter()); } + _onBalanceChangeReaction = reaction( + (_) => balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); + _sharedPrefs.complete(SharedPreferences.getInstance()); } @@ -78,6 +91,8 @@ abstract class SolanaWalletBase final String? _hexPrivateKey; final EncryptionFileUtils encryptionFileUtils; + late final ReactionDisposer _onBalanceChangeReaction; + late final SolanaWalletClient _client; @observable @@ -185,6 +200,7 @@ abstract class SolanaWalletBase Future close({bool shouldCleanup = false}) async { _client.stop(); _transactionsUpdateTimer?.cancel(); + _onBalanceChangeReaction.reaction.dispose(); } @action diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index 80afb2f8ee..bf3a108248 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -66,6 +66,19 @@ abstract class TronWalletBase if (!CakeHive.isAdapterRegistered(TronToken.typeId)) { CakeHive.registerAdapter(TronTokenAdapter()); } + + _onBalanceChangeReaction = reaction( + (_) => balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); } final String? _mnemonic; @@ -73,6 +86,8 @@ abstract class TronWalletBase final String _password; final EncryptionFileUtils encryptionFileUtils; + late final ReactionDisposer _onBalanceChangeReaction; + late final Box tronTokensBox; late final TronPrivateKey _tronPrivateKey; @@ -228,7 +243,10 @@ abstract class TronWalletBase Future changePassword(String password) => throw UnimplementedError("changePassword"); @override - Future close({bool shouldCleanup = false}) async => _transactionsUpdateTimer?.cancel(); + Future close({bool shouldCleanup = false}) async { + _transactionsUpdateTimer?.cancel(); + _onBalanceChangeReaction.reaction.dispose(); + } @action @override diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index 08b7e65416..54223a81c4 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -89,12 +89,27 @@ abstract class WowneroWalletBase _onTxHistoryChangeReaction = reaction((_) => transactionHistory, (__) { _updateSubAddress(isEnabledAutoGenerateSubaddress, account: walletAddresses.account); }); + + _onBalanceChangeReaction = reaction( + (_) => balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); } static const int _autoSaveInterval = 30; Box unspentCoinsInfo; + late final ReactionDisposer _onBalanceChangeReaction; + void Function(FlutterErrorDetails)? onError; @override @@ -198,6 +213,7 @@ abstract class WowneroWalletBase _onAccountChangeReaction?.reaction.dispose(); _onTxHistoryChangeReaction?.reaction.dispose(); _autoSaveTimer?.cancel(); + _onBalanceChangeReaction.reaction.dispose(); } @override @@ -802,4 +818,6 @@ abstract class WowneroWalletBase String formatCryptoAmount(String amount) { return wowneroAmountToString(amount: int.parse(amount)); } + + } diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index 913bc083ad..06b88fa04f 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -78,6 +78,8 @@ abstract class ZanoWalletBase @observable ObservableMap balance; + late final ReactionDisposer _onBalanceChangeReaction; + @override String seed = ''; @@ -117,6 +119,19 @@ abstract class ZanoWalletBase if (!CakeHive.isAdapterRegistered(ZanoAsset.typeId)) { CakeHive.registerAdapter(ZanoAssetAdapter()); } + + _onBalanceChangeReaction = reaction( + (_) => balance.entries.map((e) => e.value).toList(), + (_) { + for (final bal in balance.keys) { + if (balance[bal]?.formattedAvailableBalance != null) { + BalanceCache(bal.title, bal.tag ?? "", walletInfo.internalId, + balance[bal]!.formattedAvailableBalance) + .save(); + } + } + }, + ); } @override @@ -217,6 +232,7 @@ abstract class ZanoWalletBase closeWallet(null); _updateSyncInfoTimer?.cancel(); _autoSaveTimer?.cancel(); + _onBalanceChangeReaction.reaction.dispose(); } @override diff --git a/lib/di.dart b/lib/di.dart index ed93b65587..f232d97283 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -874,6 +874,7 @@ Future setup({ getIt.get(), getIt.get(), getIt.get(), + getIt.get(), ), ); } else { @@ -884,6 +885,7 @@ Future setup({ getIt.get(), getIt.get(), getIt.get(), + getIt.get() ), ); } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 9f2ac0bcea..0ec50fdf8f 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/haven_seed_store.dart'; import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/db/sqlite.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cw_core/root_dir.dart'; @@ -555,6 +556,18 @@ Future defaultSettingsMigration( currentNodePreferenceKey: PreferencesKey.currentArbitrumNodeIdKey, ); break; + case 54: + await db.execute(''' +CREATE TABLE BalanceCache ( + title TEXT NOT NULL, + tag TEXT DEFAULT "", + walletInfoId INTEGER NOT NULL, + cachedBalance TEXT DEFAULT "", + PRIMARY KEY (walletInfoId, title, tag), + FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) +); + '''); + break; default: break; diff --git a/lib/main.dart b/lib/main.dart index 77430f08fe..6d96e67877 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -266,7 +266,7 @@ Future initializeAppConfigs({bool loadWallet = true}) async { payjoinSessionSource: payjoinSessionSource, anonpayInvoiceInfo: anonpayInvoiceInfo, havenSeedStore: havenSeedStore, - initialMigrationVersion: 53, + initialMigrationVersion: 54, ); } diff --git a/lib/new-ui/pages/wallets_page.dart b/lib/new-ui/pages/wallets_page.dart index 36b56e6cbb..7efc7b8b5a 100644 --- a/lib/new-ui/pages/wallets_page.dart +++ b/lib/new-ui/pages/wallets_page.dart @@ -8,18 +8,24 @@ class NewWalletListPage extends StatelessWidget { final WalletListViewModel walletListViewModel; - @override Widget build(BuildContext context) { return SafeArea( - child: ListView.builder( - itemCount: walletListViewModel.wallets.length, - itemBuilder: (context, index){ - final wallet = walletListViewModel.wallets[index]; + child: ListView.builder( + itemCount: walletListViewModel.wallets.length, + itemBuilder: (context, index) { + final wallet = walletListViewModel.wallets[index]; + final balance = + walletListViewModel.cachedBalanceFor(walletTypeToCryptoCurrency(wallet.type)); + final fiatBalance = + walletListViewModel.fiatCachedBalanceFor(walletTypeToCryptoCurrency(wallet.type)); - return AssetTile(iconPath: walletTypeToCryptoCurrency(wallet.type).iconPath!, name: wallet.name, amount: "123 BTC", amountFiat: "123 USD"); - }, - ) - ); + return AssetTile( + iconPath: walletTypeToCryptoCurrency(wallet.type).iconPath!, + name: wallet.name, + amount: balance, + amountFiat: fiatBalance); + }, + )); } } diff --git a/lib/view_model/wallet_list/wallet_list_item.dart b/lib/view_model/wallet_list/wallet_list_item.dart index 46e814f073..8f8c58ea9f 100644 --- a/lib/view_model/wallet_list/wallet_list_item.dart +++ b/lib/view_model/wallet_list/wallet_list_item.dart @@ -6,7 +6,6 @@ class WalletListItem { required this.type, required this.key, required this.isHardware, - this.balance, this.isCurrent = false, this.isEnabled = true, this.isTestnet = false, @@ -16,7 +15,6 @@ class WalletListItem { final WalletType type; final bool isCurrent; final dynamic key; - final String? balance; final bool isEnabled; final bool isTestnet; final bool isHardware; diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 1915842403..49c9ce83cc 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -1,13 +1,19 @@ +import 'dart:async'; +import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; +import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; +import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/wallet_group.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:cake_wallet/entities/wallet_manager.dart'; +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cake_wallet/wallet_types.g.dart'; part 'wallet_list_view_model.g.dart'; @@ -19,17 +25,30 @@ abstract class WalletListViewModelBase with Store { this._appStore, this._walletLoadingService, this._walletManager, + this.fiatConversionStore, ) : wallets = ObservableList(), multiWalletGroups = ObservableList(), singleWalletsList = ObservableList(), - expansionTileStateTrack = ObservableMap() { + expansionTileStateTrack = ObservableMap(), + cachedBalances = ObservableList() { setOrderType(_appStore.settingsStore.walletListOrder); updateList(); + + _updateFiatStore(); + Timer.periodic( + Duration(seconds: 5), + (timer) => _updateFiatStore(), + ); } + final FiatConversionStore fiatConversionStore; + @observable ObservableList wallets; + @observable + ObservableList cachedBalances; + // @observable // ObservableList walletGroups; @@ -51,6 +70,37 @@ abstract class WalletListViewModelBase with Store { } } + String cachedBalanceFor(CryptoCurrency currency) => cachedBalances + .where((element) => + (element.tag == currency.tag || element.tag == "" && currency.tag == null) && + element.title == currency.title) + .first + .cachedBalance; + + Future _updateFiatStoreForCurrency(CryptoCurrency currency) async { + fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( + crypto: currency, + fiat: _appStore.settingsStore.fiatCurrency, + torOnly: _appStore.settingsStore.fiatApiMode == FiatApiMode.torOnly); + } + + + Future _updateFiatStore() async { + for (final wallet in wallets) { + final currency = walletTypeToCryptoCurrency(wallet.type); + _updateFiatStoreForCurrency(currency); + } + } + + String fiatCachedBalanceFor(CryptoCurrency currency) { + if (fiatConversionStore.prices[currency] == null) { + _updateFiatStoreForCurrency(currency); + } + + final price = fiatConversionStore.prices[currency]; + return calculateFiatAmount(cryptoAmount: cachedBalanceFor(currency), price: price); + } + @computed bool get shouldRequireTOTP2FAForAccessingWallet => _appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; @@ -100,6 +150,7 @@ abstract class WalletListViewModelBase with Store { for (var info in list) { wallets.add(await convertWalletInfoToWalletListItem(info)); + cachedBalances.addAll(await BalanceCache.fromWalletId(info.internalId)); } //========== Split into shared seed groups and single wallets list @@ -238,7 +289,6 @@ abstract class WalletListViewModelBase with Store { name: info.name, type: info.type, key: info.id, - balance: derivationInfoItem.balance, isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type, isEnabled: availableWalletTypes.contains(info.type), isTestnet: info.network?.toLowerCase().contains('testnet') ?? false, From 80a1928aefdc0ff34937ee1b51cdf9a7dbc60bcb Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Sun, 30 Nov 2025 16:30:17 +0100 Subject: [PATCH 3/8] feat: sync all wallets from wallets page to update balance cache --- lib/new-ui/pages/wallets_page.dart | 53 +++++++++++++------ lib/new-ui/widgets/asset_tile.dart | 19 ++++--- .../assets_history/assets_section.dart | 3 ++ .../widgets/wallets_page/total_balance.dart | 24 +++++++++ .../wallet_list/wallet_list_view_model.dart | 39 +++++++++++++- 5 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 lib/new-ui/widgets/wallets_page/total_balance.dart diff --git a/lib/new-ui/pages/wallets_page.dart b/lib/new-ui/pages/wallets_page.dart index 7efc7b8b5a..a9133646ca 100644 --- a/lib/new-ui/pages/wallets_page.dart +++ b/lib/new-ui/pages/wallets_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/new-ui/widgets/asset_tile.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; class NewWalletListPage extends StatelessWidget { const NewWalletListPage({super.key, required this.walletListViewModel}); @@ -10,22 +11,44 @@ class NewWalletListPage extends StatelessWidget { @override Widget build(BuildContext context) { + final fiatCurrency = walletListViewModel.fiatCurrency; + return SafeArea( - child: ListView.builder( - itemCount: walletListViewModel.wallets.length, - itemBuilder: (context, index) { - final wallet = walletListViewModel.wallets[index]; - final balance = - walletListViewModel.cachedBalanceFor(walletTypeToCryptoCurrency(wallet.type)); - final fiatBalance = - walletListViewModel.fiatCachedBalanceFor(walletTypeToCryptoCurrency(wallet.type)); + child: Column( + children: [ + Expanded( + child: RefreshIndicator( + onRefresh: () async { + walletListViewModel.refreshCachedBalances(); + }, + child: Observer( + builder: (_) => ListView.builder( + itemCount: walletListViewModel.wallets.length, + itemBuilder: (context, index) { + return Observer( + builder: (_) { + final wallet = walletListViewModel.wallets[index]; + final currency = walletTypeToCryptoCurrency(wallet.type); + final balance = walletListViewModel.cachedBalanceFor(currency); + final fiatBalance = walletListViewModel.fiatCachedBalanceFor(currency); + final cacheUpdateStatus = walletListViewModel.cacheUpdateStatuses[index]; - return AssetTile( - iconPath: walletTypeToCryptoCurrency(wallet.type).iconPath!, - name: wallet.name, - amount: balance, - amountFiat: fiatBalance); - }, - )); + return AssetTile( + iconPath: currency.iconPath!, + name: wallet.name, + amount: "$balance ${currency.name.toUpperCase()}", + amountFiat: "$fiatBalance $fiatCurrency", + showLoading: !cacheUpdateStatus, + ); + }, + ); + }, + ), + ), + ), + ), + ], + ), + ); } } diff --git a/lib/new-ui/widgets/asset_tile.dart b/lib/new-ui/widgets/asset_tile.dart index 914a2d8bca..3eeebe609a 100644 --- a/lib/new-ui/widgets/asset_tile.dart +++ b/lib/new-ui/widgets/asset_tile.dart @@ -1,4 +1,3 @@ -import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; class AssetTile extends StatelessWidget { @@ -7,12 +6,14 @@ class AssetTile extends StatelessWidget { required this.iconPath, required this.name, required this.amount, - required this.amountFiat}); + required this.amountFiat, + required this.showLoading}); final String iconPath; final String name; final String amount; final String amountFiat; + final bool showLoading; @override Widget build(BuildContext context) { @@ -66,12 +67,14 @@ class AssetTile extends StatelessWidget { ), ], ), - Text( - amountFiat, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), + showLoading + ? CircularProgressIndicator() + : Text( + amountFiat, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), ], ), ), diff --git a/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart b/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart index cabe08986f..7de6df6b86 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/assets_section.dart @@ -20,6 +20,9 @@ class AssetsSection extends StatelessWidget { name: "DummyCoin", amount: "0.000 DMC", amountFiat: "\$ 0.00", + // don't worry about this, it's mostly for wallets page + // unless you load each asset's balance separately for some reason? + showLoading: false, ); }, ); diff --git a/lib/new-ui/widgets/wallets_page/total_balance.dart b/lib/new-ui/widgets/wallets_page/total_balance.dart new file mode 100644 index 0000000000..3cb08efdc0 --- /dev/null +++ b/lib/new-ui/widgets/wallets_page/total_balance.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class TotalBalanceWidget extends StatelessWidget { + const TotalBalanceWidget({super.key, required this.totalBalance, required this.currency}); + + final String totalBalance; + final String currency; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Column( + children: [ + Text("Total assets"), + Row( + children: [Text(totalBalance), Text(currency)], + ) + ], + ) + ], + ); + } +} diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 49c9ce83cc..6c589d52f2 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/wallet_group.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:cake_wallet/entities/wallet_manager.dart'; @@ -30,7 +31,8 @@ abstract class WalletListViewModelBase with Store { multiWalletGroups = ObservableList(), singleWalletsList = ObservableList(), expansionTileStateTrack = ObservableMap(), - cachedBalances = ObservableList() { + cachedBalances = ObservableList(), + cacheUpdateStatuses = ObservableList() { setOrderType(_appStore.settingsStore.walletListOrder); updateList(); @@ -49,6 +51,9 @@ abstract class WalletListViewModelBase with Store { @observable ObservableList cachedBalances; + @observable + ObservableList cacheUpdateStatuses; + // @observable // ObservableList walletGroups; @@ -84,7 +89,6 @@ abstract class WalletListViewModelBase with Store { torOnly: _appStore.settingsStore.fiatApiMode == FiatApiMode.torOnly); } - Future _updateFiatStore() async { for (final wallet in wallets) { final currency = walletTypeToCryptoCurrency(wallet.type); @@ -101,6 +105,16 @@ abstract class WalletListViewModelBase with Store { return calculateFiatAmount(cryptoAmount: cachedBalanceFor(currency), price: price); } + String totalFiatBalance() { + double ret = 0; + + for (final wallet in wallets) { + ret += double.parse(fiatCachedBalanceFor(walletTypeToCryptoCurrency(wallet.type))); + } + + return ret.toString(); + } + @computed bool get shouldRequireTOTP2FAForAccessingWallet => _appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; @@ -109,6 +123,9 @@ abstract class WalletListViewModelBase with Store { bool get shouldRequireTOTP2FAForCreatingNewWallets => _appStore.settingsStore.shouldRequireTOTP2FAForCreatingNewWallets; + @computed + FiatCurrency get fiatCurrency => _appStore.settingsStore.fiatCurrency; + final AppStore _appStore; final WalletManager _walletManager; final WalletLoadingService _walletLoadingService; @@ -145,12 +162,14 @@ abstract class WalletListViewModelBase with Store { wallets.clear(); multiWalletGroups.clear(); singleWalletsList.clear(); + cacheUpdateStatuses.clear(); final list = await WalletInfo.getAll(); for (var info in list) { wallets.add(await convertWalletInfoToWalletListItem(info)); cachedBalances.addAll(await BalanceCache.fromWalletId(info.internalId)); + cacheUpdateStatuses.add(true); } //========== Split into shared seed groups and single wallets list @@ -171,6 +190,22 @@ abstract class WalletListViewModelBase with Store { } } + @action + Future refreshCachedBalances() async { + for (final wallet in wallets) { + cacheUpdateStatuses[wallets.indexOf(wallet)] = false; + + final tmpWallet = await _walletLoadingService.load(wallet.type, wallet.name); + await tmpWallet.startSync(); + while (tmpWallet.syncStatus.progress() < 1.0) { + await Future.delayed(Duration(milliseconds: 100)); + } + await tmpWallet.close(); + + cacheUpdateStatuses[wallets.indexOf(wallet)] = true; + } + } + Future reorderAccordingToWalletList() async { if (wallets.isEmpty) { await updateList(); From 09eeee5355910ef25c4592e5623830db6f12563b Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Sun, 30 Nov 2025 18:18:47 +0100 Subject: [PATCH 4/8] fix: clearer name for sync status bool list --- lib/new-ui/pages/wallets_page.dart | 2 +- .../wallet_list/wallet_list_view_model.dart | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/new-ui/pages/wallets_page.dart b/lib/new-ui/pages/wallets_page.dart index a9133646ca..fa63ccbc37 100644 --- a/lib/new-ui/pages/wallets_page.dart +++ b/lib/new-ui/pages/wallets_page.dart @@ -31,7 +31,7 @@ class NewWalletListPage extends StatelessWidget { final currency = walletTypeToCryptoCurrency(wallet.type); final balance = walletListViewModel.cachedBalanceFor(currency); final fiatBalance = walletListViewModel.fiatCachedBalanceFor(currency); - final cacheUpdateStatus = walletListViewModel.cacheUpdateStatuses[index]; + final cacheUpdateStatus = walletListViewModel.isBalanceCacheSynced[index]; return AssetTile( iconPath: currency.iconPath!, diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 6c589d52f2..571d0d3430 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -32,7 +32,7 @@ abstract class WalletListViewModelBase with Store { singleWalletsList = ObservableList(), expansionTileStateTrack = ObservableMap(), cachedBalances = ObservableList(), - cacheUpdateStatuses = ObservableList() { + isBalanceCacheSynced = ObservableList() { setOrderType(_appStore.settingsStore.walletListOrder); updateList(); @@ -52,7 +52,7 @@ abstract class WalletListViewModelBase with Store { ObservableList cachedBalances; @observable - ObservableList cacheUpdateStatuses; + ObservableList isBalanceCacheSynced; // @observable // ObservableList walletGroups; @@ -162,14 +162,14 @@ abstract class WalletListViewModelBase with Store { wallets.clear(); multiWalletGroups.clear(); singleWalletsList.clear(); - cacheUpdateStatuses.clear(); + isBalanceCacheSynced.clear(); final list = await WalletInfo.getAll(); for (var info in list) { wallets.add(await convertWalletInfoToWalletListItem(info)); cachedBalances.addAll(await BalanceCache.fromWalletId(info.internalId)); - cacheUpdateStatuses.add(true); + isBalanceCacheSynced.add(true); } //========== Split into shared seed groups and single wallets list @@ -193,7 +193,7 @@ abstract class WalletListViewModelBase with Store { @action Future refreshCachedBalances() async { for (final wallet in wallets) { - cacheUpdateStatuses[wallets.indexOf(wallet)] = false; + isBalanceCacheSynced[wallets.indexOf(wallet)] = false; final tmpWallet = await _walletLoadingService.load(wallet.type, wallet.name); await tmpWallet.startSync(); @@ -202,7 +202,7 @@ abstract class WalletListViewModelBase with Store { } await tmpWallet.close(); - cacheUpdateStatuses[wallets.indexOf(wallet)] = true; + isBalanceCacheSynced[wallets.indexOf(wallet)] = true; } } From 8b9c8cecc5a2a78180bc860f6603e7fb0227dc41 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Sun, 30 Nov 2025 18:22:10 +0100 Subject: [PATCH 5/8] fix: restore previous behavior of wallet list page --- .../screens/wallet_list/wallet_list_page.dart | 217 ++++++++---------- .../wallet_list/wallet_list_view_model.dart | 2 +- 2 files changed, 102 insertions(+), 117 deletions(-) diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 48a81b6e8c..ec0a54664a 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -29,7 +29,6 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -49,9 +48,9 @@ class WalletListPage extends BasePage { String get title => S.current.wallets; @override - Widget body(BuildContext context) => Observer(builder: (_) { - if (walletListViewModel.singleWalletsList.isEmpty && - walletListViewModel.multiWalletGroups.isEmpty) { + Widget body(BuildContext context) => Observer( + builder: (_) { + if (walletListViewModel.singleWalletsList.isEmpty && walletListViewModel.multiWalletGroups.isEmpty) { return Center( child: CircularProgressIndicator(), ); @@ -61,7 +60,8 @@ class WalletListPage extends BasePage { authService: authService, onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(), ); - }); + } + ); @override Widget trailing(BuildContext context) { @@ -153,10 +153,10 @@ class WalletListBodyState extends State { child: Text( S.current.shared_seed_wallet_groups, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18, - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurface, - ), + fontSize: 18, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onSurface, + ), ), ), SizedBox(height: 16), @@ -165,91 +165,76 @@ class WalletListBodyState extends State { padding: EdgeInsets.only(left: 20, right: 20), child: Observer( builder: (_) => FilteredList( - shrinkWrap: true, - list: widget.walletListViewModel.multiWalletGroups, - updateFunction: widget.walletListViewModel.reorderAccordingToWalletList, - itemBuilder: (context, index) { - final group = widget.walletListViewModel.multiWalletGroups[index]; - final groupName = - group.groupName ?? '${S.current.wallet_group} ${index + 1}'; - - widget.walletListViewModel.updateTileState( - index, - widget.walletListViewModel.expansionTileStateTrack[index] ?? false, - ); - - return FutureBuilder>( - future: Future.wait( - group.wallets.map((w) { - return widget.walletListViewModel - .convertWalletInfoToWalletListItem(w); - }), - ), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return SizedBox( - height: 60, - child: Center(child: CircularProgressIndicator()), - ); - } + shrinkWrap: true, + list: widget.walletListViewModel.multiWalletGroups, + updateFunction: widget.walletListViewModel.reorderAccordingToWalletList, + itemBuilder: (context, index) { + final group = widget.walletListViewModel.multiWalletGroups[index]; + final groupName = + group.groupName ?? '${S.current.wallet_group} ${index + 1}'; - final childWallets = snapshot.data!; + widget.walletListViewModel.updateTileState( + index, + widget.walletListViewModel.expansionTileStateTrack[index] ?? false, + ); - return GroupedWalletExpansionTile( - onExpansionChanged: (value) { - widget.walletListViewModel.updateTileState(index, value); - setState(() {}); - }, - shouldShowCurrentWalletPointer: true, - borderRadius: BorderRadius.all(Radius.circular(16)), - title: groupName, - tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'), - leadingWidget: Icon( - Icons.account_balance_wallet_outlined, - size: 28, - ), - trailingWidget: EditWalletButtonWidget( - width: 88, - isGroup: true, - isExpanded: widget - .walletListViewModel.expansionTileStateTrack[index]!, - onTap: () async { - final wallet = await widget.walletListViewModel - .convertWalletInfoToWalletListItem(group.wallets.first); - Navigator.of(context).pushNamed( - Routes.walletEdit, - arguments: WalletEditPageArguments( - walletListViewModel: widget.walletListViewModel, - editingWallet: wallet, - isWalletGroup: true, - groupName: groupName, - walletGroupKey: group.groupKey, - ), - ); - }, + return GroupedWalletExpansionTile( + onExpansionChanged: (value) { + widget.walletListViewModel.updateTileState(index, value); + setState(() {}); + }, + shouldShowCurrentWalletPointer: true, + borderRadius: BorderRadius.all(Radius.circular(16)), + title: groupName, + tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'), + leadingWidget: Icon( + Icons.account_balance_wallet_outlined, + size: 28, + ), + trailingWidget: EditWalletButtonWidget( + width: 88, + isGroup: true, + isExpanded: + widget.walletListViewModel.expansionTileStateTrack[index]!, + onTap: () { + final wallet = widget.walletListViewModel + .convertWalletInfoToWalletListItem(group.wallets.first); + Navigator.of(context).pushNamed( + Routes.walletEdit, + arguments: WalletEditPageArguments( + walletListViewModel: widget.walletListViewModel, + editingWallet: wallet, + isWalletGroup: true, + groupName: groupName, + walletGroupKey: group.groupKey, ), - childWallets: childWallets, - isSelected: false, - onChildItemTapped: (wallet) => - wallet.isCurrent ? null : _loadWallet(wallet), - childTrailingWidget: (item) { - return item.isCurrent - ? SizedBox.shrink() - : EditWalletButtonWidget( - width: 60, - onTap: () => Navigator.of(context).pushNamed( - Routes.walletEdit, - arguments: WalletEditPageArguments( - walletListViewModel: widget.walletListViewModel, - editingWallet: item, - ), - ), - ); - }, ); }, - ); - }), + ), + childWallets: group.wallets.map((walletInfo) { + return widget.walletListViewModel + .convertWalletInfoToWalletListItem(walletInfo); + }).toList(), + isSelected: false, + onChildItemTapped: (wallet) => + wallet.isCurrent ? null : _loadWallet(wallet), + childTrailingWidget: (item) { + return item.isCurrent + ? SizedBox.shrink() + : EditWalletButtonWidget( + width: 60, + onTap: () => Navigator.of(context).pushNamed( + Routes.walletEdit, + arguments: WalletEditPageArguments( + walletListViewModel: widget.walletListViewModel, + editingWallet: item, + ), + ), + ); + }, + ); + }, + ), ), ), ), @@ -261,10 +246,10 @@ class WalletListBodyState extends State { child: Text( S.current.single_seed_wallets_group, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18, - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurface, - ), + fontSize: 18, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onSurface, + ), ), ), SizedBox(height: 16), @@ -290,17 +275,17 @@ class WalletListBodyState extends State { children: [ wallet.isCurrent ? Container( - height: 35, - width: 6, - margin: EdgeInsets.only(right: 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(16), - bottomRight: Radius.circular(16), - ), - color: currentColor, - ), - ) + height: 35, + width: 6, + margin: EdgeInsets.only(right: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + color: currentColor, + ), + ) : SizedBox(width: 6), Image.asset( walletTypeToCryptoCurrency(wallet.type).iconPath!, @@ -318,17 +303,17 @@ class WalletListBodyState extends State { trailingWidget: wallet.isCurrent ? null : EditWalletButtonWidget( - width: 64, - onTap: () { - Navigator.of(context).pushNamed( - Routes.walletEdit, - arguments: WalletEditPageArguments( - walletListViewModel: widget.walletListViewModel, - editingWallet: wallet, - ), - ); - }, + width: 64, + onTap: () { + Navigator.of(context).pushNamed( + Routes.walletEdit, + arguments: WalletEditPageArguments( + walletListViewModel: widget.walletListViewModel, + editingWallet: wallet, ), + ); + }, + ), ); }, ), @@ -489,7 +474,7 @@ class WalletListBodyState extends State { try { final requireHardwareWalletConnection = - await widget.walletListViewModel.requireHardwareWalletConnection(wallet); + await widget.walletListViewModel.requireHardwareWalletConnection(wallet); if (requireHardwareWalletConnection) { bool didConnect = false; await Navigator.of(context).pushNamed( @@ -545,7 +530,7 @@ class WalletListBodyState extends State { } }, conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet, + widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet, ); } diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 571d0d3430..9dcb7c4b92 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -319,7 +319,7 @@ abstract class WalletListViewModelBase with Store { } } - Future convertWalletInfoToWalletListItem(WalletInfo info) async { + WalletListItem convertWalletInfoToWalletListItem(WalletInfo info) { return WalletListItem( name: info.name, type: info.type, From 1e10edb04047f4f45c3a21158d733d9fce6f619d Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Sun, 30 Nov 2025 18:22:10 +0100 Subject: [PATCH 6/8] fix: restore previous behavior of wallet list page --- .../screens/wallet_list/wallet_list_page.dart | 217 ++++++++---------- .../wallet_list/wallet_list_view_model.dart | 13 +- 2 files changed, 108 insertions(+), 122 deletions(-) diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 48a81b6e8c..ec0a54664a 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -29,7 +29,6 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -49,9 +48,9 @@ class WalletListPage extends BasePage { String get title => S.current.wallets; @override - Widget body(BuildContext context) => Observer(builder: (_) { - if (walletListViewModel.singleWalletsList.isEmpty && - walletListViewModel.multiWalletGroups.isEmpty) { + Widget body(BuildContext context) => Observer( + builder: (_) { + if (walletListViewModel.singleWalletsList.isEmpty && walletListViewModel.multiWalletGroups.isEmpty) { return Center( child: CircularProgressIndicator(), ); @@ -61,7 +60,8 @@ class WalletListPage extends BasePage { authService: authService, onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(), ); - }); + } + ); @override Widget trailing(BuildContext context) { @@ -153,10 +153,10 @@ class WalletListBodyState extends State { child: Text( S.current.shared_seed_wallet_groups, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18, - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurface, - ), + fontSize: 18, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onSurface, + ), ), ), SizedBox(height: 16), @@ -165,91 +165,76 @@ class WalletListBodyState extends State { padding: EdgeInsets.only(left: 20, right: 20), child: Observer( builder: (_) => FilteredList( - shrinkWrap: true, - list: widget.walletListViewModel.multiWalletGroups, - updateFunction: widget.walletListViewModel.reorderAccordingToWalletList, - itemBuilder: (context, index) { - final group = widget.walletListViewModel.multiWalletGroups[index]; - final groupName = - group.groupName ?? '${S.current.wallet_group} ${index + 1}'; - - widget.walletListViewModel.updateTileState( - index, - widget.walletListViewModel.expansionTileStateTrack[index] ?? false, - ); - - return FutureBuilder>( - future: Future.wait( - group.wallets.map((w) { - return widget.walletListViewModel - .convertWalletInfoToWalletListItem(w); - }), - ), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return SizedBox( - height: 60, - child: Center(child: CircularProgressIndicator()), - ); - } + shrinkWrap: true, + list: widget.walletListViewModel.multiWalletGroups, + updateFunction: widget.walletListViewModel.reorderAccordingToWalletList, + itemBuilder: (context, index) { + final group = widget.walletListViewModel.multiWalletGroups[index]; + final groupName = + group.groupName ?? '${S.current.wallet_group} ${index + 1}'; - final childWallets = snapshot.data!; + widget.walletListViewModel.updateTileState( + index, + widget.walletListViewModel.expansionTileStateTrack[index] ?? false, + ); - return GroupedWalletExpansionTile( - onExpansionChanged: (value) { - widget.walletListViewModel.updateTileState(index, value); - setState(() {}); - }, - shouldShowCurrentWalletPointer: true, - borderRadius: BorderRadius.all(Radius.circular(16)), - title: groupName, - tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'), - leadingWidget: Icon( - Icons.account_balance_wallet_outlined, - size: 28, - ), - trailingWidget: EditWalletButtonWidget( - width: 88, - isGroup: true, - isExpanded: widget - .walletListViewModel.expansionTileStateTrack[index]!, - onTap: () async { - final wallet = await widget.walletListViewModel - .convertWalletInfoToWalletListItem(group.wallets.first); - Navigator.of(context).pushNamed( - Routes.walletEdit, - arguments: WalletEditPageArguments( - walletListViewModel: widget.walletListViewModel, - editingWallet: wallet, - isWalletGroup: true, - groupName: groupName, - walletGroupKey: group.groupKey, - ), - ); - }, + return GroupedWalletExpansionTile( + onExpansionChanged: (value) { + widget.walletListViewModel.updateTileState(index, value); + setState(() {}); + }, + shouldShowCurrentWalletPointer: true, + borderRadius: BorderRadius.all(Radius.circular(16)), + title: groupName, + tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'), + leadingWidget: Icon( + Icons.account_balance_wallet_outlined, + size: 28, + ), + trailingWidget: EditWalletButtonWidget( + width: 88, + isGroup: true, + isExpanded: + widget.walletListViewModel.expansionTileStateTrack[index]!, + onTap: () { + final wallet = widget.walletListViewModel + .convertWalletInfoToWalletListItem(group.wallets.first); + Navigator.of(context).pushNamed( + Routes.walletEdit, + arguments: WalletEditPageArguments( + walletListViewModel: widget.walletListViewModel, + editingWallet: wallet, + isWalletGroup: true, + groupName: groupName, + walletGroupKey: group.groupKey, ), - childWallets: childWallets, - isSelected: false, - onChildItemTapped: (wallet) => - wallet.isCurrent ? null : _loadWallet(wallet), - childTrailingWidget: (item) { - return item.isCurrent - ? SizedBox.shrink() - : EditWalletButtonWidget( - width: 60, - onTap: () => Navigator.of(context).pushNamed( - Routes.walletEdit, - arguments: WalletEditPageArguments( - walletListViewModel: widget.walletListViewModel, - editingWallet: item, - ), - ), - ); - }, ); }, - ); - }), + ), + childWallets: group.wallets.map((walletInfo) { + return widget.walletListViewModel + .convertWalletInfoToWalletListItem(walletInfo); + }).toList(), + isSelected: false, + onChildItemTapped: (wallet) => + wallet.isCurrent ? null : _loadWallet(wallet), + childTrailingWidget: (item) { + return item.isCurrent + ? SizedBox.shrink() + : EditWalletButtonWidget( + width: 60, + onTap: () => Navigator.of(context).pushNamed( + Routes.walletEdit, + arguments: WalletEditPageArguments( + walletListViewModel: widget.walletListViewModel, + editingWallet: item, + ), + ), + ); + }, + ); + }, + ), ), ), ), @@ -261,10 +246,10 @@ class WalletListBodyState extends State { child: Text( S.current.single_seed_wallets_group, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18, - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurface, - ), + fontSize: 18, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onSurface, + ), ), ), SizedBox(height: 16), @@ -290,17 +275,17 @@ class WalletListBodyState extends State { children: [ wallet.isCurrent ? Container( - height: 35, - width: 6, - margin: EdgeInsets.only(right: 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(16), - bottomRight: Radius.circular(16), - ), - color: currentColor, - ), - ) + height: 35, + width: 6, + margin: EdgeInsets.only(right: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + color: currentColor, + ), + ) : SizedBox(width: 6), Image.asset( walletTypeToCryptoCurrency(wallet.type).iconPath!, @@ -318,17 +303,17 @@ class WalletListBodyState extends State { trailingWidget: wallet.isCurrent ? null : EditWalletButtonWidget( - width: 64, - onTap: () { - Navigator.of(context).pushNamed( - Routes.walletEdit, - arguments: WalletEditPageArguments( - walletListViewModel: widget.walletListViewModel, - editingWallet: wallet, - ), - ); - }, + width: 64, + onTap: () { + Navigator.of(context).pushNamed( + Routes.walletEdit, + arguments: WalletEditPageArguments( + walletListViewModel: widget.walletListViewModel, + editingWallet: wallet, ), + ); + }, + ), ); }, ), @@ -489,7 +474,7 @@ class WalletListBodyState extends State { try { final requireHardwareWalletConnection = - await widget.walletListViewModel.requireHardwareWalletConnection(wallet); + await widget.walletListViewModel.requireHardwareWalletConnection(wallet); if (requireHardwareWalletConnection) { bool didConnect = false; await Navigator.of(context).pushNamed( @@ -545,7 +530,7 @@ class WalletListBodyState extends State { } }, conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet, + widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet, ); } diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 571d0d3430..9ceb176f08 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; @@ -7,15 +8,15 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/wallet_group.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:cake_wallet/entities/wallet_manager.dart'; +import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; +import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:mobx/mobx.dart'; part 'wallet_list_view_model.g.dart'; @@ -179,7 +180,7 @@ abstract class WalletListViewModelBase with Store { for (var group in walletGroupsFromManager) { if (group.wallets.length == 1) { - singleWalletsList.add(await convertWalletInfoToWalletListItem(group.wallets.first)); + singleWalletsList.add(convertWalletInfoToWalletListItem(group.wallets.first)); continue; } @@ -319,7 +320,7 @@ abstract class WalletListViewModelBase with Store { } } - Future convertWalletInfoToWalletListItem(WalletInfo info) async { + WalletListItem convertWalletInfoToWalletListItem(WalletInfo info) { return WalletListItem( name: info.name, type: info.type, From ea979cf819c7d6814905a874cd0c392d86fc95a6 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Wed, 3 Dec 2025 13:46:13 +0100 Subject: [PATCH 7/8] fix: connect to node on balance cache refresh --- lib/view_model/wallet_list/wallet_list_view_model.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 9ceb176f08..0e09b53384 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -197,6 +197,7 @@ abstract class WalletListViewModelBase with Store { isBalanceCacheSynced[wallets.indexOf(wallet)] = false; final tmpWallet = await _walletLoadingService.load(wallet.type, wallet.name); + await tmpWallet.connectToNode(node: _appStore.settingsStore.getCurrentNode(tmpWallet.type)); await tmpWallet.startSync(); while (tmpWallet.syncStatus.progress() < 1.0) { await Future.delayed(Duration(milliseconds: 100)); From 193a0c22f2bdc48143a79d6a556f4be387d47b92 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Wed, 3 Dec 2025 13:46:49 +0100 Subject: [PATCH 8/8] skeletonizer-based wallet list refresh ui --- lib/new-ui/widgets/asset_tile.dart | 29 +++++++++++-------- .../wallet_list/wallet_list_view_model.dart | 2 ++ pubspec_base.yaml | 1 + 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/new-ui/widgets/asset_tile.dart b/lib/new-ui/widgets/asset_tile.dart index 3eeebe609a..44451bbf11 100644 --- a/lib/new-ui/widgets/asset_tile.dart +++ b/lib/new-ui/widgets/asset_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class AssetTile extends StatelessWidget { const AssetTile( @@ -57,24 +58,28 @@ class AssetTile extends StatelessWidget { name, style: TextStyle(fontWeight: FontWeight.bold), ), - Text( - amount, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, + Skeletonizer( + enabled: showLoading, + child: Text( + amount, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ), ), ], ), ], ), - showLoading - ? CircularProgressIndicator() - : Text( - amountFiat, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), + Skeletonizer( + enabled: showLoading, + child: Text( + amountFiat, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), ], ), ), diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 0e09b53384..bdbd464118 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -195,7 +195,9 @@ abstract class WalletListViewModelBase with Store { Future refreshCachedBalances() async { for (final wallet in wallets) { isBalanceCacheSynced[wallets.indexOf(wallet)] = false; + } + for (final wallet in wallets) { final tmpWallet = await _walletLoadingService.load(wallet.type, wallet.name); await tmpWallet.connectToNode(node: _appStore.settingsStore.getCurrentNode(tmpWallet.type)); await tmpWallet.startSync(); diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 79f4acc696..9a39b8260d 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -59,6 +59,7 @@ dependencies: another_flushbar: ^1.12.29 archive: ^4.0.3 cryptography: ^2.0.5 + skeletonizer: ^2.1.1 file_picker: git: url: https://github.com/cake-tech/flutter_file_picker.git