Merge remote-tracking branch 'origin/main'

This commit is contained in:
OmarHatem
2025-07-29 17:16:18 +03:00
174 changed files with 2039 additions and 682 deletions
+25 -7
View File
@@ -18,6 +18,14 @@ class CWBitcoin extends Bitcoin {
passphrase: passphrase,
);
@override
WalletCredentials createBitcoinWalletFromKeys({
required String name,
required String password,
required String xpub,
}) =>
BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub);
@override
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
{required String name,
@@ -62,11 +70,7 @@ class CWBitcoin extends Bitcoin {
final bitcoinWallet = wallet as ElectrumWallet;
final keys = bitcoinWallet.keys;
return <String, String>{
'wif': keys.wif,
'privateKey': keys.privateKey,
'publicKey': keys.publicKey
};
return bitcoinWallet.keys.toJson();
}
@override
@@ -126,7 +130,8 @@ class CWBitcoin extends Bitcoin {
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
memo: out.memo,
extra: out.extra))
.toList(),
priority: priority as BitcoinTransactionPriority,
feeRate: bitcoinFeeRate,
@@ -275,7 +280,14 @@ class CWBitcoin extends Bitcoin {
}
@override
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
List<ReceivePageOption> getBitcoinReceivePageOptions(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
final keys = bitcoinWallet.keys;
if (keys.privateKey.isEmpty) {
return BitcoinReceivePageOption.allViewOnly;
}
return BitcoinReceivePageOption.all;
}
@override
List<ReceivePageOption> getLitecoinReceivePageOptions() {
@@ -727,6 +739,12 @@ class CWBitcoin extends Bitcoin {
}
}
@override
Future<void> commitPsbtUR(Object wallet, List<String> urCodes) {
final _wallet = wallet as BitcoinWalletBase;
return _wallet.commitPsbtUR(urCodes);
}
@override
String getPayjoinEndpoint(Object wallet) {
final _wallet = wallet as ElectrumWallet;
+126
View File
@@ -0,0 +1,126 @@
import 'dart:async';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
class NodeSwitchingService {
NodeSwitchingService({
required this.appStore,
required this.settingsStore,
required this.nodeSource,
});
static const int _healthCheckIntervalSeconds = 30;
Timer? _healthCheckTimer;
bool _isSwitching = false;
bool get isSwitching => _isSwitching;
final AppStore appStore;
final SettingsStore settingsStore;
final Box<Node> nodeSource;
// Track used nodes to cycle through all trusted nodes
final Map<WalletType, List<dynamic>> _usedNodeKeys = {};
void startHealthCheckTimer() {
_healthCheckTimer?.cancel();
_healthCheckTimer = Timer.periodic(
Duration(seconds: _healthCheckIntervalSeconds),
(_) => performHealthCheck(),
);
performHealthCheck();
}
void stopHealthCheckTimer() {
_healthCheckTimer?.cancel();
_healthCheckTimer = null;
}
Future<void> performHealthCheck() async {
if (appStore.wallet == null) return;
if (!settingsStore.enableAutomaticNodeSwitching) return;
if (_isSwitching) return;
try {
final isHealthy = await appStore.wallet!.checkNodeHealth();
if (!isHealthy) {
await _switchToNextTrustedNode();
}
} catch (e) {
printV('Error during health check: $e');
}
}
/// Switch to the next available trusted node
Future<void> _switchToNextTrustedNode() async {
_isSwitching = true;
try {
final walletType = appStore.wallet!.type;
final currentNode = settingsStore.getCurrentNode(walletType);
// Get all trusted nodes for this wallet type
final trustedNodes = nodeSource.values
.where((node) => node.type == walletType && node.isEnabledForAutoSwitching)
.toList();
if (trustedNodes.isEmpty) {
printV('No trusted nodes available for switching');
return;
}
// Initialize used nodes list for this wallet type if it does not exist
_usedNodeKeys.putIfAbsent(walletType, () => []);
// Add current node to used list if not already there
if (!_usedNodeKeys[walletType]!.contains(currentNode.key)) {
_usedNodeKeys[walletType]!.add(currentNode.key);
}
// Get next unused trusted node from the list
Node? nextNode;
for (final node in trustedNodes) {
if (!_usedNodeKeys[walletType]!.contains(node.key)) {
nextNode = node;
break;
}
}
// If all trusted nodes have been used, reset the list and start over
if (nextNode == null) {
printV('All trusted nodes have been tried, resetting and starting over');
_usedNodeKeys[walletType]!.clear();
nextNode = trustedNodes.first;
}
// Add the next node to used list
_usedNodeKeys[walletType]!.add(nextNode.key);
printV('Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw}');
printV('Used nodes for ${walletType}: ${_usedNodeKeys[walletType]}');
// Update the current node in settings
settingsStore.nodes[walletType] = nextNode;
// Connect the wallet to the new node
await appStore.wallet!.connectToNode(node: nextNode);
await appStore.wallet!.startSync();
printV('Successfully switched to node: ${nextNode.uriRaw}');
} catch (e) {
printV('Error switching to next trusted node: $e');
} finally {
_isSwitching = false;
}
}
}
+8 -1
View File
@@ -23,6 +23,7 @@ import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/core/node_switching_service.dart';
import 'package:cake_wallet/entities/biometric_auth.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/contact_record.dart';
@@ -977,7 +978,7 @@ Future<void> setup({
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
getIt.registerFactoryParam<AnimatedURPage, String, void>((String urQr, _) =>
getIt.registerFactoryParam<AnimatedURPage, Map<String, String>, void>((Map<String, String> urQr, _) =>
AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
@@ -1516,5 +1517,11 @@ Future<void> setup({
getIt.registerFactory(() => DEuroSavingsPage(getIt<DEuroViewModel>()));
getIt.registerLazySingleton(() => NodeSwitchingService(
appStore: getIt.get<AppStore>(),
settingsStore: getIt.get<SettingsStore>(),
nodeSource: _nodeSource,
));
_isSetupFinished = true;
}
+25 -1
View File
@@ -1,3 +1,6 @@
import 'dart:convert';
import 'dart:isolate';
import 'package:basic_utils/basic_utils.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_picker_option.dart';
@@ -5,6 +8,7 @@ import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:dnssec_proof/dnssec_proof.dart';
import 'package:flutter/material.dart';
class Bip353Record {
@@ -22,6 +26,19 @@ class Bip353Record {
'address': 'On-Chain Address',
};
static Future<String?> fetchDnsProof(String bip353Name) async {
if (bip353Name.startsWith('₿')) {
bip353Name = bip353Name.substring(1);
}
final parts = bip353Name.split('@');
if (parts.length != 2) return null;
final userPart = parts[0];
final domainPart = parts[1];
final bip353Domain = '$userPart.user._bitcoin-payment.$domainPart.';
final proof = await Isolate.run(() => DnsProver.getTxtProof(bip353Domain));
return base64.encode(proof);
}
static Future<Map<String, String>?> fetchUriByCryptoCurrency(
String bip353Name, String asset) async {
try {
@@ -30,7 +47,10 @@ class Bip353Record {
if (parts.length != 2) return null;
final userPart = parts[0];
String userPart = parts[0];
if (userPart.startsWith('₿')) {
userPart = userPart.substring(1);
}
final domainPart = parts[1];
// 2. Construct the correct subdomain: "user._bitcoin-payment.domain"
@@ -42,6 +62,10 @@ class Bip353Record {
RRecordType.TXT,
dnssec: true,
);
final proof = await fetchDnsProof(bip353Name);
if (proof == null) {
throw Exception('DNSSEC proof not found');
}
if (txtRecords == null) return null;
+50 -3
View File
@@ -510,7 +510,10 @@ Future<void> defaultSettingsMigration(
providerName: "SwapTrade",
enabled: true,
);
break;
break;
case 50:
migrateExistingNodesToUseAutoSwitching(nodes: nodes, powNodes: powNodes);
break;
default:
break;
}
@@ -1088,7 +1091,7 @@ Future<void> checkCurrentNodes(
if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false, isEnabledForAutoSwitching: true);
await nodeSource.add(cakeWalletElectrum);
final cakeWalletElectrumTestnet =
Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
@@ -1188,7 +1191,7 @@ Future<void> resetBitcoinElectrumServer(
if (cakeWalletNode == null) {
cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false, isEnabledForAutoSwitching: true);
// final cakeWalletElectrumTestnet =
// Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
// await nodeSource.add(cakeWalletElectrumTestnet);
@@ -1272,3 +1275,47 @@ Future<void> removeMoneroWorld(
);
}
}
Future<void> migrateExistingNodesToUseAutoSwitching(
{required Box<Node> nodes, required Box<Node> powNodes}) async {
final listOfDefaultNodesWithAutoSwitching = [
'bitcoincash.stackwallet.com:50002',
'bch.aftrek.org:50002',
'btc-electrum.cakewallet.com:50002',
'fulcrum.sethforprivacy.com:50002',
'default-spv-nodes',
'dcrd.sethforprivacy.com:9108',
'ethereum-rpc.publicnode.com',
'eth.nownodes.io',
'ltc-electrum.cakewallet.com:50002',
'litecoin.stackwallet.com:20063',
'nano.nownodes.io',
'rpc.nano.to',
'node.nautilus.io',
'rpc.nano.to',
'workers.perish.co',
'worker.nanoriver.cc',
'xmr-node.cakewallet.com:18081',
'node.sethforprivacy.com:443',
'nodes.hashvault.pro:18081',
'polygon-bor-rpc.publicnode.com',
'matic.nownodes.io',
'api.mainnet-beta.solana.com:443',
'solana-rpc.publicnode.com:443',
'solana-mainnet.core.chainstack.com',
'api.trongrid.io',
'trx.nownodes.io',
'node3.monerodevs.org:34568',
'node2.monerodevs.org:34568',
'37.27.100.59:10500',
'zano.cakewallet.com:11211',
'electrum.cakewallet.com:50002',
];
for (var node in [...nodes.values.toList(), ...powNodes.values.toList()]) {
if (listOfDefaultNodesWithAutoSwitching.contains(node.uriRaw)) {
node.isEnabledForAutoSwitching = true;
await node.save();
}
}
}
+7 -1
View File
@@ -344,7 +344,13 @@ class AddressResolver {
if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) {
final chosenAddress = await Bip353Record.pickBip353AddressChoice(context, text, bip353AddressMap);
if (chosenAddress != null) {
return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text);
try {
final dnsProof = await Bip353Record.fetchDnsProof(text);
return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text, dnsProof: dnsProof);
} catch (e) {
printV('Bip353Record.fetchBip353AddressAddress error: $e');
return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text);
}
}
}
+4
View File
@@ -26,6 +26,7 @@ class ParsedAddress {
this.profileImageUrl = '',
this.profileName = '',
this.parseFrom = ParseFrom.notParsed,
this.bip353DnsProof,
});
factory ParsedAddress.fetchEmojiAddress({
@@ -59,11 +60,13 @@ class ParsedAddress {
factory ParsedAddress.fetchBip353AddressAddress ({
required String address,
required String name,
String? dnsProof,
}) {
return ParsedAddress(
addresses: [address],
name: name,
parseFrom: ParseFrom.bip353,
bip353DnsProof: dnsProof,
);
}
@@ -178,4 +181,5 @@ class ParsedAddress {
final String profileImageUrl;
final String profileName;
final ParseFrom parseFrom;
final String? bip353DnsProof;
}
+1
View File
@@ -112,4 +112,5 @@ class PreferencesKey {
'${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}';
static String backgroundSyncLastTrigger(String walletId) => 'background_sync_last_trigger_${walletId}';
static const backgroundSyncNotificationsEnabled = 'background_sync_notifications_enabled';
static const enableAutomaticNodeSwitching = 'enable_automatic_node_switching';
}
+4 -1
View File
@@ -6,6 +6,7 @@ import 'package:cake_wallet/app_scroll_behavior.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/background_sync.dart';
import 'package:cake_wallet/core/node_switching_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/default_settings_migration.dart';
@@ -249,7 +250,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
payjoinSessionSource: payjoinSessionSource,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
initialMigrationVersion: 49,
initialMigrationVersion: 50,
);
}
@@ -327,6 +328,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
final authService = getIt.get<AuthService>();
final linkViewModel = getIt.get<LinkViewModel>();
final tradeMonitor = getIt.get<TradeMonitor>();
final nodeSwitchingService = getIt.get<NodeSwitchingService>();
final settingsStore = appStore.settingsStore;
final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>();
@@ -351,6 +353,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
authService: authService,
linkViewModel: linkViewModel,
tradeMonitor: tradeMonitor,
nodeSwitchingService: nodeSwitchingService,
child: ThemeProvider(
themeStore: appStore.themeStore,
materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
+2 -2
View File
@@ -399,9 +399,9 @@ class CWMonero extends Monero {
}
@override
String exportOutputsUR(Object wallet, bool all) {
Map<String, String> exportOutputsUR(Object wallet) {
final moneroWallet = wallet as MoneroWallet;
return moneroWallet.exportOutputsUR(all);
return moneroWallet.exportOutputsUR();
}
@override
+3
View File
@@ -14,6 +14,7 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/core/node_switching_service.dart';
Future<void> bootstrapOffline() async {
final authenticationStore = getIt.get<AuthenticationStore>();
@@ -40,4 +41,6 @@ void bootstrapOnline(GlobalKey<NavigatorState> navigatorKey, {required bool load
startCurrentFiatApiModeChangeReaction(appStore, settingsStore, fiatConversionStore);
startOnCurrentNodeChangeReaction(appStore);
startFiatRateUpdate(appStore, settingsStore, fiatConversionStore);
getIt.get<NodeSwitchingService>().startHealthCheckTimer();
}
@@ -197,9 +197,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
Platform.isIOS
? S.of(context).connect_your_hardware_wallet_ios
: S.of(context).connect_your_hardware_wallet,
Platform.isAndroid
? S.of(context).connect_your_hardware_wallet
: S.of(context).connect_your_hardware_wallet_ios,
style: Theme.of(context)
.textTheme.titleMedium,
textAlign: TextAlign.center,
@@ -174,8 +174,12 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
onPressed: () async {
Navigator.of(context).pushNamed(Routes.exchangeTradeExternalSendPage);
},
color: Theme.of(context).colorScheme.surfaceContainer,
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
color: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.primary,
textColor: widget.exchangeTradeViewModel.isSendable
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.onPrimary,
),
SizedBox(height: 16),
Observer(
@@ -183,18 +187,19 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
final trade = widget.exchangeTradeViewModel.trade;
final sendingState = widget.exchangeTradeViewModel.sendViewModel.state;
return widget.exchangeTradeViewModel.isSendable &&
!(sendingState is TransactionCommitted)
? LoadingPrimaryButton(
key: ValueKey('exchange_trade_page_send_from_cake_button_key'),
isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty,
isLoading: sendingState is IsExecutingState,
onPressed: () => widget.exchangeTradeViewModel.confirmSending(),
text: S.current.send_from_cake_wallet,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
)
: Offstage();
return Offstage(
offstage: !(widget.exchangeTradeViewModel.isSendable &&
!(sendingState is TransactionCommitted)),
child: LoadingPrimaryButton(
key: ValueKey('exchange_trade_page_send_from_cake_button_key'),
isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty,
isLoading: sendingState is IsExecutingState,
onPressed: () => widget.exchangeTradeViewModel.confirmSending(),
text: S.current.send_from_cake_wallet,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
);
},
),
],
@@ -9,7 +9,9 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/tooltip_bottom_sheet.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
@@ -37,86 +39,71 @@ class DEuroSavingsPage extends BasePage {
@override
String get title => S.current.deuro_savings;
Widget trailing(BuildContext context) => MergeSemantics(
child: SizedBox(
height: 37,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: "Refresh",
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent),
),
onPressed: _dEuroViewModel.reloadSavingsUserData,
child: Icon(
Icons.refresh,
color: pageIconColor(context),
size: 20,
),
),
),
),
),
);
@override
Widget body(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, _dEuroViewModel));
return Container(
width: double.infinity,
child: Column(
children: <Widget>[
Observer(
builder: (_) => SavingsCard(
isDarkTheme: currentTheme.isDark,
interestRate: "${_dEuroViewModel.interestRate}%",
savingsBalance: _dEuroViewModel.savingsBalance,
fiatSavingsBalance: _dEuroViewModel.fiatSavingsBalance,
currency: CryptoCurrency.deuro,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
onAddSavingsPressed: () => _onSavingsAdd(context),
onRemoveSavingsPressed: () => _onSavingsRemove(context),
onApproveSavingsPressed: _dEuroViewModel.prepareApproval,
onTooltipPressed: () => _onSavingsTooltipPressed(context),
isEnabled: _dEuroViewModel.isEnabled,
isLoading: _dEuroViewModel.isLoading,
),
),
Observer(
builder: (_) => InterestCardWidget(
isDarkTheme: currentTheme.isDark,
title: S.of(context).deuro_savings_collect_interest,
fiatAccruedInterest: _dEuroViewModel.fiatAccruedInterest,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
accruedInterest: _dEuroViewModel.accruedInterest,
onCollectInterest: _dEuroViewModel.prepareCollectInterest,
onReinvestInterest: _dEuroViewModel.prepareReinvestInterest,
onTooltipPressed: () => _onInterestTooltipPressed(context),
isEnabled: _dEuroViewModel.isEnabled,
),
),
Spacer(),
SafeArea(
child: Padding(
padding: EdgeInsets.only(bottom: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoChip(
label: S.of(context).deuro_about_deuro,
icon: CupertinoIcons.info,
onPressed: () => _showWelcomeTooltip(context),
return RefreshIndicator(
displacement: responsiveLayoutUtil.screenHeight * 0.1,
onRefresh: _dEuroViewModel.reloadSavingsUserData,
child: CustomScrollView(
slivers: <Widget>[
SliverFillRemaining(
child: SizedBox(
width: double.infinity,
child: Column(
children: <Widget>[
Observer(
builder: (_) => SavingsCard(
isDarkTheme: currentTheme.isDark,
interestRate: "${_dEuroViewModel.interestRateFormated}%",
savingsBalance: _dEuroViewModel.savingsBalanceFormated,
fiatSavingsBalance: _dEuroViewModel.fiatSavingsBalanceFormated,
currency: CryptoCurrency.deuro,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
onAddSavingsPressed: () => _onSavingsAdd(context),
onRemoveSavingsPressed: () => _onSavingsRemove(context),
onApproveSavingsPressed: _dEuroViewModel.prepareApproval,
onTooltipPressed: () => _onSavingsTooltipPressed(context),
isEnabled: _dEuroViewModel.isEnabled,
isLoading: _dEuroViewModel.isLoading,
),
),
Observer(
builder: (_) => InterestCardWidget(
isDarkTheme: currentTheme.isDark,
title: S.of(context).deuro_savings_collect_interest,
fiatAccruedInterest: _dEuroViewModel.fiatAccruedInterestFormated,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
accruedInterest: _dEuroViewModel.accruedInterestFormated,
onCollectInterest: _onCollectInterest,
onReinvestInterest: _onReinvestInterest,
onTooltipPressed: () => _onInterestTooltipPressed(context),
isEnabled: _dEuroViewModel.isSavingsActionsEnabled,
),
),
Spacer(),
SafeArea(
child: Padding(
padding: EdgeInsets.only(bottom: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoChip(
label: S.of(context).deuro_about_deuro,
icon: CupertinoIcons.info,
onPressed: () => _showWelcomeTooltip(context),
),
SizedBox(width: 10),
InfoChip(
label: S.of(context).website,
icon: CupertinoIcons.globe,
onPressed: () => launchUrlString("https://deuro.com/"),
)
],
),
),
),
SizedBox(width: 10),
InfoChip(
label: S.of(context).website,
icon: CupertinoIcons.globe,
onPressed: () => launchUrlString("https://deuro.com/"),
)
],
),
),
@@ -126,14 +113,36 @@ class DEuroSavingsPage extends BasePage {
);
}
bool _editSheetIsOpen = false;
Future<void> _onSavingsAdd(BuildContext context) async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
final amount = await _showEditBottomSheet(context, isAdding: true);
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, true);
_editSheetIsOpen = false;
}
Future<void> _onSavingsRemove(BuildContext context) async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
final amount = await _showEditBottomSheet(context, isAdding: false);
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, false);
_editSheetIsOpen = false;
}
Future<void> _onReinvestInterest() async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
await _dEuroViewModel.prepareReinvestInterest();
_editSheetIsOpen = false;
}
Future<void> _onCollectInterest() async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
await _dEuroViewModel.prepareCollectInterest();
_editSheetIsOpen = false;
}
bool _isReactionsSet = false;
@@ -175,10 +184,10 @@ class DEuroSavingsPage extends BasePage {
currency: CryptoCurrency.deuro,
amount: S.of(bottomSheetContext).send_amount,
amountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
? _dEuroViewModel.accruedInterest
? _dEuroViewModel.accruedInterestFormated
: tx.amountFormatted,
fiatAmountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
? _dEuroViewModel.fiatAccruedInterest
? _dEuroViewModel.fiatAccruedInterestFormated
: _dEuroViewModel.pendingTransactionFiatAmountFormatted,
fee: S.of(bottomSheetContext).send_estimated_fee,
feeValue: tx.feeFormatted,
@@ -301,11 +310,11 @@ class DEuroSavingsPage extends BasePage {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
builder: (BuildContext bottomSheetContext) => TooltipSheet(
currentTheme: currentTheme,
titleText: title,
titleIconPath: CryptoCurrency.deuro.iconPath,
content: content,
tooltip: content,
footerType: FooterType.doubleActionButton,
doubleActionRightButtonText: S.of(context).close,
rightActionButtonKey: ValueKey('deuro_page_tooltip_dialog_${key}_ok_button_key'),
@@ -358,7 +367,8 @@ class DEuroSavingsPage extends BasePage {
content: S.of(context).deuro_tooltip_no_eth,
singleActionButtonKey: ValueKey('deuro_page_tooltip_dialog_no_eth_ok_button_key'),
singleActionButtonText: S.of(context).close,
onSingleActionButtonPressed: () => Navigator.of(bottomSheetContext).pop(), footerType: FooterType.singleActionButton,
onSingleActionButtonPressed: () => Navigator.of(bottomSheetContext).pop(),
footerType: FooterType.singleActionButton,
),
);
}
@@ -375,7 +385,9 @@ class DEuroSavingsPage extends BasePage {
balanceTitle: isAdding
? S.of(context).deuro_savings_available_to_add
: S.of(context).deuro_savings_available_to_remove,
balance: isAdding ? _dEuroViewModel.accountBalance : _dEuroViewModel.savingsBalance,
balance: isAdding
? _dEuroViewModel.accountBalanceFormated
: _dEuroViewModel.savingsBalanceFormated,
footerType: FooterType.none,
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
@@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/numpad.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cw_core/parse_fixed.dart';
import 'package:flutter/material.dart';
class SavingsEditSheet extends BaseBottomSheet {
@@ -14,7 +15,8 @@ class SavingsEditSheet extends BaseBottomSheet {
super.titleIconPath,
this.balance,
this.balanceTitle,
required super.footerType, required super.maxHeight,
required super.footerType,
required super.maxHeight,
});
@override
@@ -53,8 +55,35 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
}
String amount = '0';
bool isValid = false;
final FocusNode _numpadFocusNode = FocusNode();
void _onPressedAll() => setState(() {
amount = widget.balance!;
isValid = _validate();
});
void _onNumberPressed(int i) => setState(() {
amount = amount == '0' ? i.toString() : '${amount}${i}';
isValid = _validate();
});
void _onDeletePressed() => setState(() {
amount = amount.length > 1 ? amount.substring(0, amount.length - 1) : '0';
isValid = _validate();
});
void _onDecimalPressed() => setState(() {
amount = '${amount.replaceAll('.', '')}.';
isValid = _validate();
});
bool _validate() {
final amountBigInt = parseFixed(amount, 18);
final balanceBigInt = parseFixed(widget.balance!, 18);
return balanceBigInt >= amountBigInt && amountBigInt > BigInt.zero;
}
@override
Widget build(BuildContext context) => SafeArea(
child: Column(children: [
@@ -80,18 +109,14 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
AssetBalanceRow(
title: widget.balanceTitle!,
amount: widget.balance!,
onAllPressed: () => setState(() => amount = widget.balance!),
onAllPressed: _onPressedAll,
),
],
NumberPad(
focusNode: _numpadFocusNode,
onNumberPressed: (i) => setState(
() => amount = amount == '0' ? i.toString() : '${amount}${i}',
),
onDeletePressed: () => setState(
() => amount = amount.length > 1 ? amount.substring(0, amount.length - 1) : '0',
),
onDecimalPressed: () => setState(() => amount = '${amount.replaceAll('.', '')}.'),
onNumberPressed: _onNumberPressed,
onDeletePressed: _onDeletePressed,
onDecimalPressed: _onDecimalPressed,
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
@@ -101,7 +126,7 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: false,
isDisabled: false,
isDisabled: !isValid,
),
)
]),
@@ -32,6 +32,7 @@ class NodeForm extends StatelessWidget {
..setLogin((editingNode!.login ?? ''))
..setSSL((editingNode!.isSSL))
..setTrusted((editingNode!.trusted))
..setIsEnabledForAutoSwitching((editingNode!.isEnabledForAutoSwitching))
..setSocksProxy((editingNode!.useSocksProxy))
..setSocksProxyAddress((editingNode!.socksProxyAddress ?? ''));
}
@@ -153,6 +154,25 @@ class NodeForm extends StatelessWidget {
),
),
SizedBox(height: 10.0),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.isEnabledForAutoSwitching,
borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
iconColor: Theme.of(context).colorScheme.primary,
onChanged: (value) => nodeViewModel.isEnabledForAutoSwitching = value,
caption: S.current.enable_for_auto_switching,
),
),
],
),
),
SizedBox(height: 10.0),
if (nodeViewModel.hasAuthCredentials) ...[
Row(
children: <Widget>[
@@ -115,7 +115,8 @@ class QRWidget extends StatelessWidget {
),
),
if (addressListViewModel.isPayjoinUnavailable &&
!addressListViewModel.isSilentPayments) ...[
!addressListViewModel.isSilentPayments &&
!addressListViewModel.isCupcake) ...[
GestureDetector(
onTap: () => _onPayjoinInactivePressed(context),
child: Row(
@@ -6,6 +6,7 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/option_tile.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -45,10 +46,6 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
bool isRestoring = false;
bool get _doesSupportHardwareWallets {
if (!DeviceInfo.instance.isMobile) {
return false;
}
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
.isNotEmpty;
@@ -57,13 +54,40 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
return true;
}
String get imageRestoreHWPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_hw_dark.png'
: 'assets/images/restore_hw.png';
String get imageRestoreCupcakePath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_cupcake_dark.png'
: 'assets/images/restore_cupcake.png';
String get imageRestoreQRPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_qr_dark.png'
: 'assets/images/restore_qr.png';
String get imageRestoreHotWalletPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_hot_wallet_dark.png'
: 'assets/images/restore_hot_wallet.png';
String get imageRestoreBackupPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_backup_dark.png'
: 'assets/images/restore_backup.png';
@override
Widget build(BuildContext context) {
final imageColor = Theme.of(context).colorScheme.primary;
final imageLedger = Image.asset('assets/images/hardware_wallet/ledger_nano_x.png',
width: 40, color: imageColor);
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
final imageRestoreHW = Image.asset(imageRestoreHWPath, width: 55);
final imageRestoreCupcake = Image.asset(imageRestoreCupcakePath, width: 55);
final imageRestoreQR = Image.asset(imageRestoreQRPath, width: 55);
final imageSeedKeys = Image.asset(imageRestoreHotWalletPath, width: 55);
final imageRestoreBackup = Image.asset(imageRestoreBackupPath, width: 55);
return Center(
child: Container(
@@ -80,15 +104,16 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
title: S.of(context).restore_title_from_seed_keys,
description: S.of(context).restore_description_from_seed_keys,
),
if (widget.isNewInstall)
if (FeatureFlag.hasBitcoinViewOnly && DeviceInfo.instance.isMobile)
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_backup_button_key'),
onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
image: imageBackup,
title: S.of(context).restore_title_from_backup,
description: S.of(context).restore_description_from_backup,
key: ValueKey('restore_options_from_cupcake_button_key'),
onPressed: () => _onScanQRCode(context),
image: imageRestoreCupcake,
title: S.of(context).restore_title_from_cupcake,
description: S.of(context).restore_description_from_cupcake,
tag: S.of(context).new_tag,
),
),
if (_doesSupportHardwareWallets)
@@ -98,25 +123,33 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
onPressed: () =>
Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet),
image: imageLedger,
image: imageRestoreHW,
title: S.of(context).restore_title_from_hardware_wallet,
description: S.of(context).restore_description_from_hardware_wallet,
),
),
if (widget.isNewInstall)
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_backup_button_key'),
onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
image: imageRestoreBackup,
title: S.of(context).restore_title_from_backup,
description: S.of(context).restore_description_from_backup,
),
),
if (DeviceInfo.instance.isMobile)
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_qr_button_key'),
onPressed: () => _onScanQRCode(context),
icon: Icon(
Icons.qr_code_rounded,
color: imageColor,
size: 50,
),
image: imageRestoreQR,
title: S.of(context).scan_qr_code,
description: S.of(context).cold_or_recover_wallet),
)
description: S.of(context).cold_or_recover_wallet,
),
),
],
),
)),
@@ -198,14 +198,14 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
Widget _restoreFromKeysFormFields() {
// Decred can only restore a view only wallet with an account pubkey. Other
// fields are not used.
if (widget.walletRestoreViewModel.type == WalletType.decred) {
if (widget.walletRestoreViewModel.onlyViewKeyRestore) {
return Column(
children: [
BaseTextFormField(
controller: viewKeyController,
hintText: S.of(context).view_key_public,
maxLines: null,
)
),
],
);
}
@@ -253,13 +253,14 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
maxLines: null,
),
),
BlockchainHeightWidget(
key: blockchainHeightKey,
hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven,
onHeightChange: (_) => null,
onHeightOrDateEntered: widget.onHeightOrDateEntered,
walletType: widget.walletRestoreViewModel.type,
),
if (widget.walletRestoreViewModel.hasBlockchainHeightSelector)
BlockchainHeightWidget(
key: blockchainHeightKey,
hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven,
onHeightChange: (_) => null,
onHeightOrDateEntered: widget.onHeightOrDateEntered,
walletType: widget.walletRestoreViewModel.type,
),
],
);
}
@@ -199,7 +199,7 @@ class WalletRestorePage extends BasePage {
credentials['seed'] =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text;
if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) {
if (walletRestoreViewModel.hasBlockchainHeightSelector) {
credentials['height'] =
walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState?.height ??
-1;
@@ -219,7 +219,7 @@ class WalletRestorePage extends BasePage {
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
if (walletRestoreViewModel.type != WalletType.decred) {
if (!walletRestoreViewModel.onlyViewKeyRestore) {
credentials['address'] =
walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['spendKey'] =
@@ -519,7 +519,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
}
},
onViewKeyEntered: (bool entered) {
if (walletRestoreViewModel.type == WalletType.decred) {
if (widget.walletRestoreViewModel.onlyViewKeyRestore) {
walletRestoreViewModel.isButtonEnabled = entered;
}
},
@@ -536,7 +536,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
key: widget.walletRestoreFromSeedFormKey,
restoredWallet: walletRestoreViewModel.restoredWallet,
seedSettingsViewModel: widget.seedSettingsViewModel,
displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightSelector,
displayLanguageSelector: widget.walletRestoreViewModel.hasSeedLanguageSelector,
type: widget.walletRestoreViewModel.type,
blockHeightFocusNode: widget.blockHeightFocusNode,
@@ -563,7 +563,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
}
void _validateOnChange({bool isPolyseed = false}) {
if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightLanguageSelector) {
if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightSelector) {
final hasHeight = walletRestoreFromSeedFormKey
.currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty;
+8
View File
@@ -2,8 +2,10 @@ import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/node_switching_service.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/trade_monitor.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -29,6 +31,7 @@ class Root extends StatefulWidget {
required this.authService,
required this.linkViewModel,
required this.tradeMonitor,
required this.nodeSwitchingService,
}) : super(key: key);
final AuthenticationStore authenticationStore;
@@ -38,6 +41,7 @@ class Root extends StatefulWidget {
final Widget child;
final LinkViewModel linkViewModel;
final TradeMonitor tradeMonitor;
final NodeSwitchingService nodeSwitchingService;
@override
RootState createState() => RootState();
@@ -161,6 +165,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
}
widget.tradeMonitor.resumeTradeMonitoring();
// Trigger node health check when app resumes
widget.nodeSwitchingService.performHealthCheck();
break;
default:
break;
+2 -1
View File
@@ -450,7 +450,7 @@ class SendPage extends BasePage {
}
if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
arguments: 'export-outputs');
arguments: monero!.exportOutputsUR(sendViewModel.wallet));
await Future.delayed(
Duration(seconds: 1)); // wait for monero to refresh the state
}
@@ -570,6 +570,7 @@ class SendPage extends BasePage {
return ConfirmSendingBottomSheet(
key: ValueKey('send_page_confirm_sending_bottom_sheet_key'),
titleText: S.of(bottomSheetContext).confirm_transaction,
accessibleNavigationModeSlideActionButtonText: S.of(bottomSheetContext).send,
currentTheme: currentTheme,
footerType: FooterType.slideActionButton,
walletType: sendViewModel.walletType,
+1 -1
View File
@@ -258,7 +258,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
!sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
currencyValueValidator: output.sendAll
? sendViewModel.allAmountValidator
: sendViewModel.amountValidator,
: sendViewModel.amountValidator(output),
allAmountCallback: () async =>
output.setSendAll(await sendViewModel.sendingBalance),
),
@@ -2,6 +2,7 @@ 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/nodes/widgets/node_list_row.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -34,7 +35,23 @@ class ManageNodesPage extends BasePage {
onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode),
),
),
SizedBox(height: 20),
Observer(
builder: (_) => SettingsSwitcherCell(
key: ValueKey('manage_nodes_page_enable_auto_node_switching_button_key'),
title: S.current.enable_auto_node_switching,
value: isPow
? powNodeListViewModel!.enableAutomaticNodeSwitching
: nodeListViewModel!.enableAutomaticNodeSwitching,
onValueChange: (BuildContext context, bool value) {
if (isPow) {
powNodeListViewModel!.setEnableAutomaticNodeSwitching(value);
} else {
nodeListViewModel!.setEnableAutomaticNodeSwitching(value);
}
},
),
),
SizedBox(height: 8),
Observer(
builder: (BuildContext context) {
int itemsCount =
@@ -36,22 +36,22 @@ class OtherSettingsPage extends BasePage {
if (_otherSettingsViewModel.displayTransactionPriority)
_otherSettingsViewModel.walletType == WalletType.bitcoin ?
SettingsPriorityPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
) :
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,
+85 -37
View File
@@ -1,5 +1,7 @@
import 'dart:async';
import 'package:bbqrdart/bbqrdart.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
@@ -7,31 +9,49 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/animated_ur_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// ur:xmr-txunsigned - unsigned transaction
// should show a scanner afterwards.
class AnimatedURPage extends BasePage {
final bool isAll;
AnimatedURPage(this.animatedURmodel, {required String urQr, this.isAll = false}) {
if (urQr == "export-outputs") {
this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, false);
} else if (urQr == "export-outputs-all") {
this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, true);
} else {
this.urQr = urQr;
AnimatedURPage(this.animatedURmodel, {
required Map<String, String> this.urQr,
this.isAll = false,
}) {
if (urQr.keys.first.startsWith("export-outputs") && animatedURmodel.wallet.type == WalletType.monero) {
if (animatedURmodel.wallet.type == WalletType.monero) {
urQr = monero!.exportOutputsUR(animatedURmodel.wallet);
} else {
throw UnimplementedError("unable to handle UR: ${urQr.keys.first}");
}
}
for (var key in urQr.keys) {
if (key.isEmpty || urQr[key]!.isEmpty) {
urQr.remove(key);
continue;
}
urQr[key] = urQr[key]!.trim();
}
}
late String urQr;
Map<String, String> urQr = {};
final AnimatedURModel animatedURmodel;
String get urQrType {
final first = urQr.trim().split("\n")[0];
if (urQr.values.first.trim().substring(0, 2) == BBQR.header) {
return BBQR.header;
}
if (urQr.isEmpty) return "unknown";
final first = urQr.values.first.trim().split("\n")[0];
return first.split('/')[0];
}
@@ -43,11 +63,11 @@ class AnimatedURPage extends BasePage {
Padding(
padding: const EdgeInsets.only(top: 64.0),
child: URQR(
frames: urQr.trim().split("\n"),
urqr: urQr,
),
),
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header].contains(urQrType)) ...{
SizedBox(height: 32),
if (urQrType == "ur:xmr-txunsigned" || urQrType == "ur:xmr-output")
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
@@ -60,30 +80,8 @@ class AnimatedURPage extends BasePage {
),
),
),
SizedBox(height: 32),
if (urQrType == "ur:xmr-output" && !isAll) Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: double.maxFinite,
child: PrimaryButton(
onPressed: () => _exportAll(context),
text: "Export all",
color: Theme.of(context).colorScheme.secondary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
}
void _exportAll(BuildContext context) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) {
return AnimatedURPage(animatedURmodel, urQr: "export-outputs-all", isAll: true);
},
),
],
);
}
@@ -106,6 +104,11 @@ class AnimatedURPage extends BasePage {
Navigator.of(context).pop(true);
}
break;
case "ur:psbt": // psbt
final ur = await presentQRScanner(context);
if (ur == null) return;
await bitcoin!.commitPsbtUR(animatedURmodel.wallet, ur.trim().split("\n"));
Navigator.of(context).pop(true);
default:
throw UnimplementedError("unable to handle UR: ${urQrType}");
}
@@ -124,9 +127,9 @@ class AnimatedURPage extends BasePage {
}
class URQR extends StatefulWidget {
URQR({super.key, required this.frames});
URQR({super.key, required this.urqr});
List<String> frames;
final Map<String, String> urqr;
@override
// ignore: library_private_types_in_public_api
@@ -160,6 +163,24 @@ class _URQRState extends State<URQR> {
super.dispose();
}
late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
int selectedInt = 0;
List<String> get frames {
return widget.urqr[selected]?.split("\n") ?? [];
}
late String nextLabel = widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length] ;
void next() {
final keys = widget.urqr.keys.toList();
setState(() {
selectedInt++;
nextLabel = keys[(selectedInt + 1) % keys.length];
selected = keys[(selectedInt) % keys.length];
});
}
@override
Widget build(BuildContext context) {
return Column(
@@ -168,10 +189,37 @@ class _URQRState extends State<URQR> {
children: [
Center(
child: QrImage(
data: widget.frames[frame % widget.frames.length], version: -1,
data: frames[frame % frames.length],
version: -1,
size: 400,
),
),
if (widget.urqr.values.length > 1)
SizedBox(
width: double.maxFinite,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: double.maxFinite,
child: PrimaryButton(
onPressed: next,
text: nextLabel,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
),
),
),
if (FeatureFlag.hasDevOptions) ...{
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: """Current frame (${frame % frames.length}): ${frames[frame % frames.length]},
All frames:
- ${frames.join("\n - ")}"""));
},
child: Text("[dev] copy debug info"),
),
}
],
);
}
@@ -82,7 +82,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
showLegacySeedTab = widget.walletKeysViewModel.legacySeedSplit.isNotEmpty;
isLegacySeedOnly = widget.walletKeysViewModel.isLegacySeedOnly;
final totalTabs = 1 + (showKeyTab ? 1 : 0) + (showLegacySeedTab ? 1 : 0);
final totalTabs = (_hasSeeds ? 1 : 0) + (showKeyTab ? 1 : 0) + (showLegacySeedTab ? 1 : 0);
_tabController = TabController(length: totalTabs, vsync: this);
}
@@ -126,7 +126,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
dividerColor: Colors.transparent,
padding: EdgeInsets.zero,
tabs: [
Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')),
if (_hasSeeds) Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')),
if (showKeyTab) Tab(text: S.of(context).keys, key: ValueKey('wallet_keys_page_keys'),),
if (showLegacySeedTab) Tab(text: S.of(context).legacy, key: ValueKey('wallet_keys_page_seed_legacy')),
],
@@ -137,10 +137,11 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
child: TabBarView(
controller: _tabController,
children: [
Padding(
padding: const EdgeInsets.only(left: 22, right: 22),
child: _buildSeedTab(context, false),
),
if (_hasSeeds)
Padding(
padding: const EdgeInsets.only(left: 22, right: 22),
child: _buildSeedTab(context, false),
),
if (showKeyTab)
Padding(
padding: const EdgeInsets.only(left: 22, right: 22),
@@ -1,9 +1,11 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/rounded_icon_button.dart';
import 'package:cake_wallet/utils/image_utill.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CakePayTransactionSentBottomSheet extends StatelessWidget {
const CakePayTransactionSentBottomSheet({
@@ -39,18 +41,36 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
final String paymentId;
final String paymentIdValue;
TextStyle _titleStyle(BuildContext ctx) => Theme.of(ctx).textTheme.bodyLarge!;
TextStyle _titleStyle(BuildContext ctx) =>
Theme
.of(ctx)
.textTheme
.bodyLarge!;
TextStyle _valueStyle(BuildContext ctx) => Theme.of(ctx).textTheme.titleMedium!.copyWith(
TextStyle _valueStyle(BuildContext ctx) =>
Theme
.of(ctx)
.textTheme
.titleMedium!
.copyWith(
fontSize: 18,
fontWeight: FontWeight.w600,
);
TextStyle _subStyle(BuildContext ctx) => Theme.of(ctx).textTheme.labelSmall!.copyWith(
color: Theme.of(ctx).colorScheme.onSurfaceVariant,
TextStyle _subStyle(BuildContext ctx) =>
Theme
.of(ctx)
.textTheme
.labelSmall!
.copyWith(
color: Theme
.of(ctx)
.colorScheme
.onSurfaceVariant,
);
Widget _buildHeader(BuildContext ctx) => Column(
Widget _buildHeader(BuildContext ctx) =>
Column(
children: [
const SizedBox(height: 12),
Container(
@@ -58,7 +78,10 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
height: 5,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Theme.of(ctx).colorScheme.onSurface,
color: Theme
.of(ctx)
.colorScheme
.onSurface,
),
),
const SizedBox(height: 20),
@@ -69,11 +92,15 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
const SizedBox(width: 6),
Text(
titleText,
style: Theme.of(ctx).textTheme.titleLarge!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
),
style: Theme
.of(ctx)
.textTheme
.titleLarge!
.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
),
),
],
),
@@ -81,7 +108,8 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
],
);
Widget _buildBody(BuildContext context) => Padding(
Widget _buildBody(BuildContext context) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
children: [
@@ -112,25 +140,49 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
itemSubTitleStyle: _subStyle(context),
imagePath: output.parsedAddress.profileImageUrl,
),
const SizedBox(height: 8),
_StandardTile(
itemTitle: paymentId + ': \n' + paymentIdValue,
titleStyle: _titleStyle(context).copyWith(
fontSize: 14
),
itemValue: '',
itemValueStyle: _valueStyle(context),
copyButton: true,
copyButtonOnPressed: () async =>
await Clipboard.setData(ClipboardData(text: paymentIdValue)),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
children: [
Text(paymentId + ': ' + paymentIdValue,
style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center),
const SizedBox(height: 18),
Image.asset('assets/images/envelope.png'),
const SizedBox(height: 18),
Text(
S.of(context).cake_pay_card_email_delivered_message,
style: Theme.of(context).textTheme.bodyLarge,
S
.of(context)
.cake_pay_card_email_delivered_message,
style: Theme
.of(context)
.textTheme
.bodyLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
PrimaryButton(
text: S.of(context).close,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimaryContainer,
text: S
.of(context)
.close,
color: Theme
.of(context)
.colorScheme
.primary,
textColor: Theme
.of(context)
.colorScheme
.onPrimaryContainer,
onPressed: onClose,
),
],
@@ -142,12 +194,18 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
final maxHeight = MediaQuery.of(context).size.height * 0.9;
final maxHeight = MediaQuery
.of(context)
.size
.height * 0.9;
return ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
child: Material(
color: Theme.of(context).colorScheme.surface,
color: Theme
.of(context)
.colorScheme
.surface,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: maxHeight),
child: SingleChildScrollView(
@@ -174,6 +232,8 @@ class _StandardTile extends StatelessWidget {
this.itemSubTitle,
this.itemSubTitleStyle,
this.imagePath,
this.copyButton = false,
this.copyButtonOnPressed,
});
final String itemTitle;
@@ -183,6 +243,8 @@ class _StandardTile extends StatelessWidget {
final String? itemSubTitle;
final TextStyle? itemSubTitleStyle;
final String? imagePath;
final bool copyButton;
final VoidCallback? copyButtonOnPressed;
@override
Widget build(BuildContext context) {
@@ -193,12 +255,17 @@ class _StandardTile extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Theme.of(context).colorScheme.surfaceContainerLowest.withAlpha(80)),
color: Theme
.of(context)
.colorScheme
.surfaceContainerLowest
.withAlpha(80)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
mainAxisAlignment: copyButton ? MainAxisAlignment.spaceBetween : MainAxisAlignment.start,
children: [
if (imagePath != null)
Padding(
@@ -216,10 +283,19 @@ class _StandardTile extends StatelessWidget {
itemTitle,
style: titleStyle,
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
maxLines: 2,
softWrap: true,
)),
),
SizedBox(width: 8),
if (copyButton)
RoundedIconButton(
icon: Icons.copy_all_outlined,
onPressed: copyButtonOnPressed ?? () {},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
],
),
),
@@ -1,5 +1,5 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/standard_slide_button_widget.dart';
import 'package:cake_wallet/src/widgets/rounded_icon_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
@@ -10,6 +10,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'base_bottom_sheet_widget.dart';
@@ -45,7 +46,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
_currentTheme = currentTheme,
super(
titleText: titleText,
maxHeight: 900,
maxHeight: 900,
titleIconPath: titleIconPath,
currentTheme: currentTheme,
footerType: footerType,
@@ -115,6 +116,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
applyAddressFormatting: false,
copyButton: true,
)),
),
if (explanation != null)
@@ -307,20 +309,20 @@ class StandardInfoTile extends StatelessWidget {
}
class AddressTile extends StatelessWidget {
const AddressTile({
super.key,
required this.itemTitle,
required this.itemTitleTextStyle,
required this.address,
required this.itemSubTitleTextStyle,
required this.tileBackgroundColor,
required this.walletType,
this.amountTextStyle,
this.applyAddressFormatting = true,
this.imagePath,
this.amount,
this.itemSubTitle,
});
const AddressTile(
{super.key,
required this.itemTitle,
required this.itemTitleTextStyle,
required this.address,
required this.itemSubTitleTextStyle,
required this.tileBackgroundColor,
required this.walletType,
this.amountTextStyle,
this.applyAddressFormatting = true,
this.imagePath,
this.amount,
this.itemSubTitle,
this.copyButton = false});
final String itemTitle;
final TextStyle itemTitleTextStyle;
@@ -333,6 +335,7 @@ class AddressTile extends StatelessWidget {
final bool applyAddressFormatting;
final String? imagePath;
final String? itemSubTitle;
final bool copyButton;
@override
Widget build(BuildContext context) {
@@ -387,9 +390,24 @@ class AddressTile extends StatelessWidget {
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
))
: Text(
address,
style: itemTitleTextStyle,
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(address,
style: copyButton
? itemTitleTextStyle.copyWith(fontSize: 12)
: itemTitleTextStyle),
SizedBox(width: 8),
if (copyButton)
RoundedIconButton(
icon: Icons.copy_all_outlined,
onPressed: () async =>
await Clipboard.setData(ClipboardData(text: address)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
],
),
itemSubTitle == null
? Container()
@@ -0,0 +1,46 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:flutter/material.dart';
class TooltipSheet extends BaseBottomSheet {
final String tooltip;
const TooltipSheet({
required super.currentTheme,
required super.titleText,
super.titleIconPath,
required this.tooltip,
required super.footerType,
super.maxHeight = 900,
super.singleActionButtonText,
super.onSingleActionButtonPressed,
super.singleActionButtonKey,
super.doubleActionLeftButtonText,
super.doubleActionRightButtonText,
super.onLeftActionButtonPressed,
super.onRightActionButtonPressed,
super.leftActionButtonKey,
super.rightActionButtonKey,
});
@override
Widget contentWidget(BuildContext context) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Theme.of(context).colorScheme.surfaceContainer),
margin: const EdgeInsets.all(12),
padding: const EdgeInsets.all(12),
child: AutoSizeText(
tooltip,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
decoration: TextDecoration.none,
),
),
);
Widget footerWidget(BuildContext context) => SizedBox.shrink();
}
+20 -9
View File
@@ -7,6 +7,7 @@ class OptionTile extends StatelessWidget {
this.icon,
required this.title,
required this.description,
this.tag,
super.key,
}) : assert(image != null || icon != null);
@@ -15,6 +16,7 @@ class OptionTile extends StatelessWidget {
final Icon? icon;
final String title;
final String description;
final String? tag;
@override
Widget build(BuildContext context) {
@@ -36,25 +38,34 @@ class OptionTile extends StatelessWidget {
icon ?? image!,
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
padding: EdgeInsets.only(left: 20),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
title,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 220),
child: Text(title, style: Theme.of(context).textTheme.titleMedium),
),
if (tag != null) Container(
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.onSurfaceVariant
),
padding: EdgeInsets.symmetric(horizontal: 5),
margin: EdgeInsets.only(left: 5),
child: Text(tag!))
],
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
description,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
+38
View File
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
class RoundedIconButton extends StatelessWidget {
const RoundedIconButton(
{this.icon,
this.iconWidget,
required this.onPressed,
this.shape,
this.width,
this.height,
this.iconSize,
this.fillColor});
final IconData? icon;
final Widget? iconWidget;
final VoidCallback onPressed;
final ShapeBorder? shape;
final double? width;
final double? height;
final double? iconSize;
final Color? fillColor;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return RawMaterialButton(
onPressed: onPressed,
fillColor: fillColor ?? colorScheme.surfaceContainerHighest,
elevation: 0,
constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30),
padding: EdgeInsets.zero,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: shape ?? const CircleBorder(),
child: icon != null
? Icon(icon, size: iconSize ?? 14, color: colorScheme.onSurface)
: iconWidget);
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ class SettingActions {
name: (context) => S.of(context).export_outputs,
image: 'assets/images/monero_menu.png',
onTap: (BuildContext context) {
Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: 'export-outputs');
Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: {'export-outputs': 'export-outputs'});
},
);
+16
View File
@@ -128,6 +128,7 @@ abstract class SettingsStoreBase with Store {
required this.mwebEnabled,
required this.hasEnabledMwebBefore,
required this.mwebNodeUri,
required bool initialEnableAutomaticNodeSwitching,
TransactionPriority? initialBitcoinTransactionPriority,
TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialWowneroTransactionPriority,
@@ -186,6 +187,7 @@ abstract class SettingsStoreBase with Store {
currentSyncMode = initialSyncMode,
currentSyncAll = initialSyncAll,
currentBuiltinTor = initialBuiltinTor,
enableAutomaticNodeSwitching = initialEnableAutomaticNodeSwitching,
priority = ObservableMap<WalletType, TransactionPriority>() {
//this.nodes = ObservableMap<WalletType, Node>.of(nodes);
@@ -613,6 +615,11 @@ abstract class SettingsStoreBase with Store {
(_) => mwebNodeUri,
(String mwebNodeUri) =>
_sharedPreferences.setString(PreferencesKey.mwebNodeUri, mwebNodeUri));
reaction(
(_) => enableAutomaticNodeSwitching,
(bool enableAutomaticNodeSwitching) => _sharedPreferences.setBool(
PreferencesKey.enableAutomaticNodeSwitching, enableAutomaticNodeSwitching));
this.nodes.observe((change) {
if (change.newValue != null && change.key != null) {
@@ -859,6 +866,9 @@ abstract class SettingsStoreBase with Store {
@observable
String mwebNodeUri;
@observable
bool enableAutomaticNodeSwitching;
final SecureStorage _secureStorage;
final SharedPreferences _sharedPreferences;
@@ -1034,6 +1044,8 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false;
final mwebNodeUri = sharedPreferences.getString(PreferencesKey.mwebNodeUri) ??
"ltc-electrum.cakewallet.com:9333";
final enableAutomaticNodeSwitching =
sharedPreferences.getBool(PreferencesKey.enableAutomaticNodeSwitching) ?? true;
// If no value
if (pinLength == null || pinLength == 0) {
@@ -1335,6 +1347,7 @@ abstract class SettingsStoreBase with Store {
mwebEnabled: mwebEnabled,
mwebNodeUri: mwebNodeUri,
hasEnabledMwebBefore: hasEnabledMwebBefore,
initialEnableAutomaticNodeSwitching: enableAutomaticNodeSwitching,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialWowneroTransactionPriority: wowneroTransactionPriority,
initialZanoTransactionPriority: zanoTransactionPriority,
@@ -1478,6 +1491,9 @@ abstract class SettingsStoreBase with Store {
actionlistDisplayMode = ObservableList<ActionListDisplayMode>();
actionlistDisplayMode.addAll(deserializeActionlistDisplayModes(
sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode));
enableAutomaticNodeSwitching =
sharedPreferences.getBool(PreferencesKey.enableAutomaticNodeSwitching) ??
enableAutomaticNodeSwitching;
var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength);
// If no value
if (pinLength == null || pinLength == 0) {
+1
View File
@@ -9,4 +9,5 @@ class FeatureFlag {
static final bool isInAppTorEnabled = (Platform.isAndroid);
static const int verificationWordsCount = kDebugMode ? 0 : 2;
static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode);
static const bool hasBitcoinViewOnly = true;
}
@@ -536,7 +536,10 @@ abstract class DashboardViewModelBase with Store {
}
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin && !wallet.isHardwareWallet;
bool get hasSilentPayments =>
wallet.type == WalletType.bitcoin &&
(bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isNotEmpty &&
!wallet.isHardwareWallet;
@computed
bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
@@ -22,7 +22,7 @@ abstract class ReceiveOptionViewModelBase with Store {
switch (walletType) {
case WalletType.bitcoin:
_options = [
...bitcoin!.getBitcoinReceivePageOptions(),
...bitcoin!.getBitcoinReceivePageOptions(_wallet),
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
];
break;
@@ -95,13 +95,9 @@ abstract class ExchangeTradeViewModelBase with Store {
@observable
bool isSendable;
@computed
String get extraInfo => switch (trade.from) {
CryptoCurrency.xlm => '\n\n' + S.current.xlm_extra_info,
CryptoCurrency.xrp => '\n\n' + S.current.xrp_extra_info,
CryptoCurrency.ton => '\n\n' + S.current.ton_extra_info,
_ => ''
};
String get extraInfo => trade.extraId != null && trade.extraId!.isNotEmpty
? '\n\n' + S.current.exchange_extra_info
: '';
@computed
String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled
@@ -203,10 +199,9 @@ abstract class ExchangeTradeViewModelBase with Store {
),
]);
if (trade.extraId != null) {
final shouldAddExtraId = trade.from == CryptoCurrency.xrp || trade.from == CryptoCurrency.xlm || trade.from == CryptoCurrency.ton;
final isExtraIdExist = trade.extraId != null && trade.extraId!.isNotEmpty;
if (shouldAddExtraId) {
if (isExtraIdExist) {
final title = trade.from == CryptoCurrency.xrp
? S.current.destination_tag
: trade.from == CryptoCurrency.xlm || trade.from == CryptoCurrency.ton
@@ -215,14 +210,12 @@ abstract class ExchangeTradeViewModelBase with Store {
items.add(
ExchangeTradeItem(
title: title,
data: '${trade.extraId}',
isCopied: true,
isReceiveDetail: !shouldAddExtraId,
isExternalSendDetail: shouldAddExtraId
),
title: title,
data: trade.extraId ?? '',
isCopied: true,
isReceiveDetail: !isExtraIdExist,
isExternalSendDetail: isExtraIdExist),
);
}
}
items.add(
@@ -9,7 +9,6 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -30,10 +29,6 @@ abstract class LedgerViewModelBase with Store {
late final sdk.LedgerInterface ledgerPlusUSB;
bool get _doesSupportHardwareWallets {
if (!DeviceInfo.instance.isMobile) {
return false;
}
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(
WalletType.monero, Platform.isIOS)
@@ -65,6 +60,8 @@ abstract class LedgerViewModelBase with Store {
if (bleIsEnabled && !_bleIsInitialized) {
ledgerPlusBLE = sdk.LedgerInterface.ble(
onPermissionRequest: (_) async {
if (Platform.isMacOS) return true;
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
@@ -21,6 +21,8 @@ class DEuroViewModel = DEuroViewModelBase with _$DEuroViewModel;
abstract class DEuroViewModelBase with Store {
final AppStore _appStore;
static BigInt get MIN_ACCRUED_INTEREST => BigInt.parse("1000000000000");
DEuroViewModelBase(
this._appStore,
this.balanceViewModel,
@@ -44,39 +46,31 @@ abstract class DEuroViewModelBase with Store {
@action
void acceptDisclaimer() => _settingsStore.shouldShowDEuroDisclaimer = false;
@computed
String get pendingTransactionFiatAmountFormatted =>
isFiatDisabled ? '' : '$pendingTransactionFiatAmount ${fiat.title}';
@computed
String get pendingTransactionFeeFiatAmountFormatted =>
isFiatDisabled ? '' : '$pendingTransactionFeeFiatAmount ${fiat.title}';
FiatCurrency get fiat => _settingsStore.fiatCurrency;
@computed
String get pendingTransactionFiatAmount =>
transaction == null ? '0.00' : _getDEuroFiatAmount(transaction!.amountFormatted);
@computed
String get pendingTransactionFeeFiatAmount {
try {
if (transaction != null) {
final currency = CryptoCurrency.eth;
return calculateFiatAmount(
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: transaction!.feeFormattedValue,
);
} else {
return '0.00';
}
} catch (_) {
return '0.00';
}
String get pendingTransactionFiatAmountFormatted {
final amount = transaction == null ? '0.00' : _getDEuroFiatAmount(transaction!.amountFormatted);
return isFiatDisabled ? '' : '$amount ${fiat.title}';
}
@computed
String get accountBalance {
String get pendingTransactionFeeFiatAmountFormatted {
var amount = '0.00';
try {
if (transaction != null) {
final feeCurrency = CryptoCurrency.eth;
amount = calculateFiatAmount(
price: _fiatConversationStore.prices[feeCurrency]!,
cryptoAmount: transaction!.feeFormattedValue,
);
}
} catch (_) {}
return isFiatDisabled ? '' : '$amount ${fiat.title}';
}
@computed
String get accountBalanceFormated {
final dEuroKey = balanceViewModel.balances.keys
.firstWhereOrNull((e) => e.title == CryptoCurrency.deuro.title);
if (dEuroKey == null) return '0.00';
@@ -84,22 +78,30 @@ abstract class DEuroViewModelBase with Store {
}
@observable
String savingsBalance = '0.00';
BigInt savingsBalance = BigInt.zero;
@computed
String get fiatSavingsBalance => _getDEuroFiatAmount(savingsBalance);
String get savingsBalanceFormated =>
ethereum!.formatterEthereumAmountToDouble(amount: savingsBalance).toStringAsFixed(6);
@computed
String get fiatSavingsBalanceFormated => _getDEuroFiatAmount(savingsBalanceFormated);
@observable
ExecutionState state = InitialExecutionState();
@observable
String interestRate = '0';
String interestRateFormated = '0';
@observable
String accruedInterest = '0.00';
BigInt accruedInterest = BigInt.zero;
@computed
String get fiatAccruedInterest => _getDEuroFiatAmount(accruedInterest);
String get accruedInterestFormated =>
ethereum!.formatterEthereumAmountToDouble(amount: accruedInterest).toStringAsFixed(6);
@computed
String get fiatAccruedInterestFormated => _getDEuroFiatAmount(accruedInterestFormated);
@observable
BigInt approvedTokens = BigInt.zero;
@@ -107,6 +109,9 @@ abstract class DEuroViewModelBase with Store {
@computed
bool get isEnabled => approvedTokens > BigInt.zero;
@computed
bool get isSavingsActionsEnabled => isEnabled && accruedInterest >= MIN_ACCRUED_INTEREST;
@observable
bool isLoading = true;
@@ -121,17 +126,9 @@ abstract class DEuroViewModelBase with Store {
@action
Future<void> reloadSavingsUserData() async {
final savingsBalanceRaw = ethereum!.getDEuroSavingsBalance(_appStore.wallet!);
final accruedInterestRaw = ethereum!.getDEuroAccruedInterest(_appStore.wallet!);
approvedTokens = await ethereum!.getDEuroSavingsApproved(_appStore.wallet!);
savingsBalance = ethereum!
.formatterEthereumAmountToDouble(amount: await savingsBalanceRaw)
.toStringAsFixed(6);
accruedInterest = ethereum!
.formatterEthereumAmountToDouble(amount: await accruedInterestRaw)
.toStringAsFixed(6);
savingsBalance = await ethereum!.getDEuroSavingsBalance(_appStore.wallet!);
accruedInterest = await ethereum!.getDEuroAccruedInterest(_appStore.wallet!);
isLoading = false;
}
@@ -139,7 +136,7 @@ abstract class DEuroViewModelBase with Store {
Future<void> reloadInterestRate() async {
final interestRateRaw = await ethereum!.getDEuroInterestRate(_appStore.wallet!);
interestRate = (interestRateRaw / BigInt.from(10000)).toString();
interestRateFormated = (interestRateRaw / BigInt.from(10000)).toString();
}
@action
@@ -175,7 +172,7 @@ abstract class DEuroViewModelBase with Store {
}
}
Future<void> prepareCollectInterest() => prepareSavingsEdit(accruedInterest, false);
Future<void> prepareCollectInterest() => prepareSavingsEdit(accruedInterestFormated, false);
Future<void> prepareReinvestInterest() async {
try {
@@ -230,13 +227,13 @@ abstract class DEuroViewModelBase with Store {
String _getDEuroFiatAmount(String amount) {
try {
CryptoCurrency deuro = CryptoCurrency.deuro;
var dEuro = CryptoCurrency.deuro;
final keys = _fiatConversationStore.prices.keys.toList();
for (final key in keys) {
if (key.title == "DEURO") deuro = key;
if (key.title == "DEURO") dEuro = key;
}
return calculateFiatAmount(
price: _fiatConversationStore.prices[deuro]!,
price: _fiatConversationStore.prices[dEuro]!,
cryptoAmount: amount,
);
} catch (_) {
@@ -26,6 +26,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
login = '',
password = '',
trusted = false,
isEnabledForAutoSwitching = false,
useSocksProxy = false,
socksProxyAddress = '';
@@ -56,6 +57,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@observable
bool trusted;
@observable
bool isEnabledForAutoSwitching;
@observable
bool useSocksProxy;
@@ -118,6 +122,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
password = '';
useSSL = false;
trusted = false;
isEnabledForAutoSwitching = false;
useSocksProxy = false;
socksProxyAddress = '';
}
@@ -143,6 +148,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@action
void setTrusted(bool val) => trusted = val;
@action
void setIsEnabledForAutoSwitching(bool val) => isEnabledForAutoSwitching = val;
@action
void setSocksProxy(bool val) => useSocksProxy = val;
@@ -159,6 +167,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
password: password,
useSSL: useSSL,
trusted: trusted,
isEnabledForAutoSwitching: isEnabledForAutoSwitching,
socksProxyAddress: socksProxyAddress);
try {
state = IsExecutingState();
@@ -190,6 +199,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
password: password,
useSSL: useSSL,
trusted: trusted,
isEnabledForAutoSwitching: isEnabledForAutoSwitching,
socksProxyAddress: socksProxyAddress);
try {
connectionState = IsExecutingState();
@@ -41,6 +41,13 @@ abstract class NodeListViewModelBase with Store {
S.current.change_current_node(uri) +
'${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}';
@computed
bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching;
@action
void setEnableAutomaticNodeSwitching(bool value) =>
settingsStore.enableAutomaticNodeSwitching = value;
final ObservableList<Node> nodes;
final SettingsStore settingsStore;
final Box<Node> _nodeSource;
@@ -41,6 +41,13 @@ abstract class PowNodeListViewModelBase with Store {
S.current.change_current_node(uri) +
'${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}';
@computed
bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching;
@action
void setEnableAutomaticNodeSwitching(bool value) =>
settingsStore.enableAutomaticNodeSwitching = value;
final ObservableList<Node> nodes;
final SettingsStore settingsStore;
final Box<Node> _nodeSource;
+3 -2
View File
@@ -35,7 +35,7 @@ class RestoredWallet {
factory RestoredWallet.fromKey(Map<String, dynamic> json) {
try {
final codeParsed = jsonDecode(json['raw_qr'].toString());
final codeParsed = jsonDecode(json['raw_qr'].toString());
if (codeParsed["version"] == 0) {
json['address'] = codeParsed["primaryAddress"];
json['view_key'] = codeParsed["privateViewKey"];
@@ -44,6 +44,7 @@ class RestoredWallet {
} catch (e) {
// fine, we don't care, it is only for monero anyway
}
json['view_key'] ??= json['xpub'];
final height = json['height'] as String?;
return RestoredWallet(
restoreMode: json['mode'] as WalletRestoreMode,
@@ -51,7 +52,7 @@ class RestoredWallet {
address: json['address'] as String?,
spendKey: json['spend_key'] as String?,
viewKey: json['view_key'] as String?,
height: height != null ? int.tryParse(height)??0 : 0,
height: height != null ? int.tryParse(height) ?? 0 : 0,
privateKey: json['private_key'] as String?,
);
}
@@ -54,6 +54,8 @@ class WalletRestoreFromQRCode {
final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key));
if (code.startsWith("xpub")) return WalletType.bitcoin;
if (extracted == null) {
// Special case for view-only monero wallet
try {
@@ -117,11 +119,15 @@ class WalletRestoreFromQRCode {
formattedUri = seedPhrase != null
? '$walletType:?seed=$seedPhrase'
: throw Exception('Failed to determine valid seed phrase');
: code.startsWith('xpub')
? '$walletType:?xpub=$code'
: throw Exception('Failed to determine valid seed phrase');
} else {
final index = code.indexOf(':');
final query = code.substring(index + 1).replaceAll('?', '&');
formattedUri = '$walletType:?$query';
formattedUri = code.startsWith('xpub')
? '$walletType:?xpub=$code'
:'$walletType:?$query';
}
final uri = Uri.parse(formattedUri);
@@ -158,6 +164,10 @@ class WalletRestoreFromQRCode {
throw Exception('Unexpected restore mode: tx_payment_id is invalid');
}
if (credentials.containsKey("xpub")) {
return WalletRestoreMode.keys;
}
if (credentials['seed'] != null) {
final seedValue = credentials['seed'] as String;
final words = SeedValidator.getWordList(type: type, language: 'english');
+9
View File
@@ -289,6 +289,15 @@ abstract class OutputBase with Store {
}
}
Map<String, dynamic> get extra {
final fields = <String, dynamic>{};
if (parsedAddress.parseFrom == ParseFrom.bip353) {
fields['bip353_name'] = parsedAddress.name;
fields['bip353_proof'] = parsedAddress.bip353DnsProof;
}
return fields;
}
void _setCryptoNumMaximumFractionDigits() {
var maximumFractionDigits = 0;
+21 -5
View File
@@ -182,9 +182,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (pendingTransaction != null) {
final currency = pendingTransactionFeeCurrency(walletType);
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: pendingTransaction!.feeFormattedValue,
);
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: pendingTransaction!.feeFormattedValue,
);
return fiat;
} else {
return '0.00';
@@ -210,8 +210,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
CryptoCurrency get currency => wallet.currency;
Validator<String> get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(wallet.type));
Validator<String> amountValidator(Output output) => AmountValidator(
currency: walletTypeToCryptoCurrency(wallet.type),
minValue: isSendToSilentPayments(output)
?
// TODO: get from server
// bitcoin!.silentPaymentsMinAmount
'0.00001'
: null,
);
Validator<String> get allAmountValidator => AllAmountValidator();
@@ -289,6 +296,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// If silent payments scanning, can still send payments
(wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
bool isSendToSilentPayments(Output output) =>
wallet.type == WalletType.bitcoin &&
(RegExp(AddressValidator.silentPaymentAddressPatternMainnet).hasMatch(output.address) ||
RegExp(AddressValidator.silentPaymentAddressPatternMainnet)
.hasMatch(output.extractedAddress) ||
(output.parsedAddress.addresses.isNotEmpty &&
RegExp(AddressValidator.silentPaymentAddressPatternMainnet)
.hasMatch(output.parsedAddress.addresses[0])));
@computed
List<Template> get templates => sendTemplateViewModel.templates
.where((template) => _isEqualCurrency(template.cryptoCurrency))
@@ -603,6 +603,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
bool get isSilentPayments =>
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
@computed
bool get isCupcake =>
wallet.type == WalletType.bitcoin &&
(bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isEmpty;
@computed
bool get isAutoGenerateSubaddressEnabled =>
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
+14 -10
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
@@ -162,18 +163,21 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
final keys = bitcoin!.getWalletKeys(_appStore.wallet!);
items.addAll([
if ((keys['wif']??'').isNotEmpty)
StandartListItem(title: "WIF", value: keys['wif']!),
if ((keys['privateKey']??'').isNotEmpty)
StandartListItem(title: S.current.private_key, value: keys['privateKey']!),
if (keys['publicKey'] != null)
StandartListItem(title: S.current.public_key, value: keys['publicKey']!),
if (keys['xpub'] != null)
StandartListItem(title: "xPub", value: keys['xpub']!),
]);
break;
case WalletType.none:
case WalletType.haven:
// final keys = bitcoin!.getWalletKeys(_appStore.wallet!);
//
// items.addAll([
// if (keys['wif'] != null)
// StandartListItem(title: "WIF", value: keys['wif']!),
// if (keys['privateKey'] != null)
// StandartListItem(title: S.current.private_key, value: keys['privateKey']!),
// if (keys['publicKey'] != null)
// StandartListItem(title: S.current.public_key, value: keys['publicKey']!),
// ]);
break;
}
+38 -16
View File
@@ -11,6 +11,7 @@ import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
@@ -32,17 +33,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
{required WalletType type, this.restoredWallet})
: hasSeedLanguageSelector =
type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero,
hasBlockchainHeightLanguageSelector =
type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero,
hasRestoreFromPrivateKey = type == WalletType.ethereum ||
type == WalletType.polygon ||
type == WalletType.nano ||
type == WalletType.banano ||
type == WalletType.solana ||
type == WalletType.tron,
isButtonEnabled = false,
: isButtonEnabled = restoredWallet != null,
hasPassphrase = false,
mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
@@ -60,9 +51,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.decred:
case WalletType.bitcoin:
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
break;
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.zano:
@@ -82,9 +73,32 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
static const decredSeedMnemonicLength = 15;
late List<WalletRestoreMode> availableModes;
final bool hasSeedLanguageSelector;
final bool hasBlockchainHeightLanguageSelector;
final bool hasRestoreFromPrivateKey;
late final bool hasSeedLanguageSelector = [
WalletType.monero,
WalletType.haven,
WalletType.wownero
].contains(type);
late final bool hasBlockchainHeightSelector = [
WalletType.monero,
WalletType.haven,
WalletType.wownero
].contains(type);
late final bool hasRestoreFromPrivateKey = [
WalletType.ethereum,
WalletType.polygon,
WalletType.nano,
WalletType.banano,
WalletType.solana,
WalletType.tron
].contains(type);
late final bool onlyViewKeyRestore = [
if (FeatureFlag.hasBitcoinViewOnly) WalletType.bitcoin,
WalletType.decred
].contains(type);
final RestoredWallet? restoredWallet;
@observable
@@ -198,6 +212,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final address = options['address'] as String?;
switch (type) {
case WalletType.bitcoin:
return bitcoin!.createBitcoinWalletFromKeys(
name: name,
password: password,
xpub: viewKey!,
);
case WalletType.monero:
return monero!.createMoneroRestoreWalletFromKeysCredentials(
name: name,
@@ -276,8 +297,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.litecoin:
String? mnemonic = credentials['seed'] as String?;
String? passphrase = credentials['passphrase'] as String?;
if (mnemonic == null) break;
return bitcoin!.getDerivationsFromMnemonic(
mnemonic: mnemonic!,
mnemonic: mnemonic,
node: node,
passphrase: passphrase,
);