mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-19 05:58:50 +02:00
Cw 453 (#1306)
* feat: rebase btc-addr-types, migrate to bitcoin_base * feat: allow scanning elect-rs using get_tweaks * feat: scanning and adding addresses working with getTweaks, add btc SP address type * chore: pubspec.lock * chore: pubspec.lock * fix: scan when switching, fix multiple unspents in same tx * fix: initial scan * fix: initial scan * fix: scanning issues * fix: sync, storing silent unspents * chore: deps * fix: label issues, clear spent utxo * chore: deps * fix: build * fix: missing types * feat: new electrs API & changes, fixes for last block scanning * feat: Scan Silent Payments homepage toggle * chore: build configure * feat: generic fixes, testnet UI improvements, useSSL on bitcoin nodes * fix: invalid Object in sendData * feat: improve addresses page & address book displays * feat: silent payments labeled addresses disclaimer * fix: missing i18n * chore: print * feat: single block scan, rescan by date working for btc mainnet * feat: new cake features page replace market page, move sp scan toggle, auto switch node pop up alert * feat: delete silent addresses * fix: red dot in non ssl nodes * fix: inconsistent connection states, fix tx history * fix: tx & balance displays, cpfp sending * feat: new rust lib * chore: node path * fix: check node based on network * fix: missing txcount from addresses * style: padding in feature page cards * fix: restore not getting all wallet addresses by type * fix: auto switch node broken * fix: silent payment txs not being restored * feat: change scanning to subscription model, sync improvements * fix: scan re-subscription * fix: default nodes * fix: improve scanning by date, fix single block scan * refactor: common function for input tx selection * fix: nodes & build * fix: send all with multiple outs * refactor: unchanged file * Update pr_test_build.yml * chore: upgrade * chore: merge changes * refactor: unchanged files [skip ci] * fix: scan fixes, add date, allow sending while scanning * feat: sync fixes, sp settings * feat: fix resyncing * fix: date from height logic, status disconnected & chain tip get * fix: params * feat: electrum migration if using cake electrum * fix nodes update versions * re-enable tron * update sp_scanner to work on iOS [skip ci] * fix: wrong socket for old electrum nodes * Fix unchecked wallet type call * fix: double balance * feat: node domain * fix: menu name * fix: update tip on set scanning * fix: connection switching back and forth * feat: check if node is electrs, and supports sp * chore: fix build * minor enhancements * fixes and enhancements * solve conflicts with main * fix: status toggle * minor enhancement * Monero.com fixes * update sp_scanner to include windows and linux --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
+143
-12
@@ -121,20 +121,12 @@ class CWBitcoin extends Bitcoin {
|
||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||
feeRate: feeRate);
|
||||
|
||||
@override
|
||||
List<String> getAddresses(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.walletAddresses.addressesByReceiveType
|
||||
.map((BitcoinAddressRecord addr) => addr.address)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
@computed
|
||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
return electrumWallet.walletAddresses.addressesByReceiveType
|
||||
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||
.map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||
id: addr.index,
|
||||
name: addr.name,
|
||||
address: addr.address,
|
||||
@@ -207,12 +199,12 @@ class CWBitcoin extends Bitcoin {
|
||||
|
||||
Future<void> updateUnspents(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.updateUnspent();
|
||||
await bitcoinWallet.updateAllUnspents();
|
||||
}
|
||||
|
||||
WalletService createBitcoinWalletService(
|
||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
|
||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource);
|
||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan) {
|
||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan);
|
||||
}
|
||||
|
||||
WalletService createLitecoinWalletService(
|
||||
@@ -247,6 +239,12 @@ class CWBitcoin extends Bitcoin {
|
||||
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
|
||||
}
|
||||
|
||||
@override
|
||||
bool hasSelectedSilentPayments(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
|
||||
|
||||
@@ -465,4 +463,137 @@ class CWBitcoin extends Bitcoin {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.walletAddresses.silentAddresses
|
||||
.where((addr) => addr.type != SegwitAddresType.p2tr)
|
||||
.map((addr) => ElectrumSubAddress(
|
||||
id: addr.index,
|
||||
name: addr.name,
|
||||
address: addr.address,
|
||||
txCount: addr.txCount,
|
||||
balance: addr.balance,
|
||||
isChange: addr.isHidden))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<ElectrumSubAddress> getSilentPaymentReceivedAddresses(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.walletAddresses.silentAddresses
|
||||
.where((addr) => addr.type == SegwitAddresType.p2tr)
|
||||
.map((addr) => ElectrumSubAddress(
|
||||
id: addr.index,
|
||||
name: addr.name,
|
||||
address: addr.address,
|
||||
txCount: addr.txCount,
|
||||
balance: addr.balance,
|
||||
isChange: addr.isHidden))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isBitcoinReceivePageOption(ReceivePageOption option) {
|
||||
return option is BitcoinReceivePageOption;
|
||||
}
|
||||
|
||||
@override
|
||||
BitcoinAddressType getOptionToType(ReceivePageOption option) {
|
||||
return (option as BitcoinReceivePageOption).toType();
|
||||
}
|
||||
|
||||
@override
|
||||
@computed
|
||||
bool getScanningActive(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.silentPaymentsScanningActive;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setScanningActive(Object wallet, bool active) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
||||
if (active && !(await getNodeIsElectrsSPEnabled(wallet))) {
|
||||
final node = Node(
|
||||
useSSL: false,
|
||||
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
|
||||
);
|
||||
node.type = WalletType.bitcoin;
|
||||
|
||||
await bitcoinWallet.connectToNode(node: node);
|
||||
}
|
||||
|
||||
bitcoinWallet.setSilentPaymentsScanning(active);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isTestnet(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.isTestnet ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date);
|
||||
|
||||
@override
|
||||
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
if (!(await getNodeIsElectrsSPEnabled(wallet))) {
|
||||
final node = Node(
|
||||
useSSL: false,
|
||||
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
|
||||
);
|
||||
node.type = WalletType.bitcoin;
|
||||
await bitcoinWallet.connectToNode(node: node);
|
||||
}
|
||||
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
|
||||
}
|
||||
|
||||
Future<bool> getNodeIsElectrs(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
||||
final version = await bitcoinWallet.electrumClient.version();
|
||||
|
||||
if (version.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final server = version[0];
|
||||
|
||||
if (server.toLowerCase().contains('electrs')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
|
||||
if (!(await getNodeIsElectrs(wallet))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0);
|
||||
|
||||
if (tweaksResponse != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteSilentPaymentAddress(Object wallet, String address) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateFeeRates(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.updateFeeRates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class AddressValidator extends TextValidator {
|
||||
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
|
||||
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
|
||||
case CryptoCurrency.btc:
|
||||
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$';
|
||||
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$';
|
||||
case CryptoCurrency.nano:
|
||||
return '[0-9a-zA-Z_]';
|
||||
case CryptoCurrency.banano:
|
||||
@@ -274,7 +274,9 @@ class AddressValidator extends TextValidator {
|
||||
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)'; //P2trAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)' //P2trAddress type
|
||||
'|${SilentPaymentAddress.regex.pattern}\$';
|
||||
|
||||
case CryptoCurrency.ltc:
|
||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
|
||||
@@ -3,7 +3,13 @@ import 'package:cw_core/sync_status.dart';
|
||||
|
||||
String syncStatusTitle(SyncStatus syncStatus) {
|
||||
if (syncStatus is SyncingSyncStatus) {
|
||||
return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
|
||||
return syncStatus.blocksLeft == 1
|
||||
? S.current.block_remaining
|
||||
: S.current.Blocks_remaining('${syncStatus.blocksLeft}');
|
||||
}
|
||||
|
||||
if (syncStatus is SyncedTipSyncStatus) {
|
||||
return S.current.silent_payments_scanned_tip(syncStatus.tip.toString());
|
||||
}
|
||||
|
||||
if (syncStatus is SyncedSyncStatus) {
|
||||
@@ -34,5 +40,17 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
||||
return S.current.sync_status_failed_connect;
|
||||
}
|
||||
|
||||
if (syncStatus is UnsupportedSyncStatus) {
|
||||
return S.current.sync_status_unsupported;
|
||||
}
|
||||
|
||||
if (syncStatus is TimedOutSyncStatus) {
|
||||
return S.current.sync_status_timed_out;
|
||||
}
|
||||
|
||||
if (syncStatus is SyncronizingSyncStatus) {
|
||||
return S.current.sync_status_syncronizing;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
+18
-7
@@ -6,6 +6,7 @@ import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
|
||||
import 'package:cake_wallet/anypay/anypay_api.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
|
||||
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
@@ -15,6 +16,7 @@ import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
@@ -102,12 +104,14 @@ import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/send_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||
@@ -159,10 +163,10 @@ import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||
@@ -199,6 +203,7 @@ import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/support_view_model.dart';
|
||||
@@ -235,10 +240,6 @@ import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'buy/dfx/dfx_buy_provider.dart';
|
||||
import 'core/totp_request_details.dart';
|
||||
import 'src/screens/settings/desktop_settings/desktop_settings_page.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
var _isSetupFinished = false;
|
||||
@@ -745,6 +746,9 @@ Future<void> setup({
|
||||
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
|
||||
});
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||
});
|
||||
@@ -806,6 +810,9 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => SilentPaymentsSettingsPage(getIt.get<SilentPaymentsSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => NanoChangeRepPage(
|
||||
@@ -893,7 +900,11 @@ Future<void> setup({
|
||||
case WalletType.monero:
|
||||
return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
return bitcoin!.createBitcoinWalletService(
|
||||
_walletInfoSource,
|
||||
_unspentCoinsInfoSource,
|
||||
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
|
||||
);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
case WalletType.ethereum:
|
||||
@@ -1089,7 +1100,7 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactory(() => MarketPlaceViewModel(getIt.get<IoniaService>()));
|
||||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
|
||||
@@ -24,8 +24,9 @@ import 'package:collection/collection.dart';
|
||||
|
||||
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
||||
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
||||
const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info';
|
||||
const publicBitcoinTestnetElectrumPort = '60002';
|
||||
const cakeWalletSilentPaymentsElectrsUri = 'electrs.cakewallet.com:50001';
|
||||
const publicBitcoinTestnetElectrumAddress = 'electrs.cakewallet.com';
|
||||
const publicBitcoinTestnetElectrumPort = '50002';
|
||||
const publicBitcoinTestnetElectrumUri =
|
||||
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
|
||||
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
||||
@@ -224,6 +225,9 @@ Future<void> defaultSettingsMigration(
|
||||
await addTronNodeList(nodes: nodes);
|
||||
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
case 34:
|
||||
await _addElectRsNode(nodes, sharedPreferences);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -790,7 +794,8 @@ Future<void> changeDefaultBitcoinNode(
|
||||
final needToReplaceCurrentBitcoinNode =
|
||||
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
|
||||
|
||||
final newCakeWalletBitcoinNode = Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin);
|
||||
final newCakeWalletBitcoinNode =
|
||||
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
|
||||
|
||||
await nodeSource.add(newCakeWalletBitcoinNode);
|
||||
|
||||
@@ -800,6 +805,26 @@ Future<void> changeDefaultBitcoinNode(
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
|
||||
final currentBitcoinNodeId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
final currentBitcoinNode =
|
||||
nodeSource.values.firstWhere((node) => node.key == currentBitcoinNodeId);
|
||||
final needToReplaceCurrentBitcoinNode =
|
||||
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
|
||||
|
||||
final newElectRsBitcoinNode =
|
||||
Node(uri: cakeWalletSilentPaymentsElectrsUri, type: WalletType.bitcoin, useSSL: false);
|
||||
|
||||
await nodeSource.add(newElectRsBitcoinNode);
|
||||
|
||||
if (needToReplaceCurrentBitcoinNode) {
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, newElectRsBitcoinNode.key as int);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkCurrentNodes(
|
||||
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
@@ -845,14 +870,19 @@ Future<void> checkCurrentNodes(
|
||||
}
|
||||
|
||||
if (currentBitcoinElectrumServer == null) {
|
||||
final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
|
||||
final cakeWalletElectrum =
|
||||
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||
await nodeSource.add(cakeWalletElectrum);
|
||||
final cakeWalletElectrumTestnet =
|
||||
Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||
await nodeSource.add(cakeWalletElectrumTestnet);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
}
|
||||
|
||||
if (currentLitecoinElectrumServer == null) {
|
||||
final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
|
||||
final cakeWalletElectrum =
|
||||
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin, useSSL: false);
|
||||
await nodeSource.add(cakeWalletElectrum);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
@@ -887,7 +917,8 @@ Future<void> checkCurrentNodes(
|
||||
}
|
||||
|
||||
if (currentBitcoinCashNodeServer == null) {
|
||||
final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash);
|
||||
final node =
|
||||
Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash, useSSL: false);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
|
||||
}
|
||||
@@ -921,7 +952,11 @@ Future<void> resetBitcoinElectrumServer(
|
||||
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
|
||||
|
||||
if (cakeWalletNode == null) {
|
||||
cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
|
||||
cakeWalletNode =
|
||||
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||
// final cakeWalletElectrumTestnet =
|
||||
// Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||
// await nodeSource.add(cakeWalletElectrumTestnet);
|
||||
await nodeSource.add(cakeWalletNode);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ class PreferencesKey {
|
||||
static const polygonTransactionPriority = 'current_fee_priority_polygon';
|
||||
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
|
||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
|
||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||
static const shouldShowYatPopup = 'should_show_yat_popup';
|
||||
static const shouldShowRepWarning = 'should_show_rep_warning';
|
||||
|
||||
+1
-1
@@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
|
||||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 33,
|
||||
initialMigrationVersion: 34,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,19 @@ import 'dart:async';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
|
||||
Timer? _checkConnectionTimer;
|
||||
|
||||
void startCheckConnectionReaction(
|
||||
WalletBase wallet, SettingsStore settingsStore,
|
||||
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore,
|
||||
{int timeInterval = 5}) {
|
||||
_checkConnectionTimer?.cancel();
|
||||
_checkConnectionTimer =
|
||||
Timer.periodic(Duration(seconds: timeInterval), (_) async {
|
||||
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
|
||||
if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||
|
||||
@@ -20,14 +24,11 @@ void startCheckConnectionReaction(
|
||||
return;
|
||||
}
|
||||
|
||||
if (wallet.syncStatus is LostConnectionSyncStatus ||
|
||||
wallet.syncStatus is FailedSyncStatus) {
|
||||
final alive =
|
||||
await settingsStore.getCurrentNode(wallet.type).requestNode();
|
||||
if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) {
|
||||
final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
|
||||
|
||||
if (alive) {
|
||||
await wallet.connectToNode(
|
||||
node: settingsStore.getCurrentNode(wallet.type));
|
||||
await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -12,12 +12,10 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
ReactionDisposer? _onWalletSyncStatusChangeReaction;
|
||||
|
||||
void startWalletSyncStatusChangeReaction(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||
TransactionInfo> wallet,
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
|
||||
FiatConversionStore fiatConversionStore) {
|
||||
_onWalletSyncStatusChangeReaction?.reaction.dispose();
|
||||
_onWalletSyncStatusChangeReaction =
|
||||
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
||||
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
||||
try {
|
||||
if (status is ConnectedSyncStatus) {
|
||||
await wallet.startSync();
|
||||
@@ -32,7 +30,7 @@ void startWalletSyncStatusChangeReaction(
|
||||
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -76,6 +76,7 @@ import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
|
||||
@@ -366,6 +367,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
param1: settings.arguments as OnAuthenticationFinished, param2: false),
|
||||
onWillPop: () async => false));
|
||||
|
||||
case Routes.silentPaymentsSettings:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<SilentPaymentsSettingsPage>());
|
||||
|
||||
case Routes.connectionSync:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
|
||||
|
||||
@@ -81,6 +81,7 @@ class Routes {
|
||||
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
|
||||
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
|
||||
static const webViewPage = '/web_view_page';
|
||||
static const silentPaymentsSettings = '/silent_payments_settings';
|
||||
static const connectionSync = '/connection_sync_page';
|
||||
static const securityBackupPage = '/security_and_backup_page';
|
||||
static const privacyPage = '/privacy_page';
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/main_actions.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
|
||||
@@ -12,7 +12,7 @@ import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
|
||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/version_comparator.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
|
||||
@@ -330,10 +330,10 @@ class _DashboardPageView extends BasePage {
|
||||
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
|
||||
pages.add(
|
||||
Semantics(
|
||||
label: S.of(context).market_place,
|
||||
child: MarketPlacePage(
|
||||
label: 'Cake ${S.of(context).features}',
|
||||
child: CakeFeaturesPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
|
||||
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/main_actions.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
@@ -74,9 +74,9 @@ class DesktopDashboardActions extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: MarketPlacePage(
|
||||
child: CakeFeaturesPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
|
||||
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -30,6 +30,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget {
|
||||
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
|
||||
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
|
||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||
@@ -68,8 +69,11 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: DropDownItemWidget(
|
||||
title: wallet.name,
|
||||
image: wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon),
|
||||
title: wallet.name,
|
||||
image: wallet.isEnabled
|
||||
? _imageFor(type: wallet.type, isTestnet: wallet.isTestnet)
|
||||
: nonWalletTypeIcon,
|
||||
),
|
||||
),
|
||||
onSelected: () => _onSelectedWallet(wallet),
|
||||
))
|
||||
@@ -120,16 +124,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final confirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_wallet_alert_title,
|
||||
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).change,
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
||||
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
||||
}) ??
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_wallet_alert_title,
|
||||
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).change,
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
||||
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
||||
}) ??
|
||||
false;
|
||||
|
||||
if (confirmed) {
|
||||
@@ -138,9 +142,12 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
});
|
||||
}
|
||||
|
||||
Image _imageFor({required WalletType type}) {
|
||||
Image _imageFor({required WalletType type, bool? isTestnet}) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
if (isTestnet == true) {
|
||||
return tBitcoinIcon;
|
||||
}
|
||||
return bitcoinIcon;
|
||||
case WalletType.monero:
|
||||
return moneroIcon;
|
||||
@@ -160,7 +167,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
return polygonIcon;
|
||||
case WalletType.solana:
|
||||
return solanaIcon;
|
||||
case WalletType.tron:
|
||||
case WalletType.tron:
|
||||
return tronIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
@@ -168,24 +175,25 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
}
|
||||
|
||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||
widget._authService.authenticateAction(context,
|
||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
widget._authService.authenticateAction(
|
||||
context,
|
||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (context.mounted) {
|
||||
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
||||
try {
|
||||
if (context.mounted) {
|
||||
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
||||
}
|
||||
await widget.walletListViewModel.loadWallet(wallet);
|
||||
hideProgressText();
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||
}
|
||||
}
|
||||
await widget.walletListViewModel.loadWallet(wallet);
|
||||
hideProgressText();
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||
}
|
||||
}
|
||||
},
|
||||
conditionToDetermineIfToUse2FA:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
||||
@@ -198,17 +206,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||
context,
|
||||
route: Routes.newWallet,
|
||||
arguments: widget.walletListViewModel.currentWalletType,
|
||||
conditionToDetermineIfToUse2FA: widget
|
||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
conditionToDetermineIfToUse2FA:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
} else {
|
||||
widget._authService.authenticateAction(
|
||||
context,
|
||||
route: Routes.newWalletType,
|
||||
conditionToDetermineIfToUse2FA: widget
|
||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
conditionToDetermineIfToUse2FA:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,11 @@ class AddressPage extends BasePage {
|
||||
}
|
||||
|
||||
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
|
||||
if (bitcoin!.isBitcoinReceivePageOption(option)) {
|
||||
addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case ReceivePageOption.anonPayInvoice:
|
||||
Navigator.pushNamed(
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/introducing_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
@@ -21,6 +24,7 @@ import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class BalancePage extends StatelessWidget {
|
||||
BalancePage({
|
||||
@@ -221,30 +225,136 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||
itemBuilder: (__, index) {
|
||||
final balance =
|
||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||
return BalanceRowWidget(
|
||||
availableBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||
availableBalance: balance.availableBalance,
|
||||
availableFiatBalance: balance.fiatAvailableBalance,
|
||||
additionalBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
||||
additionalBalance: balance.additionalBalance,
|
||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||
frozenBalance: balance.frozenBalance,
|
||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||
currency: balance.asset,
|
||||
hasAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||
);
|
||||
return Observer(builder: (_) {
|
||||
return BalanceRowWidget(
|
||||
availableBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||
availableBalance: balance.availableBalance,
|
||||
availableFiatBalance: balance.fiatAvailableBalance,
|
||||
additionalBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
||||
additionalBalance: balance.additionalBalance,
|
||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||
frozenBalance: balance.frozenBalance,
|
||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||
currency: balance.asset,
|
||||
hasAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||
isTestnet: dashboardViewModel.isTestnet,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
Observer(builder: (context) {
|
||||
return Column(
|
||||
children: [
|
||||
if (dashboardViewModel.showSilentPaymentsCard) ...[
|
||||
SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: DashBoardRoundedCardWidget(
|
||||
customBorder: 30,
|
||||
title: S.of(context).silent_payments,
|
||||
subTitle: S.of(context).enable_silent_payments_scanning,
|
||||
hint: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse(
|
||||
"https://guides.cakewallet.com/docs/cryptos/bitcoin/#silent-payments"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).what_is_silent_payments,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
height: 1,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => StandardSwitch(
|
||||
value: dashboardViewModel.silentPaymentsScanningActive,
|
||||
onTaped: () => _toggleSilentPaymentsScanning(context),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => _toggleSilentPaymentsScanning(context),
|
||||
icon: Icon(
|
||||
Icons.lock,
|
||||
color:
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
|
||||
size: 50,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
|
||||
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
|
||||
final newValue = !isSilentPaymentsScanningActive;
|
||||
|
||||
dashboardViewModel.silentPaymentsScanningActive = newValue;
|
||||
|
||||
final needsToSwitch = !isSilentPaymentsScanningActive &&
|
||||
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
|
||||
|
||||
if (needsToSwitch) {
|
||||
return showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_current_node_title,
|
||||
alertContent: S.of(context).confirm_silent_payments_switch_node,
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
dashboardViewModel.setSilentPaymentsScanning(newValue);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
return dashboardViewModel.setSilentPaymentsScanning(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
class BalanceRowWidget extends StatelessWidget {
|
||||
@@ -259,6 +369,7 @@ class BalanceRowWidget extends StatelessWidget {
|
||||
required this.frozenFiatBalance,
|
||||
required this.currency,
|
||||
required this.hasAdditionalBalance,
|
||||
required this.isTestnet,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -272,6 +383,7 @@ class BalanceRowWidget extends StatelessWidget {
|
||||
final String frozenFiatBalance;
|
||||
final CryptoCurrency currency;
|
||||
final bool hasAdditionalBalance;
|
||||
final bool isTestnet;
|
||||
|
||||
// void _showBalanceDescription(BuildContext context) {
|
||||
// showPopUp<void>(
|
||||
@@ -346,14 +458,24 @@ class BalanceRowWidget extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.start),
|
||||
SizedBox(height: 6),
|
||||
Text('${availableFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1)),
|
||||
if (isTestnet)
|
||||
Text(S.current.testnet_coins_no_value,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1)),
|
||||
if (!isTestnet)
|
||||
Text('${availableFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -362,27 +484,23 @@ class BalanceRowWidget extends StatelessWidget {
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: currency.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
currency.title.substring(0, min(currency.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
CakeImageWidget(
|
||||
imageUrl: currency.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
currency.title.substring(0, min(currency.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
@@ -449,17 +567,18 @@ class BalanceRowWidget extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
if (!isTestnet)
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -493,17 +612,18 @@ class BalanceRowWidget extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
if (!isTestnet)
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
+18
-44
@@ -1,23 +1,20 @@
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class MarketPlacePage extends StatelessWidget {
|
||||
MarketPlacePage({
|
||||
class CakeFeaturesPage extends StatelessWidget {
|
||||
CakeFeaturesPage({
|
||||
required this.dashboardViewModel,
|
||||
required this.marketPlaceViewModel,
|
||||
required this.cakeFeaturesViewModel,
|
||||
});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final MarketPlaceViewModel marketPlaceViewModel;
|
||||
final CakeFeaturesViewModel cakeFeaturesViewModel;
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
@@ -37,7 +34,7 @@ class MarketPlacePage extends StatelessWidget {
|
||||
children: [
|
||||
SizedBox(height: 50),
|
||||
Text(
|
||||
S.of(context).market_place,
|
||||
'Cake ${S.of(context).features}',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -59,15 +56,21 @@ class MarketPlacePage extends StatelessWidget {
|
||||
// ),
|
||||
SizedBox(height: 20),
|
||||
DashBoardRoundedCardWidget(
|
||||
onTap: () => _launchUrl("buy.cakepay.com"),
|
||||
title: S.of(context).cake_pay_web_cards_title,
|
||||
subTitle: S.of(context).cake_pay_web_cards_subtitle,
|
||||
onTap: () => _launchMarketPlaceUrl("buy.cakepay.com"),
|
||||
svgPicture: SvgPicture.asset(
|
||||
'assets/images/cards.svg',
|
||||
height: 125,
|
||||
width: 125,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(height: 10),
|
||||
DashBoardRoundedCardWidget(
|
||||
title: "NanoGPT",
|
||||
subTitle: S.of(context).nanogpt_subtitle,
|
||||
onTap: () => _launchMarketPlaceUrl("cake.nano-gpt.com"),
|
||||
onTap: () => _launchUrl("cake.nano-gpt.com"),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -79,41 +82,12 @@ class MarketPlacePage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _launchMarketPlaceUrl(String url) async {
|
||||
void _launchUrl(String url) {
|
||||
try {
|
||||
launchUrl(
|
||||
Uri.https(url),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove ionia flow/files if we will discard it
|
||||
void _navigatorToGiftCardsPage(BuildContext context) {
|
||||
final walletType = dashboardViewModel.type;
|
||||
|
||||
switch (walletType) {
|
||||
case WalletType.haven:
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: S.of(context).gift_cards_unavailable,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
break;
|
||||
default:
|
||||
marketPlaceViewModel.isIoniaUserAuthenticated().then((value) {
|
||||
if (value) {
|
||||
Navigator.pushNamed(context, Routes.ioniaManageCardsPage);
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/setting_action_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/setting_actions.dart';
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
@@ -180,6 +181,11 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||
|
||||
final item = SettingActions.all[index];
|
||||
|
||||
if (!widget.dashboardViewModel.hasSilentPayments &&
|
||||
item.name(context) == S.of(context).silent_payments_settings) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
|
||||
return SettingActionButton(
|
||||
|
||||
@@ -10,8 +10,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class PresentReceiveOptionPicker extends StatelessWidget {
|
||||
PresentReceiveOptionPicker(
|
||||
{required this.receiveOptionViewModel, required this.color});
|
||||
PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color});
|
||||
|
||||
final ReceiveOptionViewModel receiveOptionViewModel;
|
||||
final Color color;
|
||||
@@ -43,17 +42,17 @@ class PresentReceiveOptionPicker extends StatelessWidget {
|
||||
Text(
|
||||
S.current.receive,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: color),
|
||||
fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 10.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: color)))
|
||||
builder: (_) => Text(
|
||||
receiveOptionViewModel.selectedReceiveOption
|
||||
.toString()
|
||||
.replaceAll(RegExp(r'silent payments', caseSensitive: false),
|
||||
S.current.silent_payments)
|
||||
.replaceAll(
|
||||
RegExp(r'default', caseSensitive: false), S.current.string_default),
|
||||
style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color)))
|
||||
],
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
@@ -73,65 +72,75 @@ class PresentReceiveOptionPicker extends StatelessWidget {
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children:[ AlertBackground(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Spacer(),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24, bottom: 24),
|
||||
child: (ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: receiveOptionViewModel.options.length,
|
||||
itemBuilder: (_, index) {
|
||||
final option = receiveOptionViewModel.options[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(popUpContext);
|
||||
children: [
|
||||
AlertBackground(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Spacer(),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24, bottom: 24),
|
||||
child: (ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: receiveOptionViewModel.options.length,
|
||||
itemBuilder: (_, index) {
|
||||
final option = receiveOptionViewModel.options[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(popUpContext);
|
||||
|
||||
receiveOptionViewModel.selectReceiveOption(option);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Observer(builder: (_) {
|
||||
final value = receiveOptionViewModel.selectedReceiveOption;
|
||||
receiveOptionViewModel.selectReceiveOption(option);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Observer(builder: (_) {
|
||||
final value = receiveOptionViewModel.selectedReceiveOption;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(option.toString(),
|
||||
textAlign: TextAlign.left,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
).copyWith(
|
||||
fontWeight:
|
||||
value == option ? FontWeight.w800 : FontWeight.w500,
|
||||
)),
|
||||
RoundedCheckbox(
|
||||
value: value == option,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, index) => SizedBox(height: 30),
|
||||
)),
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
option
|
||||
.toString()
|
||||
.replaceAll(
|
||||
RegExp(r'silent payments', caseSensitive: false),
|
||||
S.current.silent_payments)
|
||||
.replaceAll(RegExp(r'default', caseSensitive: false),
|
||||
S.current.string_default),
|
||||
textAlign: TextAlign.left,
|
||||
style: textSmall(
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.titleColor,
|
||||
).copyWith(
|
||||
fontWeight:
|
||||
value == option ? FontWeight.w800 : FontWeight.w500,
|
||||
)),
|
||||
RoundedCheckbox(
|
||||
value: value == option,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, index) => SizedBox(height: 30),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer()
|
||||
],
|
||||
Spacer()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -67,8 +67,7 @@ class ReceivePage extends BasePage {
|
||||
|
||||
@override
|
||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||
(BuildContext context, Widget scaffold) =>
|
||||
GradientBackground(scaffold: scaffold);
|
||||
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
@@ -99,115 +98,144 @@ class ReceivePage extends BasePage {
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
]),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
||||
child: QRWidget(
|
||||
addressListViewModel: addressListViewModel,
|
||||
formKey: _formKey,
|
||||
heroTag: _heroTag,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
amountController: _amountController,
|
||||
isLight: currentTheme.type == ThemeType.light),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
padding: EdgeInsets.all(0),
|
||||
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: addressListViewModel.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = addressListViewModel.items[index];
|
||||
Widget cell = Container();
|
||||
|
||||
if (item is WalletAccountListHeader) {
|
||||
cell = HeaderTile(
|
||||
showTrailingButton: true,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
trailingButtonTap: () async {
|
||||
if (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.haven) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||
} else {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => getIt.get<NanoAccountListPage>());
|
||||
}
|
||||
},
|
||||
title: S.of(context).accounts,
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListHeader) {
|
||||
cell = HeaderTile(
|
||||
title: S.of(context).addresses,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
|
||||
showSearchButton: true,
|
||||
trailingButtonTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||
trailingIcon: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListItem) {
|
||||
cell = Observer(builder: (_) {
|
||||
final isCurrent =
|
||||
item.address == addressListViewModel.address.address;
|
||||
final backgroundColor = isCurrent
|
||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
||||
final textColor = isCurrent
|
||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
||||
|
||||
return AddressCell.fromItem(item,
|
||||
isCurrent: isCurrent,
|
||||
hasBalance: addressListViewModel.isElectrumWallet,
|
||||
backgroundColor: backgroundColor,
|
||||
textColor: textColor,
|
||||
onTap: (_) => addressListViewModel.setAddress(item),
|
||||
onEdit: () => Navigator.of(context)
|
||||
.pushNamed(Routes.newSubaddress, arguments: item));
|
||||
});
|
||||
}
|
||||
|
||||
return index != 0
|
||||
? cell
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(30),
|
||||
topRight: Radius.circular(30)),
|
||||
child: cell,
|
||||
);
|
||||
})),
|
||||
],
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
]),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
||||
child: QRWidget(
|
||||
addressListViewModel: addressListViewModel,
|
||||
formKey: _formKey,
|
||||
heroTag: _heroTag,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
amountController: _amountController,
|
||||
isLight: currentTheme.type == ThemeType.light),
|
||||
),
|
||||
));
|
||||
Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
padding: EdgeInsets.all(0),
|
||||
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: addressListViewModel.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = addressListViewModel.items[index];
|
||||
Widget cell = Container();
|
||||
|
||||
if (item is WalletAccountListHeader) {
|
||||
cell = HeaderTile(
|
||||
showTrailingButton: true,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
trailingButtonTap: () async {
|
||||
if (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.haven) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||
} else {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => getIt.get<NanoAccountListPage>());
|
||||
}
|
||||
},
|
||||
title: S.of(context).accounts,
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListHeader) {
|
||||
final hasTitle = item.title != null;
|
||||
|
||||
cell = HeaderTile(
|
||||
title: hasTitle ? item.title! : S.of(context).addresses,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
showTrailingButton:
|
||||
!addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle,
|
||||
showSearchButton: true,
|
||||
trailingButtonTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||
trailingIcon: hasTitle
|
||||
? null
|
||||
: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color:
|
||||
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is WalletAddressListItem) {
|
||||
cell = Observer(builder: (_) {
|
||||
final isCurrent = item.address == addressListViewModel.address.address;
|
||||
final backgroundColor = isCurrent
|
||||
? Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.currentTileBackgroundColor
|
||||
: Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.tilesBackgroundColor;
|
||||
final textColor = isCurrent
|
||||
? Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.currentTileTextColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
||||
|
||||
return AddressCell.fromItem(
|
||||
item,
|
||||
isCurrent: isCurrent,
|
||||
hasBalance: addressListViewModel.isElectrumWallet,
|
||||
backgroundColor: backgroundColor,
|
||||
textColor: textColor,
|
||||
onTap: item.isOneTimeReceiveAddress == true
|
||||
? null
|
||||
: (_) => addressListViewModel.setAddress(item),
|
||||
onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary
|
||||
? null
|
||||
: () => Navigator.of(context)
|
||||
.pushNamed(Routes.newSubaddress, arguments: item),
|
||||
onDelete: !addressListViewModel.isSilentPayments || item.isPrimary
|
||||
? null
|
||||
: () => addressListViewModel.deleteAddress(item),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return index != 0
|
||||
? cell
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
||||
child: cell,
|
||||
);
|
||||
})),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
|
||||
child: Text(
|
||||
addressListViewModel.isSilentPayments
|
||||
? S.of(context).silent_payments_disclaimer
|
||||
: S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor)),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,22 @@ class AddressCell extends StatelessWidget {
|
||||
required this.textColor,
|
||||
this.onTap,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
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}) =>
|
||||
factory AddressCell.fromItem(
|
||||
WalletAddressListItem item, {
|
||||
required bool isCurrent,
|
||||
required Color backgroundColor,
|
||||
required Color textColor,
|
||||
Function(String)? onTap,
|
||||
bool hasBalance = false,
|
||||
Function()? onEdit,
|
||||
Function()? onDelete,
|
||||
}) =>
|
||||
AddressCell(
|
||||
address: item.address,
|
||||
name: item.name ?? '',
|
||||
@@ -36,6 +40,7 @@ class AddressCell extends StatelessWidget {
|
||||
textColor: textColor,
|
||||
onTap: onTap,
|
||||
onEdit: onEdit,
|
||||
onDelete: onDelete,
|
||||
txCount: item.txCount,
|
||||
balance: item.balance,
|
||||
isChange: item.isChange,
|
||||
@@ -49,6 +54,7 @@ class AddressCell extends StatelessWidget {
|
||||
final Color textColor;
|
||||
final Function(String)? onTap;
|
||||
final Function()? onEdit;
|
||||
final Function()? onDelete;
|
||||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
@@ -64,7 +70,8 @@ class AddressCell extends StatelessWidget {
|
||||
} else {
|
||||
return formatIfCashAddr.substring(0, addressPreviewLength) +
|
||||
'...' +
|
||||
formatIfCashAddr.substring(formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
|
||||
formatIfCashAddr.substring(
|
||||
formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +146,7 @@ class AddressCell extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
'Balance: $balance',
|
||||
'${S.of(context).balance}: $balance',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -180,7 +187,7 @@ class AddressCell extends StatelessWidget {
|
||||
|
||||
ActionPane _actionPane(BuildContext context) => ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: 0.3,
|
||||
extentRatio: onDelete != null ? 0.4 : 0.3,
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (_) => onEdit?.call(),
|
||||
@@ -189,6 +196,14 @@ class AddressCell extends StatelessWidget {
|
||||
icon: Icons.edit,
|
||||
label: S.of(context).edit,
|
||||
),
|
||||
if (onDelete != null)
|
||||
SlidableAction(
|
||||
onPressed: (_) => onDelete!.call(),
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.delete,
|
||||
label: S.of(context).delete,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
||||
@@ -11,7 +15,8 @@ class RescanPage extends BasePage {
|
||||
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
|
||||
|
||||
@override
|
||||
String get title => S.current.rescan;
|
||||
String get title =>
|
||||
_rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan;
|
||||
final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
|
||||
final RescanViewModel _rescanViewModel;
|
||||
|
||||
@@ -19,20 +24,28 @@ class RescanPage extends BasePage {
|
||||
Widget body(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child:
|
||||
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
BlockchainHeightWidget(key: _blockchainHeightWidgetKey,
|
||||
onHeightOrDateEntered: (value) =>
|
||||
_rescanViewModel.isButtonEnabled = value),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Observer(
|
||||
builder: (_) => BlockchainHeightWidget(
|
||||
key: _blockchainHeightWidgetKey,
|
||||
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
|
||||
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
|
||||
doSingleScan: _rescanViewModel.doSingleScan,
|
||||
toggleSingleScan: () =>
|
||||
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
|
||||
)),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
isLoading:
|
||||
_rescanViewModel.state == RescanWalletState.rescaning,
|
||||
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
|
||||
text: S.of(context).rescan,
|
||||
onPressed: () async {
|
||||
await _rescanViewModel.rescanCurrentWallet(
|
||||
restoreHeight:
|
||||
_blockchainHeightWidgetKey.currentState!.height);
|
||||
if (_rescanViewModel.isSilentPaymentsScan) {
|
||||
return _toggleSilentPaymentsScanning(context);
|
||||
}
|
||||
|
||||
_rescanViewModel.rescanCurrentWallet(
|
||||
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
color: Theme.of(context).primaryColor,
|
||||
@@ -42,4 +55,32 @@ class RescanPage extends BasePage {
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
|
||||
final height = _blockchainHeightWidgetKey.currentState!.height;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final needsToSwitch =
|
||||
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
|
||||
|
||||
if (needsToSwitch) {
|
||||
return showPopUp<void>(
|
||||
context: navigatorKey.currentState!.context,
|
||||
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
|
||||
alertTitle: S.of(_dialogContext).change_current_node_title,
|
||||
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
|
||||
rightButtonText: S.of(_dialogContext).ok,
|
||||
leftButtonText: S.of(_dialogContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
|
||||
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(_dialogContext).pop(),
|
||||
));
|
||||
}
|
||||
|
||||
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
@@ -420,6 +421,10 @@ class SendPage extends BasePage {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sendViewModel.isElectrumWallet) {
|
||||
bitcoin!.updateFeeRates(sendViewModel.wallet);
|
||||
}
|
||||
|
||||
reaction((_) => sendViewModel.state, (ExecutionState state) {
|
||||
if (dialogContext != null && dialogContext?.mounted == true) {
|
||||
Navigator.of(dialogContext!).pop();
|
||||
|
||||
@@ -39,7 +39,9 @@ class ConnectionSyncPage extends BasePage {
|
||||
),
|
||||
if (dashboardViewModel.hasRescan) ...[
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.rescan,
|
||||
title: dashboardViewModel.hasSilentPayments
|
||||
? S.current.silent_payments_scanning
|
||||
: S.current.rescan,
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||
),
|
||||
if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class SilentPaymentsSettingsPage extends BasePage {
|
||||
SilentPaymentsSettingsPage(this._silentPaymentsSettingsViewModel);
|
||||
|
||||
@override
|
||||
String get title => S.current.silent_payments_settings;
|
||||
|
||||
final SilentPaymentsSettingsViewModel _silentPaymentsSettingsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Observer(builder: (_) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.silent_payments_display_card,
|
||||
value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay,
|
||||
onValueChange: (_, bool value) {
|
||||
_silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value);
|
||||
},
|
||||
),
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.silent_payments_always_scan,
|
||||
value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan,
|
||||
onValueChange: (_, bool value) {
|
||||
_silentPaymentsSettingsViewModel.setSilentPaymentsAlwaysScan(value);
|
||||
},
|
||||
),
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.silent_payments_scanning,
|
||||
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
isSending: item.isSending,
|
||||
isFrozen: item.isFrozen,
|
||||
isChange: item.isChange,
|
||||
isSilentPayment: item.isSilentPayment,
|
||||
onCheckBoxTap: item.isFrozen
|
||||
? null
|
||||
: () async {
|
||||
|
||||
@@ -12,6 +12,7 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
required this.isSending,
|
||||
required this.isFrozen,
|
||||
required this.isChange,
|
||||
required this.isSilentPayment,
|
||||
this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
@@ -21,18 +22,16 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
final bool isSending;
|
||||
final bool isFrozen;
|
||||
final bool isChange;
|
||||
final bool isSilentPayment;
|
||||
final Function()? onCheckBoxTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final unselectedItemColor = Theme.of(context).cardColor;
|
||||
final selectedItemColor = Theme.of(context).primaryColor;
|
||||
final itemColor = isSending
|
||||
? selectedItemColor
|
||||
: unselectedItemColor;
|
||||
final amountColor = isSending
|
||||
? Colors.white
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
|
||||
final amountColor =
|
||||
isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final addressColor = isSending
|
||||
? Colors.white.withOpacity(0.5)
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor;
|
||||
@@ -121,6 +120,23 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSilentPayment)
|
||||
Container(
|
||||
height: 17,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: Colors.white),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
S.of(context).silent_payments,
|
||||
style: TextStyle(
|
||||
color: itemColor,
|
||||
fontSize: 7,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -96,6 +96,7 @@ class WalletListBody extends StatefulWidget {
|
||||
class WalletListBodyState extends State<WalletListBody> {
|
||||
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
|
||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
|
||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||
@@ -162,7 +163,10 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
wallet.isEnabled
|
||||
? _imageFor(type: wallet.type)
|
||||
? _imageFor(
|
||||
type: wallet.type,
|
||||
isTestnet: wallet.isTestnet,
|
||||
)
|
||||
: nonWalletTypeIcon,
|
||||
SizedBox(width: 10),
|
||||
Flexible(
|
||||
@@ -297,9 +301,12 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
);
|
||||
}
|
||||
|
||||
Image _imageFor({required WalletType type}) {
|
||||
Image _imageFor({required WalletType type, bool? isTestnet}) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
if (isTestnet == true) {
|
||||
return tBitcoinIcon;
|
||||
}
|
||||
return bitcoinIcon;
|
||||
case WalletType.monero:
|
||||
return moneroIcon;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/date_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -12,13 +14,19 @@ class BlockchainHeightWidget extends StatefulWidget {
|
||||
this.onHeightChange,
|
||||
this.focusNode,
|
||||
this.onHeightOrDateEntered,
|
||||
this.hasDatePicker = true})
|
||||
: super(key: key);
|
||||
this.hasDatePicker = true,
|
||||
this.isSilentPaymentsScan = false,
|
||||
this.toggleSingleScan,
|
||||
this.doSingleScan = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final Function(int)? onHeightChange;
|
||||
final Function(bool)? onHeightOrDateEntered;
|
||||
final FocusNode? focusNode;
|
||||
final bool hasDatePicker;
|
||||
final bool isSilentPaymentsScan;
|
||||
final bool doSingleScan;
|
||||
final Function()? toggleSingleScan;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BlockchainHeightState();
|
||||
@@ -64,9 +72,10 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
child: BaseTextFormField(
|
||||
focusNode: widget.focusNode,
|
||||
controller: restoreHeightController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: false),
|
||||
hintText: S.of(context).widgets_restore_from_blockheight,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
|
||||
hintText: widget.isSilentPaymentsScan
|
||||
? S.of(context).silent_payments_scan_from_height
|
||||
: S.of(context).widgets_restore_from_blockheight,
|
||||
)))
|
||||
],
|
||||
),
|
||||
@@ -78,8 +87,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
@@ -91,22 +99,47 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
child: IgnorePointer(
|
||||
child: BaseTextFormField(
|
||||
controller: dateController,
|
||||
hintText: S.of(context).widgets_restore_from_date,
|
||||
hintText: widget.isSilentPaymentsScan
|
||||
? S.of(context).silent_payments_scan_from_date
|
||||
: S.of(context).widgets_restore_from_date,
|
||||
)),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
if (widget.isSilentPaymentsScan)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).scan_one_block,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: StandardSwitch(
|
||||
value: widget.doSingleScan,
|
||||
onTaped: () => widget.toggleSingleScan?.call(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 40, right: 40, top: 24),
|
||||
child: Text(
|
||||
S.of(context).restore_from_date_or_blockheight,
|
||||
widget.isSilentPaymentsScan
|
||||
? S.of(context).silent_payments_scan_from_date_or_blockheight
|
||||
: S.of(context).restore_from_date_or_blockheight,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).hintColor
|
||||
),
|
||||
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
|
||||
),
|
||||
)
|
||||
]
|
||||
@@ -123,7 +156,12 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
lastDate: now);
|
||||
|
||||
if (date != null) {
|
||||
final height = monero!.getHeightByDate(date: date);
|
||||
int height;
|
||||
if (widget.isSilentPaymentsScan) {
|
||||
height = bitcoin!.getHeightByDate(date: date);
|
||||
} else {
|
||||
height = monero!.getHeightByDate(date: date);
|
||||
}
|
||||
setState(() {
|
||||
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
||||
restoreHeightController.text = '$height';
|
||||
|
||||
@@ -2,19 +2,28 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class DashBoardRoundedCardWidget extends StatelessWidget {
|
||||
DashBoardRoundedCardWidget({
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
this.hint,
|
||||
this.svgPicture,
|
||||
this.icon,
|
||||
this.onClose,
|
||||
this.customBorder,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback? onClose;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final Widget? hint;
|
||||
final SvgPicture? svgPicture;
|
||||
final Icon? icon;
|
||||
final double? customBorder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -26,34 +35,56 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.fromLTRB(20, 20, 40, 20),
|
||||
padding: EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(customBorder ?? 20),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.cardTextColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
softWrap: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (svgPicture != null) svgPicture!,
|
||||
if (icon != null) icon!
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
)
|
||||
if (hint != null) ...[
|
||||
SizedBox(height: 10),
|
||||
hint!,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -17,6 +17,7 @@ class SettingActions {
|
||||
connectionSettingAction,
|
||||
walletSettingAction,
|
||||
addressBookSettingAction,
|
||||
silentPaymentsSettingAction,
|
||||
securityBackupSettingAction,
|
||||
privacySettingAction,
|
||||
displaySettingAction,
|
||||
@@ -35,6 +36,15 @@ class SettingActions {
|
||||
supportSettingAction,
|
||||
];
|
||||
|
||||
static SettingActions silentPaymentsSettingAction = SettingActions._(
|
||||
name: (context) => S.of(context).silent_payments_settings,
|
||||
image: 'assets/images/bitcoin_menu.png',
|
||||
onTap: (BuildContext context) {
|
||||
Navigator.pop(context);
|
||||
Navigator.of(context).pushNamed(Routes.silentPaymentsSettings);
|
||||
},
|
||||
);
|
||||
|
||||
static SettingActions connectionSettingAction = SettingActions._(
|
||||
name: (context) => S.of(context).connection_sync,
|
||||
image: 'assets/images/nodes_menu.png',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
@@ -108,6 +107,8 @@ abstract class SettingsStoreBase with Store {
|
||||
required this.lookupsOpenAlias,
|
||||
required this.lookupsENS,
|
||||
required this.customBitcoinFeeRate,
|
||||
required this.silentPaymentsCardDisplay,
|
||||
required this.silentPaymentsAlwaysScan,
|
||||
TransactionPriority? initialBitcoinTransactionPriority,
|
||||
TransactionPriority? initialMoneroTransactionPriority,
|
||||
TransactionPriority? initialHavenTransactionPriority,
|
||||
@@ -518,6 +519,16 @@ abstract class SettingsStoreBase with Store {
|
||||
(int customBitcoinFeeRate) =>
|
||||
_sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, customBitcoinFeeRate));
|
||||
|
||||
reaction((_) => silentPaymentsCardDisplay, (bool silentPaymentsCardDisplay) {
|
||||
_sharedPreferences.setBool(
|
||||
PreferencesKey.silentPaymentsCardDisplay, silentPaymentsCardDisplay);
|
||||
});
|
||||
|
||||
reaction(
|
||||
(_) => silentPaymentsAlwaysScan,
|
||||
(bool silentPaymentsAlwaysScan) => _sharedPreferences.setBool(
|
||||
PreferencesKey.silentPaymentsAlwaysScan, silentPaymentsAlwaysScan));
|
||||
|
||||
this.nodes.observe((change) {
|
||||
if (change.newValue != null && change.key != null) {
|
||||
_saveCurrentNode(change.newValue!, change.key!);
|
||||
@@ -713,6 +724,12 @@ abstract class SettingsStoreBase with Store {
|
||||
@observable
|
||||
int customBitcoinFeeRate;
|
||||
|
||||
@observable
|
||||
bool silentPaymentsCardDisplay;
|
||||
|
||||
@observable
|
||||
bool silentPaymentsAlwaysScan;
|
||||
|
||||
final SecureStorage _secureStorage;
|
||||
final SharedPreferences _sharedPreferences;
|
||||
final BackgroundTasks _backgroundTasks;
|
||||
@@ -859,6 +876,10 @@ abstract class SettingsStoreBase with Store {
|
||||
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
||||
final silentPaymentsCardDisplay =
|
||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
||||
final silentPaymentsAlwaysScan =
|
||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
||||
|
||||
// If no value
|
||||
if (pinLength == null || pinLength == 0) {
|
||||
@@ -1103,6 +1124,8 @@ abstract class SettingsStoreBase with Store {
|
||||
lookupsOpenAlias: lookupsOpenAlias,
|
||||
lookupsENS: lookupsENS,
|
||||
customBitcoinFeeRate: customBitcoinFeeRate,
|
||||
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
||||
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||
initialHavenTransactionPriority: havenTransactionPriority,
|
||||
@@ -1242,6 +1265,10 @@ abstract class SettingsStoreBase with Store {
|
||||
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
||||
silentPaymentsCardDisplay =
|
||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
||||
silentPaymentsAlwaysScan =
|
||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final bitcoinElectrumServerId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/contact_base.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
@@ -40,16 +41,19 @@ abstract class ContactListViewModelBase with Store {
|
||||
});
|
||||
} else if (info.addresses?.isNotEmpty == true) {
|
||||
info.addresses!.forEach((address, label) {
|
||||
if (label.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final name = _createName(info.name, label);
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
walletTypeToCryptoCurrency(info.type,
|
||||
isTestnet:
|
||||
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
|
||||
));
|
||||
// Only one contact address per wallet
|
||||
return;
|
||||
});
|
||||
} else if (info.address != null) {
|
||||
} else {
|
||||
walletContacts.add(WalletContact(
|
||||
info.address,
|
||||
info.name,
|
||||
@@ -64,7 +68,9 @@ abstract class ContactListViewModelBase with Store {
|
||||
}
|
||||
|
||||
String _createName(String walletName, String label) {
|
||||
return label.isNotEmpty ? '$walletName ($label)' : walletName;
|
||||
return label.isNotEmpty
|
||||
? '$walletName (${label.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active).replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments)})'
|
||||
: walletName;
|
||||
}
|
||||
|
||||
final bool isAutoGenerateEnabled;
|
||||
|
||||
@@ -60,6 +60,9 @@ abstract class BalanceViewModelBase with Store {
|
||||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
@computed
|
||||
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@computed
|
||||
double get price {
|
||||
final price = fiatConvertationStore.prices[appStore.wallet!.currency];
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'cake_features_view_model.g.dart';
|
||||
|
||||
class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel;
|
||||
|
||||
abstract class CakeFeaturesViewModelBase with Store {
|
||||
final IoniaService _ioniaService;
|
||||
|
||||
CakeFeaturesViewModelBase(this._ioniaService);
|
||||
|
||||
Future<bool> isIoniaUserAuthenticated() async {
|
||||
return await _ioniaService.isLogined();
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:eth_sig_util/util/utils.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -201,6 +202,14 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (hasSilentPayments) {
|
||||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||
|
||||
reaction((_) => wallet.syncStatus, (SyncStatus syncStatus) {
|
||||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -287,11 +296,36 @@ abstract class DashboardViewModelBase with Store {
|
||||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
|
||||
@computed
|
||||
bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet);
|
||||
|
||||
@computed
|
||||
bool get hasRescan =>
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.haven;
|
||||
|
||||
@computed
|
||||
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@computed
|
||||
bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
|
||||
|
||||
final KeyService keyService;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
bool silentPaymentsScanningActive = false;
|
||||
|
||||
@action
|
||||
void setSilentPaymentsScanning(bool active) {
|
||||
silentPaymentsScanningActive = active;
|
||||
|
||||
if (hasSilentPayments) {
|
||||
bitcoin!.setScanningActive(wallet, active);
|
||||
}
|
||||
}
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
AppStore appStore;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'market_place_view_model.g.dart';
|
||||
|
||||
class MarketPlaceViewModel = MarketPlaceViewModelBase with _$MarketPlaceViewModel;
|
||||
|
||||
abstract class MarketPlaceViewModelBase with Store {
|
||||
final IoniaService _ioniaService;
|
||||
|
||||
MarketPlaceViewModelBase(this._ioniaService);
|
||||
|
||||
|
||||
Future<bool> isIoniaUserAuthenticated() async {
|
||||
return await _ioniaService.isLogined();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'rescan_view_model.g.dart';
|
||||
@@ -8,11 +10,12 @@ class RescanViewModel = RescanViewModelBase with _$RescanViewModel;
|
||||
enum RescanWalletState { rescaning, none }
|
||||
|
||||
abstract class RescanViewModelBase with Store {
|
||||
RescanViewModelBase(this._wallet)
|
||||
: state = RescanWalletState.none,
|
||||
isButtonEnabled = false;
|
||||
RescanViewModelBase(this.wallet)
|
||||
: state = RescanWalletState.none,
|
||||
isButtonEnabled = false,
|
||||
doSingleScan = false;
|
||||
|
||||
final WalletBase _wallet;
|
||||
final WalletBase wallet;
|
||||
|
||||
@observable
|
||||
RescanWalletState state;
|
||||
@@ -20,11 +23,21 @@ abstract class RescanViewModelBase with Store {
|
||||
@observable
|
||||
bool isButtonEnabled;
|
||||
|
||||
@observable
|
||||
bool doSingleScan;
|
||||
|
||||
@computed
|
||||
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@action
|
||||
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
|
||||
state = RescanWalletState.rescaning;
|
||||
await _wallet.rescan(height: restoreHeight);
|
||||
_wallet.transactionHistory.clear();
|
||||
if (wallet.type != WalletType.bitcoin) {
|
||||
wallet.rescan(height: restoreHeight);
|
||||
wallet.transactionHistory.clear();
|
||||
} else {
|
||||
bitcoin!.rescan(wallet, height: restoreHeight, doSingleScan: doSingleScan);
|
||||
}
|
||||
state = RescanWalletState.none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
|
||||
|
||||
@computed
|
||||
bool get isReadyForSend => wallet.syncStatus is SyncedSyncStatus;
|
||||
bool get isReadyForSend =>
|
||||
wallet.syncStatus is SyncedSyncStatus ||
|
||||
// If silent payments scanning, can still send payments
|
||||
(wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
|
||||
|
||||
@computed
|
||||
List<Template> get templates => sendTemplateViewModel.templates
|
||||
@@ -599,6 +602,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
walletType == WalletType.litecoin ||
|
||||
walletType == WalletType.bitcoinCash) {
|
||||
if (error is TransactionWrongBalanceException) {
|
||||
if (error.amount != null)
|
||||
return S.current
|
||||
.tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
|
||||
|
||||
return S.current.tx_wrong_balance_exception(currency.toString());
|
||||
}
|
||||
if (error is TransactionNoInputsException) {
|
||||
@@ -625,9 +632,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
if (error is TransactionCommitFailedVoutNegative) {
|
||||
return S.current.tx_rejected_vout_negative;
|
||||
}
|
||||
if (error is TransactionCommitFailedBIP68Final) {
|
||||
return S.current.tx_rejected_bip68_final;
|
||||
}
|
||||
if (error is TransactionNoDustOnChangeException) {
|
||||
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
|
||||
}
|
||||
if (error is TransactionInputNotSupported) {
|
||||
return S.current.tx_invalid_input;
|
||||
}
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'silent_payments_settings_view_model.g.dart';
|
||||
|
||||
class SilentPaymentsSettingsViewModel = SilentPaymentsSettingsViewModelBase
|
||||
with _$SilentPaymentsSettingsViewModel;
|
||||
|
||||
abstract class SilentPaymentsSettingsViewModelBase with Store {
|
||||
SilentPaymentsSettingsViewModelBase(this._settingsStore, this._wallet);
|
||||
|
||||
final SettingsStore _settingsStore;
|
||||
final WalletBase _wallet;
|
||||
|
||||
@computed
|
||||
bool get silentPaymentsCardDisplay => _settingsStore.silentPaymentsCardDisplay;
|
||||
|
||||
@computed
|
||||
bool get silentPaymentsAlwaysScan => _settingsStore.silentPaymentsAlwaysScan;
|
||||
|
||||
@action
|
||||
void setSilentPaymentsCardDisplay(bool value) {
|
||||
_settingsStore.silentPaymentsCardDisplay = value;
|
||||
}
|
||||
|
||||
@action
|
||||
void setSilentPaymentsAlwaysScan(bool value) {
|
||||
_settingsStore.silentPaymentsAlwaysScan = value;
|
||||
if (value) bitcoin!.setScanningActive(_wallet, true);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store {
|
||||
required this.isChange,
|
||||
required this.amountRaw,
|
||||
required this.vout,
|
||||
required this.keyImage
|
||||
required this.keyImage,
|
||||
required this.isSilentPayment,
|
||||
});
|
||||
|
||||
@observable
|
||||
@@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store {
|
||||
|
||||
@observable
|
||||
String? keyImage;
|
||||
|
||||
@observable
|
||||
bool isSilentPayment;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
@@ -86,22 +88,33 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
||||
@action
|
||||
void _updateUnspentCoinsInfo() {
|
||||
_items.clear();
|
||||
_items.addAll(_getUnspents().map((elem) {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
return UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
isFrozen: info.isFrozen,
|
||||
note: info.note,
|
||||
isSending: info.isSending,
|
||||
amountRaw: elem.value,
|
||||
vout: elem.vout,
|
||||
keyImage: elem.keyImage,
|
||||
isChange: elem.isChange,
|
||||
);
|
||||
}));
|
||||
List<UnspentCoinsItem> unspents = [];
|
||||
_getUnspents().forEach((elem) {
|
||||
try {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
unspents.add(UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
isFrozen: info.isFrozen,
|
||||
note: info.note,
|
||||
isSending: info.isSending,
|
||||
amountRaw: elem.value,
|
||||
vout: elem.vout,
|
||||
keyImage: elem.keyImage,
|
||||
isChange: elem.isChange,
|
||||
isSilentPayment: info.isSilentPayment ?? false,
|
||||
));
|
||||
} catch (e, s) {
|
||||
print(s);
|
||||
print(e.toString());
|
||||
ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s));
|
||||
}
|
||||
});
|
||||
|
||||
_items.addAll(unspents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class WalletAddressListHeader extends ListItem {}
|
||||
class WalletAddressListHeader extends ListItem {
|
||||
final String? title;
|
||||
WalletAddressListHeader({this.title});
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ class WalletAddressListItem extends ListItem {
|
||||
this.name,
|
||||
this.txCount,
|
||||
this.balance,
|
||||
this.isChange = false})
|
||||
: super();
|
||||
this.isChange = false,
|
||||
// Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc
|
||||
this.isOneTimeReceiveAddress = false,
|
||||
}) : super();
|
||||
|
||||
final int? id;
|
||||
final bool isPrimary;
|
||||
@@ -18,7 +20,8 @@ class WalletAddressListItem extends ListItem {
|
||||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
final bool? isOneTimeReceiveAddress;
|
||||
|
||||
@override
|
||||
String toString() => name ?? address;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,20 +334,55 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
}
|
||||
|
||||
if (isElectrumWallet) {
|
||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
|
||||
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
|
||||
final isPrimary = address.id == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
return WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: AmountConverter.amountIntToString(
|
||||
walletTypeToCryptoCurrency(type), subaddress.balance),
|
||||
isChange: subaddress.isChange);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
walletTypeToCryptoCurrency(type), address.balance),
|
||||
isChange: address.isChange,
|
||||
);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
addressList.add(WalletAddressListHeader(title: S.current.received));
|
||||
|
||||
final receivedAddressItems =
|
||||
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
|
||||
return WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: false,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: AmountConverter.amountIntToString(
|
||||
walletTypeToCryptoCurrency(type), address.balance),
|
||||
isChange: address.isChange,
|
||||
isOneTimeReceiveAddress: true,
|
||||
);
|
||||
});
|
||||
addressList.addAll(receivedAddressItems);
|
||||
} else {
|
||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
|
||||
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(addressItems);
|
||||
}
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.ethereum) {
|
||||
@@ -416,9 +451,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
@computed
|
||||
bool get isSilentPayments =>
|
||||
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
|
||||
|
||||
@computed
|
||||
bool get isAutoGenerateSubaddressEnabled =>
|
||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
|
||||
!isSilentPayments;
|
||||
|
||||
List<ListItem> _baseItems;
|
||||
|
||||
@@ -478,4 +518,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
amount = '';
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void deleteAddress(ListItem item) {
|
||||
if (wallet.type == WalletType.bitcoin && item is WalletAddressListItem) {
|
||||
bitcoin!.deleteSilentPaymentAddress(wallet, item.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ class WalletListItem {
|
||||
required this.key,
|
||||
this.isCurrent = false,
|
||||
this.isEnabled = true,
|
||||
this.isTestnet = false,
|
||||
});
|
||||
|
||||
final String name;
|
||||
@@ -14,4 +15,5 @@ class WalletListItem {
|
||||
final bool isCurrent;
|
||||
final dynamic key;
|
||||
final bool isEnabled;
|
||||
final bool isTestnet;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ abstract class WalletListViewModelBase with Store {
|
||||
key: info.key,
|
||||
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
|
||||
isEnabled: availableWalletTypes.contains(info.type),
|
||||
isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user