mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-18 08:45:05 +00:00
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:
parent
62e0c2a592
commit
f8b0c0ad2a
35 changed files with 775 additions and 242 deletions
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -190,6 +190,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 ?? '';
|
||||||
|
|
||||||
set yatLastUsedAddress(String address) {
|
set yatLastUsedAddress(String address) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
return Subaddress(
|
final ttDetailsLocal = ttDetails.where((element) {
|
||||||
|
final address = getAddress(
|
||||||
accountIndex: subaddress!.accountIndex,
|
accountIndex: subaddress!.accountIndex,
|
||||||
addressIndex: index,
|
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(
|
||||||
|
accountIndex: subaddress!.accountIndex,
|
||||||
|
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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
return Subaddress(
|
final ttDetailsLocal = ttDetails.where((element) {
|
||||||
|
final address = getAddress(
|
||||||
accountIndex: subaddress!.accountIndex,
|
accountIndex: subaddress!.accountIndex,
|
||||||
addressIndex: index,
|
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(
|
||||||
|
accountIndex: subaddress!.accountIndex,
|
||||||
|
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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -37,10 +60,13 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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: [
|
||||||
|
|
|
@ -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,32 +32,74 @@ 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: addressListViewModel.items.length,
|
itemCount: items.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = addressListViewModel.items[index];
|
final item = 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>());
|
||||||
|
updateItems();
|
||||||
} else {
|
} else {
|
||||||
await showPopUp<void>(
|
await showPopUp<void>(
|
||||||
context: context, builder: (_) => getIt.get<NanoAccountListPage>());
|
context: context, builder: (_) => getIt.get<NanoAccountListPage>());
|
||||||
|
updateItems();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: S.of(context).accounts,
|
title: S.of(context).accounts,
|
||||||
|
@ -65,13 +110,30 @@ class AddressList extends StatelessWidget {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is WalletAddressHiddenListHeader) {
|
||||||
|
cell = HeaderTile(
|
||||||
|
title: S.of(context).hidden_addresses,
|
||||||
|
walletAddressListViewModel: widget.addressListViewModel,
|
||||||
|
showTrailingButton: true,
|
||||||
|
showSearchButton: false,
|
||||||
|
trailingButtonTap: _toggleHiddenAddresses,
|
||||||
|
trailingIcon: Icon(
|
||||||
|
showHiddenAddresses ? Icons.toggle_on : Icons.toggle_off,
|
||||||
|
size: 20,
|
||||||
|
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if (item is WalletAddressListHeader) {
|
if (item is WalletAddressListHeader) {
|
||||||
cell = HeaderTile(
|
cell = HeaderTile(
|
||||||
title: S.of(context).addresses,
|
title: S.of(context).addresses,
|
||||||
walletAddressListViewModel: addressListViewModel,
|
walletAddressListViewModel: widget.addressListViewModel,
|
||||||
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
|
showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
|
||||||
showSearchButton: true,
|
showSearchButton: true,
|
||||||
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress),
|
onSearchCallback: updateItems,
|
||||||
|
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
|
||||||
|
updateItems(); // refresh the new address
|
||||||
|
}),
|
||||||
trailingIcon: Icon(
|
trailingIcon: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
size: 20,
|
size: 20,
|
||||||
|
@ -80,8 +142,13 @@ class AddressList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is WalletAddressListItem) {
|
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,25 +156,36 @@ 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
|
||||||
|
@ -117,7 +195,12 @@ class AddressList extends StatelessWidget {
|
||||||
child: cell,
|
child: cell,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _hideAddress(WalletAddressListItem item) async {
|
||||||
|
await widget.addressListViewModel.toggleHideAddress(item);
|
||||||
|
updateItems();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -27,19 +27,27 @@ 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) {
|
||||||
|
if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)) {
|
||||||
|
final address = info.address;
|
||||||
|
final name = _createName(info.name, "");
|
||||||
|
walletContacts.add(WalletContact(
|
||||||
|
address,
|
||||||
|
name,
|
||||||
|
walletTypeToCryptoCurrency(info.type),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
info.addresses!.forEach((address, label) {
|
info.addresses!.forEach((address, label) {
|
||||||
if (label.isEmpty) {
|
if (label.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
@ -53,6 +61,7 @@ abstract class ContactListViewModelBase with Store {
|
||||||
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
|
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
walletContacts.add(WalletContact(
|
walletContacts.add(WalletContact(
|
||||||
info.address,
|
info.address,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import 'package:cake_wallet/utils/list_item.dart';
|
||||||
|
|
||||||
|
class WalletAddressHiddenListHeader extends ListItem {}
|
|
@ -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
|
||||||
|
|
|
@ -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,57 +275,41 @@ 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) {
|
||||||
|
case WalletType.monero:
|
||||||
return MoneroURI(amount: amount, address: address.address);
|
return MoneroURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.haven:
|
||||||
|
|
||||||
if (wallet.type == WalletType.haven) {
|
|
||||||
return HavenURI(amount: amount, address: address.address);
|
return HavenURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.bitcoin:
|
||||||
|
|
||||||
if (wallet.type == WalletType.bitcoin) {
|
|
||||||
return BitcoinURI(amount: amount, address: address.address);
|
return BitcoinURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.litecoin:
|
||||||
|
|
||||||
if (wallet.type == WalletType.litecoin) {
|
|
||||||
return LitecoinURI(amount: amount, address: address.address);
|
return LitecoinURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.ethereum:
|
||||||
|
|
||||||
if (wallet.type == WalletType.ethereum) {
|
|
||||||
return EthereumURI(amount: amount, address: address.address);
|
return EthereumURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.bitcoinCash:
|
||||||
|
|
||||||
if (wallet.type == WalletType.bitcoinCash) {
|
|
||||||
return BitcoinCashURI(amount: amount, address: address.address);
|
return BitcoinCashURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.banano:
|
||||||
|
|
||||||
if (wallet.type == WalletType.nano) {
|
|
||||||
return NanoURI(amount: amount, address: address.address);
|
return NanoURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.nano:
|
||||||
|
return NanoURI(amount: amount, address: address.address);
|
||||||
if (wallet.type == WalletType.polygon) {
|
case WalletType.polygon:
|
||||||
return PolygonURI(amount: amount, address: address.address);
|
return PolygonURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.solana:
|
||||||
|
|
||||||
if (wallet.type == WalletType.solana) {
|
|
||||||
return SolanaURI(amount: amount, address: address.address);
|
return SolanaURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.tron:
|
||||||
|
|
||||||
if (wallet.type == WalletType.tron) {
|
|
||||||
return TronURI(amount: amount, address: address.address);
|
return TronURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.wownero:
|
||||||
|
|
||||||
if (wallet.type == WalletType.wownero) {
|
|
||||||
return WowneroURI(amount: amount, address: address.address);
|
return WowneroURI(amount: amount, address: address.address);
|
||||||
}
|
case WalletType.none:
|
||||||
|
|
||||||
throw Exception('Unexpected type: ${type.toString()}');
|
throw Exception('Unexpected type: ${type.toString()}');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
ObservableList<ListItem> get items => ObservableList<ListItem>()
|
ObservableList<ListItem> get items => ObservableList<ListItem>()
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue