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>
Serhii committed
Jan 23, 2024 at 07:15 UTC
9754d676012d2d5195ac0d1f56bc8c7f5f06c373
20 files changed
+662
-328
cw_bitcoin/lib/bitcoin_address_record.dart
+48
-18
@@ -1,40 +1,70 @@
1
import 'dart:convert';
2
+import 'package:bitbox/bitbox.dart' as bitbox;
3
4
class BitcoinAddressRecord {
4
- BitcoinAddressRecord(this.address,
5
- {required this.index, this.isHidden = false, bool isUsed = false})
6
- : _isUsed = isUsed;
5
+ BitcoinAddressRecord(
6
+ this.address, {
7
+ required this.index,
8
+ this.isHidden = false,
9
+ int txCount = 0,
10
+ int balance = 0,
11
+ String name = '',
12
+ bool isUsed = false,
13
+ }) : _txCount = txCount,
14
+ _balance = balance,
15
+ _name = name,
16
+ _isUsed = isUsed;
17
18
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
19
final decoded = json.decode(jsonSource) as Map;
20
11
- return BitcoinAddressRecord(
12
- decoded['address'] as String,
13
- index: decoded['index'] as int,
14
- isHidden: decoded['isHidden'] as bool? ?? false,
15
- isUsed: decoded['isUsed'] as bool? ?? false);
21
+ return BitcoinAddressRecord(decoded['address'] as String,
22
+ index: decoded['index'] as int,
23
+ isHidden: decoded['isHidden'] as bool? ?? false,
24
+ isUsed: decoded['isUsed'] as bool? ?? false,
25
+ txCount: decoded['txCount'] as int? ?? 0,
26
+ name: decoded['name'] as String? ?? '',
27
+ balance: decoded['balance'] as int? ?? 0);
28
}
29
18
- @override
19
- bool operator ==(Object o) =>
20
- o is BitcoinAddressRecord && address == o.address;
21
-
30
final String address;
31
final bool isHidden;
32
final int index;
33
+ int _txCount;
34
+ int _balance;
35
+ String _name;
36
+ bool _isUsed;
37
+
38
+ int get txCount => _txCount;
39
+
40
+ String get name => _name;
41
+
42
+ int get balance => _balance;
43
+
44
+ set txCount(int value) => _txCount = value;
45
+
46
+ set balance(int value) => _balance = value;
47
+
48
bool get isUsed => _isUsed;
49
50
+ void setAsUsed() => _isUsed = true;
51
+ void setNewName(String label) => _name = label;
52
+
53
@override
28
- int get hashCode => address.hashCode;
54
+ bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
55
30
- bool _isUsed;
56
+ @override
57
+ int get hashCode => address.hashCode;
58
32
- void setAsUsed() => _isUsed = true;
59
+ String get cashAddr => bitbox.Address.toCashAddress(address);
60
34
- String toJSON() =>
35
- json.encode({
61
+ String toJSON() => json.encode({
62
'address': address,
63
'index': index,
64
'isHidden': isHidden,
39
- 'isUsed': isUsed});
65
+ 'txCount': txCount,
66
+ 'name': name,
67
+ 'isUsed': isUsed,
68
+ 'balance': balance,
69
+ });
70
}
cw_bitcoin/lib/bitcoin_wallet.dart
+3
@@ -47,6 +47,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
47
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
48
.derivePath("m/0'/1"),
49
networkType: networkType);
50
+ autorun((_) {
51
+ this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
52
+ });
53
}
54
55
static Future<BitcoinWallet> create({
cw_bitcoin/lib/electrum_wallet.dart
+58
-25
@@ -63,6 +63,7 @@ abstract class ElectrumWalletBase
63
_password = password,
64
_feeRates = <int>[],
65
_isTransactionUpdating = false,
66
+ isEnabledAutoGenerateSubaddress = true,
67
unspentCoins = [],
68
_scripthashesUpdateSubject = {},
69
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
@@ -87,6 +88,10 @@ abstract class ElectrumWalletBase
88
final bitcoin.HDWallet hd;
89
final String mnemonic;
90
91
+ @override
92
+ @observable
93
+ bool isEnabledAutoGenerateSubaddress;
94
+
95
late ElectrumClient electrumClient;
96
Box<UnspentCoinsInfo> unspentCoinsInfo;
97
@@ -583,38 +588,66 @@ abstract class ElectrumWalletBase
588
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
589
final addressHashes = <String, BitcoinAddressRecord>{};
590
final normalizedHistories = <Map<String, dynamic>>[];
591
+ final newTxCounts = <String, int>{};
592
+
593
walletAddresses.addresses.forEach((addressRecord) {
594
final sh = scriptHash(addressRecord.address, networkType: networkType);
595
addressHashes[sh] = addressRecord;
596
+ newTxCounts[sh] = 0;
597
});
590
- final histories = addressHashes.keys.map((scriptHash) =>
591
- electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
592
- final historyResults = await Future.wait(histories);
593
- historyResults.forEach((history) {
594
- history.entries.forEach((historyItem) {
595
- if (historyItem.value.isNotEmpty) {
596
- final address = addressHashes[historyItem.key];
597
- address?.setAsUsed();
598
- normalizedHistories.addAll(historyItem.value);
599
- }
598
+
599
+ try {
600
+ final histories = addressHashes.keys.map((scriptHash) =>
601
+ electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
602
+ final historyResults = await Future.wait(histories);
603
+
604
+
605
+
606
+ historyResults.forEach((history) {
607
+ history.entries.forEach((historyItem) {
608
+ if (historyItem.value.isNotEmpty) {
609
+ final address = addressHashes[historyItem.key];
610
+ address?.setAsUsed();
611
+ newTxCounts[historyItem.key] = historyItem.value.length;
612
+ normalizedHistories.addAll(historyItem.value);
613
+ }
614
+ });
615
});
601
- });
602
- final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
603
- try {
604
- return fetchTransactionInfo(
605
- hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
606
- } catch (_) {
607
- return Future.value(null);
616
+
617
+ for (var sh in addressHashes.keys) {
618
+ var balanceData = await electrumClient.getBalance(sh);
619
+ var addressRecord = addressHashes[sh];
620
+ if (addressRecord != null) {
621
+ addressRecord.balance = balanceData['confirmed'] as int? ?? 0;
622
+ }
623
}
609
- }));
610
- return historiesWithDetails
611
- .fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
612
- if (tx == null) {
624
+
625
+
626
+ addressHashes.forEach((sh, addressRecord) {
627
+ addressRecord.txCount = newTxCounts[sh] ?? 0;
628
+ });
629
+
630
+ final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
631
+ try {
632
+ return fetchTransactionInfo(
633
+ hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
634
+ } catch (_) {
635
+ return Future.value(null);
636
+ }
637
+ }));
638
+
639
+ return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
640
+ <String, ElectrumTransactionInfo>{}, (acc, tx) {
641
+ if (tx == null) {
642
+ return acc;
643
+ }
644
+ acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
645
return acc;
614
- }
615
- acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
616
- return acc;
617
- });
646
+ });
647
+ } catch (e) {
648
+ print(e.toString());
649
+ return {};
650
+ }
651
}
652
653
Future<void> updateTransactions() async {
cw_bitcoin/lib/electrum_wallet_addresses.dart
+98
-80
@@ -1,5 +1,5 @@
1
-import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
1
import 'package:bitbox/bitbox.dart' as bitbox;
2
+import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3
import 'package:cw_bitcoin/bitcoin_address_record.dart';
4
import 'package:cw_bitcoin/electrum.dart';
5
import 'package:cw_bitcoin/script_hash.dart';
@@ -10,8 +10,7 @@ import 'package:mobx/mobx.dart';
10
11
part 'electrum_wallet_addresses.g.dart';
12
13
-class ElectrumWalletAddresses = ElectrumWalletAddressesBase
14
- with _$ElectrumWalletAddresses;
13
+class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
14
15
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
16
ElectrumWalletAddressesBase(WalletInfo walletInfo,
@@ -22,19 +21,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
21
List<BitcoinAddressRecord>? initialAddresses,
22
int initialRegularAddressIndex = 0,
23
int initialChangeAddressIndex = 0})
25
- : addresses = ObservableList<BitcoinAddressRecord>.of(
26
- (initialAddresses ?? []).toSet()),
27
- receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
28
- (initialAddresses ?? [])
24
+ : addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
25
+ receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
26
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
30
- .toSet()),
31
- changeAddresses = ObservableList<BitcoinAddressRecord>.of(
32
- (initialAddresses ?? [])
27
+ .toSet()),
28
+ changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
29
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
34
- .toSet()),
30
+ .toSet()),
31
currentReceiveAddressIndex = initialRegularAddressIndex,
32
currentChangeAddressIndex = initialChangeAddressIndex,
37
- super(walletInfo);
33
+ super(walletInfo);
34
35
static const defaultReceiveAddressesCount = 22;
36
static const defaultChangeAddressesCount = 17;
@@ -42,6 +38,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
38
39
static String toCashAddr(String address) => bitbox.Address.toCashAddress(address);
40
41
+ static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
42
+
43
final ObservableList<BitcoinAddressRecord> addresses;
44
final ObservableList<BitcoinAddressRecord> receiveAddresses;
45
final ObservableList<BitcoinAddressRecord> changeAddresses;
@@ -53,41 +51,67 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
51
@override
52
@computed
53
String get address {
56
- if (receiveAddresses.isEmpty) {
57
- final address = generateNewAddress().address;
58
- return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address;
59
- }
60
- final receiveAddress = receiveAddresses.first.address;
54
+ if (isEnabledAutoGenerateSubaddress) {
55
+ if (receiveAddresses.isEmpty) {
56
+ final newAddress = generateNewAddress().address;
57
+ return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
58
+ }
59
+ final receiveAddress = receiveAddresses.first.address;
60
62
- return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
61
+ return walletInfo.type == WalletType.bitcoinCash
62
+ ? toCashAddr(receiveAddress)
63
+ : receiveAddress;
64
+ } else {
65
+ final receiveAddress = (receiveAddresses.first.address != addresses.first.address &&
66
+ previousAddressRecord != null)
67
+ ? previousAddressRecord!.address
68
+ : addresses.first.address;
69
+
70
+ return walletInfo.type == WalletType.bitcoinCash
71
+ ? toCashAddr(receiveAddress)
72
+ : receiveAddress;
73
+ }
74
}
75
76
+ @observable
77
+ bool isEnabledAutoGenerateSubaddress = true;
78
+
79
@override
66
- String get primaryAddress => getAddress(index: 0, hd: mainHd);
80
+ set address(String addr) {
81
+ if (addr.startsWith('bitcoincash:')) {
82
+ addr = toLegacy(addr);
83
+ }
84
+ final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr);
85
+
86
+ previousAddressRecord = addressRecord;
87
+ receiveAddresses.remove(addressRecord);
88
+ receiveAddresses.insert(0, addressRecord);
89
+ }
90
91
@override
69
- set address(String addr) => null;
92
+ String get primaryAddress => getAddress(index: 0, hd: mainHd);
93
94
int currentReceiveAddressIndex;
95
int currentChangeAddressIndex;
96
97
+ @observable
98
+ BitcoinAddressRecord? previousAddressRecord;
99
+
100
@computed
75
- int get totalCountOfReceiveAddresses =>
76
- addresses.fold(0, (acc, addressRecord) {
77
- if (!addressRecord.isHidden) {
78
- return acc + 1;
79
- }
80
- return acc;
81
- });
101
+ int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
102
+ if (!addressRecord.isHidden) {
103
+ return acc + 1;
104
+ }
105
+ return acc;
106
+ });
107
108
@computed
84
- int get totalCountOfChangeAddresses =>
85
- addresses.fold(0, (acc, addressRecord) {
86
- if (addressRecord.isHidden) {
87
- return acc + 1;
88
- }
89
- return acc;
90
- });
109
+ int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
110
+ if (addressRecord.isHidden) {
111
+ return acc + 1;
112
+ }
113
+ return acc;
114
+ });
115
116
Future<void> discoverAddresses() async {
117
await _discoverAddresses(mainHd, false);
@@ -117,11 +141,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
141
142
if (changeAddresses.isEmpty) {
143
final newAddresses = await _createNewAddresses(gap,
120
- hd: sideHd,
121
- startIndex: totalCountOfChangeAddresses > 0
122
- ? totalCountOfChangeAddresses - 1
123
- : 0,
124
- isHidden: true);
144
+ hd: sideHd,
145
+ startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
146
+ isHidden: true);
147
_addAddresses(newAddresses);
148
}
149
@@ -135,14 +157,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
157
return address;
158
}
159
138
- BitcoinAddressRecord generateNewAddress(
139
- {bitcoin.HDWallet? hd, bool isHidden = false}) {
140
- currentReceiveAddressIndex += 1;
141
- // FIX-ME: Check logic for whichi HD should be used here ???
142
- final address = BitcoinAddressRecord(
143
- getAddress(index: currentReceiveAddressIndex, hd: hd ?? sideHd),
144
- index: currentReceiveAddressIndex,
145
- isHidden: isHidden);
160
+ BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) {
161
+ final isHidden = hd == sideHd;
162
+
163
+ final newAddressIndex = addresses.fold(
164
+ 0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc);
165
+
166
+ final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd),
167
+ index: newAddressIndex, isHidden: isHidden, name: label ?? '');
168
addresses.add(address);
169
return address;
170
}
@@ -160,20 +182,32 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
182
}
183
}
184
185
+ @action
186
+ void updateAddress(String address, String label) {
187
+ if (address.startsWith('bitcoincash:')) {
188
+ address = toLegacy(address);
189
+ }
190
+ final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address);
191
+ addressRecord.setNewName(label);
192
+ final index = addresses.indexOf(addressRecord);
193
+ addresses.remove(addressRecord);
194
+ addresses.insert(index, addressRecord);
195
+ }
196
+
197
@action
198
void updateReceiveAddresses() {
199
receiveAddresses.removeRange(0, receiveAddresses.length);
166
- final newAdresses = addresses
167
- .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
168
- receiveAddresses.addAll(newAdresses);
200
+ final newAddresses =
201
+ addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
202
+ receiveAddresses.addAll(newAddresses);
203
}
204
205
@action
206
void updateChangeAddresses() {
207
changeAddresses.removeRange(0, changeAddresses.length);
174
- final newAdresses = addresses
175
- .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
176
- changeAddresses.addAll(newAdresses);
208
+ final newAddresses =
209
+ addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
210
+ changeAddresses.addAll(newAddresses);
211
}
212
213
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
@@ -181,20 +215,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
215
List<BitcoinAddressRecord> addrs;
216
217
if (addresses.isNotEmpty) {
184
- addrs = addresses
185
- .where((addr) => addr.isHidden == isHidden)
186
- .toList();
218
+ addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
219
} else {
220
addrs = await _createNewAddresses(
189
- isHidden
190
- ? defaultChangeAddressesCount
191
- : defaultReceiveAddressesCount,
221
+ isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
222
startIndex: 0,
223
hd: hd,
224
isHidden: isHidden);
225
}
226
197
- while(hasAddrUse) {
227
+ while (hasAddrUse) {
228
final addr = addrs.last.address;
229
hasAddrUse = await _hasAddressUsed(addr);
230
@@ -204,11 +234,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
234
235
final start = addrs.length;
236
final count = start + gap;
207
- final batch = await _createNewAddresses(
208
- count,
209
- startIndex: start,
210
- hd: hd,
211
- isHidden: isHidden);
237
+ final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden);
238
addrs.addAll(batch);
239
}
240
@@ -232,21 +258,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
258
259
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
260
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
235
- final newAddresses = await _createNewAddresses(
236
- addressesCount,
237
- startIndex: countOfReceiveAddresses,
238
- hd: mainHd,
239
- isHidden: false);
261
+ final newAddresses = await _createNewAddresses(addressesCount,
262
+ startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false);
263
addresses.addAll(newAddresses);
264
}
265
266
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
267
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
245
- final newAddresses = await _createNewAddresses(
246
- addressesCount,
247
- startIndex: countOfHiddenAddresses,
248
- hd: sideHd,
249
- isHidden: true);
268
+ final newAddresses = await _createNewAddresses(addressesCount,
269
+ startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true);
270
addresses.addAll(newAddresses);
271
}
272
}
@@ -256,10 +276,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
276
final list = <BitcoinAddressRecord>[];
277
278
for (var i = startIndex; i < count + startIndex; i++) {
259
- final address = BitcoinAddressRecord(
260
- getAddress(index: i, hd: hd),
261
- index: i,
262
- isHidden: isHidden);
279
+ final address =
280
+ BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden);
281
list.add(address);
282
}
283
@@ -278,4 +296,4 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
296
final transactionHistory = await electrumClient.getHistory(sh);
297
return transactionHistory.isNotEmpty;
298
}
281
-}
\ No newline at end of file
299
+}
cw_bitcoin/lib/litecoin_wallet.dart
+3
@@ -51,6 +51,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
51
.fromSeed(seedBytes, network: networkType)
52
.derivePath("m/0'/1"),
53
networkType: networkType,);
54
+ autorun((_) {
55
+ this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
56
+ });
57
}
58
59
static Future<LitecoinWallet> create({
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+3
@@ -57,6 +57,9 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
57
sideHd: bitcoin.HDWallet.fromSeed(seedBytes)
58
.derivePath("m/44'/145'/0'/1"),
59
networkType: networkType);
60
+ autorun((_) {
61
+ this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
62
+ });
63
}
64
65
cw_core/lib/amount_converter.dart
+1
@@ -81,6 +81,7 @@ class AmountConverter {
81
return _moneroAmountToString(amount);
82
case CryptoCurrency.btc:
83
case CryptoCurrency.bch:
84
+ case CryptoCurrency.ltc:
85
return _bitcoinAmountToString(amount);
86
case CryptoCurrency.xhv:
87
case CryptoCurrency.xag:
lib/bitcoin/cw_bitcoin.dart
+25
-2
@@ -63,9 +63,17 @@ class CWBitcoin extends Bitcoin {
63
}
64
65
@override
66
- Future<void> generateNewAddress(Object wallet) async {
66
+ Future<void> generateNewAddress(Object wallet, String label) async {
67
final bitcoinWallet = wallet as ElectrumWallet;
68
- await bitcoinWallet.walletAddresses.generateNewAddress();
68
+ await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
69
+ await wallet.save();
70
+ }
71
+
72
+ @override
73
+ Future<void> updateAddress(Object wallet,String address, String label) async {
74
+ final bitcoinWallet = wallet as ElectrumWallet;
75
+ bitcoinWallet.walletAddresses.updateAddress(address, label);
76
+ await wallet.save();
77
}
78
79
@override
@@ -99,6 +107,21 @@ class CWBitcoin extends Bitcoin {
107
.toList();
108
}
109
110
+ @override
111
+ @computed
112
+ List<ElectrumSubAddress> getSubAddresses(Object wallet) {
113
+ final electrumWallet = wallet as ElectrumWallet;
114
+ return electrumWallet.walletAddresses.addresses
115
+ .map((BitcoinAddressRecord addr) => ElectrumSubAddress(
116
+ id: addr.index,
117
+ name: addr.name,
118
+ address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
119
+ txCount: addr.txCount,
120
+ balance: addr.balance,
121
+ isChange: addr.isHidden))
122
+ .toList();
123
+ }
124
+
125
@override
126
String getAddress(Object wallet) {
127
final bitcoinWallet = wallet as ElectrumWallet;
lib/reactions/on_current_wallet_change.dart
+2
-1
@@ -68,7 +68,8 @@ void startCurrentWalletChangeReaction(
68
.get<SharedPreferences>()
69
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
70
71
- if (wallet.type == WalletType.monero) {
71
+ if (wallet.type == WalletType.monero || wallet.type == WalletType.bitcoin ||
72
+ wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash ) {
73
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
74
}
75
lib/src/screens/dashboard/pages/address_page.dart
+17
-51
@@ -1,3 +1,4 @@
1
+import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
2
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
3
import 'package:cake_wallet/di.dart';
4
import 'package:cake_wallet/src/screens/base_page.dart';
@@ -15,6 +16,7 @@ import 'package:cake_wallet/utils/share_util.dart';
16
import 'package:cake_wallet/utils/show_pop_up.dart';
17
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
18
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
19
+import 'package:cw_core/wallet_type.dart';
20
import 'package:flutter/material.dart';
21
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
22
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
@@ -155,63 +157,27 @@ class AddressPage extends BasePage {
157
amountController: _amountController,
158
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
159
ThemeType.light))),
160
+ SizedBox(height: 16),
161
Observer(builder: (_) {
162
if (addressListViewModel.hasAddressList) {
160
- return GestureDetector(
161
- onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled
163
+ return SelectButton(
164
+ text: addressListViewModel.buttonTitle,
165
+ onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled &&
166
+ (WalletType.monero == addressListViewModel.wallet.type ||
167
+ WalletType.haven == addressListViewModel.wallet.type)
168
? await showPopUp<void>(
163
- context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
169
+ context: context,
170
+ builder: (_) => getIt.get<MoneroAccountListPage>())
171
: Navigator.of(context).pushNamed(Routes.receive),
165
- child: Container(
166
- height: 50,
167
- padding: EdgeInsets.only(left: 24, right: 12),
168
- alignment: Alignment.center,
169
- decoration: BoxDecoration(
170
- borderRadius: BorderRadius.all(Radius.circular(25)),
171
- border: Border.all(
172
- color:
173
- Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
174
- width: 1),
175
- color: Theme.of(context)
176
- .extension<SyncIndicatorTheme>()!
177
- .syncedBackgroundColor),
178
- child: Row(
179
- mainAxisSize: MainAxisSize.max,
180
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
181
- children: <Widget>[
182
- Observer(
183
- builder: (_) {
184
- String label = addressListViewModel.hasAccounts
185
- ? S.of(context).accounts_subaddresses
186
- : S.of(context).addresses;
187
-
188
- if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
189
- label = addressListViewModel.hasAccounts
190
- ? S.of(context).accounts
191
- : S.of(context).account;
192
- }
193
- return Text(
194
- label,
195
- style: TextStyle(
196
- fontSize: 14,
197
- fontWeight: FontWeight.w500,
198
- color: Theme.of(context)
199
- .extension<SyncIndicatorTheme>()!
200
- .textColor),
201
- );
202
- },
203
- ),
204
- Icon(
205
- Icons.arrow_forward_ios,
206
- size: 14,
207
- color: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
208
- )
209
- ],
210
- ),
211
- ),
172
+ textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
173
+ color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
174
+ borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
175
+ arrowColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
176
+ textSize: 14,
177
+ height: 50,
178
);
179
} else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled ||
214
- addressListViewModel.showElectrumAddressDisclaimer) {
180
+ addressListViewModel.isElectrumWallet) {
181
return Text(S.of(context).electrum_address_disclaimer,
182
textAlign: TextAlign.center,
183
style: TextStyle(
lib/src/screens/new_wallet/widgets/select_button.dart
+21
-11
@@ -11,29 +11,37 @@ class SelectButton extends StatelessWidget {
11
this.isSelected = false,
12
this.showTrailingIcon = true,
13
this.height = 60,
14
+ this.textSize = 18,
15
+ this.color,
16
+ this.textColor,
17
+ this.arrowColor,
18
+ this.borderColor,
19
});
20
21
final Image? image;
22
final String text;
23
+ final double textSize;
24
final bool isSelected;
25
final VoidCallback onTap;
26
final bool showTrailingIcon;
27
final double height;
28
+ final Color? color;
29
+ final Color? textColor;
30
+ final Color? arrowColor;
31
+ final Color? borderColor;
32
33
@override
34
Widget build(BuildContext context) {
25
- final color = isSelected
26
- ? Colors.green
27
- : Theme.of(context).cardColor;
28
- final textColor = isSelected
35
+ final backgroundColor = color ?? (isSelected ? Colors.green : Theme.of(context).cardColor);
36
+ final effectiveTextColor = textColor ?? (isSelected
37
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
30
- : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
31
- final arrowColor = isSelected
38
+ : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor);
39
+ final effectiveArrowColor = arrowColor ?? (isSelected
40
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
33
- : Theme.of(context).extension<FilterTheme>()!.titlesColor;
41
+ : Theme.of(context).extension<FilterTheme>()!.titlesColor);
42
43
final selectArrowImage = Image.asset('assets/images/select_arrow.png',
36
- color: arrowColor);
44
+ color: effectiveArrowColor);
45
46
return GestureDetector(
47
onTap: onTap,
@@ -44,7 +52,9 @@ class SelectButton extends StatelessWidget {
52
alignment: Alignment.center,
53
decoration: BoxDecoration(
54
borderRadius: BorderRadius.all(Radius.circular(30)),
47
- color: color
55
+ color: backgroundColor,
56
+ border: borderColor != null ? Border.all(color: borderColor!) : null,
57
+
58
),
59
child: Row(
60
mainAxisSize: MainAxisSize.max,
@@ -63,9 +73,9 @@ class SelectButton extends StatelessWidget {
73
child: Text(
74
text,
75
style: TextStyle(
66
- fontSize: 18,
76
+ fontSize: textSize,
77
fontWeight: FontWeight.w500,
68
- color: textColor
78
+ color: effectiveTextColor,
79
),
80
),
81
)
lib/src/screens/receive/receive_page.dart
+23
-14
@@ -49,7 +49,7 @@ class ReceivePage extends BasePage {
49
bool get gradientBackground => true;
50
51
@override
52
- bool get resizeToAvoidBottomInset => false;
52
+ bool get resizeToAvoidBottomInset => true;
53
54
final FocusNode _cryptoAmountFocus;
55
@@ -99,10 +99,11 @@ class ReceivePage extends BasePage {
99
100
@override
101
Widget body(BuildContext context) {
102
+ final isElectrumWallet = addressListViewModel.isElectrumWallet;
103
return (addressListViewModel.type == WalletType.monero ||
104
addressListViewModel.type == WalletType.haven ||
105
addressListViewModel.type == WalletType.nano ||
105
- addressListViewModel.type == WalletType.banano)
106
+ isElectrumWallet)
107
? KeyboardActions(
108
config: KeyboardActionsConfig(
109
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
@@ -140,7 +141,9 @@ class ReceivePage extends BasePage {
141
142
if (item is WalletAccountListHeader) {
143
cell = HeaderTile(
143
- onTap: () async {
144
+ showTrailingButton: true,
145
+ walletAddressListViewModel: addressListViewModel,
146
+ trailingButtonTap: () async {
147
if (addressListViewModel.type == WalletType.monero ||
148
addressListViewModel.type == WalletType.haven) {
149
await showPopUp<void>(
@@ -153,7 +156,7 @@ class ReceivePage extends BasePage {
156
}
157
},
158
title: S.of(context).accounts,
156
- icon: Icon(
159
+ trailingIcon: Icon(
160
Icons.arrow_forward_ios,
161
size: 14,
162
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
@@ -161,16 +164,21 @@ class ReceivePage extends BasePage {
164
}
165
166
if (item is WalletAddressListHeader) {
164
- cell = HeaderTile(
165
- onTap: () =>
166
- Navigator.of(context).pushNamed(Routes.newSubaddress),
167
- title: S.of(context).addresses,
168
- icon: Icon(
169
- Icons.add,
170
- size: 20,
171
- color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
172
- ));
173
- }
167
+ cell = HeaderTile(
168
+ title: S.of(context).addresses,
169
+ walletAddressListViewModel: addressListViewModel,
170
+ showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
171
+ showSearchButton: true,
172
+ trailingButtonTap: () =>
173
+ Navigator.of(context).pushNamed(Routes.newSubaddress),
174
+ trailingIcon: Icon(
175
+ Icons.add,
176
+ size: 20,
177
+ color: Theme.of(context)
178
+ .extension<ReceivePageTheme>()!
179
+ .iconsColor,
180
+ ));
181
+ }
182
183
if (item is WalletAddressListItem) {
184
cell = Observer(builder: (_) {
@@ -185,6 +193,7 @@ class ReceivePage extends BasePage {
193
194
return AddressCell.fromItem(item,
195
isCurrent: isCurrent,
196
+ hasBalance: addressListViewModel.isElectrumWallet,
197
backgroundColor: backgroundColor,
198
textColor: textColor,
199
onTap: (_) => addressListViewModel.setAddress(item),
lib/src/screens/receive/widgets/address_cell.dart
+134
-46
@@ -1,7 +1,8 @@
1
-import 'package:flutter/material.dart';
2
-import 'package:flutter_slidable/flutter_slidable.dart';
1
+import 'package:auto_size_text/auto_size_text.dart';
2
import 'package:cake_wallet/generated/i18n.dart';
3
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
4
+import 'package:flutter/material.dart';
5
+import 'package:flutter_slidable/flutter_slidable.dart';
6
7
class AddressCell extends StatelessWidget {
8
AddressCell(
@@ -12,13 +13,18 @@ class AddressCell extends StatelessWidget {
13
required this.backgroundColor,
14
required this.textColor,
15
this.onTap,
15
- this.onEdit});
16
+ this.onEdit,
17
+ this.txCount,
18
+ this.balance,
19
+ this.isChange = false,
20
+ this.hasBalance = false});
21
22
factory AddressCell.fromItem(WalletAddressListItem item,
23
{required bool isCurrent,
24
required Color backgroundColor,
25
required Color textColor,
26
Function(String)? onTap,
27
+ bool hasBalance = false,
28
Function()? onEdit}) =>
29
AddressCell(
30
address: item.address,
@@ -28,7 +34,11 @@ class AddressCell extends StatelessWidget {
34
backgroundColor: backgroundColor,
35
textColor: textColor,
36
onTap: onTap,
31
- onEdit: onEdit);
37
+ onEdit: onEdit,
38
+ txCount: item.txCount,
39
+ balance: item.balance,
40
+ isChange: item.isChange,
41
+ hasBalance: hasBalance);
42
43
final String address;
44
final String name;
@@ -38,17 +48,22 @@ class AddressCell extends StatelessWidget {
48
final Color textColor;
49
final Function(String)? onTap;
50
final Function()? onEdit;
51
+ final int? txCount;
52
+ final String? balance;
53
+ final bool isChange;
54
+ final bool hasBalance;
55
+
56
+ static const int addressPreviewLength = 8;
57
42
- String get label {
43
- if (name.isEmpty){
44
- if(address.length<=16){
45
- return address;
46
- }else{
47
- return address.substring(0,8)+'...'+
48
- address.substring(address.length-8,address.length);
49
- }
50
- }else{
51
- return name;
58
+ String get formattedAddress {
59
+ final formatIfCashAddr = address.replaceAll('bitcoincash:', '');
60
+
61
+ if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) {
62
+ return formatIfCashAddr;
63
+ } else {
64
+ return formatIfCashAddr.substring(0, addressPreviewLength) +
65
+ '...' +
66
+ formatIfCashAddr.substring(formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
67
}
68
}
69
@@ -59,41 +74,114 @@ class AddressCell extends StatelessWidget {
74
child: Container(
75
width: double.infinity,
76
color: backgroundColor,
62
- padding: EdgeInsets.only(left: 24, right: 24, top: 28, bottom: 28),
63
- child: Text(
64
- label,
65
- maxLines: 1,
66
- overflow: TextOverflow.ellipsis,
67
- style: TextStyle(
68
- fontSize: 14,
69
- color: textColor,
70
- ),
77
+ padding: EdgeInsets.only(left: 24, right: 24, top: 20, bottom: 20),
78
+ child: Row(
79
+ children: [
80
+ Expanded(
81
+ child: Column(
82
+ children: [
83
+ Row(
84
+ mainAxisAlignment: MainAxisAlignment.center,
85
+ mainAxisSize: MainAxisSize.max,
86
+ children: [
87
+ if (isChange)
88
+ Padding(
89
+ padding: const EdgeInsets.only(right: 8.0),
90
+ child: Container(
91
+ height: 20,
92
+ padding: EdgeInsets.all(4),
93
+ decoration: BoxDecoration(
94
+ borderRadius: BorderRadius.all(Radius.circular(8.5)),
95
+ color: textColor),
96
+ alignment: Alignment.center,
97
+ child: Text(
98
+ S.of(context).unspent_change,
99
+ style: TextStyle(
100
+ color: backgroundColor,
101
+ fontSize: 10,
102
+ fontWeight: FontWeight.w600,
103
+ ),
104
+ ),
105
+ ),
106
+ ),
107
+ if (name.isNotEmpty)
108
+ Text(
109
+ '$name - ',
110
+ style: TextStyle(
111
+ fontSize: 14,
112
+ fontWeight: FontWeight.w600,
113
+ color: textColor,
114
+ ),
115
+ ),
116
+ AutoSizeText(
117
+ formattedAddress,
118
+ maxLines: 1,
119
+ overflow: TextOverflow.ellipsis,
120
+ style: TextStyle(
121
+ fontSize: isChange ? 10 : 14,
122
+ color: textColor,
123
+ ),
124
+ ),
125
+ ],
126
+ ),
127
+ if (hasBalance)
128
+ Padding(
129
+ padding: const EdgeInsets.only(top: 8.0),
130
+ child: Row(
131
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
132
+ mainAxisSize: MainAxisSize.max,
133
+ children: [
134
+ Text(
135
+ 'Balance: $balance',
136
+ style: TextStyle(
137
+ fontSize: 16,
138
+ fontWeight: FontWeight.w600,
139
+ color: textColor,
140
+ ),
141
+ ),
142
+ Text(
143
+ '${S.of(context).transactions.toLowerCase()}: $txCount',
144
+ style: TextStyle(
145
+ fontSize: 16,
146
+ fontWeight: FontWeight.w600,
147
+ color: textColor,
148
+ ),
149
+ ),
150
+ ],
151
+ ),
152
+ ),
153
+ ],
154
+ ),
155
+ ),
156
+ ],
157
),
158
));
73
- return Semantics(
74
- label: S.of(context).slidable,
75
- selected: isCurrent,
76
- enabled: !isCurrent,
77
- child: Slidable(
78
- key: Key(address),
79
- startActionPane: _actionPane(context),
80
- endActionPane: _actionPane(context),
81
- child: cell,
82
- ),
83
- );
159
+ return onEdit == null
160
+ ? cell
161
+ : Semantics(
162
+ label: S.of(context).slidable,
163
+ selected: isCurrent,
164
+ enabled: !isCurrent,
165
+ child: Slidable(
166
+ key: Key(address),
167
+ startActionPane: _actionPane(context),
168
+ endActionPane: _actionPane(context),
169
+ child: cell,
170
+ ),
171
+ );
172
}
173
174
ActionPane _actionPane(BuildContext context) => ActionPane(
87
- motion: const ScrollMotion(),
88
- extentRatio: 0.3,
89
- children: [
90
- SlidableAction(
91
- onPressed: (_) => onEdit?.call(),
92
- backgroundColor: Colors.blue,
93
- foregroundColor: Colors.white,
94
- icon: Icons.edit,
95
- label: S.of(context).edit,
96
- ),
97
- ],
98
- );
175
+ motion: const ScrollMotion(),
176
+ extentRatio: 0.3,
177
+ children: [
178
+ SlidableAction(
179
+ onPressed: (_) => onEdit?.call(),
180
+ backgroundColor: Colors.blue,
181
+ foregroundColor: Colors.white,
182
+ icon: Icons.edit,
183
+ label: S.of(context).edit,
184
+ ),
185
+ ],
186
+ );
187
}
lib/src/screens/receive/widgets/header_tile.dart
+101
-37
@@ -1,50 +1,114 @@
1
-import 'package:flutter/material.dart';
1
+import 'package:cake_wallet/generated/i18n.dart';
2
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
3
+import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
4
+import 'package:flutter/material.dart';
5
4
-class HeaderTile extends StatelessWidget {
6
+class HeaderTile extends StatefulWidget {
7
HeaderTile({
6
- required this.onTap,
8
required this.title,
8
- required this.icon
9
+ required this.walletAddressListViewModel,
10
+ this.showSearchButton = false,
11
+ this.showTrailingButton = false,
12
+ this.trailingButtonTap,
13
+ this.trailingIcon,
14
});
15
11
- final VoidCallback onTap;
16
final String title;
13
- final Icon icon;
17
+ final WalletAddressListViewModel walletAddressListViewModel;
18
+ final bool showSearchButton;
19
+ final bool showTrailingButton;
20
+ final VoidCallback? trailingButtonTap;
21
+ final Icon? trailingIcon;
22
+
23
+ @override
24
+ _HeaderTileState createState() => _HeaderTileState();
25
+}
26
+
27
+class _HeaderTileState extends State<HeaderTile> {
28
+ bool _isSearchActive = false;
29
30
@override
31
Widget build(BuildContext context) {
17
- return GestureDetector(
18
- onTap: onTap,
19
- child: Container(
20
- padding: EdgeInsets.only(
21
- left: 24,
22
- right: 24,
23
- top: 24,
24
- bottom: 24
25
- ),
26
- color: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor,
27
- child: Row(
28
- mainAxisSize: MainAxisSize.max,
29
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
30
- children: <Widget>[
31
- Text(
32
- title,
33
- style: TextStyle(
34
- fontSize: 18,
35
- fontWeight: FontWeight.w600,
36
- color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
37
- ),
38
- Container(
39
- height: 32,
40
- width: 32,
41
- decoration: BoxDecoration(
42
- shape: BoxShape.circle,
43
- color: Theme.of(context).extension<ReceivePageTheme>()!.iconsBackgroundColor),
44
- child: icon,
45
- )
46
- ],
47
- ),
32
+ final searchIcon = Image.asset("assets/images/search_icon.png",
33
+ color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor);
34
+
35
+ return Container(
36
+ padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
37
+ color: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor,
38
+ child: Row(
39
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
40
+ children: <Widget>[
41
+ _isSearchActive
42
+ ? Expanded(
43
+ child: TextField(
44
+ onChanged: (value) => widget.walletAddressListViewModel.updateSearchText(value),
45
+ cursorColor: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
46
+ cursorWidth: 0.5,
47
+ decoration: InputDecoration(
48
+ hintText: '${S.of(context).search}...',
49
+ isDense: true,
50
+ contentPadding: EdgeInsets.zero,
51
+ hintStyle: TextStyle(
52
+ fontSize: 16,
53
+ fontWeight: FontWeight.w600,
54
+ color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
55
+ border: UnderlineInputBorder(
56
+ borderSide: BorderSide(color: Theme.of(context).dividerColor),
57
+ ),
58
+ focusedBorder: UnderlineInputBorder(
59
+ borderSide: BorderSide(color: Theme.of(context).dividerColor),
60
+ ),
61
+ enabledBorder: UnderlineInputBorder(
62
+ borderSide: BorderSide(color: Theme.of(context).dividerColor),
63
+ ),
64
+ ),
65
+ autofocus: true,
66
+ ),
67
+ )
68
+ : Text(
69
+ widget.title,
70
+ style: TextStyle(
71
+ fontSize: 16,
72
+ fontWeight: FontWeight.w600,
73
+ color: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor),
74
+ ),
75
+ Row(
76
+ children: [
77
+ if (widget.showSearchButton)
78
+ GestureDetector(
79
+ onTap: () {
80
+ setState(() {
81
+ _isSearchActive = !_isSearchActive;
82
+ widget.walletAddressListViewModel.updateSearchText('');
83
+ });
84
+ },
85
+ child: Container(
86
+ height: 32,
87
+ width: 32,
88
+ decoration: BoxDecoration(
89
+ shape: BoxShape.circle,
90
+ color: Theme.of(context)
91
+ .extension<ReceivePageTheme>()!
92
+ .iconsBackgroundColor),
93
+ child: searchIcon,
94
+ )),
95
+ const SizedBox(width: 8),
96
+ if (widget.showTrailingButton)
97
+ GestureDetector(
98
+ onTap: widget.trailingButtonTap,
99
+ child: Container(
100
+ height: 32,
101
+ width: 32,
102
+ decoration: BoxDecoration(
103
+ shape: BoxShape.circle,
104
+ color:
105
+ Theme.of(context).extension<ReceivePageTheme>()!.iconsBackgroundColor),
106
+ child: widget.trailingIcon,
107
+ ),
108
+ ),
109
+ ],
110
+ ),
111
+ ],
112
),
113
);
114
}
lib/src/screens/receive/widgets/qr_widget.dart
+4
-1
@@ -1,3 +1,4 @@
1
+import 'package:auto_size_text/auto_size_text.dart';
2
import 'package:cake_wallet/entities/qr_view_data.dart';
3
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
4
import 'package:cake_wallet/routes.dart';
@@ -6,6 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dar
7
import 'package:cake_wallet/utils/brightness_util.dart';
8
import 'package:cake_wallet/utils/show_bar.dart';
9
import 'package:cake_wallet/utils/show_pop_up.dart';
10
+import 'package:cw_core/wallet_type.dart';
11
import 'package:flutter/material.dart';
12
import 'package:flutter/services.dart';
13
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -144,9 +146,10 @@ class QRWidget extends StatelessWidget {
146
crossAxisAlignment: CrossAxisAlignment.start,
147
children: <Widget>[
148
Expanded(
147
- child: Text(
149
+ child: AutoSizeText(
150
addressListViewModel.address.address,
151
textAlign: TextAlign.center,
152
+ maxLines: addressListViewModel.wallet.type == WalletType.monero ? 2 : 1,
153
style: TextStyle(
154
fontSize: 15,
155
fontWeight: FontWeight.w500,
lib/view_model/settings/privacy_settings_view_model.dart
+5
-1
@@ -38,7 +38,11 @@ abstract class PrivacySettingsViewModelBase with Store {
38
}
39
}
40
41
- bool get isAutoGenerateSubaddressesVisible => _wallet.type == WalletType.monero;
41
+ bool get isAutoGenerateSubaddressesVisible =>
42
+ _wallet.type == WalletType.monero ||
43
+ _wallet.type == WalletType.bitcoin ||
44
+ _wallet.type == WalletType.litecoin ||
45
+ _wallet.type == WalletType.bitcoinCash;
46
47
@computed
48
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart
+8
-12
@@ -1,6 +1,5 @@
1
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
2
import 'package:mobx/mobx.dart';
3
-import 'package:flutter/foundation.dart';
3
import 'package:cw_core/wallet_base.dart';
4
import 'package:cake_wallet/bitcoin/bitcoin.dart';
5
import 'package:cake_wallet/monero/monero.dart';
@@ -33,7 +32,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
32
state = AddressEditOrCreateStateInitial(),
33
label = item?.name ?? '',
34
_item = item,
36
- _wallet = wallet;
35
+ _wallet = wallet;
36
37
@observable
38
AddressEditOrCreateState state;
@@ -46,6 +45,10 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
45
final WalletAddressListItem? _item;
46
final WalletBase _wallet;
47
48
+ bool get isElectrum => _wallet.type == WalletType.bitcoin ||
49
+ _wallet.type == WalletType.bitcoinCash ||
50
+ _wallet.type == WalletType.litecoin;
51
+
52
Future<void> save() async {
53
try {
54
state = AddressIsSaving();
@@ -65,12 +68,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
68
Future<void> _createNew() async {
69
final wallet = _wallet;
70
68
- if (wallet.type == WalletType.bitcoin
69
- || wallet.type == WalletType.litecoin
70
- || wallet.type == WalletType.bitcoinCash) {
71
- await bitcoin!.generateNewAddress(wallet);
72
- await wallet.save();
73
- }
71
+ if (isElectrum) await bitcoin!.generateNewAddress(wallet, label);
72
73
if (wallet.type == WalletType.monero) {
74
await monero
@@ -96,10 +94,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
94
Future<void> _update() async {
95
final wallet = _wallet;
96
99
- /*if (wallet is BitcoinWallet) {
100
- await wallet.walletAddresses.updateAddress(_item.address as String);
101
- await wallet.save();
102
- }*/
97
+ if (isElectrum) await bitcoin!.updateAddress(wallet, _item!.address, label);
98
+
99
final index = _item?.id;
100
if (index != null) {
101
if (wallet.type == WalletType.monero) {
lib/view_model/wallet_address_list/wallet_address_list_item.dart
+7
-2
@@ -1,4 +1,3 @@
1
-import 'package:flutter/foundation.dart';
1
import 'package:cake_wallet/utils/list_item.dart';
2
3
class WalletAddressListItem extends ListItem {
@@ -6,13 +5,19 @@ class WalletAddressListItem extends ListItem {
5
required this.address,
6
required this.isPrimary,
7
this.id,
9
- this.name})
8
+ this.name,
9
+ this.txCount,
10
+ this.balance,
11
+ this.isChange = false})
12
: super();
13
14
final int? id;
15
final bool isPrimary;
16
final String address;
17
final String? name;
18
+ final int? txCount;
19
+ final String? balance;
20
+ final bool isChange;
21
22
@override
23
String toString() => name ?? address;
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+78
-26
@@ -1,21 +1,25 @@
1
+import 'package:cake_wallet/bitcoin/bitcoin.dart';
2
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
2
-import 'package:cake_wallet/ethereum/ethereum.dart';
3
+import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
4
import 'package:cake_wallet/entities/fiat_currency.dart';
5
+import 'package:cake_wallet/ethereum/ethereum.dart';
6
+import 'package:cake_wallet/generated/i18n.dart';
7
+import 'package:cake_wallet/haven/haven.dart';
8
+import 'package:cake_wallet/monero/monero.dart';
9
import 'package:cake_wallet/polygon/polygon.dart';
10
+import 'package:cake_wallet/store/app_store.dart';
11
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
12
+import 'package:cake_wallet/store/settings_store.dart';
13
import 'package:cake_wallet/store/yat/yat_store.dart';
7
-import 'package:cw_core/currency.dart';
8
-import 'package:intl/intl.dart';
9
-import 'package:mobx/mobx.dart';
14
import 'package:cake_wallet/utils/list_item.dart';
15
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
16
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
17
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
18
+import 'package:cw_core/amount_converter.dart';
19
+import 'package:cw_core/currency.dart';
20
import 'package:cw_core/wallet_type.dart';
15
-import 'package:cake_wallet/bitcoin/bitcoin.dart';
16
-import 'package:cake_wallet/store/app_store.dart';
17
-import 'package:cake_wallet/monero/monero.dart';
18
-import 'package:cake_wallet/haven/haven.dart';
21
+import 'package:intl/intl.dart';
22
+import 'package:mobx/mobx.dart';
23
24
part 'wallet_address_list_view_model.g.dart';
25
@@ -110,7 +114,8 @@ class EthereumURI extends PaymentURI {
114
115
class BitcoinCashURI extends PaymentURI {
116
BitcoinCashURI({required String amount, required String address})
113
- : super(amount: amount, address: address);
117
+ : super(amount: amount, address: address);
118
+
119
@override
120
String toString() {
121
var base = address;
@@ -121,9 +126,7 @@ class BitcoinCashURI extends PaymentURI {
126
127
return base;
128
}
124
- }
125
-
126
-
129
+}
130
131
class NanoURI extends PaymentURI {
132
NanoURI({required String amount, required String address})
@@ -167,6 +170,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
170
hasAccounts =
171
appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven,
172
amount = '',
173
+ _settingsStore = appStore.settingsStore,
174
super(appStore: appStore) {
175
_init();
176
}
@@ -184,12 +188,28 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
188
final NumberFormat _cryptoNumberFormat;
189
190
final FiatConversionStore fiatConversionStore;
191
+ final SettingsStore _settingsStore;
192
193
List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
194
195
+ String get buttonTitle {
196
+ if (isElectrumWallet) {
197
+ return S.current.addresses;
198
+ }
199
+
200
+ if (isAutoGenerateSubaddressEnabled) {
201
+ return hasAccounts ? S.current.accounts : S.current.account;
202
+ }
203
+
204
+ return hasAccounts ? S.current.accounts_subaddresses : S.current.addresses;
205
+ }
206
+
207
@observable
208
Currency selectedCurrency;
209
210
+ @observable
211
+ String searchText = '';
212
+
213
@computed
214
int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency);
215
@@ -277,14 +297,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
297
addressList.addAll(addressItems);
298
}
299
280
- if (wallet.type == WalletType.bitcoin) {
281
- final primaryAddress = bitcoin!.getAddress(wallet);
282
- final bitcoinAddresses = bitcoin!.getAddresses(wallet).map((addr) {
283
- final isPrimary = addr == primaryAddress;
300
+ if (isElectrumWallet) {
301
+ final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
302
+ final isPrimary = subaddress.id == 0;
303
285
- return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr);
304
+ return WalletAddressListItem(
305
+ id: subaddress.id,
306
+ isPrimary: isPrimary,
307
+ name: subaddress.name,
308
+ address: subaddress.address,
309
+ txCount: subaddress.txCount,
310
+ balance: AmountConverter.amountIntToString(
311
+ walletTypeToCryptoCurrency(type), subaddress.balance),
312
+ isChange: subaddress.isChange);
313
});
287
- addressList.addAll(bitcoinAddresses);
314
+ addressList.addAll(addressItems);
315
}
316
317
if (wallet.type == WalletType.ethereum) {
@@ -299,6 +326,15 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
326
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
327
}
328
329
+ if (searchText.isNotEmpty) {
330
+ return ObservableList.of(addressList.where((item) {
331
+ if (item is WalletAddressListItem) {
332
+ return item.address.toLowerCase().contains(searchText.toLowerCase());
333
+ }
334
+ return false;
335
+ }));
336
+ }
337
+
338
return addressList;
339
}
340
@@ -321,15 +357,23 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
357
@computed
358
bool get hasAddressList =>
359
wallet.type == WalletType.monero ||
324
- wallet.type == WalletType.haven;/* ||
325
- wallet.type == WalletType.nano ||
326
- wallet.type == WalletType.banano;*/// TODO: nano accounts are disabled for now
360
+ wallet.type == WalletType.haven ||
361
+ wallet.type == WalletType.bitcoinCash ||
362
+ wallet.type == WalletType.bitcoin ||
363
+ wallet.type == WalletType.litecoin;
364
+
365
+ // wallet.type == WalletType.nano ||
366
+ // wallet.type == WalletType.banano; TODO: nano accounts are disabled for now
367
368
@computed
329
- bool get showElectrumAddressDisclaimer =>
369
+ bool get isElectrumWallet =>
370
wallet.type == WalletType.bitcoin ||
331
- wallet.type == WalletType.litecoin ||
332
- wallet.type == WalletType.bitcoinCash;
371
+ wallet.type == WalletType.litecoin ||
372
+ wallet.type == WalletType.bitcoinCash;
373
+
374
+ @computed
375
+ bool get isAutoGenerateSubaddressEnabled =>
376
+ _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
377
378
List<ListItem> _baseItems;
379
@@ -343,9 +387,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
387
_baseItems = [];
388
389
if (wallet.type == WalletType.monero ||
346
- wallet.type == WalletType.haven /*||
390
+ wallet.type ==
391
+ WalletType
392
+ .haven /*||
393
wallet.type == WalletType.nano ||
348
- wallet.type == WalletType.banano*/) {
394
+ wallet.type == WalletType.banano*/
395
+ ) {
396
_baseItems.add(WalletAccountListHeader());
397
}
398
@@ -367,6 +414,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
414
}
415
}
416
417
+ @action
418
+ void updateSearchText(String text) {
419
+ searchText = text;
420
+ }
421
+
422
void _convertAmountToCrypto() {
423
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
424
try {
tool/configure.dart
+23
-1
@@ -64,6 +64,7 @@ import 'package:cw_core/output_info.dart';
64
import 'package:cw_core/unspent_coins_info.dart';
65
import 'package:cw_core/wallet_service.dart';
66
import 'package:cake_wallet/view_model/send/output.dart';
67
+import 'package:cw_core/wallet_type.dart';
68
import 'package:hive/hive.dart';""";
69
const bitcoinCWHeaders = """
70
import 'package:cw_bitcoin/electrum_wallet.dart';
@@ -76,9 +77,27 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
77
import 'package:cw_bitcoin/bitcoin_address_record.dart';
78
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
79
import 'package:cw_bitcoin/litecoin_wallet_service.dart';
80
+import 'package:mobx/mobx.dart';
81
""";
82
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
83
const bitcoinContent = """
84
+
85
+ class ElectrumSubAddress {
86
+ ElectrumSubAddress({
87
+ required this.id,
88
+ required this.name,
89
+ required this.address,
90
+ required this.txCount,
91
+ required this.balance,
92
+ required this.isChange});
93
+ final int id;
94
+ final String name;
95
+ final String address;
96
+ final int txCount;
97
+ final int balance;
98
+ final bool isChange;
99
+}
100
+
101
abstract class Bitcoin {
102
TransactionPriority getMediumTransactionPriority();
103
@@ -92,13 +111,16 @@ abstract class Bitcoin {
111
TransactionPriority deserializeBitcoinTransactionPriority(int raw);
112
TransactionPriority deserializeLitecoinTransactionPriority(int raw);
113
int getFeeRate(Object wallet, TransactionPriority priority);
95
- Future<void> generateNewAddress(Object wallet);
114
+ Future<void> generateNewAddress(Object wallet, String label);
115
+ Future<void> updateAddress(Object wallet,String address, String label);
116
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate});
117
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate});
118
119
List<String> getAddresses(Object wallet);
120
String getAddress(Object wallet);
121
122
+ List<ElectrumSubAddress> getSubAddresses(Object wallet);
123
+
124
String formatterBitcoinAmountToString({required int amount});
125
double formatterBitcoinAmountToDouble({required int amount});
126
int formatterStringDoubleToBitcoinAmount(String amount);