CW-611-Refactor-Address-Handling (#1630)

* subaddress fix

* fix subaddress generation

* rewrite usedAddresses for xmr and wow

* [skip ci] remove print statements

* refactor address handling

* do not remove manual addresses, just mark them

* monero display latest address on receive page when autogenerate is enabled [skip ci]

* WIP subaddresses, hidden addresses, and UI improvements for monero

* update configure script

* fix subaddress generation, display latest address

* Update lib/core/wallet_loading_service.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Exclude manually created addresses

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* don't call .save function multiple times

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* - revert usedAddress functinality
- add mutex to prevent crashes
- fix UI flashing in tx screen
- fixes from comments

* account index fixes
added code to wownero
code comment

* - added subaddress index
- fixed received count also accounting for change (we don't want that)
- fix bad state: no element
- fix search
- fix automatic generation

* prevent crashes by acquiring mutex before setting the pointer

* - fix ttDetails generation in larger/restored wallets
- show manual add icon in monero/wownero even when autogeneration is enabled
- disable colors on non-debug builds
- cache getAddress call in xmr/wow
[skip ci]

* fix: silent payment error in address setter
enable fancy new features only for xmr / wow

* refresh subaddress list, when we add new address
fix manual addresses marking

* add toggle to hide and show address

* update transaction details after restore

* show only one address in address book for xmr, wow and haven

* fix address book
reset address only when autogenerate is on

* enable isEnabledAutoGenerateSubaddress on new wallets

* hide addresses after exchange only for XMR and WOW

* fix: bad-state no element

* Update cw_monero/lib/monero_wallet_addresses.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update cw_monero/lib/monero_wallet_addresses.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* improvements to performance

* 0, 0 -> accountIndex, addressIndex

* make constant variables final

* Update cw_wownero/lib/wownero_wallet_addresses.dart [skip ci]

* Update cw_wownero/lib/wownero_wallet_addresses.dart [skip ci]

* Update cw_monero/lib/monero_wallet.dart [skip ci]

* fix potential exception

* fix after removing late

* remove orElse, replaced it with a try catch block.
fix strings

* fix valid seed function

* fix null check error [skip ci]

* fix updateSubaddressList for wow and haven

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
cyan 2024-09-28 04:38:23 +02:00 committed by GitHub
parent 62e0c2a592
commit f8b0c0ad2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 775 additions and 242 deletions

View file

@ -1772,7 +1772,8 @@ abstract class ElectrumWalletBase
final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
final hiddenAddresses = addressesByType.where((addr) => addr.isHidden == true); final hiddenAddresses = addressesByType.where((addr) => addr.isHidden == true);
final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false); final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false);
walletAddresses.hiddenAddresses.addAll(hiddenAddresses.map((e) => e.address));
await walletAddresses.saveAddressesInBox();
await Future.wait(addressesByType.map((addressRecord) async { await Future.wait(addressesByType.map((addressRecord) async {
final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip()); final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip());

View file

@ -163,6 +163,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override @override
set address(String addr) { set address(String addr) {
if (addr == "Silent Payments" && SilentPaymentsAddresType.p2sp != addressPageType) {
return;
}
if (addressPageType == SilentPaymentsAddresType.p2sp) { if (addressPageType == SilentPaymentsAddresType.p2sp) {
final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr); final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
@ -174,12 +177,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
return; return;
} }
try {
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr); final addressRecord = _addresses.firstWhere(
(addressRecord) => addressRecord.address == addr,
);
previousAddressRecord = addressRecord; previousAddressRecord = addressRecord;
receiveAddresses.remove(addressRecord); receiveAddresses.remove(addressRecord);
receiveAddresses.insert(0, addressRecord); receiveAddresses.insert(0, addressRecord);
} catch (e) {
print("ElectrumWalletAddressBase: set address ($addr): $e");
}
} }
@override @override

View file

@ -1,12 +1,22 @@
class Subaddress { class Subaddress {
Subaddress({required this.id, required this.address, required this.label}); Subaddress({
required this.id,
required this.address,
required this.label,
this.balance = null,
this.txCount = null,
});
Subaddress.fromMap(Map<String, Object?> map) Subaddress.fromMap(Map<String, Object?> map)
: this.id = map['id'] == null ? 0 : int.parse(map['id'] as String), : this.id = map['id'] == null ? 0 : int.parse(map['id'] as String),
this.address = (map['address'] ?? '') as String, this.address = (map['address'] ?? '') as String,
this.label = (map['label'] ?? '') as String; this.label = (map['label'] ?? '') as String,
this.balance = (map['balance'] ?? '') as String?,
this.txCount = (map['txCount'] ?? '') as int?;
final int id; final int id;
final String address; final String address;
final String label; final String label;
final String? balance;
final int? txCount;
} }

View file

@ -1,26 +1,58 @@
import 'package:cw_core/address_info.dart'; import 'package:cw_core/address_info.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletAddresses { abstract class WalletAddresses {
WalletAddresses(this.walletInfo) WalletAddresses(this.walletInfo)
: addressesMap = {}, : addressesMap = {},
allAddressesMap = {}, allAddressesMap = {},
addressInfos = {}; addressInfos = {},
usedAddresses = {},
hiddenAddresses = walletInfo.hiddenAddresses?.toSet() ?? {},
manualAddresses = walletInfo.manualAddresses?.toSet() ?? {};
final WalletInfo walletInfo; final WalletInfo walletInfo;
String get address; String get address;
String get latestAddress {
if (walletInfo.type == WalletType.monero || walletInfo.type == WalletType.wownero) {
if (addressesMap.keys.length == 0) return address;
return addressesMap[addressesMap.keys.last] ?? address;
}
return _localAddress ?? address;
}
String? get primaryAddress => null; String? get primaryAddress => null;
set address(String address); String? _localAddress;
set address(String address) => _localAddress = address;
String get addressForExchange => address;
Map<String, String> addressesMap; Map<String, String> addressesMap;
Map<String, String> allAddressesMap; Map<String, String> allAddressesMap;
Map<String, String> get usableAddressesMap {
final tmp = addressesMap.map((key, value) => MapEntry(key, value)); // copy address map
tmp.removeWhere((key, value) => hiddenAddresses.contains(key) || manualAddresses.contains(key));
return tmp;
}
Map<String, String> get usableAllAddressesMap {
final tmp = allAddressesMap.map((key, value) => MapEntry(key, value)); // copy address map
tmp.removeWhere((key, value) => hiddenAddresses.contains(key) || manualAddresses.contains(key));
return tmp;
}
Map<int, List<AddressInfo>> addressInfos; Map<int, List<AddressInfo>> addressInfos;
Set<String> usedAddresses = {}; Set<String> usedAddresses;
Set<String> hiddenAddresses;
Set<String> manualAddresses;
Future<void> init(); Future<void> init();
@ -32,6 +64,8 @@ abstract class WalletAddresses {
walletInfo.addresses = addressesMap; walletInfo.addresses = addressesMap;
walletInfo.addressInfos = addressInfos; walletInfo.addressInfos = addressInfos;
walletInfo.usedAddresses = usedAddresses.toList(); walletInfo.usedAddresses = usedAddresses.toList();
walletInfo.hiddenAddresses = hiddenAddresses.toList();
walletInfo.manualAddresses = manualAddresses.toList();
if (walletInfo.isInBox) { if (walletInfo.isInBox) {
await walletInfo.save(); await walletInfo.save();

View file

@ -189,6 +189,15 @@ class WalletInfo extends HiveObject {
@HiveField(22) @HiveField(22)
String? parentAddress; String? parentAddress;
@HiveField(23)
List<String>? hiddenAddresses;
@HiveField(24)
List<String>? manualAddresses;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';

View file

@ -1,6 +1,7 @@
import 'package:cw_core/wallet_addresses_with_account.dart'; import 'package:cw_core/wallet_addresses_with_account.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/account.dart'; import 'package:cw_core/account.dart';
import 'package:cw_haven/api/wallet.dart';
import 'package:cw_haven/haven_account_list.dart'; import 'package:cw_haven/haven_account_list.dart';
import 'package:cw_haven/haven_subaddress_list.dart'; import 'package:cw_haven/haven_subaddress_list.dart';
import 'package:cw_core/subaddress.dart'; import 'package:cw_core/subaddress.dart';
@ -36,7 +37,7 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount<Accou
@override @override
Future<void> init() async { Future<void> init() async {
accountList.update(); accountList.update();
account = accountList.accounts.first; account = accountList.accounts.isEmpty ? Account(id: 0, label: "Primary address") : accountList.accounts.first;
updateSubaddressList(accountIndex: account?.id ?? 0); updateSubaddressList(accountIndex: account?.id ?? 0);
await updateAddressesInBox(); await updateAddressesInBox();
} }
@ -81,8 +82,9 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount<Accou
void updateSubaddressList({required int accountIndex}) { void updateSubaddressList({required int accountIndex}) {
subaddressList.update(accountIndex: accountIndex); subaddressList.update(accountIndex: accountIndex);
subaddress = subaddressList.subaddresses.first; address = subaddressList.subaddresses.isNotEmpty
address = subaddress!.address; ? subaddressList.subaddresses.first.address
: getAddress();
} }
@override @override

View file

@ -1,5 +1,6 @@
import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/transaction_history.dart';
import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/api/wallet.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
@ -14,6 +15,10 @@ class SubaddressInfoMetadata {
SubaddressInfoMetadata? subaddress = null; SubaddressInfoMetadata? subaddress = null;
String getRawLabel({required int accountIndex, required int addressIndex}) {
return monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
}
void refreshSubaddresses({required int accountIndex}) { void refreshSubaddresses({required int accountIndex}) {
try { try {
isUpdating = true; isUpdating = true;
@ -29,31 +34,94 @@ class Subaddress {
Subaddress({ Subaddress({
required this.addressIndex, required this.addressIndex,
required this.accountIndex, required this.accountIndex,
required this.received,
required this.txCount,
}); });
String get address => monero.Wallet_address( late String address = getAddress(
wptr!, accountIndex: accountIndex,
accountIndex: accountIndex, addressIndex: addressIndex,
addressIndex: addressIndex, );
);
final int addressIndex; final int addressIndex;
final int accountIndex; final int accountIndex;
String get label => monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); final int received;
final int txCount;
String get label {
final localLabel = monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
if (localLabel.startsWith("#$addressIndex")) return localLabel; // don't duplicate the ID if it was user-providen
return "#$addressIndex ${localLabel}".trim();
}
} }
class TinyTransactionDetails {
TinyTransactionDetails({
required this.address,
required this.amount,
});
final List<String> address;
final int amount;
}
int lastWptr = 0;
int lastTxCount = 0;
List<TinyTransactionDetails> ttDetails = [];
List<Subaddress> getAllSubaddresses() { List<Subaddress> getAllSubaddresses() {
txhistory = monero.Wallet_history(wptr!);
final txCount = monero.TransactionHistory_count(txhistory!);
if (lastTxCount != txCount && lastWptr != wptr!.address) {
final List<TinyTransactionDetails> newttDetails = [];
lastTxCount = txCount;
lastWptr = wptr!.address;
for (var i = 0; i < txCount; i++) {
final tx = monero.TransactionHistory_transaction(txhistory!, index: i);
if (monero.TransactionInfo_direction(tx) == monero.TransactionInfo_Direction.Out) continue;
final subaddrs = monero.TransactionInfo_subaddrIndex(tx).split(",");
final account = monero.TransactionInfo_subaddrAccount(tx);
newttDetails.add(TinyTransactionDetails(
address: List.generate(subaddrs.length, (index) => getAddress(accountIndex: account, addressIndex: int.tryParse(subaddrs[index])??0)),
amount: monero.TransactionInfo_amount(tx),
));
}
ttDetails.clear();
ttDetails.addAll(newttDetails);
}
final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
final list = List.generate(size, (index) { final list = List.generate(size, (index) {
final ttDetailsLocal = ttDetails.where((element) {
final address = getAddress(
accountIndex: subaddress!.accountIndex,
addressIndex: index,
);
if (element.address.contains(address)) return true;
return false;
}).toList();
int received = 0;
for (var i = 0; i < ttDetailsLocal.length; i++) {
received += ttDetailsLocal[i].amount;
}
return Subaddress( return Subaddress(
accountIndex: subaddress!.accountIndex, accountIndex: subaddress!.accountIndex,
addressIndex: index, addressIndex: index,
received: received,
txCount: ttDetailsLocal.length,
); );
}).reversed.toList(); }).reversed.toList();
if (list.length == 0) { if (list.length == 0) {
list.add(Subaddress(addressIndex: subaddress!.accountIndex, accountIndex: 0)); list.add(
Subaddress(
addressIndex: subaddress!.accountIndex,
accountIndex: 0,
received: 0,
txCount: 0,
));
} }
return list; return list;
} }
int numSubaddresses(int subaccountIndex) {
return monero.Wallet_numSubaddresses(wptr!, accountIndex: subaccountIndex);
}
void addSubaddressSync({required int accountIndex, required String label}) { void addSubaddressSync({required int accountIndex, required String label}) {
monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label); monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label);
refreshSubaddresses(accountIndex: accountIndex); refreshSubaddresses(accountIndex: accountIndex);

View file

@ -5,32 +5,42 @@ import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart';
import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/monero_output.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen; import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen;
import 'package:mutex/mutex.dart';
String getTxKey(String txId) { String getTxKey(String txId) {
return monero.Wallet_getTxKey(wptr!, txid: txId); return monero.Wallet_getTxKey(wptr!, txid: txId);
} }
final txHistoryMutex = Mutex();
monero.TransactionHistory? txhistory; monero.TransactionHistory? txhistory;
bool isRefreshingTx = false;
void refreshTransactions() { Future<void> refreshTransactions() async {
if (isRefreshingTx == true) return;
isRefreshingTx = true;
txhistory ??= monero.Wallet_history(wptr!); txhistory ??= monero.Wallet_history(wptr!);
monero.TransactionHistory_refresh(txhistory!); final ptr = txhistory!.address;
await txHistoryMutex.acquire();
await Isolate.run(() {
monero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
});
txHistoryMutex.release();
isRefreshingTx = false;
} }
int countOfTransactions() => monero.TransactionHistory_count(txhistory!); int countOfTransactions() => monero.TransactionHistory_count(txhistory!);
List<Transaction> getAllTransactions() { Future<List<Transaction>> getAllTransactions() async {
List<Transaction> dummyTxs = []; List<Transaction> dummyTxs = [];
await txHistoryMutex.acquire();
txhistory ??= monero.Wallet_history(wptr!); txhistory ??= monero.Wallet_history(wptr!);
monero.TransactionHistory_refresh(txhistory!);
int size = countOfTransactions(); int size = countOfTransactions();
final list = List.generate(size, (index) => Transaction(txInfo: monero.TransactionHistory_transaction(txhistory!, index: index))); final list = List.generate(size, (index) => Transaction(txInfo: monero.TransactionHistory_transaction(txhistory!, index: index)));
txHistoryMutex.release();
final accts = monero.Wallet_numSubaddressAccounts(wptr!); final accts = monero.Wallet_numSubaddressAccounts(wptr!);
for (var i = 0; i < accts; i++) { for (var i = 0; i < accts; i++) {
final fullBalance = monero.Wallet_balance(wptr!, accountIndex: i); final fullBalance = monero.Wallet_balance(wptr!, accountIndex: i);
@ -45,6 +55,8 @@ List<Transaction> getAllTransactions() {
confirmations: 0, confirmations: 0,
blockheight: 0, blockheight: 0,
accountIndex: i, accountIndex: i,
addressIndex: 0,
addressIndexList: [0],
paymentId: "", paymentId: "",
amount: fullBalance - availBalance, amount: fullBalance - availBalance,
isSpend: false, isSpend: false,
@ -251,19 +263,28 @@ Future<PendingTransactionDescription> createTransactionMultDest(
class Transaction { class Transaction {
final String displayLabel; final String displayLabel;
String subaddressLabel = monero.Wallet_getSubaddressLabel(wptr!, accountIndex: 0, addressIndex: 0); late final String subaddressLabel = monero.Wallet_getSubaddressLabel(
late final String address = monero.Wallet_address(
wptr!, wptr!,
accountIndex: 0, accountIndex: accountIndex,
addressIndex: 0, addressIndex: addressIndex,
); );
late final String address = getAddress(
accountIndex: accountIndex,
addressIndex: addressIndex,
);
late final List<String> addressList = List.generate(addressIndexList.length, (index) =>
getAddress(
accountIndex: accountIndex,
addressIndex: addressIndexList[index],
));
final String description; final String description;
final int fee; final int fee;
final int confirmations; final int confirmations;
late final bool isPending = confirmations < 10; late final bool isPending = confirmations < 10;
final int blockheight; final int blockheight;
final int addressIndex = 0; final int addressIndex;
final int accountIndex; final int accountIndex;
final List<int> addressIndexList;
final String paymentId; final String paymentId;
final int amount; final int amount;
final bool isSpend; final bool isSpend;
@ -309,6 +330,8 @@ class Transaction {
amount = monero.TransactionInfo_amount(txInfo), amount = monero.TransactionInfo_amount(txInfo),
paymentId = monero.TransactionInfo_paymentId(txInfo), paymentId = monero.TransactionInfo_paymentId(txInfo),
accountIndex = monero.TransactionInfo_subaddrAccount(txInfo), accountIndex = monero.TransactionInfo_subaddrAccount(txInfo),
addressIndex = int.tryParse(monero.TransactionInfo_subaddrIndex(txInfo).split(", ")[0]) ?? 0,
addressIndexList = monero.TransactionInfo_subaddrIndex(txInfo).split(", ").map((e) => int.tryParse(e) ?? 0).toList(),
blockheight = monero.TransactionInfo_blockHeight(txInfo), blockheight = monero.TransactionInfo_blockHeight(txInfo),
confirmations = monero.TransactionInfo_confirmations(txInfo), confirmations = monero.TransactionInfo_confirmations(txInfo),
fee = monero.TransactionInfo_fee(txInfo), fee = monero.TransactionInfo_fee(txInfo),
@ -331,6 +354,8 @@ class Transaction {
required this.confirmations, required this.confirmations,
required this.blockheight, required this.blockheight,
required this.accountIndex, required this.accountIndex,
required this.addressIndexList,
required this.addressIndex,
required this.paymentId, required this.paymentId,
required this.amount, required this.amount,
required this.isSpend, required this.isSpend,

View file

@ -66,9 +66,20 @@ String getSeedLegacy(String? language) {
return legacy; return legacy;
} }
String getAddress({int accountIndex = 0, int addressIndex = 0}) => Map<int, Map<int, Map<int, String>>> addressCache = {};
monero.Wallet_address(wptr!,
String getAddress({int accountIndex = 0, int addressIndex = 0}) {
// print("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}");
while (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
print("adding subaddress");
monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
}
addressCache[wptr!.address] ??= {};
addressCache[wptr!.address]![accountIndex] ??= {};
addressCache[wptr!.address]![accountIndex]![addressIndex] ??= monero.Wallet_address(wptr!,
accountIndex: accountIndex, addressIndex: addressIndex); accountIndex: accountIndex, addressIndex: addressIndex);
return addressCache[wptr!.address]![accountIndex]![addressIndex]!;
}
int getFullBalance({int accountIndex = 0}) => int getFullBalance({int accountIndex = 0}) =>
monero.Wallet_balance(wptr!, accountIndex: accountIndex); monero.Wallet_balance(wptr!, accountIndex: accountIndex);

View file

@ -1,6 +1,7 @@
import 'package:cw_core/subaddress.dart'; import 'package:cw_core/subaddress.dart';
import 'package:cw_monero/api/coins_info.dart'; import 'package:cw_monero/api/coins_info.dart';
import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list; import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list;
import 'package:cw_monero/api/wallet.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -54,18 +55,12 @@ abstract class MoneroSubaddressListBase with Store {
final address = s.address; final address = s.address;
final label = s.label; final label = s.label;
final id = s.addressIndex; final id = s.addressIndex;
final hasDefaultAddressName =
label.toLowerCase() == 'Primary account'.toLowerCase() ||
label.toLowerCase() == 'Untitled account'.toLowerCase();
final isPrimaryAddress = id == 0 && hasDefaultAddressName;
return Subaddress( return Subaddress(
id: id, id: id,
address: address, address: address,
label: isPrimaryAddress balance: (s.received/1e12).toStringAsFixed(6),
? 'Primary address' txCount: s.txCount,
: hasDefaultAddressName label: label);
? ''
: label);
}).toList(); }).toList();
} }
@ -103,6 +98,9 @@ abstract class MoneroSubaddressListBase with Store {
required List<String> usedAddresses, required List<String> usedAddresses,
}) async { }) async {
_usedAddresses.addAll(usedAddresses); _usedAddresses.addAll(usedAddresses);
final _all = _usedAddresses.toSet().toList();
_usedAddresses.clear();
_usedAddresses.addAll(_all);
if (_isUpdating) { if (_isUpdating) {
return; return;
} }
@ -124,7 +122,8 @@ abstract class MoneroSubaddressListBase with Store {
Future<List<Subaddress>> _getAllUnusedAddresses( Future<List<Subaddress>> _getAllUnusedAddresses(
{required int accountIndex, required String label}) async { {required int accountIndex, required String label}) async {
final allAddresses = subaddress_list.getAllSubaddresses(); final allAddresses = subaddress_list.getAllSubaddresses();
if (allAddresses.isEmpty || _usedAddresses.contains(allAddresses.last)) { // first because addresses come in reversed order.
if (allAddresses.isEmpty || _usedAddresses.contains(allAddresses.first.address)) {
final isAddressUnused = await _newSubaddress(accountIndex: accountIndex, label: label); final isAddressUnused = await _newSubaddress(accountIndex: accountIndex, label: label);
if (!isAddressUnused) { if (!isAddressUnused) {
return await _getAllUnusedAddresses(accountIndex: accountIndex, label: label); return await _getAllUnusedAddresses(accountIndex: accountIndex, label: label);
@ -139,12 +138,13 @@ abstract class MoneroSubaddressListBase with Store {
return Subaddress( return Subaddress(
id: id, id: id,
address: address, address: address,
balance: (s.received/1e12).toStringAsFixed(6),
txCount: s.txCount,
label: id == 0 && label: id == 0 &&
label.toLowerCase() == 'Primary account'.toLowerCase() label.toLowerCase() == 'Primary account'.toLowerCase()
? 'Primary address' ? 'Primary address'
: label); : label);
}) }).toList().reversed.toList();
.toList();
} }
Future<bool> _newSubaddress({required int accountIndex, required String label}) async { Future<bool> _newSubaddress({required int accountIndex, required String label}) async {

View file

@ -59,7 +59,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}), }),
_isTransactionUpdating = false, _isTransactionUpdating = false,
_hasSyncAfterStartup = false, _hasSyncAfterStartup = false,
isEnabledAutoGenerateSubaddress = false, isEnabledAutoGenerateSubaddress = true,
_password = password, _password = password,
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
unspentCoins = [], unspentCoins = [],
@ -86,6 +86,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) { reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
_updateSubAddress(enabled, account: walletAddresses.account); _updateSubAddress(enabled, account: walletAddresses.account);
}); });
_onTxHistoryChangeReaction = reaction((_) => transactionHistory, (__) {
_updateSubAddress(isEnabledAutoGenerateSubaddress, account: walletAddresses.account);
});
} }
static const int _autoSaveInterval = 30; static const int _autoSaveInterval = 30;
@ -128,6 +131,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
monero_wallet.SyncListener? _listener; monero_wallet.SyncListener? _listener;
ReactionDisposer? _onAccountChangeReaction; ReactionDisposer? _onAccountChangeReaction;
ReactionDisposer? _onTxHistoryChangeReaction;
bool _isTransactionUpdating; bool _isTransactionUpdating;
bool _hasSyncAfterStartup; bool _hasSyncAfterStartup;
Timer? _autoSaveTimer; Timer? _autoSaveTimer;
@ -158,6 +162,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
_autoSaveTimer = Timer.periodic( _autoSaveTimer = Timer.periodic(
Duration(seconds: _autoSaveInterval), (_) async => await save()); Duration(seconds: _autoSaveInterval), (_) async => await save());
// update transaction details after restore
walletAddresses.subaddressList.update(accountIndex: walletAddresses.account?.id??0);
} }
@override @override
@ -167,6 +173,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
void close() async { void close() async {
_listener?.stop(); _listener?.stop();
_onAccountChangeReaction?.reaction.dispose(); _onAccountChangeReaction?.reaction.dispose();
_onTxHistoryChangeReaction?.reaction.dispose();
_autoSaveTimer?.cancel(); _autoSaveTimer?.cancel();
} }
@ -578,7 +585,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
@override @override
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async { Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
transaction_history.refreshTransactions(); transaction_history.refreshTransactions();
return _getAllTransactionsOfAccount(walletAddresses.account?.id) return (await _getAllTransactionsOfAccount(walletAddresses.account?.id))
.fold<Map<String, MoneroTransactionInfo>>( .fold<Map<String, MoneroTransactionInfo>>(
<String, MoneroTransactionInfo>{}, <String, MoneroTransactionInfo>{},
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) { (Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
@ -594,8 +601,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
} }
_isTransactionUpdating = true; _isTransactionUpdating = true;
transactionHistory.clear();
final transactions = await fetchTransactions(); final transactions = await fetchTransactions();
transactionHistory.clear();
transactionHistory.addMany(transactions); transactionHistory.addMany(transactions);
await transactionHistory.save(); await transactionHistory.save();
_isTransactionUpdating = false; _isTransactionUpdating = false;
@ -608,9 +615,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
String getSubaddressLabel(int accountIndex, int addressIndex) => String getSubaddressLabel(int accountIndex, int addressIndex) =>
monero_wallet.getSubaddressLabel(accountIndex, addressIndex); monero_wallet.getSubaddressLabel(accountIndex, addressIndex);
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) => Future<List<MoneroTransactionInfo>> _getAllTransactionsOfAccount(int? accountIndex) async =>
transaction_history (await transaction_history
.getAllTransactions() .getAllTransactions())
.map( .map(
(row) => MoneroTransactionInfo( (row) => MoneroTransactionInfo(
row.hash, row.hash,

View file

@ -3,6 +3,8 @@ import 'package:cw_core/address_info.dart';
import 'package:cw_core/subaddress.dart'; import 'package:cw_core/subaddress.dart';
import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list;
import 'package:cw_monero/api/transaction_history.dart';
import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/monero_account_list.dart'; import 'package:cw_monero/monero_account_list.dart';
import 'package:cw_monero/monero_subaddress_list.dart'; import 'package:cw_monero/monero_subaddress_list.dart';
@ -27,6 +29,30 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
String address; String address;
@override
String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
while (hiddenAddresses.contains(address)) {
addressIndex++;
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
subaddressList.update(accountIndex: account?.id??0);
}
return address;
}
@override
String get addressForExchange {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
while (hiddenAddresses.contains(address) || manualAddresses.contains(address) || subaddress_list.getRawLabel(accountIndex: account?.id??0, addressIndex: addressIndex).isNotEmpty) {
addressIndex++;
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
subaddressList.update(accountIndex: account?.id??0);
}
return address;
}
@observable @observable
Account? account; Account? account;
@ -37,10 +63,12 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
MoneroAccountList accountList; MoneroAccountList accountList;
Set<String> usedAddresses = Set();
@override @override
Future<void> init() async { Future<void> init() async {
accountList.update(); accountList.update();
account = accountList.accounts.first; account = accountList.accounts.isEmpty ? Account(id: 0, label: "Primary address") : accountList.accounts.first;
updateSubaddressList(accountIndex: account?.id ?? 0); updateSubaddressList(accountIndex: account?.id ?? 0);
await updateAddressesInBox(); await updateAddressesInBox();
} }
@ -89,8 +117,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
void updateSubaddressList({required int accountIndex}) { void updateSubaddressList({required int accountIndex}) {
subaddressList.update(accountIndex: accountIndex); subaddressList.update(accountIndex: accountIndex);
subaddress = subaddressList.subaddresses.first; address = subaddressList.subaddresses.isNotEmpty
address = subaddress!.address; ? subaddressList.subaddresses.first.address
: getAddress();
} }
Future<void> updateUsedSubaddress() async { Future<void> updateUsedSubaddress() async {
@ -109,7 +138,10 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
accountIndex: accountIndex, accountIndex: accountIndex,
defaultLabel: defaultLabel, defaultLabel: defaultLabel,
usedAddresses: usedAddresses.toList()); usedAddresses: usedAddresses.toList());
subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last; subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel, balance: '0', txCount: 0) : subaddressList.subaddresses.last;
if (num.tryParse(subaddress!.balance??'0') != 0) {
getAddress(accountIndex: accountIndex, addressIndex: (subaddress?.id??0)+1);
}
address = subaddress!.address; address = subaddress!.address;
} }

View file

@ -1,4 +1,5 @@
import 'package:cw_wownero/api/account_list.dart'; import 'package:cw_wownero/api/account_list.dart';
import 'package:cw_wownero/api/transaction_history.dart';
import 'package:cw_wownero/api/wallet.dart'; import 'package:cw_wownero/api/wallet.dart';
import 'package:monero/wownero.dart' as wownero; import 'package:monero/wownero.dart' as wownero;
@ -28,27 +29,75 @@ class Subaddress {
Subaddress({ Subaddress({
required this.addressIndex, required this.addressIndex,
required this.accountIndex, required this.accountIndex,
required this.txCount,
required this.received,
}); });
String get address => wownero.Wallet_address( late String address = getAddress(
wptr!, accountIndex: accountIndex,
accountIndex: accountIndex, addressIndex: addressIndex,
addressIndex: addressIndex, );
);
final int addressIndex; final int addressIndex;
final int accountIndex; final int accountIndex;
String get label => wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); String get label => wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
final int txCount;
final int received;
} }
class TinyTransactionDetails {
TinyTransactionDetails({
required this.address,
required this.amount,
});
final List<String> address;
final int amount;
}
int lastWptr = 0;
int lastTxCount = 0;
List<TinyTransactionDetails> ttDetails = [];
List<Subaddress> getAllSubaddresses() { List<Subaddress> getAllSubaddresses() {
txhistory = wownero.Wallet_history(wptr!);
final txCount = wownero.TransactionHistory_count(txhistory!);
if (lastTxCount != txCount && lastWptr != wptr!.address) {
final List<TinyTransactionDetails> newttDetails = [];
lastTxCount = txCount;
lastWptr = wptr!.address;
for (var i = 0; i < txCount; i++) {
final tx = wownero.TransactionHistory_transaction(txhistory!, index: i);
final subaddrs = wownero.TransactionInfo_subaddrIndex(tx).split(",");
final account = wownero.TransactionInfo_subaddrAccount(tx);
newttDetails.add(TinyTransactionDetails(
address: List.generate(subaddrs.length, (index) => getAddress(accountIndex: account, addressIndex: int.tryParse(subaddrs[index])??0)),
amount: wownero.TransactionInfo_amount(tx),
));
}
ttDetails.clear();
ttDetails.addAll(newttDetails);
}
final size = wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex); final size = wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
final list = List.generate(size, (index) { final list = List.generate(size, (index) {
final ttDetailsLocal = ttDetails.where((element) {
final address = getAddress(
accountIndex: subaddress!.accountIndex,
addressIndex: index,
);
if (address == element.address) return true;
return false;
}).toList();
int received = 0;
for (var i = 0; i < ttDetailsLocal.length; i++) {
received += ttDetailsLocal[i].amount;
}
return Subaddress( return Subaddress(
accountIndex: subaddress!.accountIndex, accountIndex: subaddress!.accountIndex,
addressIndex: index, addressIndex: index,
received: received,
txCount: ttDetailsLocal.length,
); );
}).reversed.toList(); }).reversed.toList();
if (list.isEmpty) { if (list.isEmpty) {
list.add(Subaddress(addressIndex: 0, accountIndex: subaddress!.accountIndex)); list.add(Subaddress(addressIndex: 0, accountIndex: subaddress!.accountIndex, txCount: 0, received: 0));
} }
return list; return list;
} }
@ -58,6 +107,10 @@ void addSubaddressSync({required int accountIndex, required String label}) {
refreshSubaddresses(accountIndex: accountIndex); refreshSubaddresses(accountIndex: accountIndex);
} }
int numSubaddresses(int subaccountIndex) {
return wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaccountIndex);
}
void setLabelForSubaddressSync( void setLabelForSubaddressSync(
{required int accountIndex, required int addressIndex, required String label}) { {required int accountIndex, required int addressIndex, required String label}) {
wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex, label: label); wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex, label: label);

