Address list fixes CW-883 (#1995)

* fix show/hide buttons not updating address list

* fix label not updating

* cleanup

* minor fix
This commit is contained in:
Matthew Fosse 2025-03-03 17:36:20 -08:00 committed by GitHub
parent 130f877234
commit de40b2f9aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 76 deletions

View file

@ -1,4 +1,3 @@
import 'dart:math'; import 'dart:math';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
@ -21,6 +20,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class AddressList extends StatefulWidget { class AddressList extends StatefulWidget {
const AddressList({ const AddressList({
@ -37,7 +37,6 @@ class AddressList extends StatefulWidget {
} }
class _AddressListState extends State<AddressList> { class _AddressListState extends State<AddressList> {
bool showHiddenAddresses = false; bool showHiddenAddresses = false;
void _toggleHiddenAddresses() { void _toggleHiddenAddresses() {
@ -62,7 +61,7 @@ class _AddressListState extends State<AddressList> {
void updateItems() { void updateItems() {
setState(() { setState(() {
items = getItems(widget.addressListViewModel.items, showHiddenAddresses); items = getItems(widget.addressListViewModel.forceRecomputeItems, showHiddenAddresses);
}); });
} }
@ -132,9 +131,10 @@ class _AddressListState extends State<AddressList> {
showTrailingButton: widget.addressListViewModel.showAddManualAddresses, showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
showSearchButton: true, showSearchButton: true,
onSearchCallback: updateItems, onSearchCallback: updateItems,
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) { trailingButtonTap: () =>
updateItems(); // refresh the new address Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
}), updateItems(); // refresh the new address
}),
trailingIcon: Icon( trailingIcon: Icon(
Icons.add, Icons.add,
size: 20, size: 20,
@ -149,7 +149,8 @@ class _AddressListState extends State<AddressList> {
cell = Container(); cell = Container();
} else { } else {
cell = Observer(builder: (_) { cell = Observer(builder: (_) {
final isCurrent = item.address == widget.addressListViewModel.address.address && editable; final isCurrent =
item.address == widget.addressListViewModel.address.address && editable;
final backgroundColor = isCurrent final backgroundColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor ? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor; : Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
@ -157,17 +158,17 @@ class _AddressListState extends State<AddressList> {
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor ? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor; : Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
return AddressCell.fromItem( return AddressCell.fromItem(
item, item,
isCurrent: isCurrent, isCurrent: isCurrent,
hasBalance: widget.addressListViewModel.isBalanceAvailable, hasBalance: widget.addressListViewModel.isBalanceAvailable,
hasReceived: widget.addressListViewModel.isReceivedAvailable, hasReceived: widget.addressListViewModel.isReceivedAvailable,
// hasReceived: // hasReceived:
backgroundColor: (kDebugMode && item.isHidden) ? backgroundColor: (kDebugMode && item.isHidden)
Theme.of(context).colorScheme.error : ? Theme.of(context).colorScheme.error
(kDebugMode && item.isManual) ? Theme.of(context).colorScheme.error.withBlue(255) : : (kDebugMode && item.isManual)
backgroundColor, ? Theme.of(context).colorScheme.error.withBlue(255)
: backgroundColor,
textColor: textColor, textColor: textColor,
onTap: (_) { onTap: (_) {
if (widget.onSelect != null) { if (widget.onSelect != null) {
@ -177,9 +178,11 @@ class _AddressListState extends State<AddressList> {
widget.addressListViewModel.setAddress(item); widget.addressListViewModel.setAddress(item);
}, },
onEdit: editable onEdit: editable
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item).then((value) { ? () => Navigator.of(context)
updateItems(); // refresh the new address .pushNamed(Routes.newSubaddress, arguments: item)
}) .then((value) {
updateItems(); // refresh the new address
})
: null, : null,
isHidden: item.isHidden, isHidden: item.isHidden,
onHide: () => _hideAddress(item), onHide: () => _hideAddress(item),
@ -191,8 +194,8 @@ class _AddressListState extends State<AddressList> {
return index != 0 return index != 0
? cell ? cell
: ClipRRect( : ClipRRect(
borderRadius: BorderRadius.only( borderRadius:
topLeft: Radius.circular(30), topRight: Radius.circular(30)), BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)),
child: cell, child: cell,
); );
}, },
@ -203,5 +206,4 @@ class _AddressListState extends State<AddressList> {
await widget.addressListViewModel.toggleHideAddress(item); await widget.addressListViewModel.toggleHideAddress(item);
updateItems(); updateItems();
} }
} }

View file

@ -26,14 +26,14 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart'; import 'package:cw_core/currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'wallet_address_list_view_model.g.dart'; part 'wallet_address_list_view_model.g.dart';
class WalletAddressListViewModel = WalletAddressListViewModelBase class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
with _$WalletAddressListViewModel;
abstract class PaymentURI { abstract class PaymentURI {
PaymentURI({required this.amount, required this.address}); PaymentURI({required this.amount, required this.address});
@ -222,9 +222,7 @@ class ZanoURI extends PaymentURI {
} }
} }
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
abstract class WalletAddressListViewModelBase
extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({ WalletAddressListViewModelBase({
required AppStore appStore, required AppStore appStore,
required this.yatStore, required this.yatStore,
@ -245,8 +243,7 @@ abstract class WalletAddressListViewModelBase
_init(); _init();
selectedCurrency = walletTypeToCryptoCurrency(wallet.type); selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven] hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven].contains(wallet.type);
.contains(wallet.type);
} }
static const String _cryptoNumberPattern = '0.00000000'; static const String _cryptoNumberPattern = '0.00000000';
@ -259,8 +256,7 @@ abstract class WalletAddressListViewModelBase
double? _fiatRate; double? _fiatRate;
String _rawAmount = ''; String _rawAmount = '';
List<Currency> get currencies => List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
[walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
String get buttonTitle { String get buttonTitle {
if (isElectrumWallet) { if (isElectrumWallet) {
@ -286,8 +282,8 @@ abstract class WalletAddressListViewModelBase
WalletType get type => wallet.type; WalletType get type => wallet.type;
@computed @computed
WalletAddressListItem get address => WalletAddressListItem( WalletAddressListItem get address =>
address: wallet.walletAddresses.address, isPrimary: false); WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
@computed @computed
PaymentURI get uri { PaymentURI get uri {
@ -317,25 +313,23 @@ abstract class WalletAddressListViewModelBase
case WalletType.wownero: case WalletType.wownero:
return WowneroURI(amount: amount, address: address.address); return WowneroURI(amount: amount, address: address.address);
case WalletType.zano: case WalletType.zano:
return ZanoURI(amount: amount, address: address.address); return ZanoURI(amount: amount, address: address.address);
case WalletType.none: case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}'); throw Exception('Unexpected type: ${type.toString()}');
} }
} }
@computed @computed
ObservableList<ListItem> get items => ObservableList<ListItem>() ObservableList<ListItem> get items => ObservableList<ListItem>()
..addAll(_baseItems) ..addAll(_baseItems)
..addAll(addressList); ..addAll(addressList);
@computed ObservableList<ListItem> _computeAddressList() {
ObservableList<ListItem> get addressList {
final addressList = ObservableList<ListItem>(); final addressList = ObservableList<ListItem>();
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
final primaryAddress = final primaryAddress = monero!.getSubaddressList(wallet).subaddresses.first;
monero!.getSubaddressList(wallet).subaddresses.first; final addressItems = monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final addressItems =
monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress; final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem( return WalletAddressListItem(
@ -351,10 +345,8 @@ abstract class WalletAddressListViewModelBase
} }
if (wallet.type == WalletType.wownero) { if (wallet.type == WalletType.wownero) {
final primaryAddress = final primaryAddress = wownero!.getSubaddressList(wallet).subaddresses.first;
wownero!.getSubaddressList(wallet).subaddresses.first; final addressItems = wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final addressItems =
wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress; final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem( return WalletAddressListItem(
@ -367,10 +359,8 @@ abstract class WalletAddressListViewModelBase
} }
if (wallet.type == WalletType.haven) { if (wallet.type == WalletType.haven) {
final primaryAddress = final primaryAddress = haven!.getSubaddressList(wallet).subaddresses.first;
haven!.getSubaddressList(wallet).subaddresses.first; final addressItems = haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final addressItems =
haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress; final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem( return WalletAddressListItem(
@ -384,8 +374,7 @@ abstract class WalletAddressListViewModelBase
if (isElectrumWallet) { if (isElectrumWallet) {
if (bitcoin!.hasSelectedSilentPayments(wallet)) { if (bitcoin!.hasSelectedSilentPayments(wallet)) {
final addressItems = final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final isPrimary = address.id == 0; final isPrimary = address.id == 0;
return WalletAddressListItem( return WalletAddressListItem(
@ -436,8 +425,7 @@ abstract class WalletAddressListViewModelBase
if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) { if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) {
// find the index of the last item with a txCount > 0 // find the index of the last item with a txCount > 0
final addressItemsList = addressItems.toList(); final addressItemsList = addressItems.toList();
int index = addressItemsList int index = addressItemsList.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
if (index == -1) { if (index == -1) {
index = 0; index = 0;
} }
@ -451,22 +439,19 @@ abstract class WalletAddressListViewModelBase
if (wallet.type == WalletType.ethereum) { if (wallet.type == WalletType.ethereum) {
final primaryAddress = ethereum!.getAddress(wallet); final primaryAddress = ethereum!.getAddress(wallet);
addressList.add(WalletAddressListItem( addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
isPrimary: true, name: null, address: primaryAddress));
} }
if (wallet.type == WalletType.polygon) { if (wallet.type == WalletType.polygon) {
final primaryAddress = polygon!.getAddress(wallet); final primaryAddress = polygon!.getAddress(wallet);
addressList.add(WalletAddressListItem( addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
isPrimary: true, name: null, address: primaryAddress));
} }
if (wallet.type == WalletType.solana) { if (wallet.type == WalletType.solana) {
final primaryAddress = solana!.getAddress(wallet); final primaryAddress = solana!.getAddress(wallet);
addressList.add(WalletAddressListItem( addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
isPrimary: true, name: null, address: primaryAddress));
} }
if (wallet.type == WalletType.nano) { if (wallet.type == WalletType.nano) {
@ -480,21 +465,18 @@ abstract class WalletAddressListViewModelBase
if (wallet.type == WalletType.tron) { if (wallet.type == WalletType.tron) {
final primaryAddress = tron!.getAddress(wallet); final primaryAddress = tron!.getAddress(wallet);
addressList.add(WalletAddressListItem( addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
isPrimary: true, name: null, address: primaryAddress));
} }
for (var i = 0; i < addressList.length; i++) { for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue; if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet (addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
.walletAddresses.hiddenAddresses
.contains((addressList[i] as WalletAddressListItem).address); .contains((addressList[i] as WalletAddressListItem).address);
} }
for (var i = 0; i < addressList.length; i++) { for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue; if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isManual = wallet (addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses
.walletAddresses.manualAddresses
.contains((addressList[i] as WalletAddressListItem).address); .contains((addressList[i] as WalletAddressListItem).address);
} }
@ -516,13 +498,28 @@ abstract class WalletAddressListViewModelBase
return addressList; return addressList;
} }
@computed
ObservableList<ListItem> get addressList {
return _computeAddressList();
}
List<ListItem> get forceRecomputeItems {
// necessary because the addressList contains non-observable items
List<ListItem> recomputed = [];
recomputed.addAll(_baseItems);
recomputed.addAll(_computeAddressList());
return recomputed;
}
Future<void> toggleHideAddress(WalletAddressListItem item) async { Future<void> toggleHideAddress(WalletAddressListItem item) async {
if (item.isHidden) { if (item.isHidden) {
wallet.walletAddresses.hiddenAddresses item.isHidden = false;
.removeWhere((element) => element == item.address); wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address);
} else { } else {
item.isHidden = true;
wallet.walletAddresses.hiddenAddresses.add(item.address); wallet.walletAddresses.hiddenAddresses.add(item.address);
} }
// update the address list:
await wallet.walletAddresses.saveAddressesInBox(); await wallet.walletAddresses.saveAddressesInBox();
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
monero! monero!
@ -568,28 +565,22 @@ abstract class WalletAddressListViewModelBase
].contains(wallet.type); ].contains(wallet.type);
@computed @computed
bool get isElectrumWallet => [ bool get isElectrumWallet =>
WalletType.bitcoin, [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type);
WalletType.litecoin,
WalletType.bitcoinCash
].contains(wallet.type);
@computed @computed
bool get isBalanceAvailable => isElectrumWallet; bool get isBalanceAvailable => isElectrumWallet;
@computed @computed
bool get isReceivedAvailable => bool get isReceivedAvailable => [WalletType.monero, WalletType.wownero].contains(wallet.type);
[WalletType.monero, WalletType.wownero].contains(wallet.type);
@computed @computed
bool get isSilentPayments => bool get isSilentPayments =>
wallet.type == WalletType.bitcoin && wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
bitcoin!.hasSelectedSilentPayments(wallet);
@computed @computed
bool get isAutoGenerateSubaddressEnabled => bool get isAutoGenerateSubaddressEnabled =>
_settingsStore.autoGenerateSubaddressStatus != _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
AutoGenerateSubaddressStatus.disabled &&
!isSilentPayments; !isSilentPayments;
@computed @computed
@ -672,8 +663,7 @@ abstract class WalletAddressListViewModelBase
@action @action
void _convertAmountToCrypto() { void _convertAmountToCrypto() {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type); final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
final fiatRate = final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
_fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
if (fiatRate <= 0.0) { if (fiatRate <= 0.0) {
dev.log("Invalid Fiat Rate $fiatRate"); dev.log("Invalid Fiat Rate $fiatRate");