diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 392771ab0..f2cd63904 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -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, + }); } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 2c66d02fe..9cdb78f2d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -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 create({ diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 4c5ecc7f2..6be525a1f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -63,6 +63,7 @@ abstract class ElectrumWalletBase _password = password, _feeRates = [], _isTransactionUpdating = false, + isEnabledAutoGenerateSubaddress = true, unspentCoins = [], _scripthashesUpdateSubject = {}, balance = ObservableMap.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; @@ -583,38 +588,66 @@ abstract class ElectrumWalletBase Future> fetchTransactions() async { final addressHashes = {}; final normalizedHistories = >[]; + final newTxCounts = {}; + 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>({}, (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>( + {}, (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 updateTransactions() async { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index c9e4f8200..a60a7e8ec 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -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? initialAddresses, int initialRegularAddressIndex = 0, int initialChangeAddressIndex = 0}) - : addresses = ObservableList.of( - (initialAddresses ?? []).toSet()), - receiveAddresses = ObservableList.of( - (initialAddresses ?? []) + : addresses = ObservableList.of((initialAddresses ?? []).toSet()), + receiveAddresses = ObservableList.of((initialAddresses ?? []) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) - .toSet()), - changeAddresses = ObservableList.of( - (initialAddresses ?? []) + .toSet()), + changeAddresses = ObservableList.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 addresses; final ObservableList receiveAddresses; final ObservableList 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 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 _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async { @@ -181,20 +215,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { List 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 = []; 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; } -} \ No newline at end of file +} diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 6bf1c5735..222e95acc 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -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 create({ diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index c23220423..1b87e2231 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -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; + }); } diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index 6fd43dd82..249b87bd3 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -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: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index dd713fd15..b6fb6e62d 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -63,9 +63,17 @@ class CWBitcoin extends Bitcoin { } @override - Future generateNewAddress(Object wallet) async { + Future 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 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 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; diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 42fbd182e..ade9927ff 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -68,7 +68,8 @@ void startCurrentWalletChangeReaction( .get() .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); } diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index ff21d4aad..b4460edc9 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -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( - context: context, builder: (_) => getIt.get()) + context: context, + builder: (_) => getIt.get()) : 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()!.cardBorderColor, - width: 1), - color: Theme.of(context) - .extension()! - .syncedBackgroundColor), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 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()! - .textColor), - ); - }, - ), - Icon( - Icons.arrow_forward_ios, - size: 14, - color: Theme.of(context).extension()!.textColor, - ) - ], - ), - ), + textColor: Theme.of(context).extension()!.textColor, + color: Theme.of(context).extension()!.syncedBackgroundColor, + borderColor: Theme.of(context).extension()!.cardBorderColor, + arrowColor: Theme.of(context).extension()!.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( diff --git a/lib/src/screens/new_wallet/widgets/select_button.dart b/lib/src/screens/new_wallet/widgets/select_button.dart index e220b281e..d94c9767d 100644 --- a/lib/src/screens/new_wallet/widgets/select_button.dart +++ b/lib/src/screens/new_wallet/widgets/select_button.dart @@ -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()!.restoreWalletButtonTextColor - : Theme.of(context).extension()!.buttonTextColor; - final arrowColor = isSelected + : Theme.of(context).extension()!.buttonTextColor); + final effectiveArrowColor = arrowColor ?? (isSelected ? Theme.of(context).extension()!.restoreWalletButtonTextColor - : Theme.of(context).extension()!.titlesColor; + : Theme.of(context).extension()!.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, ), ), ) diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 87b668e34..75719d123 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -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( @@ -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()!.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()!.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()! + .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), diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index 733612e0b..92c870421 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -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, + ), + ], + ); } diff --git a/lib/src/screens/receive/widgets/header_tile.dart b/lib/src/screens/receive/widgets/header_tile.dart index e9c134ea5..faaa9ed07 100644 --- a/lib/src/screens/receive/widgets/header_tile.dart +++ b/lib/src/screens/receive/widgets/header_tile.dart @@ -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 { + 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()!.tilesBackgroundColor, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.tilesTextColor), - ), - Container( - height: 32, - width: 32, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).extension()!.iconsBackgroundColor), - child: icon, - ) - ], - ), + final searchIcon = Image.asset("assets/images/search_icon.png", + color: Theme.of(context).extension()!.iconsColor); + + return Container( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + color: Theme.of(context).extension()!.tilesBackgroundColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _isSearchActive + ? Expanded( + child: TextField( + onChanged: (value) => widget.walletAddressListViewModel.updateSearchText(value), + cursorColor: Theme.of(context).extension()!.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()!.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()!.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()! + .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()!.iconsBackgroundColor), + child: widget.trailingIcon, + ), + ), + ], + ), + ], ), ); } diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index bbfd4d5c1..bedb3b526 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -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: [ 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, diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index e4dd86b37..65375b3e7 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -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; diff --git a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart index 9e2aa7187..0b4f969cb 100644 --- a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart @@ -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 save() async { try { state = AddressIsSaving(); @@ -65,12 +68,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { Future _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 _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) { diff --git a/lib/view_model/wallet_address_list/wallet_address_list_item.dart b/lib/view_model/wallet_address_list/wallet_address_list_item.dart index ba7b93cf9..1152f1404 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_item.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_item.dart @@ -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; diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index ade279124..9270d1d44 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -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 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 _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 { diff --git a/tool/configure.dart b/tool/configure.dart index 9d99bf49a..ce894d197 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -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 generateNewAddress(Object wallet); + Future generateNewAddress(Object wallet, String label); + Future updateAddress(Object wallet,String address, String label); Object createBitcoinTransactionCredentials(List outputs, {required TransactionPriority priority, int? feeRate}); Object createBitcoinTransactionCredentialsRaw(List outputs, {TransactionPriority? priority, required int feeRate}); List getAddresses(Object wallet); String getAddress(Object wallet); + List getSubAddresses(Object wallet); + String formatterBitcoinAmountToString({required int amount}); double formatterBitcoinAmountToDouble({required int amount}); int formatterStringDoubleToBitcoinAmount(String amount);