View file

@ -3,6 +3,7 @@ import 'dart:isolate';
import 'package:cw_wownero/api/account_list.dart'; import 'package:cw_wownero/api/account_list.dart';
import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart';
import 'package:cw_wownero/api/wallet.dart';
import 'package:cw_wownero/api/wownero_output.dart'; import 'package:cw_wownero/api/wownero_output.dart';
import 'package:cw_wownero/api/structs/pending_transaction.dart'; import 'package:cw_wownero/api/structs/pending_transaction.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
@ -16,9 +17,16 @@ String getTxKey(String txId) {
wownero.TransactionHistory? txhistory; wownero.TransactionHistory? txhistory;
void refreshTransactions() { bool isRefreshingTx = false;
Future<void> refreshTransactions() async {
if (isRefreshingTx == true) return;
isRefreshingTx = true;
txhistory ??= wownero.Wallet_history(wptr!); txhistory ??= wownero.Wallet_history(wptr!);
wownero.TransactionHistory_refresh(txhistory!); final ptr = txhistory!.address;
await Isolate.run(() {
wownero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
});
isRefreshingTx = false;
} }
int countOfTransactions() => wownero.TransactionHistory_count(txhistory!); int countOfTransactions() => wownero.TransactionHistory_count(txhistory!);
@ -45,6 +53,8 @@ List<Transaction> getAllTransactions() {
confirmations: 0, confirmations: 0,
blockheight: 0, blockheight: 0,
accountIndex: i, accountIndex: i,
addressIndex: 0,
addressIndexList: [0],
paymentId: "", paymentId: "",
amount: fullBalance - availBalance, amount: fullBalance - availBalance,
isSpend: false, isSpend: false,
@ -243,23 +253,28 @@ Future<PendingTransactionDescription> createTransactionMultDest(
class Transaction { class Transaction {
final String displayLabel; final String displayLabel;
String subaddressLabel = wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: 0, addressIndex: 0); late final String subaddressLabel = wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
late final String address = wownero.Wallet_address( late final String address = getAddress(
wptr!, accountIndex: accountIndex,
accountIndex: 0, addressIndex: addressIndex,
addressIndex: 0,
); );
late final List<String> addressList = List.generate(addressIndexList.length, (index) =>
getAddress(
accountIndex: accountIndex,
addressIndex: addressIndexList[index],
));
final String description; final String description;
final int fee; final int fee;
final int confirmations; final int confirmations;
late final bool isPending = confirmations < 3; late final bool isPending = confirmations < 3;
final int blockheight; final int blockheight;
final int addressIndex = 0; final int addressIndex;
final int accountIndex; final int accountIndex;
final List<int> addressIndexList;
final String paymentId; final String paymentId;
final int amount; final int amount;
final bool isSpend; final bool isSpend;
late DateTime timeStamp; late final DateTime timeStamp;
late final bool isConfirmed = !isPending; late final bool isConfirmed = !isPending;
final String hash; final String hash;
final String key; final String key;
@ -301,6 +316,8 @@ class Transaction {
amount = wownero.TransactionInfo_amount(txInfo), amount = wownero.TransactionInfo_amount(txInfo),
paymentId = wownero.TransactionInfo_paymentId(txInfo), paymentId = wownero.TransactionInfo_paymentId(txInfo),
accountIndex = wownero.TransactionInfo_subaddrAccount(txInfo), accountIndex = wownero.TransactionInfo_subaddrAccount(txInfo),
addressIndex = int.tryParse(wownero.TransactionInfo_subaddrIndex(txInfo).split(", ")[0]) ?? 0,
addressIndexList = wownero.TransactionInfo_subaddrIndex(txInfo).split(", ").map((e) => int.tryParse(e) ?? 0).toList(),
blockheight = wownero.TransactionInfo_blockHeight(txInfo), blockheight = wownero.TransactionInfo_blockHeight(txInfo),
confirmations = wownero.TransactionInfo_confirmations(txInfo), confirmations = wownero.TransactionInfo_confirmations(txInfo),
fee = wownero.TransactionInfo_fee(txInfo), fee = wownero.TransactionInfo_fee(txInfo),
@ -314,6 +331,8 @@ class Transaction {
required this.confirmations, required this.confirmations,
required this.blockheight, required this.blockheight,
required this.accountIndex, required this.accountIndex,
required this.addressIndex,
required this.addressIndexList,
required this.paymentId, required this.paymentId,
required this.amount, required this.amount,
required this.isSpend, required this.isSpend,

View file

@ -67,10 +67,19 @@ String getSeedLegacy(String? language) {
} }
return legacy; return legacy;
} }
Map<int, Map<int, Map<int, String>>> addressCache = {};
String getAddress({int accountIndex = 0, int addressIndex = 1}) => String getAddress({int accountIndex = 0, int addressIndex = 1}) {
wownero.Wallet_address(wptr!, while (wownero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
print("adding subaddress");
wownero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
}
addressCache[wptr!.address] ??= {};
addressCache[wptr!.address]![accountIndex] ??= {};
addressCache[wptr!.address]![accountIndex]![addressIndex] ??= wownero.Wallet_address(wptr!,
accountIndex: accountIndex, addressIndex: addressIndex); accountIndex: accountIndex, addressIndex: addressIndex);
return addressCache[wptr!.address]![accountIndex]![addressIndex]!;
}
int getFullBalance({int accountIndex = 0}) => int getFullBalance({int accountIndex = 0}) =>
wownero.Wallet_balance(wptr!, accountIndex: accountIndex); wownero.Wallet_balance(wptr!, accountIndex: accountIndex);

View file

@ -1,6 +1,7 @@
import 'package:cw_core/subaddress.dart'; import 'package:cw_core/subaddress.dart';
import 'package:cw_wownero/api/coins_info.dart'; import 'package:cw_wownero/api/coins_info.dart';
import 'package:cw_wownero/api/subaddress_list.dart' as subaddress_list; import 'package:cw_wownero/api/subaddress_list.dart' as subaddress_list;
import 'package:cw_wownero/api/wallet.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -61,6 +62,8 @@ abstract class WowneroSubaddressListBase with Store {
return Subaddress( return Subaddress(
id: id, id: id,
address: address, address: address,
balance: (s.received/1e12).toStringAsFixed(6),
txCount: s.txCount,
label: isPrimaryAddress label: isPrimaryAddress
? 'Primary address' ? 'Primary address'
: hasDefaultAddressName : hasDefaultAddressName
@ -103,6 +106,9 @@ abstract class WowneroSubaddressListBase with Store {
required List<String> usedAddresses, required List<String> usedAddresses,
}) async { }) async {
_usedAddresses.addAll(usedAddresses); _usedAddresses.addAll(usedAddresses);
final _all = _usedAddresses.toSet().toList();
_usedAddresses.clear();
_usedAddresses.addAll(_all);
if (_isUpdating) { if (_isUpdating) {
return; return;
} }
@ -140,6 +146,8 @@ abstract class WowneroSubaddressListBase with Store {
return Subaddress( return Subaddress(
id: id, id: id,
address: address, address: address,
balance: (s.received/1e12).toStringAsFixed(6),
txCount: s.txCount,
label: id == 0 && label: id == 0 &&
label.toLowerCase() == 'Primary account'.toLowerCase() label.toLowerCase() == 'Primary account'.toLowerCase()
? 'Primary address' ? 'Primary address'

View file

@ -59,7 +59,7 @@ abstract class WowneroWalletBase
_isTransactionUpdating = false, _isTransactionUpdating = false,
_hasSyncAfterStartup = false, _hasSyncAfterStartup = false,
_password = password, _password = password,
isEnabledAutoGenerateSubaddress = false, isEnabledAutoGenerateSubaddress = true,
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
unspentCoins = [], unspentCoins = [],
this.unspentCoinsInfo = unspentCoinsInfo, this.unspentCoinsInfo = unspentCoinsInfo,
@ -82,6 +82,10 @@ abstract class WowneroWalletBase
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) { reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
_updateSubAddress(enabled, account: walletAddresses.account); _updateSubAddress(enabled, account: walletAddresses.account);
}); });
_onTxHistoryChangeReaction = reaction((_) => transactionHistory, (__) {
_updateSubAddress(isEnabledAutoGenerateSubaddress, account: walletAddresses.account);
});
} }
static const int _autoSaveInterval = 30; static const int _autoSaveInterval = 30;
@ -123,6 +127,7 @@ abstract class WowneroWalletBase
wownero_wallet.SyncListener? _listener; wownero_wallet.SyncListener? _listener;
ReactionDisposer? _onAccountChangeReaction; ReactionDisposer? _onAccountChangeReaction;
ReactionDisposer? _onTxHistoryChangeReaction;
bool _isTransactionUpdating; bool _isTransactionUpdating;
bool _hasSyncAfterStartup; bool _hasSyncAfterStartup;
Timer? _autoSaveTimer; Timer? _autoSaveTimer;
@ -158,6 +163,7 @@ abstract class WowneroWalletBase
void close() async { void close() async {
_listener?.stop(); _listener?.stop();
_onAccountChangeReaction?.reaction.dispose(); _onAccountChangeReaction?.reaction.dispose();
_onTxHistoryChangeReaction?.reaction.dispose();
_autoSaveTimer?.cancel(); _autoSaveTimer?.cancel();
} }
@ -564,8 +570,8 @@ abstract class WowneroWalletBase
} }
_isTransactionUpdating = true; _isTransactionUpdating = true;
transactionHistory.clear();
final transactions = await fetchTransactions(); final transactions = await fetchTransactions();
transactionHistory.clear();
transactionHistory.addMany(transactions); transactionHistory.addMany(transactions);
await transactionHistory.save(); await transactionHistory.save();
_isTransactionUpdating = false; _isTransactionUpdating = false;

