diff --git a/assets/node_list.yml b/assets/node_list.yml index 09fb72383..fccc968cf 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -1,31 +1,24 @@ - - uri: xmr-node-uk.cakewallet.com:18081 + uri: xmr-node.cakewallet.com:18081 is_default: true - - uri: xmr-node-eu.cakewallet.com:18081 + uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081 is_default: false - - uri: xmr-node-usa-east.cakewallet.com:18081 + uri: node.sethforprivacy.com:18089 + is_default: false +- + uri: nodes.hashvault.pro:18081 + is_default: false +- + uri: node.c3pool.com:18081 + is_default: false +- + uri: node.supportxmr.com:18081 + is_default: false +- + uri: node.community.rino.io:18081 is_default: false - uri: node.moneroworld.com:18089 is_default: false -- - uri: node.xmr.pt:18081 - is_default: false -- - uri: node.monero.net:18081 - is_default: false -- - uri: opennode.xmr-tw.org:18089 - is_default: false -- - uri: node.imonero.org:18081 - is_default: false - - uri: node.c3pool.com:18081 - is_default: false - - uri: xmr.prprpr.icu:18081 - is_default: false - diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 886add3cd..ff1d6b051 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -17,6 +17,7 @@ class Node extends HiveObject with Keyable { {this.login, this.password, this.useSSL, + this.trusted = false, String? uri, WalletType? type,}) { if (uri != null) { @@ -31,7 +32,8 @@ class Node extends HiveObject with Keyable { : uriRaw = map['uri'] as String? ?? '', login = map['login'] as String?, password = map['password'] as String?, - useSSL = map['useSSL'] as bool?; + useSSL = map['useSSL'] as bool?, + trusted = map['trusted'] as bool? ?? false; static const typeId = 1; static const boxName = 'Nodes'; @@ -51,6 +53,9 @@ class Node extends HiveObject with Keyable { @HiveField(4) bool? useSSL; + @HiveField(5) + bool trusted; + bool get isSSL => useSSL ?? false; Uri get uri { @@ -104,28 +109,28 @@ class Node extends HiveObject with Keyable { final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); final realm = 'monero-rpc'; final body = { - 'jsonrpc': '2.0', - 'id': '0', + 'jsonrpc': '2.0', + 'id': '0', 'method': 'get_info' }; try { final authenticatingClient = HttpClient(); - + authenticatingClient.addCredentials( rpcUri, - realm, + realm, HttpClientDigestCredentials(login ?? '', password ?? ''), ); - + final http.Client client = ioc.IOClient(authenticatingClient); - + final response = await client.post( rpcUri, headers: {'Content-Type': 'application/json'}, body: json.encode(body), ); - + client.close(); final resBody = json.decode(response.body) as Map; diff --git a/cw_haven/ios/Classes/haven_api.cpp b/cw_haven/ios/Classes/haven_api.cpp index c1013bf87..aecaf0016 100644 --- a/cw_haven/ios/Classes/haven_api.cpp +++ b/cw_haven/ios/Classes/haven_api.cpp @@ -927,6 +927,16 @@ extern "C" return static_cast(rates.size()); } + void set_trusted_daemon(bool arg) + { + m_wallet->setTrustedDaemon(arg); + } + + bool trusted_daemon() + { + return m_wallet->trustedDaemon(); + } + #ifdef __cplusplus } #endif diff --git a/cw_haven/lib/api/signatures.dart b/cw_haven/lib/api/signatures.dart index 774a303d7..31c911edc 100644 --- a/cw_haven/lib/api/signatures.dart +++ b/cw_haven/lib/api/signatures.dart @@ -137,4 +137,8 @@ typedef get_rate = Pointer Function(); typedef size_of_rate = Int32 Function(); -typedef update_rate = Void Function(); \ No newline at end of file +typedef update_rate = Void Function(); + +typedef set_trusted_daemon = Void Function(Int8 trusted); + +typedef trusted_daemon = Int8 Function(); \ No newline at end of file diff --git a/cw_haven/lib/api/types.dart b/cw_haven/lib/api/types.dart index f1dc0e26b..de9ff74a0 100644 --- a/cw_haven/lib/api/types.dart +++ b/cw_haven/lib/api/types.dart @@ -135,4 +135,8 @@ typedef GetRate = Pointer Function(); typedef SizeOfRate = int Function(); -typedef UpdateRate = void Function(); \ No newline at end of file +typedef UpdateRate = void Function(); + +typedef SetTrustedDaemon = void Function(int); + +typedef TrustedDaemon = int Function(); \ No newline at end of file diff --git a/cw_haven/lib/api/wallet.dart b/cw_haven/lib/api/wallet.dart index 2b85f60e2..bdf6a1af7 100644 --- a/cw_haven/lib/api/wallet.dart +++ b/cw_haven/lib/api/wallet.dart @@ -116,6 +116,14 @@ final rescanBlockchainAsyncNative = havenApi .lookup>('rescan_blockchain') .asFunction(); +final setTrustedDaemonNative = havenApi + .lookup>('set_trusted_daemon') + .asFunction(); + +final trustedDaemonNative = havenApi + .lookup>('trusted_daemon') + .asFunction(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -351,3 +359,7 @@ Future isConnected() => compute(_isConnected, 0); Future getNodeHeight() => compute(_getNodeHeight, 0); void rescanBlockchainAsync() => rescanBlockchainAsyncNative(); + +Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); + +Future trustedDaemon() async => trustedDaemonNative() != 0; diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart index cf7c9dcdd..a4b949d8f 100644 --- a/cw_haven/lib/haven_wallet.dart +++ b/cw_haven/lib/haven_wallet.dart @@ -121,6 +121,8 @@ abstract class HavenWalletBase extends WalletBasegetSubaddressLabel(accountIndex, addressIndex).c_str()); } + void set_trusted_daemon(bool arg) + { + m_wallet->setTrustedDaemon(arg); + } + + bool trusted_daemon() + { + return m_wallet->trustedDaemon(); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/ios/Classes/monero_api.h b/cw_monero/ios/Classes/monero_api.h index 3e2ebcff2..74258ba4c 100644 --- a/cw_monero/ios/Classes/monero_api.h +++ b/cw_monero/ios/Classes/monero_api.h @@ -30,6 +30,9 @@ void set_refresh_from_block_height(uint64_t height); void set_recovering_from_seed(bool is_recovery); void store(char *path); +void set_trusted_daemon(bool arg); +bool trusted_daemon(); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index f9e88153e..0b14fd557 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -125,4 +125,8 @@ typedef rescan_blockchain = Void Function(); typedef get_subaddress_label = Pointer Function( Int32 accountIndex, - Int32 addressIndex); \ No newline at end of file + Int32 addressIndex); + +typedef set_trusted_daemon = Void Function(Int8 trusted); + +typedef trusted_daemon = Int8 Function(); \ No newline at end of file diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 468ce93e2..c5918c12a 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -123,4 +123,8 @@ typedef RescanBlockchainAsync = void Function(); typedef GetSubaddressLabel = Pointer Function( int accountIndex, - int addressIndex); \ No newline at end of file + int addressIndex); + +typedef SetTrustedDaemon = void Function(int); + +typedef TrustedDaemon = int Function(); \ No newline at end of file diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 5c2f1bd27..7ddbf29dc 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -120,6 +120,14 @@ final getSubaddressLabelNative = moneroApi .lookup>('get_subaddress_label') .asFunction(); +final setTrustedDaemonNative = moneroApi + .lookup>('set_trusted_daemon') + .asFunction(); + +final trustedDaemonNative = moneroApi + .lookup>('trusted_daemon') + .asFunction(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -359,4 +367,8 @@ void rescanBlockchainAsync() => rescanBlockchainAsyncNative(); String getSubaddressLabel(int accountIndex, int addressIndex) { return convertUTF8ToString(pointer: getSubaddressLabelNative(accountIndex, addressIndex)); -} \ No newline at end of file +} + +Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); + +Future trustedDaemon() async => trustedDaemonNative() != 0; \ No newline at end of file diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 69c61b83a..a9d708c05 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -136,6 +136,8 @@ abstract class MoneroWalletBase extends WalletBase + getIt.registerFactory(() => BalancePage(dashboardViewModel: getIt.get(), settingsStore: getIt.get())); getIt.registerFactory(() => DashboardPage( balancePage: getIt.get(), walletViewModel: getIt.get(), addressListViewModel: getIt.get())); @@ -471,12 +473,11 @@ Future setup( (ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact)); - getIt.registerFactory( - () => ContactListViewModel(_contactSource, _walletInfoSource)); + getIt.registerFactoryParam( + (CryptoCurrency? cur, _) => ContactListViewModel(_contactSource, _walletInfoSource, cur)); - getIt.registerFactoryParam( - (bool isEditable, _) => ContactListPage(getIt.get(), - isEditable: isEditable)); + getIt.registerFactoryParam((CryptoCurrency? cur, _) + => ContactListPage(getIt.get(param1: cur))); getIt.registerFactoryParam( (ContactRecord? contact, _) => @@ -498,8 +499,12 @@ Future setup( getIt.registerFactory(() => OtherSettingsPage(getIt.get())); - getIt.registerFactory(() => - NodeCreateOrEditViewModel(_nodeSource, getIt.get().wallet!)); + getIt.registerFactoryParam( + (WalletType? type, _) => NodeCreateOrEditViewModel( + _nodeSource, + type ?? getIt.get().wallet!.type, + getIt.get(), + )); getIt.registerFactory( () => NodeCreateOrEditPage(getIt.get())); @@ -698,7 +703,7 @@ Future setup( getIt.registerFactoryParam( (String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,)); - + getIt.registerFactory(() => IoniaApi()); getIt.registerFactory(() => AnyPayApi()); @@ -718,7 +723,7 @@ Future setup( getIt.registerFactoryParam((double amount, merchant) { return IoniaMerchPurchaseViewModel( - ioniaAnyPayService: getIt.get(), + ioniaAnyPayService: getIt.get(), amount: amount, ioniaMerchant: merchant, sendViewModel: getIt.get() @@ -761,31 +766,31 @@ Future setup( ioniaService: getIt.get(), giftCard: giftCard); }); - + getIt.registerFactoryParam((List args, _) { final amount = args[0] as double; final merchant = args[1] as IoniaMerchant; final tip = args[2] as IoniaTip; - + return IoniaCustomTipViewModel(amount: amount, tip: tip, ioniaMerchant: merchant); }); - + getIt.registerFactoryParam((IoniaGiftCard giftCard, _) { return IoniaGiftCardDetailPage(getIt.get(param1: giftCard)); }); getIt.registerFactoryParam((List args, _){ final giftCard = args.first as IoniaGiftCard; - - return IoniaMoreOptionsPage(giftCard); + + return IoniaMoreOptionsPage(giftCard); }); getIt.registerFactoryParam((IoniaGiftCard giftCard, _) => IoniaCustomRedeemViewModel(giftCard)); getIt.registerFactoryParam((List args, _){ final giftCard = args.first as IoniaGiftCard; - - return IoniaCustomRedeemPage(getIt.get(param1: giftCard) ); + + return IoniaCustomRedeemPage(getIt.get(param1: giftCard) ); }); @@ -814,5 +819,8 @@ Future setup( (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo) => IoniaPaymentStatusPage(getIt.get(param1: paymentInfo, param2: committedInfo))); + getIt.registerFactoryParam((type, _) => + AdvancedPrivacySettingsViewModel(type, getIt.get())); + _isSetupFinished = true; } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index b2036c3f6..9aff05500 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -69,6 +69,8 @@ Future defaultSettingsMigration( sharedPreferences: sharedPreferences, nodes: nodes); await changeLitecoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); + await changeHavenCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); break; case 2: @@ -133,19 +135,32 @@ Future defaultSettingsMigration( await changeDefaultHavenNode(nodes); break; + case 18: + await addOnionNode(nodes); + break; + default: break; } await sharedPreferences.setInt( - 'current_default_settings_migration_version', version); + PreferencesKey.currentDefaultSettingsMigrationVersion, version); } catch (e) { print('Migration error: ${e.toString()}'); } }); await sharedPreferences.setInt( - 'current_default_settings_migration_version', version); + PreferencesKey.currentDefaultSettingsMigrationVersion, version); +} + +Future addOnionNode(Box nodes) async { + final onionNodeUri = "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081"; + + // check if the user has this node before (added it manually) + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == onionNodeUri) == null) { + await nodes.add(Node(uri: onionNodeUri, type: WalletType.monero)); + } } Future replaceNodesMigration({required Box nodes}) async { @@ -176,7 +191,7 @@ Future changeMoneroCurrentNodeToDefault( final node = getMoneroDefaultNode(nodes: nodes); final nodeId = node?.key as int ?? 0; // 0 - England - await sharedPreferences.setInt('current_node_id', nodeId); + await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, nodeId); } Node? getBitcoinDefaultElectrumServer({required Box nodes}) { @@ -223,7 +238,7 @@ Future changeBitcoinCurrentElectrumServerToDefault( final server = getBitcoinDefaultElectrumServer(nodes: nodes); final serverId = server?.key as int ?? 0; - await sharedPreferences.setInt('current_node_id_btc', serverId); + await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId); } Future changeLitecoinCurrentElectrumServerToDefault( @@ -232,7 +247,7 @@ Future changeLitecoinCurrentElectrumServerToDefault( final server = getLitecoinDefaultElectrumServer(nodes: nodes); final serverId = server?.key as int ?? 0; - await sharedPreferences.setInt('current_node_id_ltc', serverId); + await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId); } Future changeHavenCurrentNodeToDefault( @@ -252,7 +267,7 @@ Future replaceDefaultNode( 'eu-node.cakewallet.io:18081', 'node.cakewallet.io:18081' ]; - final currentNodeId = sharedPreferences.getInt('current_node_id'); + final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentNode = nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId); final needToReplace = @@ -277,17 +292,29 @@ Future updateNodeTypes({required Box nodes}) async { Future addBitcoinElectrumServerList({required Box nodes}) async { final serverList = await loadBitcoinElectrumServerList(); - await nodes.addAll(serverList); + for (var node in serverList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } } Future addLitecoinElectrumServerList({required Box nodes}) async { final serverList = await loadLitecoinElectrumServerList(); - await nodes.addAll(serverList); + for (var node in serverList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } } Future addHavenNodeList({required Box nodes}) async { final nodeList = await loadDefaultHavenNodes(); - await nodes.addAll(nodeList); + for (var node in nodeList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } } Future addAddressesForMoneroWallets( @@ -431,7 +458,7 @@ Future resetBitcoinElectrumServer( final oldElectrumServer = nodeSource.values.firstWhereOrNull( (node) => node.uri.toString().contains('electrumx.cakewallet.com')); var cakeWalletNode = nodeSource.values.firstWhereOrNull( - (node) => node.uri.toString() == cakeWalletBitcoinElectrumUri); + (node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri); if (cakeWalletNode == null) { cakeWalletNode = diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 7fcaeeaad..775f6e229 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -4,7 +4,6 @@ import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/foundation.dart'; import 'package:cake_wallet/entities/fio_address_provider.dart'; class AddressResolver { @@ -51,7 +50,7 @@ class AddressResolver { return ParsedAddress(addresses: [text]); } - if (unstoppableDomains.any((domain) => name.contains(domain))) { + if (unstoppableDomains.any((domain) => name.trim() == domain)) { final address = await fetchUnstoppableDomainAddress(text, ticker); return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text); } diff --git a/lib/exchange/changenow/changenow_exchange_provider.dart b/lib/exchange/changenow/changenow_exchange_provider.dart index 20f529733..a9fb7c9ca 100644 --- a/lib/exchange/changenow/changenow_exchange_provider.dart +++ b/lib/exchange/changenow/changenow_exchange_provider.dart @@ -11,7 +11,6 @@ import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/changenow/changenow_request.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; -import 'package:cake_wallet/exchange/trade_not_created_exeption.dart'; class ChangeNowExchangeProvider extends ExchangeProvider { ChangeNowExchangeProvider() @@ -21,8 +20,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { .where((i) => i != CryptoCurrency.xhv) .map((i) => CryptoCurrency.all .where((i) => i != CryptoCurrency.xhv) - .map((k) => ExchangePair(from: i, to: k, reverse: true)) - .where((c) => c != null)) + .map((k) => ExchangePair(from: i, to: k, reverse: true))) .expand((i) => i) .toList()); @@ -43,6 +41,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider { @override bool get isEnabled => true; + @override + bool get supportsFixedRate => true; + @override ExchangeProviderDescription get description => ExchangeProviderDescription.changeNow; @@ -96,25 +97,36 @@ class ChangeNowExchangeProvider extends ExchangeProvider { apiHeaderKey: apiKey, 'Content-Type': 'application/json'}; final flow = getFlow(isFixedRateMode); + final type = isFixedRateMode ? 'reverse' : 'direct'; final body = { 'fromCurrency': normalizeCryptoCurrency(_request.from), 'toCurrency': normalizeCryptoCurrency(_request.to), 'fromNetwork': networkFor(_request.from), 'toNetwork': networkFor(_request.to), - 'fromAmount': _request.fromAmount, - 'toAmount': _request.toAmount, + if (!isFixedRateMode) 'fromAmount': _request.fromAmount, + if (isFixedRateMode) 'toAmount': _request.toAmount, 'address': _request.address, 'flow': flow, + 'type': type, 'refundAddress': _request.refundAddress }; if (isFixedRateMode) { + // since we schedule to calculate the rate every 5 seconds we need to ensure that + // we have the latest rate id with the given inputs before creating the trade + await fetchRate( + from: _request.from, + to: _request.to, + amount: double.tryParse(_request.toAmount) ?? 0, + isFixedRateMode: true, + isReceiveAmount: true, + ); body['rateId'] = _lastUsedRateId; } final uri = Uri.https(apiAuthority, createTradePath); final response = await post(uri, headers: headers, body: json.encode(body)); - + if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error'] as String; @@ -130,7 +142,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final id = responseJSON['id'] as String; final inputAddress = responseJSON['payinAddress'] as String; final refundAddress = responseJSON['refundAddress'] as String; - final extraId = responseJSON['payinExtraId'] as String; + final extraId = responseJSON['payinExtraId'] as String?; return Trade( id: id, @@ -141,7 +153,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { refundAddress: refundAddress, extraId: extraId, createdAt: DateTime.now(), - amount: _request.fromAmount, + amount: responseJSON['fromAmount']?.toString() ?? _request.fromAmount, state: TradeState.created); } @@ -180,9 +192,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final extraId = responseJSON['payinExtraId'] as String; final outputTransaction = responseJSON['payoutHash'] as String; final expiredAtRaw = responseJSON['validUntil'] as String; - final expiredAt = expiredAtRaw != null - ? DateTime.parse(expiredAtRaw).toLocal() - : null; + final expiredAt = DateTime.tryParse(expiredAtRaw)?.toLocal(); return Trade( id: id, @@ -198,7 +208,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { } @override - Future calculateAmount( + Future fetchRate( {required CryptoCurrency from, required CryptoCurrency to, required double amount, @@ -214,10 +224,10 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final type = isReverse ? 'reverse' : 'direct'; final flow = getFlow(isFixedRateMode); final params = { - 'fromCurrency': isReverse ? normalizeCryptoCurrency(to) : normalizeCryptoCurrency(from), - 'toCurrency': isReverse ? normalizeCryptoCurrency(from) : normalizeCryptoCurrency(to), - 'fromNetwork': isReverse ? networkFor(to) : networkFor(from), - 'toNetwork': isReverse ? networkFor(from) : networkFor(to), + 'fromCurrency': normalizeCryptoCurrency(from), + 'toCurrency': normalizeCryptoCurrency(to), + 'fromNetwork': networkFor(from), + 'toNetwork': networkFor(to), 'type': type, 'flow': flow}; @@ -238,7 +248,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { _lastUsedRateId = rateId; } - return isReverse ? fromAmount : toAmount; + return isReverse ? (amount / fromAmount) : (toAmount / amount); } catch(e) { print(e.toString()); return 0.0; diff --git a/lib/exchange/exchange_provider.dart b/lib/exchange/exchange_provider.dart index 14fba803f..763350c94 100644 --- a/lib/exchange/exchange_provider.dart +++ b/lib/exchange/exchange_provider.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/exchange_pair.dart'; @@ -14,6 +13,7 @@ abstract class ExchangeProvider { ExchangeProviderDescription get description; bool get isAvailable; bool get isEnabled; + bool get supportsFixedRate; @override String toString() => title; @@ -26,7 +26,7 @@ abstract class ExchangeProvider { required TradeRequest request, required bool isFixedRateMode}); Future findTradeById({required String id}); - Future calculateAmount({ + Future fetchRate({ required CryptoCurrency from, required CryptoCurrency to, required double amount, diff --git a/lib/exchange/morphtoken/morphtoken_exchange_provider.dart b/lib/exchange/morphtoken/morphtoken_exchange_provider.dart index 1df4fef87..a2a72b24f 100644 --- a/lib/exchange/morphtoken/morphtoken_exchange_provider.dart +++ b/lib/exchange/morphtoken/morphtoken_exchange_provider.dart @@ -66,6 +66,9 @@ class MorphTokenExchangeProvider extends ExchangeProvider { @override bool get isEnabled => true; + @override + bool get supportsFixedRate => false; + @override ExchangeProviderDescription get description => ExchangeProviderDescription.morphToken; @@ -200,7 +203,7 @@ class MorphTokenExchangeProvider extends ExchangeProvider { } @override - Future calculateAmount( + Future fetchRate( {required CryptoCurrency from, required CryptoCurrency to, required double amount, diff --git a/lib/exchange/sideshift/sideshift_exchange_provider.dart b/lib/exchange/sideshift/sideshift_exchange_provider.dart index a732f7ef2..98f18f058 100644 --- a/lib/exchange/sideshift/sideshift_exchange_provider.dart +++ b/lib/exchange/sideshift/sideshift_exchange_provider.dart @@ -12,7 +12,6 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/limits.dart'; -import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; class SideShiftExchangeProvider extends ExchangeProvider { @@ -48,8 +47,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { return supportedCurrencies .map((i) => supportedCurrencies - .map((k) => ExchangePair(from: i, to: k, reverse: true)) - .where((c) => c != null)) + .map((k) => ExchangePair(from: i, to: k, reverse: true))) .expand((i) => i) .toList(); } @@ -59,7 +57,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { ExchangeProviderDescription.sideShift; @override - Future calculateAmount( + Future fetchRate( {required CryptoCurrency from, required CryptoCurrency to, required double amount, @@ -81,9 +79,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { if (amount > max) return 0.00; - final estimatedAmount = rate * amount; - - return estimatedAmount; + return rate; } catch (_) { return 0.00; } @@ -249,15 +245,15 @@ class SideShiftExchangeProvider extends ExchangeProvider { final expectedSendAmount = responseJSON['depositAmount'].toString(); final deposits = responseJSON['deposits'] as List?; TradeState? state; + String? status; - if (deposits != null && deposits.isNotEmpty) { - final status = deposits[0]['status'] as String; - state = TradeState.deserialize(raw: status); + if (deposits?.isNotEmpty ?? false) { + status = deposits![0]['status'] as String?; } + state = TradeState.deserialize(raw: status ?? 'created'); final expiredAtRaw = responseJSON['expiresAtISO'] as String; - final expiredAt = - expiredAtRaw != null ? DateTime.parse(expiredAtRaw).toLocal() : null; + final expiredAt = DateTime.tryParse(expiredAtRaw)?.toLocal(); return Trade( id: id, @@ -277,6 +273,9 @@ class SideShiftExchangeProvider extends ExchangeProvider { @override bool get isEnabled => true; + @override + bool get supportsFixedRate => true; + @override String get title => 'SideShift'; diff --git a/lib/exchange/simpleswap/simpleswap_exchange_provider.dart b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart index c6e1baeea..2521c1486 100644 --- a/lib/exchange/simpleswap/simpleswap_exchange_provider.dart +++ b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart @@ -20,8 +20,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { .where((i) => i != CryptoCurrency.zaddr) .map((i) => CryptoCurrency.all .where((i) => i != CryptoCurrency.zaddr) - .map((k) => ExchangePair(from: i, to: k, reverse: true)) - .where((c) => c != null)) + .map((k) => ExchangePair(from: i, to: k, reverse: true))) .expand((i) => i) .toList()); @@ -37,7 +36,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { ExchangeProviderDescription.simpleSwap; @override - Future calculateAmount( + Future fetchRate( {required CryptoCurrency from, required CryptoCurrency to, required double amount, @@ -59,9 +58,9 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { final uri = Uri.https(apiAuthority, getEstimatePath, params); final response = await get(uri); - if (response.body == null || response.body == "null") return 0.00; + if (response.body == "null") return 0.00; final data = json.decode(response.body) as String; - return double.parse(data); + return double.parse(data) / amount; } catch (_) { return 0.00; } @@ -210,6 +209,9 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { @override bool get isEnabled => true; + @override + bool get supportsFixedRate => false; + @override String get title => 'SimpleSwap'; diff --git a/lib/exchange/xmrto/xmrto_exchange_provider.dart b/lib/exchange/xmrto/xmrto_exchange_provider.dart index d5f79361e..536754e18 100644 --- a/lib/exchange/xmrto/xmrto_exchange_provider.dart +++ b/lib/exchange/xmrto/xmrto_exchange_provider.dart @@ -48,6 +48,9 @@ class XMRTOExchangeProvider extends ExchangeProvider { @override bool get isEnabled => true; + @override + bool get supportsFixedRate => false; + @override ExchangeProviderDescription get description => ExchangeProviderDescription.xmrto; @@ -191,7 +194,7 @@ class XMRTOExchangeProvider extends ExchangeProvider { } @override - Future calculateAmount( + Future fetchRate( {required CryptoCurrency from, required CryptoCurrency to, required double amount, diff --git a/lib/main.dart b/lib/main.dart index 3cd5679b3..11eee146b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -128,7 +128,7 @@ Future main() async { exchangeTemplates: exchangeTemplates, transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, - initialMigrationVersion: 17); + initialMigrationVersion: 18); runApp(App()); } catch (e, stacktrace) { runApp(MaterialApp( @@ -261,7 +261,7 @@ class AppState extends State with SingleTickerProviderStateMixin { final statusBarColor = Colors.transparent; final authenticationStore = getIt.get(); final initialRoute = - authenticationStore.state == AuthenticationState.denied + authenticationStore.state == AuthenticationState.uninitialized ? Routes.disclaimer : Routes.login; final currentTheme = settingsStore.currentTheme; diff --git a/lib/reactions/bootstrap.dart b/lib/reactions/bootstrap.dart index 8d9950e64..a40a17088 100644 --- a/lib/reactions/bootstrap.dart +++ b/lib/reactions/bootstrap.dart @@ -22,9 +22,9 @@ Future bootstrap(GlobalKey navigatorKey) async { final currentWalletName = getIt .get() .getString(PreferencesKey.currentWalletName); - authenticationStore.state = currentWalletName == null - ? AuthenticationState.denied - : AuthenticationState.installed; + if (currentWalletName != null) { + authenticationStore.state = AuthenticationState.installed; + } startAuthenticationStateChange(authenticationStore, navigatorKey); startCurrentWalletChangeReaction( diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart index c91f5277e..edadf33b0 100644 --- a/lib/reactions/on_authentication_state_change.dart +++ b/lib/reactions/on_authentication_state_change.dart @@ -26,10 +26,5 @@ void startAuthenticationStateChange(AuthenticationStore authenticationStore, await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); return; } - - if (state == AuthenticationState.denied) { - await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.welcome, (_) => false); - return; - } }); } diff --git a/lib/router.dart b/lib/router.dart index 4d3d61f5f..b59294428 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -15,6 +15,7 @@ import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dar import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart'; +import 'package:cake_wallet/src/screens/new_wallet/advanced_privacy_settings_page.dart'; import 'package:cake_wallet/src/screens/order_details/order_details_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; @@ -25,6 +26,8 @@ import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; +import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; +import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -77,6 +80,7 @@ import 'package:cake_wallet/src/screens/ionia/ionia.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart'; import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart'; +import 'package:cw_core/crypto_currency.dart'; late RouteSettings currentRouteSettings; @@ -317,11 +321,13 @@ Route createRoute(RouteSettings settings) { case Routes.addressBook: return MaterialPageRoute( - builder: (_) => getIt.get(param1: true)); + builder: (_) => + getIt.get()); case Routes.pickerAddressBook: + final selectedCurrency = settings.arguments as CryptoCurrency; return MaterialPageRoute( - builder: (_) => getIt.get(param1: false)); + builder: (_) => getIt.get(param1: selectedCurrency)); case Routes.addressBookAddContact: return CupertinoPageRoute( @@ -491,6 +497,15 @@ Route createRoute(RouteSettings settings) { case Routes.onramperPage: return CupertinoPageRoute(builder: (_) => getIt.get()); + case Routes.advancedPrivacySettings: + final type = settings.arguments as WalletType; + + return CupertinoPageRoute( + builder: (_) => AdvancedPrivacySettingsPage( + getIt.get(param1: type), + getIt.get(param1: type), + )); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index d738b13e8..5ea250310 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -81,4 +81,5 @@ class Routes { static const privacyPage = '/privacy_page'; static const displaySettingsPage = '/display_settings_page'; static const otherSettingsPage = '/other_settings_page'; + static const advancedPrivacySettings = '/advanced_privacy_settings'; } diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index 7912472d8..7cd7a0157 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -9,24 +9,22 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/src/widgets/collapsible_standart_list.dart'; class ContactListPage extends BasePage { - ContactListPage(this.contactListViewModel, {this.isEditable = true}); + ContactListPage(this.contactListViewModel); final ContactListViewModel contactListViewModel; - final bool isEditable; @override String get title => S.current.address_book; @override Widget? trailing(BuildContext context) { - if (!isEditable) { + if (!contactListViewModel.isEditable) { return null; } @@ -60,11 +58,14 @@ class ContactListPage extends BasePage { @override Widget body(BuildContext context) { + return Container( padding: EdgeInsets.only(top: 20.0, bottom: 20.0), child: Observer( - builder: (_) { - return CollapsibleSectionList( + builder: (_) { + final contacts = contactListViewModel.contactsToShow; + final walletContacts = contactListViewModel.walletContactsToShow; + return CollapsibleSectionList( context: context, sectionCount: 2, themeColor: Theme.of(context).primaryTextTheme.headline6!.color!, @@ -82,35 +83,37 @@ class ContactListPage extends BasePage { child: Text(title, style: TextStyle(fontSize: 36))); }, itemCounter: (int sectionIndex) => sectionIndex == 0 - ? contactListViewModel.walletContacts.length - : contactListViewModel.contacts.length, + ? walletContacts.length + : contacts.length, itemBuilder: (_, sectionIndex, index) { if (sectionIndex == 0) { - final walletInfo = contactListViewModel.walletContacts[index]; + final walletInfo = walletContacts[index]; return generateRaw(context, walletInfo); } - final contact = contactListViewModel.contacts[index]; + final contact = contacts[index]; final content = generateRaw(context, contact); - return !isEditable - ? content - : Slidable( + return contactListViewModel.isEditable + ? Slidable( key: Key('${contact.key}'), endActionPane: _actionPane(context, contact), child: content, - ); + ) + : content; }, - ); - }, - )); + );}) + ); } Widget generateRaw(BuildContext context, ContactBase contact) { - final image = _getCurrencyImage(contact.type); + final image = contact.type.iconPath; + final currencyIcon = image != null ? Image.asset(image, height: 24, width: 24) + : const SizedBox(height: 24, width: 24); + return GestureDetector( onTap: () async { - if (!isEditable) { + if (!contactListViewModel.isEditable) { Navigator.of(context).pop(contact); return; } @@ -131,12 +134,10 @@ class ContactListPage extends BasePage { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, children: [ - image ?? Offstage(), + currencyIcon, Expanded( child: Padding( - padding: image != null - ? EdgeInsets.only(left: 12) - : EdgeInsets.only(left: 0), + padding: EdgeInsets.only(left: 12), child: Text( contact.name, style: TextStyle( @@ -152,69 +153,6 @@ class ContactListPage extends BasePage { ); } - Image? _getCurrencyImage(CryptoCurrency currency) { - Image? image; - - switch (currency) { - case CryptoCurrency.xmr: - image = - Image.asset('assets/images/monero_logo.png', height: 24, width: 24); - break; - case CryptoCurrency.ada: - image = Image.asset('assets/images/ada.png', height: 24, width: 24); - break; - case CryptoCurrency.bch: - image = Image.asset('assets/images/bch.png', height: 24, width: 24); - break; - case CryptoCurrency.bnb: - image = Image.asset('assets/images/bnb.png', height: 24, width: 24); - break; - case CryptoCurrency.btc: - image = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); - break; - case CryptoCurrency.dai: - image = Image.asset('assets/images/dai.png', height: 24, width: 24); - break; - case CryptoCurrency.dash: - image = Image.asset('assets/images/dash.png', height: 24, width: 24); - break; - case CryptoCurrency.eos: - image = Image.asset('assets/images/eos.png', height: 24, width: 24); - break; - case CryptoCurrency.eth: - image = Image.asset('assets/images/eth.png', height: 24, width: 24); - break; - case CryptoCurrency.ltc: - image = - Image.asset('assets/images/litecoin.png', height: 24, width: 24); - break; - case CryptoCurrency.nano: - image = Image.asset('assets/images/nano.png', height: 24, width: 24); - break; - case CryptoCurrency.trx: - image = Image.asset('assets/images/trx.png', height: 24, width: 24); - break; - case CryptoCurrency.usdt: - image = Image.asset('assets/images/usdt.png', height: 24, width: 24); - break; - case CryptoCurrency.usdterc20: - image = Image.asset('assets/images/usdterc.png', height: 24, width: 24); - break; - case CryptoCurrency.xlm: - image = Image.asset('assets/images/xlm.png', height: 24, width: 24); - break; - case CryptoCurrency.xrp: - image = Image.asset('assets/images/xrp.png', height: 24, width: 24); - break; - case CryptoCurrency.xhv: - image = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); - break; - default: - image = null; - } - return image; - } - Future showAlertDialog(BuildContext context) async { return await showPopUp( context: context, diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 98843b1fb..40aa679a4 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -395,7 +395,8 @@ class ExchangeCardState extends State { buttonColor: widget.addressButtonsColor, validator: widget.addressTextFieldValidator, onPushPasteButton: widget.onPushPasteButton, - onPushAddressBookButton: widget.onPushAddressBookButton + onPushAddressBookButton: widget.onPushAddressBookButton, + selectedCurrency: _selectedCurrency ), ) diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart new file mode 100644 index 000000000..ff1c83f90 --- /dev/null +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -0,0 +1,104 @@ +import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; +import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; +import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; + +class AdvancedPrivacySettingsPage extends BasePage { + AdvancedPrivacySettingsPage(this.advancedPrivacySettingsViewModel, this.nodeViewModel); + + final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel; + final NodeCreateOrEditViewModel nodeViewModel; + + @override + String get title => S.current.privacy_settings; + + @override + Widget body(BuildContext context) => + AdvancedPrivacySettingsBody(advancedPrivacySettingsViewModel, nodeViewModel); +} + +class AdvancedPrivacySettingsBody extends StatefulWidget { + const AdvancedPrivacySettingsBody(this.privacySettingsViewModel, this.nodeViewModel, {Key? key}) + : super(key: key); + + final AdvancedPrivacySettingsViewModel privacySettingsViewModel; + final NodeCreateOrEditViewModel nodeViewModel; + + @override + _AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState(); +} + +class _AdvancedPrivacySettingsBodyState extends State { + _AdvancedPrivacySettingsBodyState(); + + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 24), + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ...widget.privacySettingsViewModel.settings.map( + (item) => Observer( + builder: (_) => SettingsSwitcherCell( + title: item.title, + value: item.value(), + onValueChange: item.onValueChange, + ), + ), + ), + Observer( + builder: (_) { + if (widget.privacySettingsViewModel.addCustomNode) { + return Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: NodeForm( + formKey: _formKey, + nodeViewModel: widget.nodeViewModel, + ), + ); + } + return const SizedBox(); + }, + ), + ], + ), + bottomSectionPadding: EdgeInsets.all(24), + bottomSection: Column( + children: [ + LoadingPrimaryButton( + onPressed: () { + widget.nodeViewModel.save(saveAsCurrent: true); + Navigator.pop(context); + }, + text: S.of(context).continue_text, + color: Theme.of(context).accentTextTheme.bodyText1!.color!, + textColor: Colors.white, + ), + const SizedBox(height: 25), + Padding( + padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.15), + child: Text( + S.of(context).settings_can_be_changed_later, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).accentTextTheme.headline2?.color, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index cd08cfb61..9e0cd898c 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -200,18 +200,30 @@ class _WalletNameFormState extends State { ] ]), bottomSectionPadding: - EdgeInsets.only(left: 24, right: 24, bottom: 24), - bottomSection: Observer( - builder: (context) { - return LoadingPrimaryButton( - onPressed: _confirmForm, - text: S.of(context).seed_language_next, - color: Colors.green, - textColor: Colors.white, - isLoading: _walletNewVM.state is IsExecutingState, - isDisabled: _walletNewVM.name.isEmpty, - ); - }, + EdgeInsets.all(24), + bottomSection: Column( + children: [ + Observer( + builder: (context) { + return LoadingPrimaryButton( + onPressed: _confirmForm, + text: S.of(context).seed_language_next, + color: Colors.green, + textColor: Colors.white, + isLoading: _walletNewVM.state is IsExecutingState, + isDisabled: _walletNewVM.name.isEmpty, + ); + }, + ), + const SizedBox(height: 25), + GestureDetector( + onTap: () { + Navigator.of(context) + .pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type); + }, + child: Text(S.of(context).advanced_privacy_settings), + ), + ], )), ); } diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart index bd8153226..8173af2f0 100644 --- a/lib/src/screens/nodes/node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/node_create_or_edit_page.dart @@ -1,16 +1,13 @@ import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/node_address_validator.dart'; -import 'package:cake_wallet/core/node_port_validator.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; @@ -108,76 +105,10 @@ class NodeCreateOrEditPage extends BasePage { padding: EdgeInsets.only(left: 24, right: 24), child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(bottom: 24.0), - content: Form( - key: _formKey, - child: Column( - children: [ - Row( - children: [ - Expanded( - child: BaseTextFormField( - controller: _addressController, - hintText: S.of(context).node_address, - validator: NodeAddressValidator(), - ) - ) - ], - ), - SizedBox(height: 10.0), - Row( - children: [ - Expanded( - child: BaseTextFormField( - controller: _portController, - hintText: S.of(context).node_port, - keyboardType: TextInputType.numberWithOptions( - signed: false, decimal: false), - validator: NodePortValidator(), - ) - ) - ], - ), - SizedBox(height: 10.0), - if (nodeCreateOrEditViewModel.hasAuthCredentials) ...[ - Row( - children: [ - Expanded( - child: BaseTextFormField( - controller: _loginController, - hintText: S.of(context).login, - ) - ) - ], - ), - SizedBox(height: 10.0), - Row( - children: [ - Expanded( - child: BaseTextFormField( - controller: _passwordController, - hintText: S.of(context).password, - ) - ) - ], - ), - Padding( - padding: EdgeInsets.only(top: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Observer( - builder: (_) => StandardCheckbox( - value: nodeCreateOrEditViewModel.useSSL, - onChanged: (value) => - nodeCreateOrEditViewModel.useSSL = value, - caption: S.of(context).use_ssl, - )) - ], - )) - ] - ], - )), + content: NodeForm( + formKey: _formKey, + nodeViewModel: nodeCreateOrEditViewModel, + ), bottomSectionPadding: EdgeInsets.only(bottom: 24), bottomSection: Observer( builder: (_) => Row( diff --git a/lib/src/screens/nodes/widgets/node_form.dart b/lib/src/screens/nodes/widgets/node_form.dart new file mode 100644 index 000000000..52851e063 --- /dev/null +++ b/lib/src/screens/nodes/widgets/node_form.dart @@ -0,0 +1,151 @@ +import 'package:cake_wallet/core/node_address_validator.dart'; +import 'package:cake_wallet/core/node_port_validator.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; +import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:mobx/mobx.dart'; + +class NodeForm extends StatelessWidget { + NodeForm({ + required this.nodeViewModel, + required this.formKey, + }) : _addressController = TextEditingController(), + _portController = TextEditingController(), + _loginController = TextEditingController(), + _passwordController = TextEditingController() { + reaction((_) => nodeViewModel.address, (String address) { + if (address != _addressController.text) { + _addressController.text = address; + } + }); + + reaction((_) => nodeViewModel.port, (String port) { + if (port != _portController.text) { + _portController.text = port; + } + }); + + if (nodeViewModel.hasAuthCredentials) { + reaction((_) => nodeViewModel.login, (String login) { + if (login != _loginController.text) { + _loginController.text = login; + } + }); + + reaction((_) => nodeViewModel.password, (String password) { + if (password != _passwordController.text) { + _passwordController.text = password; + } + }); + } + + _addressController + .addListener(() => nodeViewModel.address = _addressController.text); + _portController + .addListener(() => nodeViewModel.port = _portController.text); + _loginController + .addListener(() => nodeViewModel.login = _loginController.text); + _passwordController + .addListener(() => nodeViewModel.password = _passwordController.text); + } + + final NodeCreateOrEditViewModel nodeViewModel; + final GlobalKey formKey; + + final TextEditingController _addressController; + final TextEditingController _portController; + final TextEditingController _loginController; + final TextEditingController _passwordController; + + @override + Widget build(BuildContext context) { + return Form( + key: formKey, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: BaseTextFormField( + controller: _addressController, + hintText: S.of(context).node_address, + validator: NodeAddressValidator(), + ), + ) + ], + ), + SizedBox(height: 10.0), + Row( + children: [ + Expanded( + child: BaseTextFormField( + controller: _portController, + hintText: S.of(context).node_port, + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: false), + validator: NodePortValidator(), + )) + ], + ), + SizedBox(height: 10.0), + if (nodeViewModel.hasAuthCredentials) ...[ + Row( + children: [ + Expanded( + child: BaseTextFormField( + controller: _loginController, + hintText: S.of(context).login, + )) + ], + ), + SizedBox(height: 10.0), + Row( + children: [ + Expanded( + child: BaseTextFormField( + controller: _passwordController, + hintText: S.of(context).password, + )) + ], + ), + Padding( + padding: EdgeInsets.only(top: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Observer( + builder: (_) => StandardCheckbox( + value: nodeViewModel.useSSL, + onChanged: (value) => nodeViewModel.useSSL = value, + caption: S.of(context).use_ssl, + ), + ) + ], + ), + ), + Padding( + padding: EdgeInsets.only(top: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Observer( + builder: (_) => StandardCheckbox( + value: nodeViewModel.trusted, + onChanged: (value) => nodeViewModel.trusted = value, + caption: S.of(context).trusted, + ), + ), + ], + ), + ), + ] + ], + ), + ); + } +} diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index d5775e2b5..e875096e4 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -154,6 +154,7 @@ class SendCardState extends State await output.fetchParsedAddress(context); }, validator: validator, + selectedCurrency: sendViewModel.currency, ); }), if (output.isParsedAddress) Padding( diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 35bf3ed85..7e0727312 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/entities/contact_base.dart'; +import 'package:cw_core/crypto_currency.dart'; enum AddressTextFieldOption { paste, qrCode, addressBook } @@ -26,7 +27,8 @@ class AddressTextField extends StatelessWidget { this.hintStyle, this.validator, this.onPushPasteButton, - this.onPushAddressBookButton}); + this.onPushAddressBookButton, + this.selectedCurrency}); static const prefixIconWidth = 34.0; static const prefixIconHeight = 34.0; @@ -47,6 +49,7 @@ class AddressTextField extends StatelessWidget { final FocusNode? focusNode; final Function(BuildContext context)? onPushPasteButton; final Function(BuildContext context)? onPushAddressBookButton; + final CryptoCurrency? selectedCurrency; @override Widget build(BuildContext context) { @@ -207,7 +210,7 @@ class AddressTextField extends StatelessWidget { Future _presetAddressBookPicker(BuildContext context) async { final contact = await Navigator.of(context, rootNavigator: true) - .pushNamed(Routes.pickerAddressBook); + .pushNamed(Routes.pickerAddressBook,arguments: selectedCurrency); if (contact is ContactBase && contact.address != null) { controller?.text = contact.address; diff --git a/lib/store/authentication_store.dart b/lib/store/authentication_store.dart index 626ca2a91..b734d7acb 100644 --- a/lib/store/authentication_store.dart +++ b/lib/store/authentication_store.dart @@ -4,7 +4,7 @@ part 'authentication_store.g.dart'; class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore; -enum AuthenticationState { uninitialized, installed, allowed, denied } +enum AuthenticationState { uninitialized, installed, allowed } abstract class AuthenticationStoreBase with Store { AuthenticationStoreBase() : state = AuthenticationState.uninitialized; @@ -17,7 +17,4 @@ abstract class AuthenticationStoreBase with Store { @action void allowed() => state = AuthenticationState.allowed; - - @action - void denied() => state = AuthenticationState.denied; } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 75032975f..a09bfcc46 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -115,8 +115,8 @@ abstract class SettingsStoreBase with Store { PreferencesKey.currentBalanceDisplayModeKey, mode.serialize())); reaction( - (_) => disableExchange, - (bool disableExchange) => sharedPreferences.setBool( + (_) => disableExchange, + (bool disableExchange) => sharedPreferences.setBool( PreferencesKey.disableExchangeKey, disableExchange)); this diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart new file mode 100644 index 000000000..ef9ab878f --- /dev/null +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -0,0 +1,51 @@ +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/view_model/settings/switcher_list_item.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'advanced_privacy_settings_view_model.g.dart'; + +class AdvancedPrivacySettingsViewModel = AdvancedPrivacySettingsViewModelBase + with _$AdvancedPrivacySettingsViewModel; + +abstract class AdvancedPrivacySettingsViewModelBase with Store { + AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore) + : _disableFiat = false, + _addCustomNode = false { + settings = [ + // TODO: uncomment when Disable Fiat PR is merged + // SwitcherListItem( + // title: S.current.disable_fiat, + // value: () => _disableFiat, + // onValueChange: (_, bool value) => _disableFiat = value, + // ), + SwitcherListItem( + title: S.current.disable_exchange, + value: () => _settingsStore.disableExchange, + onValueChange: (_, bool value) { + _settingsStore.disableExchange = value; + }, + ), + SwitcherListItem( + title: S.current.add_custom_node, + value: () => _addCustomNode, + onValueChange: (_, bool value) => _addCustomNode = value, + ), + ]; + } + + late List settings; + + @observable + bool _disableFiat = false; + + @observable + bool _addCustomNode = false; + + final WalletType type; + final SettingsStore _settingsStore; + + @computed + bool get addCustomNode => _addCustomNode; +} diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index ec145ad44..5b0187fc8 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -7,27 +7,26 @@ import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/utils/mobx.dart'; +import 'package:cw_core/crypto_currency.dart'; part 'contact_list_view_model.g.dart'; -class ContactListViewModel = ContactListViewModelBase - with _$ContactListViewModel; +class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel; abstract class ContactListViewModelBase with Store { - ContactListViewModelBase(this.contactSource, this.walletInfoSource) + ContactListViewModelBase(this.contactSource, this.walletInfoSource, this._currency) : contacts = ObservableList(), walletContacts = [] { walletInfoSource.values.forEach((info) { if (info.addresses?.isNotEmpty ?? false) { info.addresses?.forEach((address, label) { - final name = label.isNotEmpty - ? info.name + ' ($label)' - : info.name; + final name = label.isNotEmpty ? info.name + ' ($label)' : info.name; walletContacts.add(WalletContact( - address, - name, - walletTypeToCryptoCurrency(info.type))); + address, + name, + walletTypeToCryptoCurrency(info.type), + )); }); } }); @@ -41,7 +40,18 @@ abstract class ContactListViewModelBase with Store { final Box walletInfoSource; final ObservableList contacts; final List walletContacts; + final CryptoCurrency? _currency; StreamSubscription? _subscription; + bool get isEditable => _currency == null; + Future delete(ContactRecord contact) async => contact.original.delete(); + + @computed + List get contactsToShow => + contacts.where((element) => _currency == null || element.type == _currency).toList(); + + @computed + List get walletContactsToShow => + walletContacts.where((element) => _currency == null || element.type == _currency).toList(); } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 68947d170..5c1f696b8 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -44,7 +44,6 @@ abstract class ExchangeViewModelBase with Store { ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore, this._settingsStore, this.sharedPreferences) : _cryptoNumberFormat = NumberFormat(), - isReverse = false, isFixedRateMode = false, isReceiveAmountEntered = false, depositAmount = '', @@ -112,7 +111,11 @@ abstract class ExchangeViewModelBase with Store { loadLimits(); reaction( (_) => isFixedRateMode, - (Object _) => loadLimits()); + (Object _) { + loadLimits(); + _bestRate = 0; + _calculateBestRate(); + }); } final WalletBase wallet; @@ -227,8 +230,6 @@ abstract class ExchangeViewModelBase with Store { Limits limits; - bool isReverse; - NumberFormat _cryptoNumberFormat; final SettingsStore _settingsStore; @@ -258,7 +259,6 @@ abstract class ExchangeViewModelBase with Store { @action Future changeReceiveAmount({required String amount}) async { receiveAmount = amount; - isReverse = true; if (amount.isEmpty) { depositAmount = ''; @@ -283,7 +283,6 @@ abstract class ExchangeViewModelBase with Store { @action Future changeDepositAmount({required String amount}) async { depositAmount = amount; - isReverse = false; if (amount.isEmpty) { depositAmount = ''; @@ -307,14 +306,17 @@ abstract class ExchangeViewModelBase with Store { } Future _calculateBestRate() async { + final amount = double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1; + final result = await Future.wait( _tradeAvailableProviders - .map((element) => element.calculateAmount( + .where((element) => !isFixedRateMode || element.supportsFixedRate) + .map((element) => element.fetchRate( from: depositCurrency, to: receiveCurrency, - amount: 1, + amount: amount, isFixedRateMode: isFixedRateMode, - isReceiveAmount: false)) + isReceiveAmount: isFixedRateMode)) ); _sortedAvailableProviders.clear(); @@ -345,7 +347,7 @@ abstract class ExchangeViewModelBase with Store { ? depositCurrency : receiveCurrency; - double lowestMin = double.maxFinite; + double? lowestMin = double.maxFinite; double? highestMax = 0.0; for (var provider in selectedProviders) { @@ -360,8 +362,8 @@ abstract class ExchangeViewModelBase with Store { to: to, isFixedRateMode: isFixedRateMode); - if (tempLimits.min != null && tempLimits.min! < lowestMin) { - lowestMin = tempLimits.min!; + if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) { + lowestMin = tempLimits.min; } if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) { highestMax = tempLimits.max; @@ -371,7 +373,7 @@ abstract class ExchangeViewModelBase with Store { } } - if (lowestMin < double.maxFinite) { + if (lowestMin != double.maxFinite) { limits = Limits(min: lowestMin, max: highestMax); limitsState = LimitsLoadedSuccessfully(limits: limits); @@ -399,7 +401,7 @@ abstract class ExchangeViewModelBase with Store { settleAddress: receiveAddress, refundAddress: depositAddress, ); - amount = depositAmount; + amount = isFixedRateMode ? receiveAmount : depositAmount; } if (provider is SimpleSwapExchangeProvider) { @@ -410,7 +412,7 @@ abstract class ExchangeViewModelBase with Store { address: receiveAddress, refundAddress: depositAddress, ); - amount = depositAmount; + amount = isFixedRateMode ? receiveAmount : depositAmount; } if (provider is XMRTOExchangeProvider) { @@ -422,7 +424,7 @@ abstract class ExchangeViewModelBase with Store { address: receiveAddress, refundAddress: depositAddress, isBTCRequest: isReceiveAmountEntered); - amount = depositAmount; + amount = isFixedRateMode ? receiveAmount : depositAmount; } if (provider is ChangeNowExchangeProvider) { @@ -433,8 +435,8 @@ abstract class ExchangeViewModelBase with Store { toAmount: receiveAmount.replaceAll(',', '.'), refundAddress: depositAddress, address: receiveAddress, - isReverse: isReverse); - amount = isReverse ? receiveAmount : depositAmount; + isReverse: isFixedRateMode); + amount = isFixedRateMode ? receiveAmount : depositAmount; } if (provider is MorphTokenExchangeProvider) { @@ -444,13 +446,13 @@ abstract class ExchangeViewModelBase with Store { amount: depositAmount.replaceAll(',', '.'), refundAddress: depositAddress, address: receiveAddress); - amount = depositAmount; + amount = isFixedRateMode ? receiveAmount : depositAmount; } amount = amount.replaceAll(',', '.'); if (limitsState is LimitsLoadedSuccessfully) { - if (double.parse(amount) < limits.min!) { + if (limits.max != null && double.parse(amount) < limits.min!) { continue; } else if (limits.max != null && double.parse(amount) > limits.max!) { continue; diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 31b83faff..a8f184589 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/wallet_type.dart'; @@ -11,14 +11,15 @@ class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase with _$NodeCreateOrEditViewModel; abstract class NodeCreateOrEditViewModelBase with Store { - NodeCreateOrEditViewModelBase(this._nodeSource, this._wallet) + NodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore) : state = InitialExecutionState(), connectionState = InitialExecutionState(), useSSL = false, address = '', port = '', login = '', - password = ''; + password = '', + trusted = false; @observable ExecutionState state; @@ -41,12 +42,15 @@ abstract class NodeCreateOrEditViewModelBase with Store { @observable bool useSSL; + @observable + bool trusted; + @computed bool get isReady => address.isNotEmpty && port.isNotEmpty; - bool get hasAuthCredentials => _wallet.type == WalletType.monero || - _wallet.type == WalletType.haven; + bool get hasAuthCredentials => _walletType == WalletType.monero || + _walletType == WalletType.haven; String get uri { var uri = address; @@ -58,8 +62,9 @@ abstract class NodeCreateOrEditViewModelBase with Store { return uri; } - final WalletBase _wallet; + final WalletType _walletType; final Box _nodeSource; + final SettingsStore _settingsStore; @action void reset() { @@ -68,16 +73,22 @@ abstract class NodeCreateOrEditViewModelBase with Store { login = ''; password = ''; useSSL = false; + trusted = false; } @action - Future save() async { + Future save({bool saveAsCurrent = false}) async { try { state = IsExecutingState(); final node = - Node(uri: uri, type: _wallet.type, login: login, password: password, - useSSL: useSSL); + Node(uri: uri, type: _walletType, login: login, password: password, + useSSL: useSSL, trusted: trusted); await _nodeSource.add(node); + + if (saveAsCurrent) { + _settingsStore.nodes[_walletType] = node; + } + state = ExecutedSuccessfullyState(); } catch (e) { state = FailureState(e.toString()); @@ -89,7 +100,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { try { connectionState = IsExecutingState(); final node = - Node(uri: uri, type: _wallet.type, login: login, password: password); + Node(uri: uri, type: _walletType, login: login, password: password); final isAlive = await node.requestNode(); connectionState = ExecutedSuccessfullyState(payload: isAlive); } catch (e) { diff --git a/model_generator.sh b/model_generator.sh new file mode 100644 index 000000000..f4ef8bdad --- /dev/null +++ b/model_generator.sh @@ -0,0 +1,5 @@ +cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +flutter packages pub run build_runner build --delete-conflicting-outputs \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 95fd0b6ff..d9eb8d78a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider}-Fehler", "use_ssl" : "SSL verwenden", + "trusted" : "Vertrauenswürdige", "color_theme" : "Farbthema", "light_theme" : "Hell", @@ -660,5 +661,8 @@ "privacy": "Datenschutz", "display_settings": "Anzeigeeinstellungen", "other_settings": "Andere Einstellungen", - "disable_exchange": "Exchange deaktivieren" + "disable_exchange": "Exchange deaktivieren", + "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", + "settings_can_be_changed_later": "Diese Einstellungen können später in den App-Einstellungen geändert werden", + "add_custom_node": "Neuen benutzerdefinierten Knoten hinzufügen" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index a470ccd70..baf955fcd 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} error", "use_ssl" : "Use SSL", + "trusted" : "Trusted", "color_theme" : "Color theme", "light_theme" : "Light", @@ -660,5 +661,8 @@ "privacy": "Privacy", "display_settings": "Display settings", "other_settings": "Other settings", - "disable_exchange": "Disable exchange" + "disable_exchange": "Disable exchange", + "advanced_privacy_settings": "Advanced Privacy Settings", + "settings_can_be_changed_later": "These settings can be changed later in the app settings", + "add_custom_node": "Add New Custom Node" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index ad0476df4..54b3fae18 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} error", "use_ssl" : "Utilice SSL", + "trusted" : "de confianza", "color_theme" : "Tema de color", "light_theme" : "Ligera", @@ -660,5 +661,8 @@ "privacy": "Privacidad", "display_settings": "Configuración de pantalla", "other_settings": "Otras configuraciones", - "disable_exchange": "Deshabilitar intercambio" + "disable_exchange": "Deshabilitar intercambio", + "advanced_privacy_settings": "Configuración avanzada de privacidad", + "settings_can_be_changed_later": "Estas configuraciones se pueden cambiar más tarde en la configuración de la aplicación", + "add_custom_node": "Agregar nuevo nodo personalizado" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 9666df9cb..b6836ab18 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -435,6 +435,7 @@ "provider_error" : "Erreur de ${provider}", "use_ssl" : "Utiliser SSL", + "trusted" : "de confiance", "color_theme" : "Thème", "light_theme" : "Clair", @@ -658,5 +659,8 @@ "privacy": "Confidentialité", "display_settings": "Paramètres d'affichage", "other_settings": "Autres paramètres", - "disable_exchange": "Désactiver l'échange" + "disable_exchange": "Désactiver l'échange", + "advanced_privacy_settings": "Paramètres de confidentialité avancés", + "settings_can_be_changed_later": "Ces paramètres peuvent être modifiés ultérieurement dans les paramètres de l'application", + "add_custom_node": "Ajouter un nouveau nœud personnalisé" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index c7144d14a..72a21f333 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} त्रुटि", "use_ssl" : "उपयोग SSL", + "trusted" : "भरोसा", "color_theme" : "रंग विषय", "light_theme" : "रोशनी", @@ -660,5 +661,8 @@ "privacy": "गोपनीयता", "display_settings": "प्रदर्शन सेटिंग्स", "other_settings": "अन्य सेटिंग्स", - "disable_exchange": "एक्सचेंज अक्षम करें" + "disable_exchange": "एक्सचेंज अक्षम करें", + "advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स", + "settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है", + "add_custom_node": "नया कस्टम नोड जोड़ें" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 897075ce7..16b388a05 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} greška", "use_ssl" : "Koristi SSL", + "trusted" : "vjerovao", "color_theme" : "Shema boja", "light_theme" : "Svijetla", @@ -660,5 +661,8 @@ "privacy": "Privatnost", "display_settings": "Postavke zaslona", "other_settings": "Ostale postavke", - "disable_exchange": "Onemogući exchange" + "disable_exchange": "Onemogući exchange", + "advanced_privacy_settings": "Napredne postavke privatnosti", + "settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije", + "add_custom_node": "Dodaj novi prilagođeni čvor" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index a8e30ae06..28ad40d47 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} errore", "use_ssl" : "Usa SSL", + "trusted" : "di fiducia", "color_theme" : "Colore tema", "light_theme" : "Bianco", @@ -660,5 +661,8 @@ "privacy": "Privacy", "display_settings": "Impostazioni di visualizzazione", "other_settings": "Altre impostazioni", - "disable_exchange": "Disabilita scambio" + "disable_exchange": "Disabilita scambio", + "advanced_privacy_settings": "Impostazioni avanzate sulla privacy", + "settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app", + "add_custom_node": "Aggiungi nuovo nodo personalizzato" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d5553ff10..d79f58e7a 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} エラー", "use_ssl" : "SSLを使用する", + "trusted" : "信頼できる", "color_theme" : "カラーテーマ", "light_theme" : "光", @@ -660,5 +661,8 @@ "privacy": "プライバシー", "display_settings": "表示設定", "other_settings": "その他の設定", - "disable_exchange": "交換を無効にする" + "disable_exchange": "交換を無効にする", + "advanced_privacy_settings": "高度なプライバシー設定", + "settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます", + "add_custom_node": "新しいカスタム ノードを追加" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index a374e18ad..0652ffc54 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} 오류", "use_ssl" : "SSL 사용", + "trusted" : "신뢰할 수 있는", "color_theme" : "색상 테마", "light_theme" : "빛", @@ -660,5 +661,8 @@ "privacy": "프라이버시", "display_settings": "디스플레이 설정", "other_settings": "기타 설정", - "disable_exchange": "교환 비활성화" + "disable_exchange": "교환 비활성화", + "advanced_privacy_settings": "고급 개인 정보 설정", + "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.", + "add_custom_node": "새 사용자 정의 노드 추가" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 286735406..2c6e528ca 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} fout", "use_ssl" : "Gebruik SSL", + "trusted" : "vertrouwd", "color_theme" : "Kleur thema", "light_theme" : "Licht", @@ -660,5 +661,8 @@ "privacy": "Privacy", "display_settings": "Weergave-instellingen", "other_settings": "Andere instellingen", - "disable_exchange": "Uitwisseling uitschakelen" + "disable_exchange": "Uitwisseling uitschakelen", + "advanced_privacy_settings": "Geavanceerde privacy-instellingen", + "settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen", + "add_custom_node": "Voeg een nieuw aangepast knooppunt toe" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 9ec4ce591..179f7267b 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} pomyłka", "use_ssl" : "Użyj SSL", + "trusted" : "zaufany", "color_theme" : "Motyw kolorystyczny", "light_theme" : "Lekki", @@ -660,5 +661,8 @@ "privacy": "Prywatność", "display_settings": "Ustawienia wyświetlania", "other_settings": "Inne ustawienia", - "disable_exchange": "Wyłącz wymianę" + "disable_exchange": "Wyłącz wymianę", + "advanced_privacy_settings": "Zaawansowane ustawienia prywatności", + "settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji", + "add_custom_node": "Dodaj nowy węzeł niestandardowy" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 6dc3df4e0..cf0669585 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} erro", "use_ssl" : "Use SSL", + "trusted" : "confiável", "color_theme" : "Tema de cor", "light_theme" : "Luz", @@ -545,7 +546,6 @@ "create_account": "Criar conta", "privacy_policy": "Política de privacidade", "welcome_to_cakepay": "Bem-vindo ao Cake Pay!", - "create_account": "Registar-se", "forgot_password": "Esqueci a senha", "reset_password": "Redefinir senha", "gift_cards": "Cartões de presente", @@ -660,5 +660,8 @@ "privacy": "Privacidade", "display_settings": "Configurações de exibição", "other_settings": "Outras configurações", - "disable_exchange": "Desativar troca" + "disable_exchange": "Desativar troca", + "advanced_privacy_settings": "Configurações de privacidade avançadas", + "settings_can_be_changed_later": "Essas configurações podem ser alteradas posteriormente nas configurações do aplicativo", + "add_custom_node": "Adicionar novo nó personalizado" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index e08cc74a3..f73ad4564 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -437,6 +437,7 @@ "provider_error" : "${provider} ошибка", "use_ssl" : "Использовать SSL", + "trusted" : "доверенный", "color_theme" : "Цветовая тема", "light_theme" : "Светлая", @@ -660,5 +661,8 @@ "privacy": "Конфиденциальность", "display_settings": "Настройки отображения", "other_settings": "Другие настройки", - "disable_exchange": "Отключить обмен" + "disable_exchange": "Отключить обмен", + "advanced_privacy_settings": "Расширенные настройки конфиденциальности", + "settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения.", + "add_custom_node": "Добавить новый пользовательский узел" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 3c98cb126..c52359d64 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -436,6 +436,7 @@ "provider_error" : "${provider} помилка", "use_ssl" : "Використати SSL", + "trusted" : "довіряють", "color_theme" : "Кольорова тема", "light_theme" : "Світла", @@ -659,5 +660,8 @@ "privacy": "Конфіденційність", "display_settings": "Налаштування дисплея", "other_settings": "Інші налаштування", - "disable_exchange": "Вимкнути exchange" + "disable_exchange": "Вимкнути exchange", + "advanced_privacy_settings": "Розширені налаштування конфіденційності", + "settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми", + "add_custom_node": "Додати новий спеціальний вузол" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 8434fa93c..a8f9119da 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -436,6 +436,7 @@ "provider_error" : "${provider} 错误", "use_ssl" : "使用SSL", + "trusted" : "值得信赖", "color_theme" : "主题", "light_theme" : "艳丽", @@ -658,5 +659,8 @@ "privacy":"隐私", "display_settings": "显示设置", "other_settings": "其他设置", - "disable_exchange": "禁用交换" + "disable_exchange": "禁用交换", + "advanced_privacy_settings": "高级隐私设置", + "settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置", + "add_custom_node": "添加新的自定义节点" }