Address list fixes CW-883 (#1995)

* fix show/hide buttons not updating address list * fix label not updating * cleanup * minor fix

Matthew Fosse committed Mar 3, 2025 at 17:36 UTC de40b2f9aa9f92d88305516d6f2acf3ae613c66b
2 files changed +68 -76
lib/src/screens/receive/widgets/address_list.dart
+21 -19
@@ -1,4 +1,3 @@
1 -
1 import 'dart:math';
2
3 import 'package:cake_wallet/di.dart';
@@ -21,6 +20,7 @@ import 'package:cw_core/wallet_type.dart';
20 import 'package:flutter/foundation.dart';
21 import 'package:flutter/material.dart';
22 import 'package:flutter_mobx/flutter_mobx.dart';
23 +import 'package:mobx/mobx.dart';
24
25 class AddressList extends StatefulWidget {
26 const AddressList({
@@ -37,7 +37,6 @@ class AddressList extends StatefulWidget {
37 }
38
39 class _AddressListState extends State<AddressList> {
40 -
40 bool showHiddenAddresses = false;
41
42 void _toggleHiddenAddresses() {
@@ -62,7 +61,7 @@ class _AddressListState extends State<AddressList> {
61
62 void updateItems() {
63 setState(() {
65 - items = getItems(widget.addressListViewModel.items, showHiddenAddresses);
64 + items = getItems(widget.addressListViewModel.forceRecomputeItems, showHiddenAddresses);
65 });
66 }
67
@@ -132,9 +131,10 @@ class _AddressListState extends State<AddressList> {
131 showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
132 showSearchButton: true,
133 onSearchCallback: updateItems,
135 - trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
136 - updateItems(); // refresh the new address
137 - }),
134 + trailingButtonTap: () =>
135 + Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
136 + updateItems(); // refresh the new address
137 + }),
138 trailingIcon: Icon(
139 Icons.add,
140 size: 20,
@@ -149,7 +149,8 @@ class _AddressListState extends State<AddressList> {
149 cell = Container();
150 } else {
151 cell = Observer(builder: (_) {
152 - final isCurrent = item.address == widget.addressListViewModel.address.address && editable;
152 + final isCurrent =
153 + item.address == widget.addressListViewModel.address.address && editable;
154 final backgroundColor = isCurrent
155 ? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
156 : Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
@@ -157,17 +158,17 @@ class _AddressListState extends State<AddressList> {
158 ? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
159 : Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
160
160 -
161 return AddressCell.fromItem(
162 item,
163 isCurrent: isCurrent,
164 hasBalance: widget.addressListViewModel.isBalanceAvailable,
165 hasReceived: widget.addressListViewModel.isReceivedAvailable,
166 - // hasReceived:
167 - backgroundColor: (kDebugMode && item.isHidden) ?
168 - Theme.of(context).colorScheme.error :
169 - (kDebugMode && item.isManual) ? Theme.of(context).colorScheme.error.withBlue(255) :
170 - backgroundColor,
166 + // hasReceived:
167 + backgroundColor: (kDebugMode && item.isHidden)
168 + ? Theme.of(context).colorScheme.error
169 + : (kDebugMode && item.isManual)
170 + ? Theme.of(context).colorScheme.error.withBlue(255)
171 + : backgroundColor,
172 textColor: textColor,
173 onTap: (_) {
174 if (widget.onSelect != null) {
@@ -177,9 +178,11 @@ class _AddressListState extends State<AddressList> {
178 widget.addressListViewModel.setAddress(item);
179 },
180 onEdit: editable
180 - ? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item).then((value) {
181 - updateItems(); // refresh the new address
182 - })
181 + ? () => Navigator.of(context)
182 + .pushNamed(Routes.newSubaddress, arguments: item)
183 + .then((value) {
184 + updateItems(); // refresh the new address
185 + })
186 : null,
187 isHidden: item.isHidden,
188 onHide: () => _hideAddress(item),
@@ -191,8 +194,8 @@ class _AddressListState extends State<AddressList> {
194 return index != 0
195 ? cell
196 : ClipRRect(
194 - borderRadius: BorderRadius.only(
195 - topLeft: Radius.circular(30), topRight: Radius.circular(30)),
197 + borderRadius:
198 + BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)),
199 child: cell,
200 );
201 },
@@ -203,5 +206,4 @@ class _AddressListState extends State<AddressList> {
206 await widget.addressListViewModel.toggleHideAddress(item);
207 updateItems();
208 }
206 -
209 }
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+47 -57
@@ -26,14 +26,14 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
26 import 'package:cake_wallet/wownero/wownero.dart';
27 import 'package:cw_core/amount_converter.dart';
28 import 'package:cw_core/currency.dart';
29 +import 'package:cw_core/utils/print_verbose.dart';
30 import 'package:cw_core/wallet_type.dart';
31 import 'package:intl/intl.dart';
32 import 'package:mobx/mobx.dart';
33
34 part 'wallet_address_list_view_model.g.dart';
35
35 -class WalletAddressListViewModel = WalletAddressListViewModelBase
36 - with _$WalletAddressListViewModel;
36 +class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
37
38 abstract class PaymentURI {
39 PaymentURI({required this.amount, required this.address});
@@ -222,9 +222,7 @@ class ZanoURI extends PaymentURI {
222 }
223 }
224
225 -
226 -abstract class WalletAddressListViewModelBase
227 - extends WalletChangeListenerViewModel with Store {
225 +abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
226 WalletAddressListViewModelBase({
227 required AppStore appStore,
228 required this.yatStore,
@@ -245,8 +243,7 @@ abstract class WalletAddressListViewModelBase
243 _init();
244
245 selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
248 - hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
249 - .contains(wallet.type);
246 + hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven].contains(wallet.type);
247 }
248
249 static const String _cryptoNumberPattern = '0.00000000';
@@ -259,8 +256,7 @@ abstract class WalletAddressListViewModelBase
256 double? _fiatRate;
257 String _rawAmount = '';
258
262 - List<Currency> get currencies =>
263 - [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
259 + List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
260
261 String get buttonTitle {
262 if (isElectrumWallet) {
@@ -286,8 +282,8 @@ abstract class WalletAddressListViewModelBase
282 WalletType get type => wallet.type;
283
284 @computed
289 - WalletAddressListItem get address => WalletAddressListItem(
290 - address: wallet.walletAddresses.address, isPrimary: false);
285 + WalletAddressListItem get address =>
286 + WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
287
288 @computed
289 PaymentURI get uri {
@@ -317,25 +313,23 @@ abstract class WalletAddressListViewModelBase
313 case WalletType.wownero:
314 return WowneroURI(amount: amount, address: address.address);
315 case WalletType.zano:
320 - return ZanoURI(amount: amount, address: address.address);
316 + return ZanoURI(amount: amount, address: address.address);
317 case WalletType.none:
318 throw Exception('Unexpected type: ${type.toString()}');
319 }
320 }
321 +
322 @computed
323 ObservableList<ListItem> get items => ObservableList<ListItem>()
324 ..addAll(_baseItems)
325 ..addAll(addressList);
326
330 - @computed
331 - ObservableList<ListItem> get addressList {
327 + ObservableList<ListItem> _computeAddressList() {
328 final addressList = ObservableList<ListItem>();
329
330 if (wallet.type == WalletType.monero) {
335 - final primaryAddress =
336 - monero!.getSubaddressList(wallet).subaddresses.first;
337 - final addressItems =
338 - monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
331 + final primaryAddress = monero!.getSubaddressList(wallet).subaddresses.first;
332 + final addressItems = monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
333 final isPrimary = subaddress == primaryAddress;
334
335 return WalletAddressListItem(
@@ -351,10 +345,8 @@ abstract class WalletAddressListViewModelBase
345 }
346
347 if (wallet.type == WalletType.wownero) {
354 - final primaryAddress =
355 - wownero!.getSubaddressList(wallet).subaddresses.first;
356 - final addressItems =
357 - wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
348 + final primaryAddress = wownero!.getSubaddressList(wallet).subaddresses.first;
349 + final addressItems = wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
350 final isPrimary = subaddress == primaryAddress;
351
352 return WalletAddressListItem(
@@ -367,10 +359,8 @@ abstract class WalletAddressListViewModelBase
359 }
360
361 if (wallet.type == WalletType.haven) {
370 - final primaryAddress =
371 - haven!.getSubaddressList(wallet).subaddresses.first;
372 - final addressItems =
373 - haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
362 + final primaryAddress = haven!.getSubaddressList(wallet).subaddresses.first;
363 + final addressItems = haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
364 final isPrimary = subaddress == primaryAddress;
365
366 return WalletAddressListItem(
@@ -384,8 +374,7 @@ abstract class WalletAddressListViewModelBase
374
375 if (isElectrumWallet) {
376 if (bitcoin!.hasSelectedSilentPayments(wallet)) {
387 - final addressItems =
388 - bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
377 + final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
378 final isPrimary = address.id == 0;
379
380 return WalletAddressListItem(
@@ -436,8 +425,7 @@ abstract class WalletAddressListViewModelBase
425 if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) {
426 // find the index of the last item with a txCount > 0
427 final addressItemsList = addressItems.toList();
439 - int index = addressItemsList
440 - .lastIndexWhere((item) => (item.txCount ?? 0) > 0);
428 + int index = addressItemsList.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
429 if (index == -1) {
430 index = 0;
431 }
@@ -451,22 +439,19 @@ abstract class WalletAddressListViewModelBase
439 if (wallet.type == WalletType.ethereum) {
440 final primaryAddress = ethereum!.getAddress(wallet);
441
454 - addressList.add(WalletAddressListItem(
455 - isPrimary: true, name: null, address: primaryAddress));
442 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
443 }
444
445 if (wallet.type == WalletType.polygon) {
446 final primaryAddress = polygon!.getAddress(wallet);
447
461 - addressList.add(WalletAddressListItem(
462 - isPrimary: true, name: null, address: primaryAddress));
448 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
449 }
450
451 if (wallet.type == WalletType.solana) {
452 final primaryAddress = solana!.getAddress(wallet);
453
468 - addressList.add(WalletAddressListItem(
469 - isPrimary: true, name: null, address: primaryAddress));
454 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
455 }
456
457 if (wallet.type == WalletType.nano) {
@@ -480,21 +465,18 @@ abstract class WalletAddressListViewModelBase
465 if (wallet.type == WalletType.tron) {
466 final primaryAddress = tron!.getAddress(wallet);
467
483 - addressList.add(WalletAddressListItem(
484 - isPrimary: true, name: null, address: primaryAddress));
468 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
469 }
470
471 for (var i = 0; i < addressList.length; i++) {
472 if (!(addressList[i] is WalletAddressListItem)) continue;
489 - (addressList[i] as WalletAddressListItem).isHidden = wallet
490 - .walletAddresses.hiddenAddresses
473 + (addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
474 .contains((addressList[i] as WalletAddressListItem).address);
475 }
476
477 for (var i = 0; i < addressList.length; i++) {
478 if (!(addressList[i] is WalletAddressListItem)) continue;
496 - (addressList[i] as WalletAddressListItem).isManual = wallet
497 - .walletAddresses.manualAddresses
479 + (addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses
480 .contains((addressList[i] as WalletAddressListItem).address);
481 }
482
@@ -516,13 +498,28 @@ abstract class WalletAddressListViewModelBase
498 return addressList;
499 }
500
501 + @computed
502 + ObservableList<ListItem> get addressList {
503 + return _computeAddressList();
504 + }
505 +
506 + List<ListItem> get forceRecomputeItems {
507 + // necessary because the addressList contains non-observable items
508 + List<ListItem> recomputed = [];
509 + recomputed.addAll(_baseItems);
510 + recomputed.addAll(_computeAddressList());
511 + return recomputed;
512 + }
513 +
514 Future<void> toggleHideAddress(WalletAddressListItem item) async {
515 if (item.isHidden) {
521 - wallet.walletAddresses.hiddenAddresses
522 - .removeWhere((element) => element == item.address);
516 + item.isHidden = false;
517 + wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address);
518 } else {
519 + item.isHidden = true;
520 wallet.walletAddresses.hiddenAddresses.add(item.address);
521 }
522 + // update the address list:
523 await wallet.walletAddresses.saveAddressesInBox();
524 if (wallet.type == WalletType.monero) {
525 monero!
@@ -568,28 +565,22 @@ abstract class WalletAddressListViewModelBase
565 ].contains(wallet.type);
566
567 @computed
571 - bool get isElectrumWallet => [
572 - WalletType.bitcoin,
573 - WalletType.litecoin,
574 - WalletType.bitcoinCash
575 - ].contains(wallet.type);
568 + bool get isElectrumWallet =>
569 + [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type);
570
571 @computed
572 bool get isBalanceAvailable => isElectrumWallet;
573
574 @computed
581 - bool get isReceivedAvailable =>
582 - [WalletType.monero, WalletType.wownero].contains(wallet.type);
575 + bool get isReceivedAvailable => [WalletType.monero, WalletType.wownero].contains(wallet.type);
576
577 @computed
578 bool get isSilentPayments =>
586 - wallet.type == WalletType.bitcoin &&
587 - bitcoin!.hasSelectedSilentPayments(wallet);
579 + wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
580
581 @computed
582 bool get isAutoGenerateSubaddressEnabled =>
591 - _settingsStore.autoGenerateSubaddressStatus !=
592 - AutoGenerateSubaddressStatus.disabled &&
583 + _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
584 !isSilentPayments;
585
586 @computed
@@ -672,8 +663,7 @@ abstract class WalletAddressListViewModelBase
663 @action
664 void _convertAmountToCrypto() {
665 final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
675 - final fiatRate =
676 - _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
666 + final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
667
668 if (fiatRate <= 0.0) {
669 dev.log("Invalid Fiat Rate $fiatRate");