View file

@ -3,6 +3,8 @@ import 'package:cw_core/address_info.dart';
import 'package:cw_core/subaddress.dart'; import 'package:cw_core/subaddress.dart';
import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_wownero/api/transaction_history.dart';
import 'package:cw_wownero/api/subaddress_list.dart' as subaddress_list;
import 'package:cw_wownero/api/wallet.dart'; import 'package:cw_wownero/api/wallet.dart';
import 'package:cw_wownero/wownero_account_list.dart'; import 'package:cw_wownero/wownero_account_list.dart';
import 'package:cw_wownero/wownero_subaddress_list.dart'; import 'package:cw_wownero/wownero_subaddress_list.dart';
@ -27,6 +29,27 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
String address; String address;
@override
String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
while (hiddenAddresses.contains(address)) {
addressIndex++;
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
}
return address;
}
@override
String get addressForExchange {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
while (hiddenAddresses.contains(address) || manualAddresses.contains(address)) {
addressIndex++;
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
}
return address;
}
@observable @observable
Account? account; Account? account;
@ -36,11 +59,14 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
WowneroSubaddressList subaddressList; WowneroSubaddressList subaddressList;
WowneroAccountList accountList; WowneroAccountList accountList;
@override
Set<String> usedAddresses = Set();
@override @override
Future<void> init() async { Future<void> init() async {
accountList.update(); accountList.update();
account = accountList.accounts.first; account = accountList.accounts.isEmpty ? Account(id: 0, label: "Primary address") : accountList.accounts.first;
updateSubaddressList(accountIndex: account?.id ?? 0); updateSubaddressList(accountIndex: account?.id ?? 0);
await updateAddressesInBox(); await updateAddressesInBox();
} }
@ -89,8 +115,9 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
void updateSubaddressList({required int accountIndex}) { void updateSubaddressList({required int accountIndex}) {
subaddressList.update(accountIndex: accountIndex); subaddressList.update(accountIndex: accountIndex);
subaddress = subaddressList.subaddresses.first; address = subaddressList.subaddresses.isNotEmpty
address = subaddress!.address; ? subaddressList.subaddresses.first.address
: getAddress();
} }
Future<void> updateUsedSubaddress() async { Future<void> updateUsedSubaddress() async {
@ -109,7 +136,7 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
accountIndex: accountIndex, accountIndex: accountIndex,
defaultLabel: defaultLabel, defaultLabel: defaultLabel,
usedAddresses: usedAddresses.toList()); usedAddresses: usedAddresses.toList());
subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last; subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel, balance: '0', txCount: 0) : subaddressList.subaddresses.last;
address = subaddress!.address; address = subaddress!.address;
} }

View file

@ -61,7 +61,13 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
ObservableList<Subaddress> get subaddresses { ObservableList<Subaddress> get subaddresses {
final moneroWallet = _wallet as MoneroWallet; final moneroWallet = _wallet as MoneroWallet;
final subAddresses = moneroWallet.walletAddresses.subaddressList.subaddresses final subAddresses = moneroWallet.walletAddresses.subaddressList.subaddresses
.map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label)) .map((sub) => Subaddress(
id: sub.id,
address: sub.address,
label: sub.label,
received: sub.balance??"unknown",
txCount: sub.txCount??0,
))
.toList(); .toList();
return ObservableList<Subaddress>.of(subAddresses); return ObservableList<Subaddress>.of(subAddresses);
} }
@ -83,7 +89,12 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
final moneroWallet = wallet as MoneroWallet; final moneroWallet = wallet as MoneroWallet;
return moneroWallet.walletAddresses.subaddressList return moneroWallet.walletAddresses.subaddressList
.getAll() .getAll()
.map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address)) .map((sub) => Subaddress(
id: sub.id,
label: sub.label,
address: sub.address,
txCount: sub.txCount??0,
received: sub.balance??'unknown'))
.toList(); .toList();
} }
@ -91,7 +102,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
Future<void> addSubaddress(Object wallet, Future<void> addSubaddress(Object wallet,
{required int accountIndex, required String label}) async { {required int accountIndex, required String label}) async {
final moneroWallet = wallet as MoneroWallet; final moneroWallet = wallet as MoneroWallet;
await moneroWallet.walletAddresses.subaddressList return await moneroWallet.walletAddresses.subaddressList
.addSubaddress(accountIndex: accountIndex, label: label); .addSubaddress(accountIndex: accountIndex, label: label);
} }

