mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 03:29:36 +00:00
Cw-343-a-list-of-previously-used-addresses (#1248)
* add used addresses list * generate new address button * fix wallet type issue * fix addresses button title * update selectButton * show all wallet addresses * add tx amount and balance * fix ui * remove cashAddr format * fix generating new address issue * disable autogenerating * fix cashAddr format * minor fix * add search bar * Update address_cell.dart * fix merge conflict * address labeling feature * review fixes --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
parent
d664f0a16f
commit
9754d67601
20 changed files with 664 additions and 330 deletions
|
@ -1,40 +1,70 @@
|
|||
import 'dart:convert';
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
|
||||
class BitcoinAddressRecord {
|
||||
BitcoinAddressRecord(this.address,
|
||||
{required this.index, this.isHidden = false, bool isUsed = false})
|
||||
: _isUsed = isUsed;
|
||||
BitcoinAddressRecord(
|
||||
this.address, {
|
||||
required this.index,
|
||||
this.isHidden = false,
|
||||
int txCount = 0,
|
||||
int balance = 0,
|
||||
String name = '',
|
||||
bool isUsed = false,
|
||||
}) : _txCount = txCount,
|
||||
_balance = balance,
|
||||
_name = name,
|
||||
_isUsed = isUsed;
|
||||
|
||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinAddressRecord(
|
||||
decoded['address'] as String,
|
||||
index: decoded['index'] as int,
|
||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||
isUsed: decoded['isUsed'] as bool? ?? false);
|
||||
return BitcoinAddressRecord(decoded['address'] as String,
|
||||
index: decoded['index'] as int,
|
||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||
txCount: decoded['txCount'] as int? ?? 0,
|
||||
name: decoded['name'] as String? ?? '',
|
||||
balance: decoded['balance'] as int? ?? 0);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object o) =>
|
||||
o is BitcoinAddressRecord && address == o.address;
|
||||
|
||||
final String address;
|
||||
final bool isHidden;
|
||||
final int index;
|
||||
int _txCount;
|
||||
int _balance;
|
||||
String _name;
|
||||
bool _isUsed;
|
||||
|
||||
int get txCount => _txCount;
|
||||
|
||||
String get name => _name;
|
||||
|
||||
int get balance => _balance;
|
||||
|
||||
set txCount(int value) => _txCount = value;
|
||||
|
||||
set balance(int value) => _balance = value;
|
||||
|
||||
bool get isUsed => _isUsed;
|
||||
|
||||
void setAsUsed() => _isUsed = true;
|
||||
void setNewName(String label) => _name = label;
|
||||
|
||||
@override
|
||||
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
|
||||
|
||||
@override
|
||||
int get hashCode => address.hashCode;
|
||||
|
||||
bool _isUsed;
|
||||
String get cashAddr => bitbox.Address.toCashAddress(address);
|
||||
|
||||
void setAsUsed() => _isUsed = true;
|
||||
|
||||
String toJSON() =>
|
||||
json.encode({
|
||||
String toJSON() => json.encode({
|
||||
'address': address,
|
||||
'index': index,
|
||||
'isHidden': isHidden,
|
||||
'isUsed': isUsed});
|
||||
'txCount': txCount,
|
||||
'name': name,
|
||||
'isUsed': isUsed,
|
||||
'balance': balance,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
|
||||
.derivePath("m/0'/1"),
|
||||
networkType: networkType);
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
}
|
||||
|
||||
static Future<BitcoinWallet> create({
|
||||
|
|
|
@ -63,6 +63,7 @@ abstract class ElectrumWalletBase
|
|||
_password = password,
|
||||
_feeRates = <int>[],
|
||||
_isTransactionUpdating = false,
|
||||
isEnabledAutoGenerateSubaddress = true,
|
||||
unspentCoins = [],
|
||||
_scripthashesUpdateSubject = {},
|
||||
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
|
||||
|
@ -87,6 +88,10 @@ abstract class ElectrumWalletBase
|
|||
final bitcoin.HDWallet hd;
|
||||
final String mnemonic;
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress;
|
||||
|
||||
late ElectrumClient electrumClient;
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
|
@ -583,38 +588,66 @@ abstract class ElectrumWalletBase
|
|||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||
final addressHashes = <String, BitcoinAddressRecord>{};
|
||||
final normalizedHistories = <Map<String, dynamic>>[];
|
||||
final newTxCounts = <String, int>{};
|
||||
|
||||
walletAddresses.addresses.forEach((addressRecord) {
|
||||
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
||||
addressHashes[sh] = addressRecord;
|
||||
newTxCounts[sh] = 0;
|
||||
});
|
||||
final histories = addressHashes.keys.map((scriptHash) =>
|
||||
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
|
||||
final historyResults = await Future.wait(histories);
|
||||
historyResults.forEach((history) {
|
||||
history.entries.forEach((historyItem) {
|
||||
if (historyItem.value.isNotEmpty) {
|
||||
final address = addressHashes[historyItem.key];
|
||||
address?.setAsUsed();
|
||||
normalizedHistories.addAll(historyItem.value);
|
||||
}
|
||||
|
||||
try {
|
||||
final histories = addressHashes.keys.map((scriptHash) =>
|
||||
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
|
||||
final historyResults = await Future.wait(histories);
|
||||
|
||||
|
||||
|
||||
historyResults.forEach((history) {
|
||||
history.entries.forEach((historyItem) {
|
||||
if (historyItem.value.isNotEmpty) {
|
||||
final address = addressHashes[historyItem.key];
|
||||
address?.setAsUsed();
|
||||
newTxCounts[historyItem.key] = historyItem.value.length;
|
||||
normalizedHistories.addAll(historyItem.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
|
||||
try {
|
||||
return fetchTransactionInfo(
|
||||
hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
|
||||
} catch (_) {
|
||||
return Future.value(null);
|
||||
|
||||
for (var sh in addressHashes.keys) {
|
||||
var balanceData = await electrumClient.getBalance(sh);
|
||||
var addressRecord = addressHashes[sh];
|
||||
if (addressRecord != null) {
|
||||
addressRecord.balance = balanceData['confirmed'] as int? ?? 0;
|
||||
}
|
||||
}
|
||||
}));
|
||||
return historiesWithDetails
|
||||
.fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
||||
if (tx == null) {
|
||||
|
||||
|
||||
addressHashes.forEach((sh, addressRecord) {
|
||||
addressRecord.txCount = newTxCounts[sh] ?? 0;
|
||||
});
|
||||
|
||||
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
|
||||
try {
|
||||
return fetchTransactionInfo(
|
||||
hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
|
||||
} catch (_) {
|
||||
return Future.value(null);
|
||||
}
|
||||
}));
|
||||
|
||||
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
|
||||
<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
||||
if (tx == null) {
|
||||
return acc;
|
||||
}
|
||||
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
|
||||
return acc;
|
||||
}
|
||||
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
|
||||
return acc;
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTransactions() async {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum.dart';
|
||||
import 'package:cw_bitcoin/script_hash.dart';
|
||||
|
@ -10,8 +10,7 @@ import 'package:mobx/mobx.dart';
|
|||
|
||||
part 'electrum_wallet_addresses.g.dart';
|
||||
|
||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase
|
||||
with _$ElectrumWalletAddresses;
|
||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
||||
|
||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
||||
|
@ -22,19 +21,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
int initialRegularAddressIndex = 0,
|
||||
int initialChangeAddressIndex = 0})
|
||||
: addresses = ObservableList<BitcoinAddressRecord>.of(
|
||||
(initialAddresses ?? []).toSet()),
|
||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
|
||||
(initialAddresses ?? [])
|
||||
: addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
||||
.toSet()),
|
||||
changeAddresses = ObservableList<BitcoinAddressRecord>.of(
|
||||
(initialAddresses ?? [])
|
||||
.toSet()),
|
||||
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
||||
.toSet()),
|
||||
.toSet()),
|
||||
currentReceiveAddressIndex = initialRegularAddressIndex,
|
||||
currentChangeAddressIndex = initialChangeAddressIndex,
|
||||
super(walletInfo);
|
||||
super(walletInfo);
|
||||
|
||||
static const defaultReceiveAddressesCount = 22;
|
||||
static const defaultChangeAddressesCount = 17;
|
||||
|
@ -42,6 +38,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
static String toCashAddr(String address) => bitbox.Address.toCashAddress(address);
|
||||
|
||||
static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
|
||||
|
||||
final ObservableList<BitcoinAddressRecord> addresses;
|
||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||
|
@ -53,41 +51,67 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
@override
|
||||
@computed
|
||||
String get address {
|
||||
if (receiveAddresses.isEmpty) {
|
||||
final address = generateNewAddress().address;
|
||||
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address;
|
||||
}
|
||||
final receiveAddress = receiveAddresses.first.address;
|
||||
if (isEnabledAutoGenerateSubaddress) {
|
||||
if (receiveAddresses.isEmpty) {
|
||||
final newAddress = generateNewAddress().address;
|
||||
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
|
||||
}
|
||||
final receiveAddress = receiveAddresses.first.address;
|
||||
|
||||
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
|
||||
return walletInfo.type == WalletType.bitcoinCash
|
||||
? toCashAddr(receiveAddress)
|
||||
: receiveAddress;
|
||||
} else {
|
||||
final receiveAddress = (receiveAddresses.first.address != addresses.first.address &&
|
||||
previousAddressRecord != null)
|
||||
? previousAddressRecord!.address
|
||||
: addresses.first.address;
|
||||
|
||||
return walletInfo.type == WalletType.bitcoinCash
|
||||
? toCashAddr(receiveAddress)
|
||||
: receiveAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@override
|
||||
set address(String addr) {
|
||||
if (addr.startsWith('bitcoincash:')) {
|
||||
addr = toLegacy(addr);
|
||||
}
|
||||
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||
|
||||
previousAddressRecord = addressRecord;
|
||||
receiveAddresses.remove(addressRecord);
|
||||
receiveAddresses.insert(0, addressRecord);
|
||||
}
|
||||
|
||||
@override
|
||||
String get primaryAddress => getAddress(index: 0, hd: mainHd);
|
||||
|
||||
@override
|
||||
set address(String addr) => null;
|
||||
|
||||
int currentReceiveAddressIndex;
|
||||
int currentChangeAddressIndex;
|
||||
|
||||
@computed
|
||||
int get totalCountOfReceiveAddresses =>
|
||||
addresses.fold(0, (acc, addressRecord) {
|
||||
if (!addressRecord.isHidden) {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
@observable
|
||||
BitcoinAddressRecord? previousAddressRecord;
|
||||
|
||||
@computed
|
||||
int get totalCountOfChangeAddresses =>
|
||||
addresses.fold(0, (acc, addressRecord) {
|
||||
if (addressRecord.isHidden) {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
|
||||
if (!addressRecord.isHidden) {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
|
||||
@computed
|
||||
int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
|
||||
if (addressRecord.isHidden) {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
|
||||
Future<void> discoverAddresses() async {
|
||||
await _discoverAddresses(mainHd, false);
|
||||
|
@ -117,11 +141,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
if (changeAddresses.isEmpty) {
|
||||
final newAddresses = await _createNewAddresses(gap,
|
||||
hd: sideHd,
|
||||
startIndex: totalCountOfChangeAddresses > 0
|
||||
? totalCountOfChangeAddresses - 1
|
||||
: 0,
|
||||
isHidden: true);
|
||||
hd: sideHd,
|
||||
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
|
||||
isHidden: true);
|
||||
_addAddresses(newAddresses);
|
||||
}
|
||||
|
||||
|
@ -135,14 +157,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
return address;
|
||||
}
|
||||
|
||||
BitcoinAddressRecord generateNewAddress(
|
||||
{bitcoin.HDWallet? hd, bool isHidden = false}) {
|
||||
currentReceiveAddressIndex += 1;
|
||||
// FIX-ME: Check logic for whichi HD should be used here ???
|
||||
final address = BitcoinAddressRecord(
|
||||
getAddress(index: currentReceiveAddressIndex, hd: hd ?? sideHd),
|
||||
index: currentReceiveAddressIndex,
|
||||
isHidden: isHidden);
|
||||
BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) {
|
||||
final isHidden = hd == sideHd;
|
||||
|
||||
final newAddressIndex = addresses.fold(
|
||||
0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc);
|
||||
|
||||
final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd),
|
||||
index: newAddressIndex, isHidden: isHidden, name: label ?? '');
|
||||
addresses.add(address);
|
||||
return address;
|
||||
}
|
||||
|
@ -160,20 +182,32 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void updateAddress(String address, String label) {
|
||||
if (address.startsWith('bitcoincash:')) {
|
||||
address = toLegacy(address);
|
||||
}
|
||||
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address);
|
||||
addressRecord.setNewName(label);
|
||||
final index = addresses.indexOf(addressRecord);
|
||||
addresses.remove(addressRecord);
|
||||
addresses.insert(index, addressRecord);
|
||||
}
|
||||
|
||||
@action
|
||||
void updateReceiveAddresses() {
|
||||
receiveAddresses.removeRange(0, receiveAddresses.length);
|
||||
final newAdresses = addresses
|
||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
||||
receiveAddresses.addAll(newAdresses);
|
||||
final newAddresses =
|
||||
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
||||
receiveAddresses.addAll(newAddresses);
|
||||
}
|
||||
|
||||
@action
|
||||
void updateChangeAddresses() {
|
||||
changeAddresses.removeRange(0, changeAddresses.length);
|
||||
final newAdresses = addresses
|
||||
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
||||
changeAddresses.addAll(newAdresses);
|
||||
final newAddresses =
|
||||
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
||||
changeAddresses.addAll(newAddresses);
|
||||
}
|
||||
|
||||
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
|
||||
|
@ -181,20 +215,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
List<BitcoinAddressRecord> addrs;
|
||||
|
||||
if (addresses.isNotEmpty) {
|
||||
addrs = addresses
|
||||
.where((addr) => addr.isHidden == isHidden)
|
||||
.toList();
|
||||
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
|
||||
} else {
|
||||
addrs = await _createNewAddresses(
|
||||
isHidden
|
||||
? defaultChangeAddressesCount
|
||||
: defaultReceiveAddressesCount,
|
||||
isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
|
||||
startIndex: 0,
|
||||
hd: hd,
|
||||
isHidden: isHidden);
|
||||
}
|
||||
|
||||
while(hasAddrUse) {
|
||||
while (hasAddrUse) {
|
||||
final addr = addrs.last.address;
|
||||
hasAddrUse = await _hasAddressUsed(addr);
|
||||
|
||||
|
@ -204,11 +234,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
final start = addrs.length;
|
||||
final count = start + gap;
|
||||
final batch = await _createNewAddresses(
|
||||
count,
|
||||
startIndex: start,
|
||||
hd: hd,
|
||||
isHidden: isHidden);
|
||||
final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden);
|
||||
addrs.addAll(batch);
|
||||
}
|
||||
|
||||
|
@ -232,21 +258,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
||||
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
||||
final newAddresses = await _createNewAddresses(
|
||||
addressesCount,
|
||||
startIndex: countOfReceiveAddresses,
|
||||
hd: mainHd,
|
||||
isHidden: false);
|
||||
final newAddresses = await _createNewAddresses(addressesCount,
|
||||
startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false);
|
||||
addresses.addAll(newAddresses);
|
||||
}
|
||||
|
||||
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
||||
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
||||
final newAddresses = await _createNewAddresses(
|
||||
addressesCount,
|
||||
startIndex: countOfHiddenAddresses,
|
||||
hd: sideHd,
|
||||
isHidden: true);
|
||||
final newAddresses = await _createNewAddresses(addressesCount,
|
||||
startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true);
|
||||
addresses.addAll(newAddresses);
|
||||
}
|
||||
}
|
||||
|
@ -256,10 +276,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
final list = <BitcoinAddressRecord>[];
|
||||
|
||||
for (var i = startIndex; i < count + startIndex; i++) {
|
||||
final address = BitcoinAddressRecord(
|
||||
getAddress(index: i, hd: hd),
|
||||
index: i,
|
||||
isHidden: isHidden);
|
||||
final address =
|
||||
BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden);
|
||||
list.add(address);
|
||||
}
|
||||
|
||||
|
@ -278,4 +296,4 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
final transactionHistory = await electrumClient.getHistory(sh);
|
||||
return transactionHistory.isNotEmpty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
.fromSeed(seedBytes, network: networkType)
|
||||
.derivePath("m/0'/1"),
|
||||
networkType: networkType,);
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
}
|
||||
|
||||
static Future<LitecoinWallet> create({
|
||||
|
|
|
@ -57,6 +57,9 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes)
|
||||
.derivePath("m/44'/145'/0'/1"),
|
||||
networkType: networkType);
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ class AmountConverter {
|
|||
return _moneroAmountToString(amount);
|
||||
case CryptoCurrency.btc:
|
||||
case CryptoCurrency.bch:
|
||||
case CryptoCurrency.ltc:
|
||||
return _bitcoinAmountToString(amount);
|
||||
case CryptoCurrency.xhv:
|
||||
case CryptoCurrency.xag:
|
||||
|
|
|
@ -63,9 +63,17 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> generateNewAddress(Object wallet) async {
|
||||
Future<void> generateNewAddress(Object wallet, String label) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.walletAddresses.generateNewAddress();
|
||||
await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
|
||||
await wallet.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddress(Object wallet,String address, String label) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
bitcoinWallet.walletAddresses.updateAddress(address, label);
|
||||
await wallet.save();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -99,6 +107,21 @@ class CWBitcoin extends Bitcoin {
|
|||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
@computed
|
||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
return electrumWallet.walletAddresses.addresses
|
||||
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||
id: addr.index,
|
||||
name: addr.name,
|
||||
address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
|
||||
txCount: addr.txCount,
|
||||
balance: addr.balance,
|
||||
isChange: addr.isHidden))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
String getAddress(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
|
|
@ -68,7 +68,8 @@ void startCurrentWalletChangeReaction(
|
|||
.get<SharedPreferences>()
|
||||
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||
|
||||
if (wallet.type == WalletType.monero) {
|
||||
if (wallet.type == WalletType.monero || wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash ) {
|
||||
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
|
@ -15,6 +16,7 @@ import 'package:cake_wallet/utils/share_util.dart';
|
|||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
|
||||
|
@ -155,63 +157,27 @@ class AddressPage extends BasePage {
|
|||
amountController: _amountController,
|
||||
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
||||
ThemeType.light))),
|
||||
SizedBox(height: 16),
|
||||
Observer(builder: (_) {
|
||||
if (addressListViewModel.hasAddressList) {
|
||||
return GestureDetector(
|
||||
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled
|
||||
return SelectButton(
|
||||
text: addressListViewModel.buttonTitle,
|
||||
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled &&
|
||||
(WalletType.monero == addressListViewModel.wallet.type ||
|
||||
WalletType.haven == addressListViewModel.wallet.type)
|
||||
? await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
|
||||
context: context,
|
||||
builder: (_) => getIt.get<MoneroAccountListPage>())
|
||||
: Navigator.of(context).pushNamed(Routes.receive),
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.only(left: 24, right: 12),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||
border: Border.all(
|
||||
color:
|
||||
Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
width: 1),
|
||||
color: Theme.of(context)
|
||||
.extension<SyncIndicatorTheme>()!
|
||||
.syncedBackgroundColor),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) {
|
||||
String label = addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts_subaddresses
|
||||
: S.of(context).addresses;
|
||||
|
||||
if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
|
||||
label = addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts
|
||||
: S.of(context).account;
|
||||
}
|
||||
return Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.extension<SyncIndicatorTheme>()!
|
||||
.textColor),
|
||||
);
|
||||
},
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
arrowColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
|
||||
textSize: 14,
|
||||
height: 50,
|
||||
);
|
||||
} else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled ||
|
||||
addressListViewModel.showElectrumAddressDisclaimer) {
|
||||
addressListViewModel.isElectrumWallet) {
|
||||
return Text(S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
|
|
|
@ -11,29 +11,37 @@ class SelectButton extends StatelessWidget {
|
|||
this.isSelected = false,
|
||||
this.showTrailingIcon = true,
|
||||
this.height = 60,
|
||||
this.textSize = 18,
|
||||
this.color,
|
||||
this.textColor,
|
||||
this.arrowColor,
|
||||
this.borderColor,
|
||||
});
|
||||
|
||||
final Image? image;
|
||||
final String text;
|
||||
final double textSize;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final bool showTrailingIcon;
|
||||
final double height;
|
||||
final Color? color;
|
||||
final Color? textColor;
|
||||
final Color? arrowColor;
|
||||
final Color? borderColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = isSelected
|
||||
? Colors.green
|
||||
: Theme.of(context).cardColor;
|
||||
final textColor = isSelected
|
||||
final backgroundColor = color ?? (isSelected ? Colors.green : Theme.of(context).cardColor);
|
||||
final effectiveTextColor = textColor ?? (isSelected
|
||||
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final arrowColor = isSelected
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor);
|
||||
final effectiveArrowColor = arrowColor ?? (isSelected
|
||||
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
|
||||
: Theme.of(context).extension<FilterTheme>()!.titlesColor;
|
||||
: Theme.of(context).extension<FilterTheme>()!.titlesColor);
|
||||
|
||||
final selectArrowImage = Image.asset('assets/images/select_arrow.png',
|
||||
color: arrowColor);
|
||||
color: effectiveArrowColor);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
|
@ -44,7 +52,9 @@ class SelectButton extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
color: color
|
||||
color: backgroundColor,
|
||||
border: borderColor != null ? Border.all(color: borderColor!) : null,
|
||||
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
|
@ -63,9 +73,9 @@ class SelectButton extends StatelessWidget {
|
|||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontSize: textSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textColor
|
||||
color: effectiveTextColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -49,7 +49,7 @@ class ReceivePage extends BasePage {
|
|||
bool get gradientBackground => true;
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
bool get resizeToAvoidBottomInset => true;
|
||||
|
||||
final FocusNode _cryptoAmountFocus;
|
||||
|
||||
|
@ -99,10 +99,11 @@ class ReceivePage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final isElectrumWallet = addressListViewModel.isElectrumWallet;
|
||||
return (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.haven ||
|
||||
addressListViewModel.type == WalletType.nano ||
|
||||
addressListViewModel.type == WalletType.banano)
|
||||
isElectrumWallet)
|
||||
? KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
|
@ -140,7 +141,9 @@ class ReceivePage extends BasePage {
|
|||
|
||||
if (item is WalletAccountListHeader) {
|
||||
cell = HeaderTile(
|
||||
onTap: () async {
|
||||
showTrailingButton: true,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
trailingButtonTap: () async {
|
||||
if (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.haven) {
|
||||
await showPopUp<void>(
|
||||
|
@ -153,7 +156,7 @@ class ReceivePage extends BasePage {
|
|||
}
|
||||
},
|
||||
title: S.of(context).accounts,
|
||||
icon: Icon(
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
|
@ -161,16 +164,21 @@ class ReceivePage extends BasePage {
|
|||
}
|
||||
|
||||
if (item is WalletAddressListHeader) {
|
||||
cell = HeaderTile(
|
||||
onTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||
title: S.of(context).addresses,
|
||||
icon: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
cell = HeaderTile(
|
||||
title: S.of(context).addresses,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
|
||||
showSearchButton: true,
|
||||
trailingButtonTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||
trailingIcon: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListItem) {
|
||||
cell = Observer(builder: (_) {
|
||||
|
@ -185,6 +193,7 @@ class ReceivePage extends BasePage {
|
|||
|
||||
return AddressCell.fromItem(item,
|
||||
isCurrent: isCurrent,
|
||||
hasBalance: addressListViewModel.isElectrumWallet,
|
||||
backgroundColor: backgroundColor,
|
||||
textColor: textColor,
|
||||
onTap: (_) => addressListViewModel.setAddress(item),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
||||
class AddressCell extends StatelessWidget {
|
||||
AddressCell(
|
||||
|
@ -12,13 +13,18 @@ class AddressCell extends StatelessWidget {
|
|||
required this.backgroundColor,
|
||||
required this.textColor,
|
||||
this.onTap,
|
||||
this.onEdit});
|
||||
this.onEdit,
|
||||
this.txCount,
|
||||
this.balance,
|
||||
this.isChange = false,
|
||||
this.hasBalance = false});
|
||||
|
||||
factory AddressCell.fromItem(WalletAddressListItem item,
|
||||
{required bool isCurrent,
|
||||
required Color backgroundColor,
|
||||
required Color textColor,
|
||||
Function(String)? onTap,
|
||||
bool hasBalance = false,
|
||||
Function()? onEdit}) =>
|
||||
AddressCell(
|
||||
address: item.address,
|
||||
|
@ -28,7 +34,11 @@ class AddressCell extends StatelessWidget {
|
|||
backgroundColor: backgroundColor,
|
||||
textColor: textColor,
|
||||
onTap: onTap,
|
||||
onEdit: onEdit);
|
||||
onEdit: onEdit,
|
||||
txCount: item.txCount,
|
||||
balance: item.balance,
|
||||
isChange: item.isChange,
|
||||
hasBalance: hasBalance);
|
||||
|
||||
final String address;
|
||||
final String name;
|
||||
|
@ -38,17 +48,22 @@ class AddressCell extends StatelessWidget {
|
|||
final Color textColor;
|
||||
final Function(String)? onTap;
|
||||
final Function()? onEdit;
|
||||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
final bool hasBalance;
|
||||
|
||||
String get label {
|
||||
if (name.isEmpty){
|
||||
if(address.length<=16){
|
||||
return address;
|
||||
}else{
|
||||
return address.substring(0,8)+'...'+
|
||||
address.substring(address.length-8,address.length);
|
||||
}
|
||||
}else{
|
||||
return name;
|
||||
static const int addressPreviewLength = 8;
|
||||
|
||||
String get formattedAddress {
|
||||
final formatIfCashAddr = address.replaceAll('bitcoincash:', '');
|
||||
|
||||
if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) {
|
||||
return formatIfCashAddr;
|
||||
} else {
|
||||
return formatIfCashAddr.substring(0, addressPreviewLength) +
|
||||
'...' +
|
||||
formatIfCashAddr.substring(formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,41 +74,114 @@ class AddressCell extends StatelessWidget {
|
|||
child: Container(
|
||||
width: double.infinity,
|
||||
color: backgroundColor,
|
||||
padding: EdgeInsets.only(left: 24, right: 24, top: 28, bottom: 28),
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: textColor,
|
||||
),
|
||||
padding: EdgeInsets.only(left: 24, right: 24, top: 20, bottom: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (isChange)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Container(
|
||||
height: 20,
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: textColor),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
S.of(context).unspent_change,
|
||||
style: TextStyle(
|
||||
color: backgroundColor,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (name.isNotEmpty)
|
||||
Text(
|
||||
'$name - ',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
AutoSizeText(
|
||||
formattedAddress,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: isChange ? 10 : 14,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (hasBalance)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
'Balance: $balance',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${S.of(context).transactions.toLowerCase()}: $txCount',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
return Semantics(
|
||||
label: S.of(context).slidable,
|
||||
selected: isCurrent,
|
||||
enabled: !isCurrent,
|
||||
child: Slidable(
|
||||
key: Key(address),
|
||||
startActionPane: _actionPane(context),
|
||||
endActionPane: _actionPane(context),
|
||||
child: cell,
|
||||
),
|
||||
);
|
||||
return onEdit == null
|
||||
? cell
|
||||
: Semantics(
|
||||
label: S.of(context).slidable,
|
||||
selected: isCurrent,
|
||||
enabled: !isCurrent,
|
||||
child: Slidable(
|
||||
key: Key(address),
|
||||
startActionPane: _actionPane(context),
|
||||
endActionPane: _actionPane(context),
|
||||
child: cell,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ActionPane _actionPane(BuildContext context) => ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: 0.3,
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (_) => onEdit?.call(),
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.edit,
|
||||
label: S.of(context).edit,
|
||||
),
|
||||
],
|
||||
);
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: 0.3,
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (_) => onEdit?.call(),
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.edit,
|
||||
label: S.of(context).edit,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,50 +1,114 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HeaderTile extends StatelessWidget {
|
||||
class HeaderTile extends StatefulWidget {
|
||||
HeaderTile({
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
required this.icon
|
||||
required this.walletAddressListViewModel,
|
||||
this.showSearchButton = false,
|
||||
this.showTrailingButton = false,
|
||||
this.trailingButtonTap,
|
||||
this.trailingIcon,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final String title;
|
||||
final Icon icon;
|
||||
final WalletAddressListViewModel walletAddressListViewModel;
|
||||
final bool showSearchButton;
|
||||
final bool showTrailingButton;
|
||||
final VoidCallback? trailingButtonTap;
|
||||
final Icon? trailingIcon;
|
||||
|
||||
@override
|
||||
_HeaderTileState createState() => _HeaderTileState();
|
||||
}
|
||||
|
||||
class _HeaderTileState extends State<HeaderTile> {
|
||||
bool _isSearchActive = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: 24,
|
||||
right: 24,
|
||||
top: 24,
|
||||
bottom: 24
|
||||
),
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
||||
),
|
||||
Container(
|
||||
height: 32,
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsBackgroundColor),
|
||||
child: icon,
|
||||
)
|
||||
],
|
||||
),
|
||||
final searchIcon = Image.asset("assets/images/search_icon.png",
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor);
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
_isSearchActive
|
||||
? Expanded(
|
||||
child: TextField(
|
||||
onChanged: (value) => widget.walletAddressListViewModel.updateSearchText(value),
|
||||
cursorColor: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
cursorWidth: 0.5,
|
||||
decoration: InputDecoration(
|
||||
hintText: '${S.of(context).search}...',
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
widget.title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (widget.showSearchButton)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_isSearchActive = !_isSearchActive;
|
||||
widget.walletAddressListViewModel.updateSearchText('');
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
height: 32,
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.iconsBackgroundColor),
|
||||
child: searchIcon,
|
||||
)),
|
||||
const SizedBox(width: 8),
|
||||
if (widget.showTrailingButton)
|
||||
GestureDetector(
|
||||
onTap: widget.trailingButtonTap,
|
||||
child: Container(
|
||||
height: 32,
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color:
|
||||
Theme.of(context).extension<ReceivePageTheme>()!.iconsBackgroundColor),
|
||||
child: widget.trailingIcon,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
@ -6,6 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dar
|
|||
import 'package:cake_wallet/utils/brightness_util.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
@ -144,9 +146,10 @@ class QRWidget extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
child: AutoSizeText(
|
||||
addressListViewModel.address.address,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: addressListViewModel.wallet.type == WalletType.monero ? 2 : 1,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
|
|
@ -38,7 +38,11 @@ abstract class PrivacySettingsViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
bool get isAutoGenerateSubaddressesVisible => _wallet.type == WalletType.monero;
|
||||
bool get isAutoGenerateSubaddressesVisible =>
|
||||
_wallet.type == WalletType.monero ||
|
||||
_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
@computed
|
||||
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
|
@ -33,7 +32,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
state = AddressEditOrCreateStateInitial(),
|
||||
label = item?.name ?? '',
|
||||
_item = item,
|
||||
_wallet = wallet;
|
||||
_wallet = wallet;
|
||||
|
||||
@observable
|
||||
AddressEditOrCreateState state;
|
||||
|
@ -46,6 +45,10 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
final WalletAddressListItem? _item;
|
||||
final WalletBase _wallet;
|
||||
|
||||
bool get isElectrum => _wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.bitcoinCash ||
|
||||
_wallet.type == WalletType.litecoin;
|
||||
|
||||
Future<void> save() async {
|
||||
try {
|
||||
state = AddressIsSaving();
|
||||
|
@ -65,12 +68,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
Future<void> _createNew() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet.type == WalletType.bitcoin
|
||||
|| wallet.type == WalletType.litecoin
|
||||
|| wallet.type == WalletType.bitcoinCash) {
|
||||
await bitcoin!.generateNewAddress(wallet);
|
||||
await wallet.save();
|
||||
}
|
||||
if (isElectrum) await bitcoin!.generateNewAddress(wallet, label);
|
||||
|
||||
if (wallet.type == WalletType.monero) {
|
||||
await monero
|
||||
|
@ -96,10 +94,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
Future<void> _update() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
/*if (wallet is BitcoinWallet) {
|
||||
await wallet.walletAddresses.updateAddress(_item.address as String);
|
||||
await wallet.save();
|
||||
}*/
|
||||
if (isElectrum) await bitcoin!.updateAddress(wallet, _item!.address, label);
|
||||
|
||||
final index = _item?.id;
|
||||
if (index != null) {
|
||||
if (wallet.type == WalletType.monero) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class WalletAddressListItem extends ListItem {
|
||||
|
@ -6,13 +5,19 @@ class WalletAddressListItem extends ListItem {
|
|||
required this.address,
|
||||
required this.isPrimary,
|
||||
this.id,
|
||||
this.name})
|
||||
this.name,
|
||||
this.txCount,
|
||||
this.balance,
|
||||
this.isChange = false})
|
||||
: super();
|
||||
|
||||
final int? id;
|
||||
final bool isPrimary;
|
||||
final String address;
|
||||
final String? name;
|
||||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
|
||||
@override
|
||||
String toString() => name ?? address;
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobx/mobx.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_address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cw_core/amount_converter.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'wallet_address_list_view_model.g.dart';
|
||||
|
||||
|
@ -110,7 +114,8 @@ class EthereumURI extends PaymentURI {
|
|||
|
||||
class BitcoinCashURI extends PaymentURI {
|
||||
BitcoinCashURI({required String amount, required String address})
|
||||
: super(amount: amount, address: address);
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = address;
|
||||
|
@ -121,9 +126,7 @@ class BitcoinCashURI extends PaymentURI {
|
|||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class NanoURI extends PaymentURI {
|
||||
NanoURI({required String amount, required String address})
|
||||
|
@ -167,6 +170,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
hasAccounts =
|
||||
appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven,
|
||||
amount = '',
|
||||
_settingsStore = appStore.settingsStore,
|
||||
super(appStore: appStore) {
|
||||
_init();
|
||||
}
|
||||
|
@ -184,12 +188,28 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
final NumberFormat _cryptoNumberFormat;
|
||||
|
||||
final FiatConversionStore fiatConversionStore;
|
||||
final SettingsStore _settingsStore;
|
||||
|
||||
List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
|
||||
|
||||
String get buttonTitle {
|
||||
if (isElectrumWallet) {
|
||||
return S.current.addresses;
|
||||
}
|
||||
|
||||
if (isAutoGenerateSubaddressEnabled) {
|
||||
return hasAccounts ? S.current.accounts : S.current.account;
|
||||
}
|
||||
|
||||
return hasAccounts ? S.current.accounts_subaddresses : S.current.addresses;
|
||||
}
|
||||
|
||||
@observable
|
||||
Currency selectedCurrency;
|
||||
|
||||
@observable
|
||||
String searchText = '';
|
||||
|
||||
@computed
|
||||
int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency);
|
||||
|
||||
|
@ -277,14 +297,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
addressList.addAll(addressItems);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
final primaryAddress = bitcoin!.getAddress(wallet);
|
||||
final bitcoinAddresses = bitcoin!.getAddresses(wallet).map((addr) {
|
||||
final isPrimary = addr == primaryAddress;
|
||||
if (isElectrumWallet) {
|
||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
|
||||
return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr);
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
balance: AmountConverter.amountIntToString(
|
||||
walletTypeToCryptoCurrency(type), subaddress.balance),
|
||||
isChange: subaddress.isChange);
|
||||
});
|
||||
addressList.addAll(bitcoinAddresses);
|
||||
addressList.addAll(addressItems);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.ethereum) {
|
||||
|
@ -299,6 +326,15 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||
}
|
||||
|
||||
if (searchText.isNotEmpty) {
|
||||
return ObservableList.of(addressList.where((item) {
|
||||
if (item is WalletAddressListItem) {
|
||||
return item.address.toLowerCase().contains(searchText.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
|
@ -321,15 +357,23 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
@computed
|
||||
bool get hasAddressList =>
|
||||
wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.haven;/* ||
|
||||
wallet.type == WalletType.nano ||
|
||||
wallet.type == WalletType.banano;*/// TODO: nano accounts are disabled for now
|
||||
wallet.type == WalletType.haven ||
|
||||
wallet.type == WalletType.bitcoinCash ||
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.litecoin;
|
||||
|
||||
// wallet.type == WalletType.nano ||
|
||||
// wallet.type == WalletType.banano; TODO: nano accounts are disabled for now
|
||||
|
||||
@computed
|
||||
bool get showElectrumAddressDisclaimer =>
|
||||
bool get isElectrumWallet =>
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash;
|
||||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
@computed
|
||||
bool get isAutoGenerateSubaddressEnabled =>
|
||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
||||
|
||||
List<ListItem> _baseItems;
|
||||
|
||||
|
@ -343,9 +387,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
_baseItems = [];
|
||||
|
||||
if (wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.haven /*||
|
||||
wallet.type ==
|
||||
WalletType
|
||||
.haven /*||
|
||||
wallet.type == WalletType.nano ||
|
||||
wallet.type == WalletType.banano*/) {
|
||||
wallet.type == WalletType.banano*/
|
||||
) {
|
||||
_baseItems.add(WalletAccountListHeader());
|
||||
}
|
||||
|
||||
|
@ -367,6 +414,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void updateSearchText(String text) {
|
||||
searchText = text;
|
||||
}
|
||||
|
||||
void _convertAmountToCrypto() {
|
||||
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
|
||||
try {
|
||||
|
|
|
@ -64,6 +64,7 @@ import 'package:cw_core/output_info.dart';
|
|||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';""";
|
||||
const bitcoinCWHeaders = """
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
|
@ -76,9 +77,27 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
|||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
""";
|
||||
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
|
||||
const bitcoinContent = """
|
||||
|
||||
class ElectrumSubAddress {
|
||||
ElectrumSubAddress({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.address,
|
||||
required this.txCount,
|
||||
required this.balance,
|
||||
required this.isChange});
|
||||
final int id;
|
||||
final String name;
|
||||
final String address;
|
||||
final int txCount;
|
||||
final int balance;
|
||||
final bool isChange;
|
||||
}
|
||||
|
||||
abstract class Bitcoin {
|
||||
TransactionPriority getMediumTransactionPriority();
|
||||
|
||||
|
@ -92,13 +111,16 @@ abstract class Bitcoin {
|
|||
TransactionPriority deserializeBitcoinTransactionPriority(int raw);
|
||||
TransactionPriority deserializeLitecoinTransactionPriority(int raw);
|
||||
int getFeeRate(Object wallet, TransactionPriority priority);
|
||||
Future<void> generateNewAddress(Object wallet);
|
||||
Future<void> generateNewAddress(Object wallet, String label);
|
||||
Future<void> updateAddress(Object wallet,String address, String label);
|
||||
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate});
|
||||
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate});
|
||||
|
||||
List<String> getAddresses(Object wallet);
|
||||
String getAddress(Object wallet);
|
||||
|
||||
List<ElectrumSubAddress> getSubAddresses(Object wallet);
|
||||
|
||||
String formatterBitcoinAmountToString({required int amount});
|
||||
double formatterBitcoinAmountToDouble({required int amount});
|
||||
int formatterStringDoubleToBitcoinAmount(String amount);
|
||||
|
|
Loading…
Reference in a new issue