View file

@ -155,13 +155,14 @@ class AddressPage extends BasePage {
amountTextFieldFocusNode: _cryptoAmountFocus, amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController, amountController: _amountController,
isLight: dashboardViewModel.settingsStore.currentTheme.type == isLight: dashboardViewModel.settingsStore.currentTheme.type ==
ThemeType.light))), ThemeType.light,
))),
SizedBox(height: 16), SizedBox(height: 16),
Observer(builder: (_) { Observer(builder: (_) {
if (addressListViewModel.hasAddressList) { if (addressListViewModel.hasAddressList) {
return SelectButton( return SelectButton(
text: addressListViewModel.buttonTitle, text: addressListViewModel.buttonTitle,
onTap: () async => Navigator.of(context).pushNamed(Routes.receive), onTap: () => Navigator.of(context).pushNamed(Routes.receive),
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor, textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor, borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,

View file

@ -509,7 +509,7 @@ class ExchangePage extends BasePage {
} }
}); });
reaction((_) => exchangeViewModel.wallet.walletAddresses.address, (String address) { reaction((_) => exchangeViewModel.wallet.walletAddresses.addressForExchange, (String address) {
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) { if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
depositKey.currentState!.changeAddress(address: address); depositKey.currentState!.changeAddress(address: address);
} }
@ -565,7 +565,7 @@ class ExchangePage extends BasePage {
key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : ''); key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
key.currentState!.changeAddress( key.currentState!.changeAddress(
address: isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.address : ''); address: isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.addressForExchange : '');
key.currentState!.changeAmount(amount: ''); key.currentState!.changeAmount(amount: '');
} }
@ -576,9 +576,9 @@ class ExchangePage extends BasePage {
if (isCurrentTypeWallet) { if (isCurrentTypeWallet) {
key.currentState!.changeWalletName(exchangeViewModel.wallet.name); key.currentState!.changeWalletName(exchangeViewModel.wallet.name);
key.currentState!.addressController.text = exchangeViewModel.wallet.walletAddresses.address; key.currentState!.addressController.text = exchangeViewModel.wallet.walletAddresses.addressForExchange;
} else if (key.currentState!.addressController.text == } else if (key.currentState!.addressController.text ==
exchangeViewModel.wallet.walletAddresses.address) { exchangeViewModel.wallet.walletAddresses.addressForExchange) {
key.currentState!.changeWalletName(''); key.currentState!.changeWalletName('');
key.currentState!.addressController.text = ''; key.currentState!.addressController.text = '';
} }
@ -629,7 +629,7 @@ class ExchangePage extends BasePage {
initialCurrency: exchangeViewModel.depositCurrency, initialCurrency: exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName ?? '', initialWalletName: depositWalletName ?? '',
initialAddress: exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency initialAddress: exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address ? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.depositAddress, : exchangeViewModel.depositAddress,
initialIsAmountEditable: true, initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled, initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
@ -694,7 +694,7 @@ class ExchangePage extends BasePage {
initialCurrency: exchangeViewModel.receiveCurrency, initialCurrency: exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName ?? '', initialWalletName: receiveWalletName ?? '',
initialAddress: exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency initialAddress: exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address ? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.receiveAddress, : exchangeViewModel.receiveAddress,
initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable, initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable,
isAmountEstimated: true, isAmountEstimated: true,

View file

@ -129,7 +129,7 @@ class ExchangeTemplatePage extends BasePage {
initialWalletName: depositWalletName ?? '', initialWalletName: depositWalletName ?? '',
initialAddress: exchangeViewModel.depositCurrency == initialAddress: exchangeViewModel.depositCurrency ==
exchangeViewModel.wallet.currency exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address ? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.depositAddress, : exchangeViewModel.depositAddress,
initialIsAmountEditable: true, initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled, initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
@ -166,7 +166,7 @@ class ExchangeTemplatePage extends BasePage {
initialWalletName: receiveWalletName ?? '', initialWalletName: receiveWalletName ?? '',
initialAddress: exchangeViewModel.receiveCurrency == initialAddress: exchangeViewModel.receiveCurrency ==
exchangeViewModel.wallet.currency exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address ? exchangeViewModel.wallet.walletAddresses.addressForExchange
: exchangeViewModel.receiveAddress, : exchangeViewModel.receiveAddress,
initialIsAmountEditable: false, initialIsAmountEditable: false,
isAmountEstimated: true, isAmountEstimated: true,

View file

@ -121,7 +121,8 @@ class ReceivePage extends BasePage {
heroTag: _heroTag, heroTag: _heroTag,
amountTextFieldFocusNode: _cryptoAmountFocus, amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController, amountController: _amountController,
isLight: currentTheme.type == ThemeType.light), isLight: currentTheme.type == ThemeType.light,
),
), ),
AddressList(addressListViewModel: addressListViewModel), AddressList(addressListViewModel: addressListViewModel),
Padding( Padding(

View file

@ -2,6 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
@ -15,11 +16,14 @@ class AddressCell extends StatelessWidget {
required this.textColor, required this.textColor,
this.onTap, this.onTap,
this.onEdit, this.onEdit,
this.onHide,
this.isHidden = false,
this.onDelete, this.onDelete,
this.txCount, this.txCount,
this.balance, this.balance,
this.isChange = false, this.isChange = false,
this.hasBalance = false}); this.hasBalance = false,
this.hasReceived = false});
factory AddressCell.fromItem( factory AddressCell.fromItem(
WalletAddressListItem item, { WalletAddressListItem item, {
@ -28,7 +32,10 @@ class AddressCell extends StatelessWidget {
required Color textColor, required Color textColor,
Function(String)? onTap, Function(String)? onTap,
bool hasBalance = false, bool hasBalance = false,
bool hasReceived = false,
Function()? onEdit, Function()? onEdit,
Function()? onHide,
bool isHidden = false,
Function()? onDelete, Function()? onDelete,
}) => }) =>
AddressCell( AddressCell(
@ -40,11 +47,14 @@ class AddressCell extends StatelessWidget {
textColor: textColor, textColor: textColor,
onTap: onTap, onTap: onTap,
onEdit: onEdit, onEdit: onEdit,
onHide: onHide,
isHidden: isHidden,
onDelete: onDelete, onDelete: onDelete,
txCount: item.txCount, txCount: item.txCount,
balance: item.balance, balance: item.balance,
isChange: item.isChange, isChange: item.isChange,
hasBalance: hasBalance); hasBalance: hasBalance,
hasReceived: hasReceived,);
final String address; final String address;
final String name; final String name;
@ -54,11 +64,14 @@ class AddressCell extends StatelessWidget {
final Color textColor; final Color textColor;
final Function(String)? onTap; final Function(String)? onTap;
final Function()? onEdit; final Function()? onEdit;
final Function()? onHide;
final bool isHidden;
final Function()? onDelete; final Function()? onDelete;
final int? txCount; final int? txCount;
final String? balance; final String? balance;
final bool isChange; final bool isChange;
final bool hasBalance; final bool hasBalance;
final bool hasReceived;
static const int addressPreviewLength = 8; static const int addressPreviewLength = 8;
@ -138,7 +151,7 @@ class AddressCell extends StatelessWidget {
), ),
], ],
), ),
if (hasBalance) if (hasBalance || hasReceived)
Padding( Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Row( child: Row(
@ -146,7 +159,7 @@ class AddressCell extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Text( Text(
'${S.of(context).balance}: $balance', '${hasReceived ? S.of(context).received : S.of(context).balance}: $balance',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -178,14 +191,28 @@ class AddressCell extends StatelessWidget {
enabled: !isCurrent, enabled: !isCurrent,
child: Slidable( child: Slidable(
key: Key(address), key: Key(address),
startActionPane: _actionPane(context), startActionPane: _actionPaneStart(context),
endActionPane: _actionPane(context), endActionPane: _actionPaneEnd(context),
child: cell, child: cell,
), ),
); );
} }
ActionPane _actionPane(BuildContext context) => ActionPane( ActionPane _actionPaneEnd(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: onDelete != null ? 0.4 : 0.3,
children: [
SlidableAction(
onPressed: (_) => onHide?.call(),
backgroundColor: isHidden ? Colors.green : Colors.red,
foregroundColor: Colors.white,
icon: isHidden ? CupertinoIcons.arrow_left : CupertinoIcons.arrow_right,
label: isHidden ? S.of(context).show : S.of(context).hide,
),
],
);
ActionPane _actionPaneStart(BuildContext context) => ActionPane(
motion: const ScrollMotion(), motion: const ScrollMotion(),
extentRatio: onDelete != null ? 0.4 : 0.3, extentRatio: onDelete != null ? 0.4 : 0.3,
children: [ children: [

View file

@ -10,16 +10,19 @@ import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:cake_wallet/utils/list_item.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.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';
class AddressList extends StatelessWidget { class AddressList extends StatefulWidget {
const AddressList({ const AddressList({
super.key, super.key,
required this.addressListViewModel, required this.addressListViewModel,
@ -29,59 +32,123 @@ class AddressList extends StatelessWidget {
final WalletAddressListViewModel addressListViewModel; final WalletAddressListViewModel addressListViewModel;
final Function(String)? onSelect; final Function(String)? onSelect;
@override
State<AddressList> createState() => _AddressListState();
}
class _AddressListState extends State<AddressList> {
bool showHiddenAddresses = false;
void _toggleHiddenAddresses() {
setState(() {
showHiddenAddresses = !showHiddenAddresses;
});
updateItems();
}
List<ListItem> getItems(List<ListItem> list, bool showHidden) {
return list.where((element) {
if (element is WalletAddressListItem) {
if (showHidden && element.isHidden) return true;
if (!showHidden && !element.isHidden) return true;
return false;
}
return true;
}).toList();
}
List<ListItem> items = [];
void updateItems() {
setState(() {
items = getItems(widget.addressListViewModel.items, showHiddenAddresses);
});
}
@override
void initState() {
super.initState();
items = getItems(widget.addressListViewModel.items, showHiddenAddresses);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool editable = onSelect == null; bool editable = widget.onSelect == null;
return Observer( return ListView.separated(
builder: (_) => ListView.separated( padding: EdgeInsets.all(0),
padding: EdgeInsets.all(0), separatorBuilder: (context, _) => const HorizontalSectionDivider(),
separatorBuilder: (context, _) => const HorizontalSectionDivider(), shrinkWrap: true,
shrinkWrap: true, physics: NeverScrollableScrollPhysics(),
physics: NeverScrollableScrollPhysics(), itemCount: items.length,
itemCount: addressListViewModel.items.length, itemBuilder: (context, index) {
itemBuilder: (context, index) { final item = items[index];
final item = addressListViewModel.items[index]; Widget cell = Container();
Widget cell = Container();
if (item is WalletAccountListHeader) { if (item is WalletAccountListHeader) {
cell = HeaderTile( cell = HeaderTile(
showTrailingButton: true, showTrailingButton: true,
walletAddressListViewModel: addressListViewModel, walletAddressListViewModel: widget.addressListViewModel,
trailingButtonTap: () async { trailingButtonTap: () async {
if (addressListViewModel.type == WalletType.monero || if (widget.addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven) { widget.addressListViewModel.type == WalletType.haven) {
await showPopUp<void>( await showPopUp<void>(
context: context, builder: (_) => getIt.get<MoneroAccountListPage>()); context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
} else { updateItems();
await showPopUp<void>( } else {
context: context, builder: (_) => getIt.get<NanoAccountListPage>()); await showPopUp<void>(
} context: context, builder: (_) => getIt.get<NanoAccountListPage>());
}, updateItems();
title: S.of(context).accounts, }
trailingIcon: Icon( },
Icons.arrow_forward_ios, title: S.of(context).accounts,
size: 14, trailingIcon: Icon(
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor, Icons.arrow_forward_ios,
)); size: 14,
} color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
if (item is WalletAddressListHeader) { if (item is WalletAddressHiddenListHeader) {
cell = HeaderTile( cell = HeaderTile(
title: S.of(context).addresses, title: S.of(context).hidden_addresses,
walletAddressListViewModel: addressListViewModel, walletAddressListViewModel: widget.addressListViewModel,
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled, showTrailingButton: true,
showSearchButton: true, showSearchButton: false,
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress), trailingButtonTap: _toggleHiddenAddresses,
trailingIcon: Icon( trailingIcon: Icon(
Icons.add, showHiddenAddresses ? Icons.toggle_on : Icons.toggle_off,
size: 20, size: 20,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor, color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
)); ));
} }
if (item is WalletAddressListItem) { if (item is WalletAddressListHeader) {
cell = HeaderTile(
title: S.of(context).addresses,
walletAddressListViewModel: widget.addressListViewModel,
showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
showSearchButton: true,
onSearchCallback: updateItems,
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
updateItems(); // refresh the new address
}),
trailingIcon: Icon(
Icons.add,
size: 20,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
if (item is WalletAddressListItem) {
if (item.isHidden && !showHiddenAddresses) {
cell = Container();
} else if (!item.isHidden && showHiddenAddresses) {
cell = Container();
} else {
cell = Observer(builder: (_) { cell = Observer(builder: (_) {
final isCurrent = item.address == 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;
@ -89,35 +156,51 @@ class AddressList extends StatelessWidget {
? 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: addressListViewModel.isElectrumWallet, hasBalance: widget.addressListViewModel.isBalanceAvailable,
backgroundColor: backgroundColor, hasReceived: widget.addressListViewModel.isReceivedAvailable,
// hasReceived:
backgroundColor: (kDebugMode && item.isHidden) ?
Theme.of(context).colorScheme.error :
(kDebugMode && item.isManual) ? Theme.of(context).colorScheme.error.withBlue(255) :
backgroundColor,
textColor: textColor, textColor: textColor,
onTap: (_) { onTap: (_) {
if (onSelect != null) { if (widget.onSelect != null) {
onSelect!(item.address); widget.onSelect!(item.address);
return; return;
} }
addressListViewModel.setAddress(item); widget.addressListViewModel.setAddress(item);
}, },
onEdit: editable onEdit: editable
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item) ? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item).then((value) {
updateItems(); // refresh the new address
})
: null, : null,
isHidden: item.isHidden,
onHide: () => _hideAddress(item),
); );
}); });
} }
}
return index != 0 return index != 0
? cell ? cell
: ClipRRect( : ClipRRect(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(30), topRight: Radius.circular(30)), topLeft: Radius.circular(30), topRight: Radius.circular(30)),
child: cell, child: cell,
); );
}, },
),
); );
} }
void _hideAddress(WalletAddressListItem item) async {
await widget.addressListViewModel.toggleHideAddress(item);
updateItems();
}
} }

View file

@ -10,6 +10,7 @@ class HeaderTile extends StatefulWidget {
this.showSearchButton = false, this.showSearchButton = false,
this.showTrailingButton = false, this.showTrailingButton = false,
this.trailingButtonTap, this.trailingButtonTap,
this.onSearchCallback,
this.trailingIcon, this.trailingIcon,
}); });
@ -18,6 +19,7 @@ class HeaderTile extends StatefulWidget {
final bool showSearchButton; final bool showSearchButton;
final bool showTrailingButton; final bool showTrailingButton;
final VoidCallback? trailingButtonTap; final VoidCallback? trailingButtonTap;
final VoidCallback? onSearchCallback;
final Icon? trailingIcon; final Icon? trailingIcon;
@override @override
@ -41,7 +43,10 @@ class _HeaderTileState extends State<HeaderTile> {
_isSearchActive _isSearchActive
? Expanded( ? Expanded(
child: TextField( child: TextField(
onChanged: (value) => widget.walletAddressListViewModel.updateSearchText(value), onChanged: (value) {
widget.walletAddressListViewModel.updateSearchText(value);
widget.onSearchCallback?.call();
},
cursorColor: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor, cursorColor: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
cursorWidth: 0.5, cursorWidth: 0.5,
decoration: InputDecoration( decoration: InputDecoration(

View file

@ -37,6 +37,10 @@ class QRWidget extends StatelessWidget {
final int? qrVersion; final int? qrVersion;
final String? heroTag; final String? heroTag;
PaymentURI get addressUri {
return addressListViewModel.uri;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_address.png', final copyImage = Image.asset('assets/images/copy_address.png',
@ -77,14 +81,14 @@ class QRWidget extends StatelessWidget {
() async { () async {
await Navigator.pushNamed(context, Routes.fullscreenQR, await Navigator.pushNamed(context, Routes.fullscreenQR,
arguments: QrViewData( arguments: QrViewData(
data: addressListViewModel.uri.toString(), data: addressUri.toString(),
heroTag: heroTag, heroTag: heroTag,
)); ));
}, },
); );
}, },
child: Hero( child: Hero(
tag: Key(heroTag ?? addressListViewModel.uri.toString()), tag: Key(heroTag ?? addressUri.toString()),
child: Center( child: Center(
child: AspectRatio( child: AspectRatio(
aspectRatio: 1.0, aspectRatio: 1.0,
@ -105,7 +109,7 @@ class QRWidget extends StatelessWidget {
color: Colors.white, color: Colors.white,
), ),
), ),
child: QrImage(data: addressListViewModel.uri.toString())), child: QrImage(data: addressUri.toString())),
), ),
), ),
), ),
@ -148,7 +152,7 @@ class QRWidget extends StatelessWidget {
builder: (context) => Observer( builder: (context) => Observer(
builder: (context) => GestureDetector( builder: (context) => GestureDetector(
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: addressListViewModel.address.address)); Clipboard.setData(ClipboardData(text: addressUri.address));
showBar<void>(context, S.of(context).copied_to_clipboard); showBar<void>(context, S.of(context).copied_to_clipboard);
}, },
child: Row( child: Row(
@ -157,7 +161,7 @@ class QRWidget extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Text( child: Text(
addressListViewModel.address.address, addressUri.address,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,

View file

@ -27,32 +27,41 @@ abstract class ContactListViewModelBase with Store {
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled { settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled {
walletInfoSource.values.forEach((info) { walletInfoSource.values.forEach((info) {
if (isAutoGenerateEnabled && info.type == WalletType.monero && info.addressInfos != null) { if (isAutoGenerateEnabled && info.type == WalletType.monero && info.addressInfos != null) {
info.addressInfos!.forEach((key, value) { final key = info.addressInfos!.keys.first;
final nextUnusedAddress = value.firstWhereOrNull( final value = info.addressInfos![key];
(addressInfo) => !(info.usedAddresses?.contains(addressInfo.address) ?? false)); final address = value?.first;
if (nextUnusedAddress != null) { if (address != null) {
final name = _createName(info.name, nextUnusedAddress.label); final name = _createName(info.name, address.label);
walletContacts.add(WalletContact( walletContacts.add(WalletContact(
nextUnusedAddress.address, address.address,
name, name,
walletTypeToCryptoCurrency(info.type), walletTypeToCryptoCurrency(info.type),
)); ));
} }
});
} else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) { } else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) {
info.addresses!.forEach((address, label) { if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)) {
if (label.isEmpty) { final address = info.address;
return; final name = _createName(info.name, "");
}
final name = _createName(info.name, label);
walletContacts.add(WalletContact( walletContacts.add(WalletContact(
address, address,
name, name,
walletTypeToCryptoCurrency(info.type, walletTypeToCryptoCurrency(info.type),
isTestnet:
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
)); ));
}); } else {
info.addresses!.forEach((address, label) {
if (label.isEmpty) {
return;
}
final name = _createName(info.name, label);
walletContacts.add(WalletContact(
address,
name,
walletTypeToCryptoCurrency(info.type,
isTestnet:
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
));
});
}
} else { } else {
walletContacts.add(WalletContact( walletContacts.add(WalletContact(
info.address, info.address,

View file

@ -121,7 +121,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositAmount = ''; depositAmount = '';
receiveAmount = ''; receiveAmount = '';
receiveAddress = ''; receiveAddress = '';
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.address : ''; depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
provider = providersForCurrentPair().first; provider = providersForCurrentPair().first;
final initialProvider = provider; final initialProvider = provider;
provider!.checkIsAvailable().then((bool isAvailable) { provider!.checkIsAvailable().then((bool isAvailable) {
@ -155,6 +155,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
wallet.type == WalletType.litecoin || wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash; wallet.type == WalletType.bitcoinCash;
bool get hideAddressAfterExchange =>
wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero;
bool _useTorOnly; bool _useTorOnly;
final Box<Trade> trades; final Box<Trade> trades;
final ExchangeTemplateStore _exchangeTemplateStore; final ExchangeTemplateStore _exchangeTemplateStore;
@ -540,6 +544,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
isFixedRate: isFixedRateMode, isFixedRate: isFixedRateMode,
); );
if (hideAddressAfterExchange) {
wallet.walletAddresses.hiddenAddresses.add(depositAddress);
await wallet.walletAddresses.saveAddressesInBox();
}
var amount = isFixedRateMode ? receiveAmount : depositAmount; var amount = isFixedRateMode ? receiveAmount : depositAmount;
amount = amount.replaceAll(',', '.'); amount = amount.replaceAll(',', '.');
@ -603,8 +612,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
isReceiveAmountEntered = false; isReceiveAmountEntered = false;
depositAmount = ''; depositAmount = '';
receiveAmount = ''; receiveAmount = '';
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.address : ''; depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
receiveAddress = receiveCurrency == wallet.currency ? wallet.walletAddresses.address : ''; receiveAddress = receiveCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
isDepositAddressEnabled = !(depositCurrency == wallet.currency); isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isFixedRateMode = false; isFixedRateMode = false;
_onPairChange(); _onPairChange();

View file

@ -78,6 +78,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
wallet, wallet,
accountIndex: monero!.getCurrentAccount(wallet).id, accountIndex: monero!.getCurrentAccount(wallet).id,
label: label); label: label);
final addr = await monero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
wallet.walletAddresses.manualAddresses.add(addr);
await wallet.save(); await wallet.save();
} }
@ -88,6 +90,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
wallet, wallet,
accountIndex: wownero!.getCurrentAccount(wallet).id, accountIndex: wownero!.getCurrentAccount(wallet).id,
label: label); label: label);
final addr = await wownero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
wallet.walletAddresses.manualAddresses.add(addr);
await wallet.save(); await wallet.save();
} }

View file

@ -0,0 +1,3 @@
import 'package:cake_wallet/utils/list_item.dart';
class WalletAddressHiddenListHeader extends ListItem {}

View file

@ -1,7 +1,7 @@
import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/utils/list_item.dart';
class WalletAddressListItem extends ListItem { class WalletAddressListItem extends ListItem {
const WalletAddressListItem({ WalletAddressListItem({
required this.address, required this.address,
required this.isPrimary, required this.isPrimary,
this.id, this.id,
@ -11,6 +11,8 @@ class WalletAddressListItem extends ListItem {
this.isChange = false, this.isChange = false,
// Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc // Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc
this.isOneTimeReceiveAddress = false, this.isOneTimeReceiveAddress = false,
this.isHidden = false,
this.isManual = false,
}) : super(); }) : super();
final int? id; final int? id;
@ -20,6 +22,8 @@ class WalletAddressListItem extends ListItem {
final int? txCount; final int? txCount;
final String? balance; final String? balance;
final bool isChange; final bool isChange;
bool isHidden;
bool isManual;
final bool? isOneTimeReceiveAddress; final bool? isOneTimeReceiveAddress;
@override @override

View file

@ -18,12 +18,16 @@ import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/utils/list_item.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
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/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -271,56 +275,40 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType get type => wallet.type; WalletType get type => wallet.type;
@computed @computed
WalletAddressListItem get address => WalletAddressListItem get address {
WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false); return WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
}
@computed @computed
PaymentURI get uri { PaymentURI get uri {
if (wallet.type == WalletType.monero) { switch (wallet.type) {
return MoneroURI(amount: amount, address: address.address); case WalletType.monero:
return MoneroURI(amount: amount, address: address.address);
case WalletType.haven:
return HavenURI(amount: amount, address: address.address);
case WalletType.bitcoin:
return BitcoinURI(amount: amount, address: address.address);
case WalletType.litecoin:
return LitecoinURI(amount: amount, address: address.address);
case WalletType.ethereum:
return EthereumURI(amount: amount, address: address.address);
case WalletType.bitcoinCash:
return BitcoinCashURI(amount: amount, address: address.address);
case WalletType.banano:
return NanoURI(amount: amount, address: address.address);
case WalletType.nano:
return NanoURI(amount: amount, address: address.address);
case WalletType.polygon:
return PolygonURI(amount: amount, address: address.address);
case WalletType.solana:
return SolanaURI(amount: amount, address: address.address);
case WalletType.tron:
return TronURI(amount: amount, address: address.address);
case WalletType.wownero:
return WowneroURI(amount: amount, address: address.address);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
} }
if (wallet.type == WalletType.haven) {
return HavenURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.bitcoin) {
return BitcoinURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.litecoin) {
return LitecoinURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.ethereum) {
return EthereumURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.bitcoinCash) {
return BitcoinCashURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.nano) {
return NanoURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.polygon) {
return PolygonURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.solana) {
return SolanaURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.tron) {
return TronURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.wownero) {
return WowneroURI(amount: amount, address: address.address);
}
throw Exception('Unexpected type: ${type.toString()}');
} }
@computed @computed
@ -341,7 +329,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
id: subaddress.id, id: subaddress.id,
isPrimary: isPrimary, isPrimary: isPrimary,
name: subaddress.label, name: subaddress.label,
address: subaddress.address); address: subaddress.address,
balance: subaddress.received,
txCount: subaddress.txCount,
);
}); });
addressList.addAll(addressItems); addressList.addAll(addressItems);
} }
@ -468,6 +459,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
} }
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses.contains((addressList[i] as WalletAddressListItem).address);
}
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses.contains((addressList[i] as WalletAddressListItem).address);
}
if (searchText.isNotEmpty) { if (searchText.isNotEmpty) {
return ObservableList.of(addressList.where((item) { return ObservableList.of(addressList.where((item) {
if (item is WalletAddressListItem) { if (item is WalletAddressListItem) {
@ -479,7 +480,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return addressList; return addressList;
} }
Future<void> toggleHideAddress(WalletAddressListItem item) async {
if (item.isHidden) {
wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address);
} else {
wallet.walletAddresses.hiddenAddresses.add(item.address);
}
await wallet.walletAddresses.saveAddressesInBox();
if (wallet.type == WalletType.monero) {
monero!.getSubaddressList(wallet).update(wallet, accountIndex: monero!.getCurrentAccount(wallet).id);
} else if (wallet.type == WalletType.wownero) {
wownero!.getSubaddressList(wallet).update(wallet, accountIndex: wownero!.getCurrentAccount(wallet).id);
} else if (wallet.type == WalletType.haven) {
haven!.getSubaddressList(wallet).update(wallet, accountIndex: haven!.getCurrentAccount(wallet).id);
}
}
@observable @observable
bool hasAccounts; bool hasAccounts;
@ -515,6 +530,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
wallet.type == WalletType.litecoin || wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash; wallet.type == WalletType.bitcoinCash;
@computed
bool get isBalanceAvailable => isElectrumWallet;
@computed
bool get isReceivedAvailable =>
wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero;
@computed @computed
bool get isSilentPayments => bool get isSilentPayments =>
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet); wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
@ -524,6 +547,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled && _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
!isSilentPayments; !isSilentPayments;
@computed
bool get showAddManualAddresses =>
!isAutoGenerateSubaddressEnabled ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero;
List<ListItem> _baseItems; List<ListItem> _baseItems;
final YatStore yatStore; final YatStore yatStore;
@ -542,6 +571,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void _init() { void _init() {
_baseItems = []; _baseItems = [];
if (wallet.walletAddresses.hiddenAddresses.isNotEmpty) {
_baseItems.add(WalletAddressHiddenListHeader());
}
if (wallet.type == WalletType.monero || if (wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero || wallet.type == WalletType.wownero ||
wallet.type == WalletType.haven) { wallet.type == WalletType.haven) {
@ -551,6 +584,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) { if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
_baseItems.add(WalletAddressListHeader()); _baseItems.add(WalletAddressListHeader());
} }
if (wallet.isEnabledAutoGenerateSubaddress) {
wallet.walletAddresses.address = wallet.walletAddresses.latestAddress;
}
} }
@action @action

View file

@ -333,6 +333,8 @@
"haven_app": "Haven by Cake Wallet", "haven_app": "Haven by Cake Wallet",
"haven_app_wallet_text": "Awesome wallet for Haven", "haven_app_wallet_text": "Awesome wallet for Haven",
"help": "help", "help": "help",
"hide": "Hide",
"hidden_addresses": "Hidden Addresses",
"hidden_balance": "Hidden Balance", "hidden_balance": "Hidden Balance",
"hide_details": "Hide Details", "hide_details": "Hide Details",
"high_contrast_theme": "High Contrast Theme", "high_contrast_theme": "High Contrast Theme",
@ -683,6 +685,7 @@
"setup_your_debit_card": "Set up your debit card", "setup_your_debit_card": "Set up your debit card",
"share": "Share", "share": "Share",
"share_address": "Share address", "share_address": "Share address",
"show": "Show",
"shared_seed_wallet_groups": "Shared Seed Wallet Groups", "shared_seed_wallet_groups": "Shared Seed Wallet Groups",
"show_details": "Show Details", "show_details": "Show Details",
"show_keys": "Show seed/keys", "show_keys": "Show seed/keys",

View file

@ -303,10 +303,14 @@ class Subaddress {
Subaddress({ Subaddress({
required this.id, required this.id,
required this.label, required this.label,
required this.address}); required this.address,
required this.received,
required this.txCount});
final int id; final int id;
final String label; final String label;
final String address; final String address;
final String? received;
final int txCount;
} }
class MoneroBalance extends Balance { class MoneroBalance extends Balance {