From 5cccde53f679c11d7485be8d019a078016175a36 Mon Sep 17 00:00:00 2001 From: Omar Date: Sun, 31 May 2026 22:44:31 +0200 Subject: [PATCH 1/6] update discord link [skip ci] --- lib/view_model/support_view_model.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart index 5459e523b..052ab6478 100644 --- a/lib/view_model/support_view_model.dart +++ b/lib/view_model/support_view_model.dart @@ -39,8 +39,8 @@ abstract class SupportViewModelBase with Store { LinkListItem( title: 'Discord', icon: 'assets/images/discord.png', - linkTitle: 'discord.gg/pwmWa6aFpX', - link: 'https://discord.gg/pwmWa6aFpX'), + linkTitle: 'discord.gg/2vqYJdXG2H', + link: 'https://discord.gg/2vqYJdXG2H'), LinkListItem( title: 'Telegram', icon: 'assets/images/Telegram.png', From e13d997622e1a662741296fcae3dde80ae21cfb0 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:27:24 +0100 Subject: [PATCH 2/6] Refactor WalletConnect UI components and enhance transaction approval and signing flow (#3233) * Refactor WalletConnect UI components and enhance transaction approval and signing flow * Refactor WalletConnect UI components and enhance transaction approval and signing flow * Remove unused ui components * Enhance pairing details page and remove unused ui components * adjust buttons on details page * Add warning banner for scam dApps on connection requests * General cleanup * Parse and display estimated network fee for WalletConnect approval requests * Revamp WalletConnect pair listing view * Remove unused action buttons and walletkit methods * update svg and sync with dev --- .../list_item/list_item_regular_row.dart | 6 +- .../services/bottom_sheet_service.dart | 4 - .../chain_service/eth/evm_chain_service.dart | 88 ++++- .../solana/solana_chain_service.dart | 1 - .../wallet_connect_key_service.dart | 5 - .../services/walletkit_service.dart | 81 ++--- .../wallet_connect/utils/method_utils.dart | 73 +++- .../utils/namespace_model_builder.dart | 76 ----- .../utils/wc_permissions_mapper.dart | 80 +++++ .../wc_connections_listing_view.dart | 316 +++++++++--------- .../wc_pairing_detail_page.dart | 255 ++++++-------- .../enter_wallet_connect_uri_widget.dart | 8 +- .../widgets/wc_connection_item_widget.dart | 94 ------ .../widgets/wc_connection_request_sheet.dart | 94 ++++++ .../widgets/wc_connection_request_widget.dart | 100 ------ .../widgets/wc_connection_widget.dart | 45 --- .../wallet_connect/widgets/wc_dapp_card.dart | 132 ++++++++ .../wallet_connect/widgets/wc_hero_card.dart | 51 +++ .../widgets/wc_message_card.dart | 86 +++++ .../widgets/wc_pairing_item_widget.dart | 89 ----- .../widgets/wc_permissions_card.dart | 64 ++++ .../widgets/wc_request_widget.dart | 69 ---- .../widgets/wc_scam_banner.dart | 56 ++++ .../wc_session_auth_request_widget.dart | 60 ---- .../widgets/wc_sheet_header.dart | 50 +++ .../widgets/wc_signing_request_sheet.dart | 111 ++++++ .../widgets/wc_verify_context_widget.dart | 131 -------- .../widgets/wc_wallet_card.dart | 95 ++++++ lib/src/widgets/cake_image_widget.dart | 16 +- .../list_item_regular_row_widget.dart | 11 +- .../new_list_row/new_list_section.dart | 2 + res/pictures/global_view.svg | 26 ++ res/pictures/green_check.svg | 21 ++ res/pictures/pencil.svg | 1 + res/pictures/walletconnect_icon.svg | 29 ++ res/values/strings_ar.arb | 33 +- res/values/strings_bg.arb | 33 +- res/values/strings_cs.arb | 33 +- res/values/strings_de.arb | 35 +- res/values/strings_en.arb | 33 +- res/values/strings_es.arb | 33 +- res/values/strings_fa.arb | 33 +- res/values/strings_fr.arb | 33 +- res/values/strings_gn.arb | 25 ++ res/values/strings_ha.arb | 33 +- res/values/strings_hi.arb | 33 +- res/values/strings_hr.arb | 33 +- res/values/strings_hy.arb | 32 +- res/values/strings_id.arb | 32 +- res/values/strings_it.arb | 32 +- res/values/strings_ja.arb | 32 +- res/values/strings_ko.arb | 32 +- res/values/strings_my.arb | 32 +- res/values/strings_nl.arb | 32 +- res/values/strings_pl.arb | 32 +- res/values/strings_pt.arb | 32 +- res/values/strings_pt_BR.arb | 32 +- res/values/strings_ru.arb | 32 +- res/values/strings_th.arb | 32 +- res/values/strings_tl.arb | 32 +- res/values/strings_tr.arb | 32 +- res/values/strings_uk.arb | 32 +- res/values/strings_ur.arb | 32 +- res/values/strings_vi.arb | 32 +- res/values/strings_yo.arb | 32 +- res/values/strings_zh.arb | 32 +- res/values/strings_zh_tw.arb | 32 +- 67 files changed, 2127 insertions(+), 1329 deletions(-) delete mode 100644 lib/src/screens/wallet_connect/utils/namespace_model_builder.dart create mode 100644 lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_dapp_card.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_hero_card.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_message_card.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_permissions_card.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_request_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_scam_banner.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_sheet_header.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart delete mode 100644 lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/wc_wallet_card.dart create mode 100644 res/pictures/global_view.svg create mode 100644 res/pictures/green_check.svg create mode 100644 res/pictures/pencil.svg create mode 100644 res/pictures/walletconnect_icon.svg diff --git a/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart b/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart index f6df44a25..e5fdc4595 100644 --- a/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart +++ b/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart @@ -16,7 +16,9 @@ class ListItemRegularRow extends ListItem { this.truncateTrailingText = false, this.foregroundColor, this.trailingIconSize, - this.copyableText + this.copyableText, + this.leadingIconErrorWidget, + this.leadingIconSize, }); final String? subtitle; @@ -31,4 +33,6 @@ class ListItemRegularRow extends ListItem { final bool truncateTrailingText; final Color? foregroundColor; final double? trailingIconSize; + final Widget? leadingIconErrorWidget; + final double? leadingIconSize; } diff --git a/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart index 44637ac1b..b8e21c9b8 100644 --- a/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart +++ b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart @@ -29,7 +29,6 @@ class BottomSheetServiceImpl implements BottomSheetService { int closeAfter = 0, bool isModalDismissible = false, }) async { - // Create the bottom sheet queue item final completer = Completer(); final queueItem = BottomSheetQueueItemModel( widget: widget, @@ -38,15 +37,12 @@ class BottomSheetServiceImpl implements BottomSheetService { isModalDismissible: isModalDismissible, ); - // If the current sheet it null, set it to the queue item if (currentSheet.value == null) { currentSheet.value = queueItem; } else { - // Otherwise, add it to the queue queue.add(queueItem); } - // Return the future return await completer.future; } diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart index 15d2bdc00..c8618d85f 100644 --- a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart @@ -1,6 +1,10 @@ import 'dart:convert'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:eth_sig_util/eth_sig_util.dart'; import 'package:eth_sig_util/util/utils.dart'; @@ -106,7 +110,6 @@ class EvmChainServiceImpl { if (isApproved) { try { - // Load the private key final keys = wcKeyService.getKeysForChain(appStore.wallet!); final credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -155,7 +158,6 @@ class EvmChainServiceImpl { if (isApproved) { try { - // Load the private key final keys = wcKeyService.getKeysForChain(appStore.wallet!); final credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -297,7 +299,6 @@ class EvmChainServiceImpl { if (transaction is Transaction) { try { - // Load the private key final keys = wcKeyService.getKeysForChain(appStore.wallet!); final credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -309,7 +310,6 @@ class EvmChainServiceImpl { chainId: int.parse(chainId), ); - // Sign the transaction final signedTx = bytesToHex(signature, include0x: true); response = response.copyWith(result: signedTx); } on RPCError catch (e) { @@ -349,7 +349,6 @@ class EvmChainServiceImpl { ); if (transaction is Transaction) { try { - // Load the private key final keys = wcKeyService.getKeysForChain(appStore.wallet!); final credentials = EthPrivateKey.fromHex(keys[0].privateKey); final chainId = getChainId().split(':').last; @@ -440,30 +439,26 @@ class EvmChainServiceImpl { transaction = await _applyWCBufferedFees(transaction); - final gweiGasPrice = - (transaction.gasPrice?.getInWei ?? transaction.maxFeePerGas?.getInWei ?? BigInt.zero) / - BigInt.from(1000000000); + final nativeCurrency = evm?.getChainInfoByChainId(reference.chainId ?? 1)?.currency; + final nativeSymbol = nativeCurrency?.title ?? 'ETH'; final amount = (transaction.value?.getInWei ?? BigInt.zero) / BigInt.from(1e18); - final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} ETH\n' + final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} $nativeSymbol\n' '${S.current.from}: ${transaction.from?.hex}\n' '${S.current.to}: ${transaction.to?.hex}'; + final feeRows = _buildFeeExtraModels(transaction, nativeCurrency, nativeSymbol); + if (await MethodsUtils.requestApproval( txMessageText, title: title, method: method, chainId: chainId, - address: address, + address: address ?? transaction.from?.hex ?? '', transportType: transportType, verifyContext: verifyContext, - extraModels: [ - WCConnectionModel( - title: S.current.gas_price, - elements: ['${gweiGasPrice.toStringAsFixed(2)} GWEI'], - ), - ], + extraModels: feeRows, )) { return transaction; } @@ -471,6 +466,66 @@ class EvmChainServiceImpl { return JsonRpcError(code: 5002, message: S.current.user_rejected_method); } + List _buildFeeExtraModels( + Transaction transaction, + CryptoCurrency? nativeCurrency, + String nativeSymbol, + ) { + final gasLimit = transaction.maxGas; + if (gasLimit == null || gasLimit <= 0) return const []; + + final gasLimitBig = BigInt.from(gasLimit); + final isEip1559 = transaction.isEIP1559; + final perGasWei = + isEip1559 ? transaction.maxFeePerGas?.getInWei : transaction.gasPrice?.getInWei; + if (perGasWei == null || perGasWei <= BigInt.zero) return const []; + + final feeWei = gasLimitBig * perGasWei; + final feeNative = feeWei.toDouble() / 1e18; + + final label = isEip1559 ? S.current.wc_max_network_fee : S.current.wc_network_fee; + + return [ + WCConnectionModel( + title: label, + elements: [_formatFeeLine(feeNative, nativeSymbol, nativeCurrency)], + ), + ]; + } + + String _formatFeeLine( + double feeNative, + String nativeSymbol, + CryptoCurrency? nativeCurrency, + ) { + final cryptoPart = '${_formatNativeAmount(feeNative)} $nativeSymbol'; + + if (nativeCurrency == null) return cryptoPart; + + try { + final fiatStore = getIt.get(); + final price = fiatStore.prices[nativeCurrency]; + if (price == null || price <= 0) return cryptoPart; + + final fiatSymbol = appStore.settingsStore.fiatCurrency.title; + final fiatValue = calculateFiatAmount( + price: price, + cryptoAmount: feeNative.toString(), + ); + if (fiatValue.isEmpty || fiatValue == '0.00') return cryptoPart; + + return '$cryptoPart (~ $fiatValue $fiatSymbol)'; + } catch (_) { + return cryptoPart; + } + } + + String _formatNativeAmount(double value) { + if (value == 0) return '0'; + if (value >= 0.0001) return value.toStringAsFixed(6); + return value.toStringAsExponential(4); + } + Future _ensureWCTransactionHasGasLimit(Transaction transaction) async { final hasGasLimit = transaction.maxGas != null && transaction.maxGas! > 0; if (hasGasLimit) return transaction; @@ -637,7 +692,6 @@ $messageDetails'''; if (value == null) continue; if (types.containsKey(fieldType)) { - // Handle nested types final nestedFields = types[fieldType] as List; if (fieldType == 'Person') { // Special formatting for Person type diff --git a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart index f7a57b51d..b5d12f357 100644 --- a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart +++ b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart @@ -80,7 +80,6 @@ class SolanaChainService { error: JsonRpcError(code: error.code, message: error.message), ); } - // } catch (e) { debugPrint('solanaSignMessage error $e'); final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS); diff --git a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart index 498585f0e..38772da10 100644 --- a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart +++ b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart @@ -6,12 +6,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; abstract class WalletConnectKeyService { - /// Returns a list of all the keys. List getKeys(WalletBase wallet); - - /// Returns a list of all the keys for a given chain id. - /// If the chain is not found, returns an empty list. - /// - [chain]: The chain to get the keys for. List getKeysForChain(WalletBase wallet); } diff --git a/lib/src/screens/wallet_connect/services/walletkit_service.dart b/lib/src/screens/wallet_connect/services/walletkit_service.dart index 5e215ea7e..7d5a0ca67 100644 --- a/lib/src/screens/wallet_connect/services/walletkit_service.dart +++ b/lib/src/screens/wallet_connect/services/walletkit_service.dart @@ -20,10 +20,9 @@ import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chai import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/utils/eth_utils.dart'; import 'package:cake_wallet/src/screens/wallet_connect/utils/method_utils.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'bottom_sheet_service.dart'; @@ -56,8 +55,6 @@ abstract class WalletKitServiceBase with Store { bool isInitialized; /// The list of requests from the dapp - /// Potential types include, but aren't limited to: - /// [SessionProposalEvent], [SessionAuthRequest] @observable ObservableList pairings; @@ -69,7 +66,6 @@ abstract class WalletKitServiceBase with Store { @action void create() { - // Create the walletkit client _walletKit = ReownWalletKit( core: ReownCore( projectId: secrets.walletConnectProjectId, @@ -85,7 +81,6 @@ abstract class WalletKitServiceBase with Store { _walletKit.core.addLogListener(_logListener); - // Setup our listeners log('Created instance of walletKit'); _walletKit.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid); @@ -288,16 +283,12 @@ abstract class WalletKitServiceBase with Store { if (args != null) { final proposer = args.params.proposer; final result = (await _bottomSheetHandler.queueBottomSheet( - widget: WCRequestWidget( + widget: WCConnectionRequestSheet( + proposalData: args.params, + requester: proposer, verifyContext: args.verifyContext, - child: WCConnectionRequestWidget( - proposalData: args.params, - verifyContext: args.verifyContext, - requester: proposer, - walletKeyService: walletKeyService, - walletKit: walletKit, - appStore: appStore, - ), + walletKeyService: walletKeyService, + appStore: appStore, ), )) ?? WCBottomSheetResult.reject; @@ -400,10 +391,8 @@ abstract class WalletKitServiceBase with Store { debugPrint('_onPairingCreate $args'); if (args != null && args.topic != null && args.topic!.isNotEmpty) { - // Save the pairing topic when pairing is created savePairingTopicToLocalStorage(args.topic!); - // Refresh pairings to show the new pairing in the list _refreshPairings(); } } @@ -440,16 +429,28 @@ abstract class WalletKitServiceBase with Store { formattedMessages.add({iss: message}); } + final requesterMetadata = args.requester.metadata; + final requesterIcon = requesterMetadata.icons.isNotEmpty + ? requesterMetadata.icons.first + : null; + final chainKeysForAuth = walletKeyService.getKeysForChain(appStore.wallet!); + final addressForAuth = + chainKeysForAuth.isNotEmpty ? chainKeysForAuth.first.publicKey : ''; + final combinedMessageBody = + formattedMessages.map((m) => m.values.first as String).join('\n\n'); + final WCBottomSheetResult result = (await _bottomSheetHandler.queueBottomSheet( - widget: WCSessionAuthRequestWidget( - child: WCConnectionRequestWidget( - sessionAuthPayload: newAuthPayload, - verifyContext: args.verifyContext, - requester: args.requester, - walletKeyService: walletKeyService, - walletKit: _walletKit, - appStore: appStore, - ), + widget: WCSigningRequestSheet( + title: S.current.wc_signing_request_title, + swipeLabel: S.current.wc_swipe_to_sign, + dappName: requesterMetadata.name, + dappIconUrl: requesterIcon, + dappSubtitle: requesterMetadata.url, + message: combinedMessageBody, + walletName: appStore.wallet?.name ?? '', + address: addressForAuth, + verifyContext: args.verifyContext, + signAllCount: formattedMessages.length, ), ) as WCBottomSheetResult?) ?? WCBottomSheetResult.reject; @@ -458,10 +459,8 @@ abstract class WalletKitServiceBase with Store { final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); final privateKey = '0x${chainKeys.first.privateKey}'; final credentials = EthPrivateKey.fromHex(privateKey); - // final messageToSign = formattedMessages.length; final count = (result == WCBottomSheetResult.one) ? 1 : messageToSign; - // final List cacaos = []; for (var i = 0; i < count; i++) { final iss = formattedMessages[i].keys.first; @@ -572,19 +571,6 @@ abstract class WalletKitServiceBase with Store { _refreshPairings(); } - @action - Future updateSession({ - required String topic, - required Map namespaces, - }) async { - await walletKit.updateSession(topic: topic, namespaces: namespaces); - } - - @action - Future extendSession({required String topic}) async { - await walletKit.extendSession(topic: topic); - } - @action Future pairWithUri(Uri uri) async { try { @@ -633,7 +619,7 @@ abstract class WalletKitServiceBase with Store { @action List getSessionsForPairingInfo(PairingInfo pairing) { - return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); + return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); } String getKeyForStoringTopicsForWallet() { @@ -664,18 +650,14 @@ abstract class WalletKitServiceBase with Store { } List getPairingTopicsForWallet(String key) { - // Get the JSON-encoded string from shared preferences final jsonString = sharedPreferences.getString(key); - // If the string is null, return an empty list if (jsonString == null) { return []; } - // Decode the JSON string to a list of strings final List jsonList = jsonDecode(jsonString) as List; - // Cast each item to a string return jsonList.map((item) => item as String).toList(); } @@ -691,12 +673,10 @@ abstract class WalletKitServiceBase with Store { } Future savePairingTopicToLocalStorage(String pairingTopic) async { - // Get key specific to the current wallet final key = getKeyForStoringTopicsForWallet(); if (key.isEmpty) return; - // Get all pairing topics attached to this key final pairingTopicsForWallet = getPairingTopicsForWallet(key); bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic); @@ -704,13 +684,10 @@ abstract class WalletKitServiceBase with Store { 'Is Pairing Topic Saved: $isPairingTopicAlreadySaved, Key: $key, Topic: $pairingTopic'); if (!isPairingTopicAlreadySaved) { - // Update the list with the most recent pairing topic pairingTopicsForWallet.add(pairingTopic); - // Convert the list of updated pairing topics to a JSON-encoded string final jsonString = jsonEncode(pairingTopicsForWallet); - // Save the encoded string to shared preferences await sharedPreferences.setString(key, jsonString); } } diff --git a/lib/src/screens/wallet_connect/utils/method_utils.dart b/lib/src/screens/wallet_connect/utils/method_utils.dart index d79adfede..b22c57259 100644 --- a/lib/src/screens/wallet_connect/utils/method_utils.dart +++ b/lib/src/screens/wallet_connect/utils/method_utils.dart @@ -3,8 +3,9 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_message_card.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart'; +import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/themes/core/custom_theme_colors.dart'; import 'package:flutter/material.dart'; import 'package:reown_walletkit/reown_walletkit.dart'; @@ -12,6 +13,14 @@ import 'package:reown_walletkit/reown_walletkit.dart'; class MethodsUtils { static final walletKit = getIt.get().walletKit; static final bottomSheetService = getIt.get(); + + static const _transactionMethods = { + 'eth_sendTransaction', + 'eth_signTransaction', + 'solana_signTransaction', + 'solana_signAllTransactions', + 'solana_signAndSendTransaction', + }; static Future requestApproval( String text, { @@ -23,23 +32,53 @@ class MethodsUtils { List extraModels = const [], VerifyContext? verifyContext, }) async { + final appStore = getIt.get(); + final pending = walletKit.pendingRequests.getAll(); + final session = pending.isNotEmpty + ? walletKit.sessions.get(pending.last.topic) + : null; + final dAppMetadata = session?.peer.metadata; + + final isTransaction = method != null && _transactionMethods.contains(method); + final resolvedTitle = title ?? + (isTransaction + ? S.current.wc_approve_request_title + : S.current.wc_signing_request_title); + final swipeLabel = + isTransaction ? S.current.wc_swipe_to_approve : S.current.wc_swipe_to_sign; + + final extraRows = []; + if (method != null && method.isNotEmpty) { + extraRows.add(WCMessageRow(label: S.current.method, value: method)); + } + if (chainId != null && chainId.isNotEmpty) { + extraRows.add(WCMessageRow(label: S.current.chain_id, value: chainId)); + } + if (transportType.isNotEmpty) { + extraRows.add(WCMessageRow( + label: S.current.transport_type, + value: transportType.toUpperCase(), + )); + } + for (final model in extraModels) { + if (model.title == null) continue; + final value = model.elements?.join(', ') ?? model.text ?? ''; + extraRows.add(WCMessageRow(label: model.title!, value: value)); + } + final WCBottomSheetResult result = (await bottomSheetService.queueBottomSheet( - widget: WCRequestWidget( + widget: WCSigningRequestSheet( + title: resolvedTitle, + swipeLabel: swipeLabel, + dappName: dAppMetadata?.name ?? '', + dappIconUrl: + (dAppMetadata?.icons.isNotEmpty ?? false) ? dAppMetadata!.icons.first : null, + dappSubtitle: method ?? dAppMetadata?.url ?? '', + message: text, + walletName: appStore.wallet?.name ?? '', + address: address ?? '', verifyContext: verifyContext, - child: WCConnectionWidget( - title: title ?? S.current.approve_request, - info: [ - WCConnectionModel( - title: '${S.current.method}: $method\n' - '${S.current.transport_type}: ${transportType.toUpperCase()}\n' - '${S.current.chain_id}: $chainId\n' - '${S.current.address}: $address\n\n' - '${S.current.message}:', - elements: [text], - ), - ...extraModels, - ], - ), + extraRows: extraRows, ), ) as WCBottomSheetResult?) ?? WCBottomSheetResult.reject; diff --git a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart deleted file mode 100644 index 26cb3bbcf..000000000 --- a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:reown_walletkit/reown_walletkit.dart'; - -class ConnectionWidgetBuilder { - static List buildFromRequiredNamespaces( - Map generatedNamespaces, - ) { - final List views = []; - for (final key in generatedNamespaces.keys) { - final namespaces = generatedNamespaces[key]!; - final chains = NamespaceUtils.getChainsFromAccounts(namespaces.accounts); - - final List models = []; - - // If the chains property is present, add the chain data to the models - models.add(WCConnectionModel(title: S.current.chains, elements: chains)); - models.add(WCConnectionModel(title: S.current.methods, elements: namespaces.methods)); - - if (namespaces.events.isNotEmpty) { - models.add(WCConnectionModel(title: S.current.events, elements: namespaces.events)); - } - - views.add(WCConnectionWidget(title: key, info: models)); - } - - return views; - } - - static List buildFromNamespaces( - String topic, - Map namespaces, - BuildContext context, - ) { - final List views = []; - for (final key in namespaces.keys) { - final ns = namespaces[key]!; - final List models = []; - - // If the chains property is present, add the chain data to the models - models.add(WCConnectionModel(title: S.current.accounts, elements: ns.accounts)); - models.add(WCConnectionModel(title: S.current.methods, elements: ns.methods)); - - if (ns.events.isNotEmpty) { - models.add(WCConnectionModel(title: S.current.events, elements: ns.events)); - } - - views.add(WCConnectionWidget(title: key, info: models)); - } - - return views; - } - - static Map updateNamespaces( - Map currentNamespaces, - String namespace, - List newChains, - ) { - final updatedNamespaces = Map.from(currentNamespaces); - - final accounts = currentNamespaces[namespace]!.accounts; - final address = NamespaceUtils.getAccount(accounts.first); - final newAccounts = newChains.map((c) => '$c:$address').toList(); - - final newNamespaces = currentNamespaces[namespace]!.copyWith( - chains: NamespaceUtils.getChainsFromAccounts(accounts)..addAll(newChains), - accounts: List.from(accounts)..addAll(newAccounts), - ); - - updatedNamespaces[namespace] = newNamespaces; - - return updatedNamespaces; - } -} diff --git a/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart new file mode 100644 index 000000000..6a84f826e --- /dev/null +++ b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart @@ -0,0 +1,80 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class WCPermission { + const WCPermission({required this.iconUrl, required this.label}); + + final String iconUrl; + final String label; +} + +class WCPermissionsMapper { + static const _transactionMethods = { + 'eth_sendTransaction', + 'eth_signTransaction', + 'solana_signTransaction', + 'solana_signAllTransactions', + 'solana_signAndSendTransaction', + }; + + static const _messageSigningMethods = { + 'personal_sign', + 'eth_sign', + 'eth_signTypedData', + 'eth_signTypedData_v1', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'solana_signMessage', + }; + + static const _chainAdminMethods = { + 'wallet_switchEthereumChain', + 'wallet_addEthereumChain', + }; + + static List fromGeneratedNamespaces(Map generatedNamespaces) { + final methods = {}; + for (final ns in generatedNamespaces.values) { + methods.addAll(ns.methods); + } + + final permissions = [ + WCPermission(iconUrl: "assets/new-ui/global_view.svg", label: S.current.wc_permission_view_balance), + ]; + + final wantsTransactionApproval = methods.any(_transactionMethods.contains); + if (wantsTransactionApproval) { + permissions.add(WCPermission( + iconUrl: "assets/new-ui/green_check.svg", + label: S.current.wc_permission_request_approval, + )); + } + + final wantsMessageSigning = methods.any(_messageSigningMethods.contains); + if (wantsMessageSigning) { + permissions.add(WCPermission( + iconUrl: "assets/new-ui/pencil.svg", + label: S.current.wc_permission_sign_messages, + )); + } + + final wantsChainAdmin = methods.any(_chainAdminMethods.contains); + if (wantsChainAdmin) { + permissions.add(WCPermission( + iconUrl: "assets/new-ui/exchange_providers.svg", + label: S.current.wc_permission_switch_chains, + )); + } + + final known = {..._transactionMethods, ..._messageSigningMethods, ..._chainAdminMethods}; + final unknown = methods.where((m) => !known.contains(m)).toList()..sort(); + for (final method in unknown) { + permissions.add(WCPermission( + iconUrl: "assets/new-ui/help.svg", + label: S.current.wc_permission_other(method), + )); + } + + return permissions; + } +} diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart index 5bc60cfd6..a6caff932 100644 --- a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart +++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart @@ -1,122 +1,86 @@ +import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart'; +import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_hero_card.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart'; import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cake_wallet/utils/permission_handler.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:reown_walletkit/reown_walletkit.dart'; -import 'package:cake_wallet/entities/qr_scanner.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/utils/permission_handler.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'widgets/wc_pairing_item_widget.dart'; import 'wc_pairing_detail_page.dart'; class WalletConnectConnectionsView extends StatelessWidget { - final WalletKitService walletKitService; - - WalletConnectConnectionsView({required this.walletKitService, Uri? launchUri, Key? key}) - : super(key: key) { + WalletConnectConnectionsView({ + required this.walletKitService, + Uri? launchUri, + Key? key, + }) : super(key: key) { _triggerPairingFromDeeplink(launchUri); } + final WalletKitService walletKitService; + void _triggerPairingFromDeeplink(Uri? launchUri) async { if (launchUri == null) return; - if(launchUri.scheme == "wc") { + if (launchUri.scheme == 'wc') { await walletKitService.pairWithUri(launchUri); return; } - final actualLinkList = launchUri.query.split("uri="); - + final actualLinkList = launchUri.query.split('uri='); if (actualLinkList.length <= 1) return; - final query = actualLinkList[1]; - - final decoded = Uri.decodeComponent(query).trim(); - + final decoded = Uri.decodeComponent(actualLinkList[1]).trim(); final sanitized = decoded.startsWith('@') ? decoded.substring(1) : decoded; - final uriData = Uri.tryParse(sanitized); - - if (uriData == null || (uriData.scheme.isEmpty)) return; + if (uriData == null || uriData.scheme.isEmpty) return; await walletKitService.pairWithUri(uriData); } - @override - Widget build(BuildContext context) { - return WCPairingsWidget(walletKitService: walletKitService); - } -} - -class WCPairingsWidget extends BasePage { - WCPairingsWidget({required this.walletKitService, Key? key}) - : walletKit = walletKitService.walletKit; - - final ReownWalletKit walletKit; - final WalletKitService walletKitService; - - @override - String get title => S.current.walletConnect; - - Future _onScanQrCode(BuildContext context, ReownWalletKit web3Wallet) async { - final String? uri; - - if (DeviceInfo.instance.isMobile) { - bool isCameraPermissionGranted = - await PermissionHandler.checkPermission(Permission.camera, context); - if (!isCameraPermissionGranted) return; - uri = await presentQRScanner(context); - } else { - uri = await _showEnterWalletConnectURIPopUp(context); - } + Future _onScanQrCode(BuildContext context) async { + final isCameraPermissionGranted = + await PermissionHandler.checkPermission(Permission.camera, context); + if (!isCameraPermissionGranted) return; + final uri = await presentQRScanner(context); await _handleWalletConnectURI(uri, context); } - Future _showEnterWalletConnectURIPopUp(BuildContext context) async { - final walletConnectURI = await showPopUp( + Future _onPasteLink(BuildContext context) async { + final uri = await showPopUp( context: context, - builder: (BuildContext context) { - return EnterWalletConnectURIWrapperWidget(); - }, + builder: (BuildContext context) => EnterWalletConnectURIWrapperWidget(), ); - return walletConnectURI; + await _handleWalletConnectURI(uri, context); } - Future _handleWalletConnectURI( - String? walletConnectURI, - BuildContext context, - ) async { + Future _handleWalletConnectURI(String? walletConnectURI, BuildContext context) async { if (walletConnectURI == null) return _invalidUriToast(context, S.current.nullURIError); - log('_onFoundUri: $walletConnectURI'); - // Accept either a raw WC URI or a full URL containing `uri=` parameter String input = walletConnectURI.trim(); - if (input.contains('uri=')) { final parts = input.split('uri='); - if (parts.length > 1) { - input = Uri.decodeComponent(parts.last); - } + if (parts.length > 1) input = Uri.decodeComponent(parts.last); } + if (input.startsWith('@')) input = input.substring(1); - // Some scanners may prefix with '@', strip it - if (input.startsWith('@')) { - input = input.substring(1); - } - final Uri? uriData = Uri.tryParse(input); - final bool hasValidScheme = uriData != null && uriData.scheme.isNotEmpty; - if (!hasValidScheme) { + final uriData = Uri.tryParse(input); + if (uriData == null || uriData.scheme.isEmpty) { return _invalidUriToast(context, S.current.invalid_input); } + await walletKitService.pairWithUri(uriData); } @@ -135,88 +99,132 @@ class WCPairingsWidget extends BasePage { ); } - @override - Widget body(BuildContext context) { - return Observer( - builder: (context) { - return Column( - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Column( - children: [ - SizedBox(height: 24), - Text( - S.current.connectWalletPrompt, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16.0, - fontWeight: FontWeight.normal, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - SizedBox(height: 16), - PrimaryButton( - text: S.current.newConnection, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - onPressed: () => _onScanQrCode(context, walletKit), - ), - SizedBox(height: 4), - TextButton( - onPressed: () async { - final uri = await _showEnterWalletConnectURIPopUp(context); - await _handleWalletConnectURI(uri, context); - }, - child: Text( - 'Click to paste WalletConnect Link', - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], - ), - ), - SizedBox(height: 16), - Expanded( - child: Visibility( - visible: walletKitService.pairings.isEmpty, - child: Center( - child: Text( - S.current.activeConnectionsPrompt, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16.0, - fontWeight: FontWeight.normal, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ), - replacement: ListView.builder( - itemCount: walletKitService.pairings.length, - itemBuilder: (BuildContext context, int index) { - final pairing = walletKitService.pairings[index]; - return WCPairingItemWidget( - key: ValueKey(pairing.topic), - pairing: pairing, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => WalletConnectPairingDetailsPage( - pairing: pairing, - walletKitService: walletKitService, - ), - ), - ); - }, - ); - }, - ), - ), - ), - SizedBox(height: 48), - ], - ); - }, + void _openPairingDetails(BuildContext context, PairingInfo pairing) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => WalletConnectPairingDetailsPage( + pairing: pairing, + walletKitService: walletKitService, + ), + ), ); } -} + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colors = theme.colorScheme; + final isMobile = DeviceInfo.instance.isMobile; + + return Scaffold( + backgroundColor: colors.surface, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 8), + Align( + alignment: Alignment.centerLeft, + child: ModernButton( + size: 40, + onPressed: () => Navigator.of(context).maybePop(), + icon: Icon(Icons.arrow_back_ios_new, size: 16), + iconColor: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 16), + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const WCHeroCard(), + const SizedBox(height: 24), + Observer( + builder: (_) { + if (walletKitService.pairings.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 32), + child: Center( + child: Text( + S.of(context).activeConnectionsPrompt, + textAlign: TextAlign.center, + style: theme.textTheme.bodyMedium?.copyWith( + color: colors.onSurfaceVariant, + ), + ), + ), + ); + } + + final items = []; + for (final pairing in walletKitService.pairings) { + final metadata = pairing.peerMetadata; + if (metadata == null) continue; + items.add( + ListItemRegularRow( + keyValue: pairing.topic, + label: metadata.name, + subtitle: metadata.url, + iconPath: metadata.icons.isNotEmpty + ? metadata.icons.first + : 'assets/new-ui/walletconnect_icon.svg', + onTap: () => _openPairingDetails(context, pairing), + leadingIconSize: 36, + leadingIconErrorWidget: CakeImageWidget( + imageUrl: 'assets/new-ui/walletconnect_icon.svg', + width: 36, + height: 36, + ), + ), + ); + } + + if (items.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 32), + child: Center( + child: Text( + S.of(context).activeConnectionsPrompt, + textAlign: TextAlign.center, + style: theme.textTheme.bodyMedium?.copyWith( + color: colors.onSurfaceVariant, + ), + ), + ), + ); + } + + return NewListSections(sections: {'': items}); + }, + ), + ], + ), + ), + ), + const SizedBox(height: 16), + NewPrimaryButton( + onPressed: () => _onPasteLink(context), + text: S.of(context).wc_paste_link, + color: colors.surfaceContainerHigh, + textColor: colors.primary, + ), + if (isMobile) ...[ + const SizedBox(height: 12), + NewPrimaryButton( + onPressed: () => _onScanQrCode(context), + text: S.of(context).wc_scan_qr, + color: colors.primary, + textColor: colors.onPrimary, + ), + ], + const SizedBox(height: 16), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart index 44f0681bd..0a01560e2 100644 --- a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart +++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart @@ -1,15 +1,18 @@ +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/wc_permissions_mapper.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_dapp_card.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_permissions_card.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_wallet_card.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:flutter/material.dart'; import 'package:reown_walletkit/reown_walletkit.dart'; -import 'utils/namespace_model_builder.dart'; - class WalletConnectPairingDetailsPage extends StatefulWidget { final PairingInfo pairing; final WalletKitService walletKitService; @@ -25,180 +28,100 @@ class WalletConnectPairingDetailsPage extends StatefulWidget { } class WalletConnectPairingDetailsPageState extends State { - List sessionWidgets = []; late String expiryDate; + List sessions = const []; + @override void initState() { super.initState(); initDateTime(); WidgetsBinding.instance.addPostFrameCallback((_) { - initSessions(); + if (!mounted) return; + setState(() { + sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing); + }); }); } void initDateTime() { - DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000); - int year = dateTime.year; - int month = dateTime.month; - int day = dateTime.day; - - expiryDate = '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; - } - - void initSessions() { - List sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing); - - for (final SessionData session in sessions) { - List namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces( - session.topic, - session.namespaces, - context, - ); - // Loop through and add the namespace widgets, but put 20 pixels between each one - for (int i = 0; i < namespaceWidget.length; i++) { - sessionWidgets.add(namespaceWidget[i]); - if (i != namespaceWidget.length - 1) { - sessionWidgets.add(const SizedBox(height: 20.0)); - } - } - - sessionWidgets.add(const SizedBox.square(dimension: 10.0)); - sessionWidgets.add( - PrimaryButton( - onPressed: () async { - try { - await widget.walletKitService.extendSession( - topic: session.topic, - ); - } catch (e) { - debugPrint('${e.toString()}'); - } - }, - text: S.current.extend_session, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ), - ); - sessionWidgets.add(const SizedBox.square(dimension: 10.0)); - sessionWidgets.add( - PrimaryButton( - onPressed: () async { - try { - await widget.walletKitService.updateSession( - topic: session.topic, - namespaces: session.namespaces, - ); - } catch (e) { - debugPrint('${e.toString()}'); - } - }, - text: S.current.update_session, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ), - ); - sessionWidgets.add(const SizedBox.square(dimension: 10.0)); - sessionWidgets.add( - PrimaryButton( - onPressed: () async { - try { - await widget.walletKitService.disconnectSession( - topic: session.topic, - ); - } catch (e) { - debugPrint('${e.toString()}'); - } - }, - text: S.current.disconnect_session, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ), - ); - } + final dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000); + expiryDate = '${dateTime.year}-' + '${dateTime.month.toString().padLeft(2, '0')}-' + '${dateTime.day.toString().padLeft(2, '0')}'; } @override Widget build(BuildContext context) { return WCCDetailsWidget( - widget.pairing, - expiryDate, - sessionWidgets, - widget.walletKitService, + pairing: widget.pairing, + expiryDate: expiryDate, + sessions: sessions, + walletKitService: widget.walletKitService, ); } } class WCCDetailsWidget extends BasePage { - WCCDetailsWidget( - this.pairing, - this.expiryDate, - this.sessionWidgets, - this.walletKitService, - ); + WCCDetailsWidget({ + required this.pairing, + required this.expiryDate, + required this.sessions, + required this.walletKitService, + }); final PairingInfo pairing; final String expiryDate; - final List sessionWidgets; + final List sessions; final WalletKitService walletKitService; @override Widget body(BuildContext context) { - return Scaffold( - body: SingleChildScrollView( - child: Container( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: CircleAvatar( - backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty - ? NetworkImage(pairing.peerMetadata!.icons[0]) - : AssetImage( - CakeTor.instance!.enabled - ? 'assets/images/tor_logo.svg' - : 'assets/images/app_logo.png')) as ImageProvider, - ), - ), - const SizedBox(height: 20.0), - Text( - pairing.peerMetadata!.name, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16.0, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 16.0), - Text( - pairing.peerMetadata!.url, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 8.0), - Text( - '${S.current.expiresOn}: $expiryDate', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - const SizedBox(height: 20.0), - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: sessionWidgets, - ), - const SizedBox(height: 20.0), - PrimaryButton( - onPressed: () => - _onDeleteButtonPressed(context, pairing.peerMetadata!.name, walletKitService), - text: S.current.delete, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, + final colors = Theme.of(context).colorScheme; + final metadata = pairing.peerMetadata; + if (metadata == null) { + return const SizedBox.shrink(); + } + + final iconUrl = metadata.icons.isNotEmpty ? metadata.icons.first : null; + final walletName = getIt.get().wallet?.name ?? ''; + + return SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + WCDappCard( + name: metadata.name, + iconUrl: iconUrl, + subtitle: metadata.url, + action: WCDappCardAction.connected, + ), + const SizedBox(height: 12), + Text( + '${S.of(context).expiresOn}: $expiryDate', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: colors.onSurfaceVariant, + ), + ), + const SizedBox(height: 24), + for (final session in sessions) ...[ + _SessionSection( + session: session, + walletName: walletName, ), + const SizedBox(height: 24), ], - ), + const SizedBox(height: 24), + NewPrimaryButton( + onPressed: () => _onDeleteButtonPressed(context, metadata.name, walletKitService), + text: S.current.delete, + color: colors.error, + textColor: colors.onError, + ), + const SizedBox(height: 12), + ], ), ), ); @@ -240,3 +163,37 @@ class WCCDetailsWidget extends BasePage { } } } + +class _SessionSection extends StatelessWidget { + const _SessionSection({ + required this.session, + required this.walletName, + }); + + final SessionData session; + final String walletName; + + String _firstAddress() { + for (final namespace in session.namespaces.values) { + if (namespace.accounts.isNotEmpty) { + return NamespaceUtils.getAccount(namespace.accounts.first); + } + } + return ''; + } + + @override + Widget build(BuildContext context) { + final permissions = WCPermissionsMapper.fromGeneratedNamespaces(session.namespaces); + final address = _firstAddress(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + WCWalletCard(walletName: walletName, address: address), + const SizedBox(height: 24), + WCPermissionsCard(permissions: permissions), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart b/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart index 7a06cc4f4..036fdd6dc 100644 --- a/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart @@ -75,15 +75,15 @@ class EnterWalletConnectURIWidget extends BaseAlertDialog { fontWeight: FontWeight.w500, ), suffixIcon: Container( - width: 24, - height: 24, + width: 36, + height: 36, padding: EdgeInsets.only(top: 0), - child: Semantics( + child: Semantics( label: S.of(context).paste, child: InkWell( onTap: () => _pasteWalletConnectURI(), child: Container( - padding: EdgeInsets.all(10), + padding: EdgeInsets.all(8), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(6)), ), diff --git a/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart deleted file mode 100644 index b701bdc62..000000000 --- a/lib/src/screens/wallet_connect/widgets/wc_connection_item_widget.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; -import 'package:flutter/material.dart'; - -class WCConnectionItemWidget extends StatelessWidget { - const WCConnectionItemWidget({required this.model, Key? key}) : super(key: key); - - final WCConnectionModel model; - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.all(8), - margin: const EdgeInsetsDirectional.only(top: 8), - child: Visibility( - visible: model.elements != null, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - model.title ?? '', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - if (model.elements != null) - Wrap( - spacing: 4, - runSpacing: 4, - direction: Axis.horizontal, - children: model.elements! - .map((e) => _ModelElementWidget(model: model, modelElement: e)) - .toList(), - ), - ], - ), - replacement: _NoModelElementWidget(model: model), - ), - ); - } -} - -class _NoModelElementWidget extends StatelessWidget { - const _NoModelElementWidget({required this.model}); - - final WCConnectionModel model; - - @override - Widget build(BuildContext context) { - return Text( - model.text!, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w600, - ), - ); - } -} - -class _ModelElementWidget extends StatelessWidget { - const _ModelElementWidget({ - required this.model, - required this.modelElement, - }); - - final WCConnectionModel model; - final String modelElement; - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: model.elementActions != null ? model.elementActions![modelElement] : null, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(6), - ), - padding: const EdgeInsets.all(8), - child: Text( - modelElement, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w600, - ), - maxLines: 50, - overflow: TextOverflow.ellipsis, - ), - ), - ); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart new file mode 100644 index 000000000..fb22e7e78 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart @@ -0,0 +1,94 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/confirm_swiper.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/utils/wc_permissions_mapper.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_dapp_card.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_permissions_card.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_scam_banner.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_sheet_header.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_wallet_card.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class WCConnectionRequestSheet extends StatelessWidget { + const WCConnectionRequestSheet({ + super.key, + required this.proposalData, + required this.requester, + required this.walletKeyService, + required this.appStore, + this.verifyContext, + }); + + final ProposalData proposalData; + final ConnectionMetadata requester; + final WalletConnectKeyService walletKeyService; + final AppStore appStore; + final VerifyContext? verifyContext; + + String _resolveAddress() { + final wallet = appStore.wallet; + if (wallet == null) return ''; + final keys = walletKeyService.getKeysForChain(wallet); + if (keys.isEmpty) return ''; + return keys.first.publicKey; + } + + @override + Widget build(BuildContext context) { + final permissions = + WCPermissionsMapper.fromGeneratedNamespaces(proposalData.generatedNamespaces ?? const {}); + + final metadata = requester.metadata; + final iconUrl = metadata.icons.isNotEmpty ? metadata.icons.first : null; + final walletName = appStore.wallet?.name ?? ''; + final address = _resolveAddress(); + final isScam = verifyContext?.validation.scam ?? false; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + WCSheetHeader(title: S.of(context).wc_connect_request_title), + const SizedBox(height: 24), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + WCDappCard( + name: metadata.name, + iconUrl: iconUrl, + subtitle: metadata.url, + action: WCDappCardAction.connect, + verifyContext: verifyContext, + ), + if (isScam) ...[ + const SizedBox(height: 16), + const WCScamBanner(), + ], + const SizedBox(height: 24), + WCWalletCard(walletName: walletName, address: address), + const SizedBox(height: 24), + WCPermissionsCard(permissions: permissions), + ], + ), + ), + ), + const SizedBox(height: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: ConfirmSwiper( + swiperText: S.of(context).wc_swipe_to_approve, + onConfirmed: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.one); + } + }, + ), + ), + const SizedBox(height: 16), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart deleted file mode 100644 index 1db99ddc0..000000000 --- a/lib/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/utils/namespace_model_builder.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:flutter/material.dart'; -import 'package:reown_walletkit/reown_walletkit.dart'; - -class WCConnectionRequestWidget extends StatelessWidget { - WCConnectionRequestWidget({ - this.sessionAuthPayload, - this.proposalData, - this.requester, - this.verifyContext, - required this.walletKeyService, - required this.walletKit, - required this.appStore, - }); - - final SessionAuthPayload? sessionAuthPayload; - final ProposalData? proposalData; - final ConnectionMetadata? requester; - final VerifyContext? verifyContext; - final WalletConnectKeyService walletKeyService; - final AppStore appStore; - final ReownWalletKit walletKit; - - @override - Widget build(BuildContext context) { - if (requester == null) { - return Text(S.current.error.toUpperCase()); - } - - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 8), - Text( - '${requester!.metadata.name} ${S.current.wouoldLikeToConnect}', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18.0, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - (sessionAuthPayload != null) - ? _buildSessionAuthRequestView() - : _buildSessionProposalView(context), - ], - ), - ); - } - - Widget _buildSessionAuthRequestView() { - final cacaoPayload = CacaoRequestPayload.fromSessionAuthPayload( - sessionAuthPayload!, - ); - - final List messagesModels = []; - for (var chain in sessionAuthPayload!.chains) { - final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); - final iss = 'did:pkh:$chain:${chainKeys.first.publicKey}'; - - final message = walletKit.formatAuthMessage( - iss: iss, - cacaoPayload: cacaoPayload, - ); - - messagesModels.add( - WCConnectionModel( - title: '${S.current.message} ${messagesModels.length + 1}', - elements: [message], - ), - ); - } - - return WCConnectionWidget( - title: '${messagesModels.length} ${S.current.messages}', - info: messagesModels, - ); - } - - Widget _buildSessionProposalView(BuildContext context) { - // Create the connection models using the required and optional namespaces provided by the proposal data - - final views = ConnectionWidgetBuilder.buildFromRequiredNamespaces( - proposalData!.generatedNamespaces ?? {}, - ); - - return Column(children: views); - } -} - diff --git a/lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart deleted file mode 100644 index 150165a95..000000000 --- a/lib/src/screens/wallet_connect/widgets/wc_connection_widget.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart'; -import 'package:flutter/material.dart'; - -import 'wc_connection_item_widget.dart'; - -class WCConnectionWidget extends StatelessWidget { - const WCConnectionWidget({required this.title, required this.info, super.key}); - - final String title; - final List info; - - @override - Widget build(BuildContext context) { - - return Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(8), - ), - padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), - child: Text( - title, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - ), - const SizedBox(height: 8), - ...info.map((e) => WCConnectionItemWidget(model: e)), - ], - ), - ); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/wc_dapp_card.dart b/lib/src/screens/wallet_connect/widgets/wc_dapp_card.dart new file mode 100644 index 000000000..255a76e82 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_dapp_card.dart @@ -0,0 +1,132 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cake_wallet/themes/core/custom_theme_colors.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +enum WCDappCardAction { connect, sign, connected } + +class WCDappCard extends StatelessWidget { + const WCDappCard({ + super.key, + required this.name, + required this.iconUrl, + required this.subtitle, + required this.action, + this.verifyContext, + }); + + final String name; + final String? iconUrl; + final String subtitle; + final WCDappCardAction action; + final VerifyContext? verifyContext; + + String _actionLine(BuildContext context) { + switch (action) { + case WCDappCardAction.connect: + return S.of(context).wc_would_like_to_connect_to(name); + case WCDappCardAction.sign: + return S.of(context).wc_would_like_to_sign(name); + case WCDappCardAction.connected: + return S.of(context).wc_connected_to(name); + } + } + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.surfaceContainer, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 56, + height: 56, + child: CakeImageWidget( + borderRadius: 16, + imageUrl: iconUrl, + fit: BoxFit.cover, + errorWidget: CakeImageWidget( + imageUrl: 'assets/new-ui/walletconnect_icon.svg', + width: 40, + height: 40, + ), + ), + ), + const SizedBox(height: 10), + Text( + _actionLine(context), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w600, + color: colors.onSurface, + ), + ), + if (subtitle.trim().isNotEmpty) ...[ + const SizedBox(height: 10), + Text( + subtitle, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: colors.onSurfaceVariant, + ), + ), + ], + if (_shouldShowBadge()) ...[ + const SizedBox(height: 10), + _VerifyBadge(verifyContext: verifyContext!), + ], + ], + ), + ); + } + + bool _shouldShowBadge() { + if (verifyContext == null) return false; + if (verifyContext!.validation.scam) return false; + return true; + } +} + +class _VerifyBadge extends StatelessWidget { + const _VerifyBadge({required this.verifyContext}); + + final VerifyContext verifyContext; + + @override + Widget build(BuildContext context) { + final IconData icon; + final Color color; + final String label; + + if (verifyContext.validation.valid) { + icon = Icons.check_circle; + color = CustomThemeColors.syncGreen; + label = S.of(context).wc_verified; + } else { + icon = Icons.warning_amber_rounded; + color = CustomThemeColors.syncYellow; + label = S.of(context).wc_not_verified; + } + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 16, color: color), + const SizedBox(width: 4), + Text( + label, + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: color), + ), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_hero_card.dart b/lib/src/screens/wallet_connect/widgets/wc_hero_card.dart new file mode 100644 index 000000000..ebb69a347 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_hero_card.dart @@ -0,0 +1,51 @@ + +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:flutter/material.dart'; + +class WCHeroCard extends StatelessWidget { + const WCHeroCard(); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colors = theme.colorScheme; + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.surfaceContainerHigh, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CakeImageWidget( + imageUrl: 'assets/new-ui/walletconnect_icon.svg', + width: 36, + height: 36, + fit: BoxFit.cover, + ), + ), + const SizedBox(height: 10), + Text( + S.of(context).walletConnect, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w500, + letterSpacing: -0.08, + ), + ), + const SizedBox(height: 10), + Text( + S.of(context).wc_pairing_list_header_subtitle, + textAlign: TextAlign.center, + style: theme.textTheme.bodySmall?.copyWith( + letterSpacing: -0.06, + color: colors.onSurfaceVariant, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_message_card.dart b/lib/src/screens/wallet_connect/widgets/wc_message_card.dart new file mode 100644 index 000000000..6dc95dda0 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_message_card.dart @@ -0,0 +1,86 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; + +class WCMessageRow { + const WCMessageRow({required this.label, required this.value}); + + final String label; + final String value; +} + +class WCMessageCard extends StatelessWidget { + const WCMessageCard({ + super.key, + required this.message, + this.title, + this.extraRows = const [], + }); + + final String? title; + final String message; + final List extraRows; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + final resolvedTitle = title ?? S.of(context).wc_message_to_sign; + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.surfaceContainer, + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + resolvedTitle, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w600, + color: colors.onSurface, + ), + ), + if (message.isNotEmpty) ...[ + const SizedBox(height: 8), + SelectableText( + message, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: colors.onSurfaceVariant, + height: 1.4, + ), + ), + ], + for (final row in extraRows) ...[ + const SizedBox(height: 12), + Divider(height: 1, color: colors.outlineVariant), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + row.label, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: colors.onSurface, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + row.value, + textAlign: TextAlign.end, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: colors.onSurfaceVariant, + ), + ), + ), + ], + ), + ], + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart deleted file mode 100644 index 3d0ce8dc0..000000000 --- a/lib/src/screens/wallet_connect/widgets/wc_pairing_item_widget.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:reown_walletkit/reown_walletkit.dart'; - -class WCPairingItemWidget extends StatelessWidget { - const WCPairingItemWidget({required this.pairing, required this.onTap, super.key}); - - final PairingInfo pairing; - final void Function() onTap; - - @override - Widget build(BuildContext context) { - PairingMetadata? metadata = pairing.peerMetadata; - - if (metadata == null) { - return SizedBox.shrink(); - } - - DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(pairing.expiry * 1000); - int year = dateTime.year; - int month = dateTime.month; - int day = dateTime.day; - - String expiryDate = - '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; - - return ListTile( - contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - leading: SizedBox( - width: 60, - height: 60, - child: CakeImageWidget( - borderRadius: 8, - width: 60, - height: 60, - imageUrl: metadata.icons.isNotEmpty ? metadata.icons[0] : null, - errorWidget: CircleAvatar( - backgroundImage: AssetImage('assets/images/walletconnect_logo.png'), - ), - ), - ), - title: Text( - metadata.name, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - metadata.url, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - Text( - '${S.current.expiresOn}: $expiryDate', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w700, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ], - ), - trailing: SizedBox( - width: 44, - height: 40, - child: Container( - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.primary, - ), - child: Icon( - Icons.edit, - size: 14, - color: Theme.of(context).colorScheme.onPrimary, - ), - ), - ), - onTap: onTap, - ); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/wc_permissions_card.dart b/lib/src/screens/wallet_connect/widgets/wc_permissions_card.dart new file mode 100644 index 000000000..ea3bd440b --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_permissions_card.dart @@ -0,0 +1,64 @@ +import 'package:cake_wallet/src/screens/wallet_connect/utils/wc_permissions_mapper.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:flutter/material.dart'; + +class WCPermissionsCard extends StatelessWidget { + const WCPermissionsCard({super.key, required this.permissions}); + + final List permissions; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + if (permissions.isEmpty) return const SizedBox.shrink(); + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.surfaceContainer, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + for (int i = 0; i < permissions.length; i++) ...[ + if (i > 0) Divider(height: 1, color: colors.outlineVariant, indent: 44), + _PermissionRow(permission: permissions[i]), + ], + ], + ), + ); + } +} + +class _PermissionRow extends StatelessWidget { + const _PermissionRow({required this.permission}); + + final WCPermission permission; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 14), + child: Row( + children: [ + Container( + child: CakeImageWidget( + imageUrl: permission.iconUrl, + width: 28, + height: 28, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + permission.label, + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_request_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_request_widget.dart deleted file mode 100644 index d06b20019..000000000 --- a/lib/src/screens/wallet_connect/widgets/wc_request_widget.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:flutter/material.dart'; -import 'package:reown_walletkit/reown_walletkit.dart'; - -class WCRequestWidget extends StatelessWidget { - WCRequestWidget({ - required this.child, - this.verifyContext, - this.onAccept, - this.onReject, - }); - - final Widget child; - final VerifyContext? verifyContext; - final VoidCallback? onAccept; - final VoidCallback? onReject; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - WCVerifyContextWidget( - verifyContext: verifyContext, - ), - const SizedBox(height: 8), - Flexible( - child: SingleChildScrollView(child: child), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: PrimaryButton( - onPressed: onReject ?? - () { - if (Navigator.canPop(context)) { - Navigator.of(context).pop(WCBottomSheetResult.reject); - } - }, - text: S.current.reject, - color: Theme.of(context).colorScheme.error, - textColor: Theme.of(context).colorScheme.onError, - ), - ), - const SizedBox(width: 16), - Expanded( - child: PrimaryButton( - onPressed: onAccept ?? - () { - if (Navigator.canPop(context)) { - Navigator.of(context).pop(WCBottomSheetResult.one); - } - }, - text: S.current.approve, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ), - ), - ], - ), - ], - ); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/wc_scam_banner.dart b/lib/src/screens/wallet_connect/widgets/wc_scam_banner.dart new file mode 100644 index 000000000..71af47397 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_scam_banner.dart @@ -0,0 +1,56 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; + +class WCScamBanner extends StatelessWidget { + const WCScamBanner({super.key}); + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.errorContainer, + border: Border.all(color: colors.onError, width: 2), + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.warning_amber_rounded, + size: 36, + color: colors.error, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + S.of(context).wc_scam_warning_title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: colors.error, + letterSpacing: -0.06, + ), + ), + const SizedBox(height: 4), + Text( + S.of(context).wc_scam_warning_message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: colors.error, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart deleted file mode 100644 index 7e10c0469..000000000 --- a/lib/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:flutter/material.dart'; - -class WCSessionAuthRequestWidget extends StatelessWidget { - const WCSessionAuthRequestWidget({super.key, required this.child}); - - final Widget child; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: SingleChildScrollView(child: child), - ), - const SizedBox(height: 16), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - PrimaryButton( - onPressed: () { - if (Navigator.canPop(context)) { - Navigator.of(context).pop(WCBottomSheetResult.reject); - } - }, - text: S.current.cancel, - color: Theme.of(context).colorScheme.error, - textColor: Theme.of(context).colorScheme.onError, - ), - const SizedBox(height: 8), - PrimaryButton( - onPressed: () { - if (Navigator.canPop(context)) { - Navigator.of(context).pop(WCBottomSheetResult.one); - } - }, - text: S.current.sign_one, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ), - const SizedBox(height: 8), - PrimaryButton( - onPressed: () { - if (Navigator.canPop(context)) { - Navigator.of(context).pop(WCBottomSheetResult.all); - } - }, - text: S.current.sign_all, - color: Theme.of(context).colorScheme.secondaryContainer, - textColor: Theme.of(context).colorScheme.onSecondaryContainer, - ), - ], - ), - ], - ); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/wc_sheet_header.dart b/lib/src/screens/wallet_connect/widgets/wc_sheet_header.dart new file mode 100644 index 000000000..2a3c3ec31 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_sheet_header.dart @@ -0,0 +1,50 @@ +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:flutter/material.dart'; + +class WCSheetHeader extends StatelessWidget { + const WCSheetHeader({super.key, required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return SizedBox( + height: 40, + child: Stack( + alignment: Alignment.center, + children: [ + Align( + alignment: AlignmentDirectional.centerStart, + child: InkResponse( + radius: 36, + onTap: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.reject); + } + }, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon( + Icons.close, + size: 20, + color: colors.onSurface, + ), + ), + ), + ), + Text( + title, + style: Theme.of(context).textTheme.headlineMedium!.copyWith(letterSpacing: -0.09), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart b/lib/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart new file mode 100644 index 000000000..3b083ba2d --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart @@ -0,0 +1,111 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/confirm_swiper.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_dapp_card.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_message_card.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_scam_banner.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_sheet_header.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_wallet_card.dart'; +import 'package:flutter/material.dart'; +import 'package:reown_walletkit/reown_walletkit.dart'; + +class WCSigningRequestSheet extends StatelessWidget { + const WCSigningRequestSheet({ + super.key, + required this.title, + required this.swipeLabel, + required this.dappName, + required this.message, + required this.walletName, + required this.address, + this.dappIconUrl, + this.dappSubtitle, + this.verifyContext, + this.messageTitle, + this.extraRows = const [], + this.signAllCount, + }); + + final String title; + final String swipeLabel; + final String dappName; + final String? dappIconUrl; + final String? dappSubtitle; + final String message; + final String? messageTitle; + final List extraRows; + final String walletName; + final String address; + final VerifyContext? verifyContext; + final int? signAllCount; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + final showSignAll = (signAllCount ?? 0) > 1; + final isScam = verifyContext?.validation.scam ?? false; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + WCSheetHeader(title: title), + const SizedBox(height: 24), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + WCDappCard( + name: dappName, + iconUrl: dappIconUrl, + subtitle: dappSubtitle ?? '', + action: WCDappCardAction.sign, + verifyContext: verifyContext, + ), + if (isScam) ...[ + const SizedBox(height: 16), + const WCScamBanner(), + ], + const SizedBox(height: 24), + WCWalletCard(walletName: walletName, address: address), + const SizedBox(height: 24), + WCMessageCard( + title: messageTitle, + message: message, + extraRows: extraRows, + ), + ], + ), + ), + ), + const SizedBox(height: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: ConfirmSwiper( + swiperText: swipeLabel, + onConfirmed: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.one); + } + }, + ), + ), + if (showSignAll) ...[ + const SizedBox(height: 24), + TextButton( + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(WCBottomSheetResult.all); + } + }, + style: TextButton.styleFrom( + foregroundColor: colors.primary, + minimumSize: const Size.fromHeight(48), + ), + child: Text(S.of(context).wc_sign_all_count(signAllCount!.toString())), + ), + ], + const SizedBox(height: 16), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart b/lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart deleted file mode 100644 index efc95cc55..000000000 --- a/lib/src/screens/wallet_connect/widgets/wc_verify_context_widget.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/themes/core/custom_theme_colors.dart'; -import 'package:flutter/material.dart'; -import 'package:reown_walletkit/reown_walletkit.dart'; - -class WCVerifyContextWidget extends StatelessWidget { - const WCVerifyContextWidget({ - super.key, - required this.verifyContext, - }); - - final VerifyContext? verifyContext; - - @override - Widget build(BuildContext context) { - if (verifyContext == null) { - return const SizedBox.shrink(); - } - - if (verifyContext!.validation.scam) { - return VerifyBanner( - color: Theme.of(context).colorScheme.errorContainer, - origin: verifyContext!.origin, - title: S.current.security_risk, - text: S.current.security_risk_description, - ); - } - if (verifyContext!.validation.invalid) { - return VerifyBanner( - color: Theme.of(context).colorScheme.errorContainer, - origin: verifyContext!.origin, - title: S.current.domain_mismatch, - text: S.current.domain_mismatch_description, - ); - } - if (verifyContext!.validation.valid) { - return VerifyHeader( - iconColor: Theme.of(context).colorScheme.onPrimary, - title: verifyContext!.origin, - ); - } - return VerifyBanner( - color: CustomThemeColors.syncYellow, - origin: verifyContext!.origin, - title: S.current.cannot_verify, - text: S.current.cannot_verify_description, - ); - } -} - -class VerifyHeader extends StatelessWidget { - const VerifyHeader({ - super.key, - required this.iconColor, - required this.title, - }); - final Color iconColor; - final String title; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.shield_outlined, - color: iconColor, - ), - const SizedBox(width: 8), - Text( - title, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: iconColor, - fontWeight: FontWeight.bold, - ), - ), - ], - ); - } -} - -class VerifyBanner extends StatelessWidget { - const VerifyBanner({ - super.key, - required this.origin, - required this.title, - required this.text, - required this.color, - }); - final String origin, title, text; - final Color color; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Text( - origin, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox.square(dimension: 8.0), - Container( - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: color.withOpacity(0.2), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - ), - child: Column( - children: [ - VerifyHeader( - iconColor: color, - title: title, - ), - const SizedBox(height: 4.0), - Text( - text, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: color, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ); - } -} diff --git a/lib/src/screens/wallet_connect/widgets/wc_wallet_card.dart b/lib/src/screens/wallet_connect/widgets/wc_wallet_card.dart new file mode 100644 index 000000000..9f407871b --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/wc_wallet_card.dart @@ -0,0 +1,95 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/utils/clipboard_util.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class WCWalletCard extends StatelessWidget { + const WCWalletCard({ + super.key, + required this.walletName, + required this.address, + }); + + final String walletName; + final String address; + + static String _truncate(String value) { + if (value.length <= 14) return value; + return '${value.substring(0, 6)}...${value.substring(value.length - 7)}'; + } + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.surfaceContainer, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + WalletDetailEntry(label: S.of(context).wallet, value: walletName), + Divider(height: 1, color: colors.outlineVariant), + WalletDetailEntry( + label: S.of(context).address, + value: _truncate(address), + onTap: address.isEmpty + ? null + : () async { + await ClipboardUtil.setSensitiveDataToClipboard( + ClipboardData(text: address), + ); + if (!context.mounted) return; + showBar(context, S.of(context).copied_to_clipboard); + }, + ), + ], + ), + ); + } +} + +class WalletDetailEntry extends StatelessWidget { + const WalletDetailEntry({required this.label, required this.value, this.onTap}); + + final String label; + final String value; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w500, + ), + ), + Flexible( + child: Text( + value, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: colors.onSurfaceVariant, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/widgets/cake_image_widget.dart b/lib/src/widgets/cake_image_widget.dart index a28072932..fcfbe64ca 100644 --- a/lib/src/widgets/cake_image_widget.dart +++ b/lib/src/widgets/cake_image_widget.dart @@ -1,6 +1,4 @@ -import 'package:cw_core/utils/print_verbose.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:vector_graphics/vector_graphics.dart'; @@ -15,7 +13,9 @@ class CakeImageWidget extends StatelessWidget { this.errorWidget, this.color, this.colorFilter, - this.borderRadius = 24.0, this.alignment, this.allowDrawingOutsideViewBox, + this.borderRadius = 24.0, + this.alignment, + this.allowDrawingOutsideViewBox, }); final String? imageUrl; @@ -38,8 +38,8 @@ class CakeImageWidget extends StatelessWidget { final isSvg = imageUrl!.toLowerCase().endsWith('.svg'); final isAsset = imageUrl!.startsWith('assets/'); - final effectiveColorFilter = colorFilter ?? - (color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null); + final effectiveColorFilter = + colorFilter ?? (color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null); Widget imageWidget; if (isAsset) { @@ -49,8 +49,7 @@ class CakeImageWidget extends StatelessWidget { width: width, alignment: alignment ?? Alignment.center, allowDrawingOutsideViewBox: allowDrawingOutsideViewBox ?? false, - colorFilter: - effectiveColorFilter, + colorFilter: effectiveColorFilter, fit: fit ?? BoxFit.contain, errorBuilder: (context, e, trace) { return SvgPicture.asset( imageUrl!, @@ -59,8 +58,7 @@ class CakeImageWidget extends StatelessWidget { allowDrawingOutsideViewBox: allowDrawingOutsideViewBox ?? false, width: width, errorBuilder: (_, __, ___) => SizedBox(height: height, width: width), - colorFilter: - effectiveColorFilter, + colorFilter: effectiveColorFilter, fit: fit ?? BoxFit.contain, ); }); diff --git a/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart b/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart index 47f5cc822..d947ef03a 100644 --- a/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart +++ b/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart @@ -23,7 +23,9 @@ class ListItemRegularRowWidget extends StatelessWidget { this.trailingIconSize, this.bottomWidget, this.trailingWidget, - this.copyableText + this.copyableText, + this.leadingIconErrorWidget, + this.leadingIconSize, }); final String keyValue; @@ -42,6 +44,8 @@ class ListItemRegularRowWidget extends StatelessWidget { final Color? foregroundColor; final double? trailingIconSize; final String? copyableText; + final Widget? leadingIconErrorWidget; + final double? leadingIconSize; @override Widget build(BuildContext context) { @@ -78,8 +82,9 @@ class ListItemRegularRowWidget extends StatelessWidget { padding: const EdgeInsets.only(right: 12.0), child: CakeImageWidget( imageUrl: iconPath!, - width: 24, - height: 24, + width: leadingIconSize ?? 24, + height: leadingIconSize ?? 24, + errorWidget: leadingIconErrorWidget, )), Flexible( child: Column( diff --git a/lib/src/widgets/new_list_row/new_list_section.dart b/lib/src/widgets/new_list_row/new_list_section.dart index 196436124..d6d46aff2 100644 --- a/lib/src/widgets/new_list_row/new_list_section.dart +++ b/lib/src/widgets/new_list_row/new_list_section.dart @@ -107,6 +107,8 @@ class NewListSections extends StatelessWidget { trailingIconSize: item.trailingIconSize, trailingWidget: item.trailingWidget, bottomWidget: item.bottomWidget, + leadingIconErrorWidget: item.leadingIconErrorWidget, + leadingIconSize: item.leadingIconSize, ); } diff --git a/res/pictures/global_view.svg b/res/pictures/global_view.svg new file mode 100644 index 000000000..c821166e3 --- /dev/null +++ b/res/pictures/global_view.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/pictures/green_check.svg b/res/pictures/green_check.svg new file mode 100644 index 000000000..50d23fe5e --- /dev/null +++ b/res/pictures/green_check.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/res/pictures/pencil.svg b/res/pictures/pencil.svg new file mode 100644 index 000000000..565db07f2 --- /dev/null +++ b/res/pictures/pencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/pictures/walletconnect_icon.svg b/res/pictures/walletconnect_icon.svg new file mode 100644 index 000000000..7945c19e8 --- /dev/null +++ b/res/pictures/walletconnect_icon.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 8a6951b90..e2ed0035b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "يلزم إذن الكاميرا.\nيرجى تفعيله من إعدادات التطبيق.", "cancel": "إلغاء", "cannot_manage_accounts_during_sync": "لا يمكنك إدارة الحسابات بينما لا تزال المحفظة قيد المزامنة. يُرجى المحاولة مرة أخرى لاحقًا.", - "cannot_verify": "تعذّر التحقق", - "cannot_verify_description": "لا يمكن التحقق من هذا النطاق. تحقّق من الطلب بعناية قبل الموافقة.", "card_address": "العنوان:", "card_order_reset_desc": "هل تريد استعادة ترتيب البطاقة إلى الإعدادات الافتراضية؟", "card_style": "نمط البطاقة", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "تعطيل تحسين البطارية", "disableBatteryOptimizationDescription": "هل تريد تعطيل تحسين استهلاك البطارية للسماح بمزامنة الخلفية بالعمل بحرية وسلاسة أكبر؟", "disabled": "معطّل", - "disconnect_session": "قطع الاتصال بالجلسة", "discount": "وفّر ${value}٪", "dismiss": "إغلاق", "display": "العرض", @@ -362,8 +359,6 @@ "do_not_show_anymore": "لا تعرض هذا مرة أخرى", "do_not_show_me": "لا تُظهر هذا مرة أخرى", "domain_looks_up": "عمليات البحث عن النطاقات", - "domain_mismatch": "عدم تطابق النطاق", - "domain_mismatch_description": "يحتوي هذا الموقع على نطاق لا يتطابق مع مُرسِل هذا الطلب. قد تؤدي الموافقة إلى فقدان الأموال.", "donation": "تبرع", "donation_link_details": "تفاصيل رابط التبرع", "done": "تم", @@ -893,8 +888,6 @@ "second_intro_title": "عنوان إيموجي واحد ليحكمها جميعًا", "security": "الأمان", "security_and_backup": "الأمان والنسخ الاحتياطي", - "security_risk": "مخاطر أمنية", - "security_risk_description": "تم وضع علامة على هذا النطاق على أنه غير آمن من قِبل عدة مزوّدي خدمات الأمان. غادر فورًا لحماية أصولك.", "seed_alert_back": "الرجوع", "seed_alert_content": "عبارة الاسترداد هي الطريقة الوحيدة لاستعادة محفظتك. هل كتبتها؟", "seed_alert_title": "انتباه", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "تفاصيل العملات غير المُنفَقة", "unspent_coins_title": "العملات غير المُنفَقة", "unsupported_asset": "لا ندعم هذا الإجراء لهذا الأصل. يُرجى إنشاء محفظة لأصل مدعوم أو التبديل إلى محفظة من نوع أصل مدعوم.", - "update_session": "تحديث الجلسة", "uptime": "مدة التشغيل", "upto": "حتى ${value}", "usb": "USB", @@ -1334,6 +1326,31 @@ "walletConnect": "WalletConnect", "wallets": "المحافظ", "warning": "تحذير", + "wc_action_approve": "يعتمد", + "wc_approve_request_title": "الموافقة على الطلب", + "wc_connect_request_title": "طلب الاتصال", + "wc_connected_to": "متصل بـ ${name}", + "wc_max_network_fee": "الحد الأقصى لرسوم الشبكة", + "wc_message_to_sign": "رسالة للتوقيع", + "wc_network_fee": "رسوم الشبكة", + "wc_not_verified": "لم يتم التحقق منها", + "wc_pairing_list_header_subtitle": "قم بتوصيل محفظتك باستخدام WalletConnect أو إدارة التطبيقات الموجودة.", + "wc_paste_link": "الصق رابط WalletConnect", + "wc_permission_other": "أخرى: ${method}", + "wc_permission_request_approval": "طلب الموافقة على المعاملات", + "wc_permission_sign_messages": "توقيع الرسائل والبيانات المكتوبة", + "wc_permission_switch_chains": "قم بالتبديل وإضافة سلاسل EVM", + "wc_permission_view_balance": "عرض رصيد محفظتك ونشاطك", + "wc_scam_warning_message": "يبدو أن هذا الطلب مرسل من عملية احتيال معروفة. يرجى المضي قدما بحذر.", + "wc_scam_warning_title": "تحذير!", + "wc_scan_qr": "مسح QR", + "wc_sign_all_count": "قم بالتوقيع على جميع رسائل ${count}.", + "wc_signing_request_title": "طلب التوقيع", + "wc_swipe_to_approve": "اسحب للموافقة", + "wc_swipe_to_sign": "اسحب للتوقيع", + "wc_verified": "تم التحقق منه", + "wc_would_like_to_connect_to": "${name} يرغب في الاتصال", + "wc_would_like_to_sign": "${name} يرغب في التوقيع", "website": "الموقع الإلكتروني", "welcome": "مرحبًا", "welcome_subtitle_new_wallet": "إذا كنت ترغب في البدء من جديد، فانقر على \"Create New Wallet\" أدناه وستنطلق.", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index a46bfbfbc..a961754b2 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Необходимо е разрешение за камера.\nМоля, активирайте го от настройките на приложението.", "cancel": "Отказ", "cannot_manage_accounts_during_sync": "Не можете да управлявате акаунтите, докато портфейлът все още се синхронизира. Моля, опитайте отново по-късно.", - "cannot_verify": "Не може да се потвърди", - "cannot_verify_description": "Този домейн не може да бъде потвърден. Проверете внимателно заявката, преди да я одобрите.", "card_address": "Адрес:", "card_order_reset_desc": "Да се възстанови подредбата на картите до настройките по подразбиране?", "card_style": "Стил на картата", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Деактивирайте оптимизацията на батерията", "disableBatteryOptimizationDescription": "Искате ли да деактивирате оптимизацията на батерията, за да може фоновата синхронизация да работи по‑свободно и гладко?", "disabled": "Деактивирано", - "disconnect_session": "Прекъсване на сесията", "discount": "Спестете ${value}%", "dismiss": "Отказ", "display": "Дисплей", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Не показвай това повече", "do_not_show_me": "Не ми показвай това отново", "domain_looks_up": "Търсене на домейни", - "domain_mismatch": "Несъответствие на домейна", - "domain_mismatch_description": "Този уебсайт има домейн, който не съответства на подателя на тази заявка. Одобряването може да доведе до загуба на средства.", "donation": "Дарение", "donation_link_details": "Подробности за връзката за дарение", "done": "Готово", @@ -893,8 +888,6 @@ "second_intro_title": "Един емоджи адрес, който да управлява всички", "security": "Сигурност", "security_and_backup": "Сигурност и резервно копие", - "security_risk": "Риск за сигурността", - "security_risk_description": "Този домейн е маркиран като опасен от множество доставчици на сигурност. Напуснете незабавно, за да защитите активите си.", "seed_alert_back": "Назад", "seed_alert_content": "Seed фразата е единственият начин да възстановите портфейла си. Записахте ли я?", "seed_alert_title": "Внимание", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Подробности за неизразходваните монети", "unspent_coins_title": "Неизразходвани монети", "unsupported_asset": "Не поддържаме това действие за този актив. Моля, създайте или превключете към портфейл с поддържан тип актив.", - "update_session": "Актуализиране на сесията", "uptime": "Време на работа", "upto": "до ${value}", "usb": "USB", @@ -1334,6 +1326,31 @@ "walletConnect": "WalletConnect", "wallets": "Портфейли", "warning": "Предупреждение", + "wc_action_approve": "Одобряване", + "wc_approve_request_title": "Одобряване на заявката", + "wc_connect_request_title": "Заявка за свързване", + "wc_connected_to": "Свързан с ${name}", + "wc_max_network_fee": "Максимална мрежова такса", + "wc_message_to_sign": "Съобщение за подпис", + "wc_network_fee": "Мрежова такса", + "wc_not_verified": "Не е проверено", + "wc_pairing_list_header_subtitle": "Свържете портфейла си с WalletConnect или управлявайте съществуващи приложения.", + "wc_paste_link": "Поставете връзката WalletConnect", + "wc_permission_other": "Друго: ${method}", + "wc_permission_request_approval": "Поискайте одобрение за транзакции", + "wc_permission_sign_messages": "Подписвайте съобщения и въведени данни", + "wc_permission_switch_chains": "Превключете и добавете EVM вериги", + "wc_permission_view_balance": "Вижте баланса и активността на вашия портфейл", + "wc_scam_warning_message": "Тази заявка изглежда е от известна измама. Моля, продължете с повишено внимание.", + "wc_scam_warning_title": "ПРЕДУПРЕЖДЕНИЕ!", + "wc_scan_qr": "Сканирайте QR", + "wc_sign_all_count": "Подпишете всички ${count} съобщения", + "wc_signing_request_title": "Искане за подписване", + "wc_swipe_to_approve": "Плъзнете, за да одобрите", + "wc_swipe_to_sign": "Плъзнете, за да подпишете", + "wc_verified": "Проверен", + "wc_would_like_to_connect_to": "${name} иска да се свърже", + "wc_would_like_to_sign": "${name} иска да подпише", "website": "Уебсайт", "welcome": "Добре дошли", "welcome_subtitle_new_wallet": "Ако искате да започнете начисто, докоснете „Създаване на нов портфейл“ по-долу и сте готови да започнете.", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 312224c82..269dbbdc3 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Je vyžadováno oprávnění k fotoaparátu.\nPovolte ho v nastavení aplikace.", "cancel": "Zrušit", "cannot_manage_accounts_during_sync": "Nemůžete spravovat účty, dokud se peněženka stále synchronizuje. Zkuste to znovu později.", - "cannot_verify": "Nelze ověřit", - "cannot_verify_description": "Tuto doménu nelze ověřit. Před schválením pečlivě zkontrolujte požadavek.", "card_address": "Adresa:", "card_order_reset_desc": "Obnovit pořadí karet na výchozí nastavení?", "card_style": "Styl karty", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Vypnout optimalizaci baterie", "disableBatteryOptimizationDescription": "Chcete deaktivovat optimalizaci baterie, aby synchronizace na pozadí běžela volněji a plynuleji?", "disabled": "Zakázáno", - "disconnect_session": "Odpojit relaci", "discount": "Ušetřete ${value}%", "dismiss": "Zavřít", "display": "Zobrazení", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Už to nezobrazovat", "do_not_show_me": "Už mi to znovu nezobrazovat", "domain_looks_up": "Vyhledávání domén", - "domain_mismatch": "Neshoda domény", - "domain_mismatch_description": "Tento web má doménu, která neodpovídá odesílateli této žádosti. Schválení může vést ke ztrátě prostředků.", "donation": "Darování", "donation_link_details": "Podrobnosti odkazu na darování", "done": "Hotovo", @@ -893,8 +888,6 @@ "second_intro_title": "Jedna emoji adresa, která vládne všem", "security": "Zabezpečení", "security_and_backup": "Zabezpečení a záloha", - "security_risk": "Bezpečnostní riziko", - "security_risk_description": "Tato doména je označena jako nebezpečná více poskytovateli zabezpečení. Okamžitě ji opusťte, abyste ochránili svá aktiva.", "seed_alert_back": "Zpět", "seed_alert_content": "Seed je jediný způsob, jak obnovit vaši peněženku. Zapsali jste si ho?", "seed_alert_title": "Pozor", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Podrobnosti o neutracených mincích", "unspent_coins_title": "Neutratné mince", "unsupported_asset": "Tuto akci pro toto aktivum nepodporujeme. Vytvořte nebo přepněte na peněženku podporovaného typu aktiva.", - "update_session": "Aktualizovat relaci", "uptime": "Doba provozu", "upto": "až do ${value}", "usb": "USB", @@ -1334,6 +1326,31 @@ "walletConnect": "WalletConnect", "wallets": "Peněženky", "warning": "Varování", + "wc_action_approve": "Schvalovat", + "wc_approve_request_title": "Schválit žádost", + "wc_connect_request_title": "Žádost o připojení", + "wc_connected_to": "Připojeno k ${name}", + "wc_max_network_fee": "Max síťový poplatek", + "wc_message_to_sign": "Zpráva k podpisu", + "wc_network_fee": "Síťový poplatek", + "wc_not_verified": "Neověřeno", + "wc_pairing_list_header_subtitle": "Propojte svou peněženku s WalletConnect nebo spravujte existující aplikace.", + "wc_paste_link": "Vložte odkaz WalletConnect", + "wc_permission_other": "Jiné: ${method}", + "wc_permission_request_approval": "Požádejte o schválení transakcí", + "wc_permission_sign_messages": "Podepisujte zprávy a zadaná data", + "wc_permission_switch_chains": "Přepněte a přidejte řetězy EVM", + "wc_permission_view_balance": "Prohlédněte si zůstatek a aktivitu v peněžence", + "wc_scam_warning_message": "Zdá se, že tento požadavek pochází od známého podvodu. Postupujte prosím opatrně.", + "wc_scam_warning_title": "VAROVÁNÍ!", + "wc_scan_qr": "Naskenujte QR", + "wc_sign_all_count": "Podepište všechny ${count} zprávy", + "wc_signing_request_title": "Žádost o podpis", + "wc_swipe_to_approve": "Schválení přejetím prstem", + "wc_swipe_to_sign": "Podepište přejetím prstem", + "wc_verified": "Ověřeno", + "wc_would_like_to_connect_to": "${name} se chce připojit", + "wc_would_like_to_sign": "${name} chce podepsat", "website": "Webová stránka", "welcome": "Vítejte", "welcome_subtitle_new_wallet": "Pokud chcete začít znovu, klepněte níže na Vytvořit novou peněženku a můžete vyrazit.", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index c20eeeec8..08ab4c074 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Kameraberechtigung ist erforderlich.\nBitte aktivieren Sie sie in den App-Einstellungen.", "cancel": "Abbrechen", "cannot_manage_accounts_during_sync": "Sie können keine Konten verwalten, während die Wallet noch synchronisiert wird. Bitte versuchen Sie es später erneut.", - "cannot_verify": "Kann nicht verifiziert werden", - "cannot_verify_description": "Diese Domain kann nicht verifiziert werden. Überprüfen Sie die Anfrage sorgfältig, bevor Sie sie genehmigen.", "card_address": "Adresse:", "card_order_reset_desc": "Kartenreihenfolge auf die Standardeinstellungen zurücksetzen?", "card_style": "Kartenstil", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Batterieoptimierung deaktivieren", "disableBatteryOptimizationDescription": "Möchten Sie die Batterieoptimierung deaktivieren, damit die Hintergrundsynchronisierung freier und reibungsloser laufen kann?", "disabled": "Deaktiviert", - "disconnect_session": "Sitzung trennen", "discount": "${value}% sparen", "dismiss": "Schließen", "display": "Anzeige", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Nicht mehr anzeigen", "do_not_show_me": "Zeig mir das nicht noch einmal", "domain_looks_up": "Domain-Abfragen", - "domain_mismatch": "Domänenkonflikt", - "domain_mismatch_description": "Diese Website hat eine Domain, die nicht mit dem Absender dieser Anfrage übereinstimmt. Eine Genehmigung kann zum Verlust von Guthaben führen.", "donation": "Spende", "donation_link_details": "Details zum Spendenlink", "done": "Fertig", @@ -749,8 +744,8 @@ "please_choose_one": "Bitte wählen Sie eine Option", "please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät angezeigt wird", "please_make_selection": "Bitte treffen Sie unten eine Auswahl, um Ihre Wallet zu erstellen oder wiederherzustellen.", - "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.", "please_reference_document": "Bitte beachten Sie die untenstehenden Dokumente für weitere Informationen.", + "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.", "please_select": "Bitte auswählen:", "please_select_backup_file": "Bitte wählen Sie eine Sicherungsdatei aus und geben Sie das Sicherungskennwort ein.", "please_try_to_connect_to_another_node": "Bitte versuchen Sie, eine Verbindung zu einem anderen Node herzustellen", @@ -894,8 +889,6 @@ "second_intro_title": "Eine Emoji-Adresse, um sie alle zu beherrschen", "security": "Sicherheit", "security_and_backup": "Sicherheit und Backup", - "security_risk": "Sicherheitsrisiko", - "security_risk_description": "Diese Domain wird von mehreren Sicherheitsanbietern als unsicher eingestuft. Verlassen Sie sie sofort, um Ihre Assets zu schützen.", "seed_alert_back": "Zurück", "seed_alert_content": "Der Seed ist die einzige Möglichkeit, Ihre Wallet wiederherzustellen. Haben Sie ihn aufgeschrieben?", "seed_alert_title": "Achtung", @@ -1261,7 +1254,6 @@ "unspent_coins_details_title": "Details zu nicht ausgegebenen Coins", "unspent_coins_title": "Nicht ausgegebene Coins", "unsupported_asset": "Wir unterstützen diese Aktion für dieses Asset nicht. Bitte erstellen Sie eine Wallet mit einem unterstützten Asset-Typ oder wechseln Sie zu einer Wallet mit einem unterstützten Asset-Typ.", - "update_session": "Sitzung aktualisieren", "uptime": "Betriebszeit", "upto": "bis zu ${value}", "usb": "USB", @@ -1338,6 +1330,31 @@ "walletConnect": "WalletConnect", "wallets": "Wallets", "warning": "Warnung", + "wc_action_approve": "Genehmigen", + "wc_approve_request_title": "Anfrage genehmigen", + "wc_connect_request_title": "Verbindungsanfrage", + "wc_connected_to": "Verbunden mit ${name}", + "wc_max_network_fee": "Maximale Netzwerkgebühr", + "wc_message_to_sign": "Nachricht zum Unterschreiben", + "wc_network_fee": "Netzwerkgebühr", + "wc_not_verified": "Nicht verifiziert", + "wc_pairing_list_header_subtitle": "Verbinden Sie Ihr Wallet mit WalletConnect oder verwalten Sie bestehende Apps.", + "wc_paste_link": "Fügen Sie den WalletConnect-Link ein", + "wc_permission_other": "Andere: ${method}", + "wc_permission_request_approval": "Fordern Sie die Genehmigung für Transaktionen an", + "wc_permission_sign_messages": "Signieren Sie Nachrichten und geben Sie Daten ein", + "wc_permission_switch_chains": "EVM-Ketten wechseln und hinzufügen", + "wc_permission_view_balance": "Sehen Sie sich den Kontostand und die Aktivität Ihres Geldbeutels an", + "wc_scam_warning_message": "Diese Anfrage scheint von einem bekannten Betrug zu stammen. Bitte gehen Sie vorsichtig vor.", + "wc_scam_warning_title": "WARNUNG!", + "wc_scan_qr": "QR scannen", + "wc_sign_all_count": "Signieren Sie alle ${count} Nachrichten", + "wc_signing_request_title": "Signierungsanfrage", + "wc_swipe_to_approve": "Zum Genehmigen wischen", + "wc_swipe_to_sign": "Zum Signieren wischen", + "wc_verified": "Verifiziert", + "wc_would_like_to_connect_to": "${name} möchte eine Verbindung herstellen", + "wc_would_like_to_sign": "${name} möchte unterschreiben", "website": "Website", "welcome": "Willkommen", "welcome_subtitle_new_wallet": "Wenn Sie neu anfangen möchten, tippen Sie unten auf „Neue Wallet erstellen“ und schon kann es losgehen.", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 160192565..70468bc30 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -158,8 +158,6 @@ "camera_permission_is_required": "Camera permission is required. \nPlease enable it from app settings.", "cancel": "Cancel", "cannot_manage_accounts_during_sync": "You can't manage accounts while the wallet is still syncing. Please try again later.", - "cannot_verify": "Cannot Verify", - "cannot_verify_description": "This domain cannot be verified. Check the request carefully before approving.", "card_address": "Address:", "card_order_reset_desc": "Restore card order to default settings?", "card_style": "Card style", @@ -352,7 +350,6 @@ "disableBatteryOptimization": "Disable Battery Optimization", "disableBatteryOptimizationDescription": "Do you want to disable battery optimization in order to make background sync run more freely and smoothly?", "disabled": "Disabled", - "disconnect_session": "Disconnect Session", "discount": "Save ${value}%", "dismiss": "Dismiss", "display": "Display", @@ -364,8 +361,6 @@ "do_not_show_anymore": "Don’t show this anymore", "do_not_show_me": "Do not show me this again", "domain_looks_up": "Domain lookups", - "domain_mismatch": "Domain Mismatch", - "domain_mismatch_description": "This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.", "donation": "Donation", "donation_link_details": "Donation link details", "done": "Done", @@ -898,8 +893,6 @@ "second_intro_title": "One emoji address to rule them all", "security": "Security", "security_and_backup": "Security and backup", - "security_risk": "Security Risk", - "security_risk_description": "This domain is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.", "seed_alert_back": "Go back", "seed_alert_content": "The seed is the only way to recover your wallet. Have you written it down?", "seed_alert_title": "Attention", @@ -1266,7 +1259,6 @@ "unspent_coins_details_title": "Unspent coins details", "unspent_coins_title": "Unspent coins", "unsupported_asset": "We don't support this action for this asset. Please create or switch to a wallet of a supported asset type.", - "update_session": "Update Session", "uptime": "Uptime", "upto": "up to ${value}", "usb": "USB", @@ -1343,6 +1335,31 @@ "walletConnect": "WalletConnect", "wallets": "Wallets", "warning": "Warning", + "wc_action_approve": "Approve", + "wc_approve_request_title": "Approve request", + "wc_connect_request_title": "Connect request", + "wc_connected_to": "Connected to ${name}", + "wc_max_network_fee": "Max network fee", + "wc_message_to_sign": "Message to sign", + "wc_network_fee": "Network fee", + "wc_not_verified": "Not Verified", + "wc_pairing_list_header_subtitle": "Connect your wallet with WalletConnect or manage existing apps.", + "wc_paste_link": "Paste WalletConnect link", + "wc_permission_other": "Other: ${method}", + "wc_permission_request_approval": "Request approval for transactions", + "wc_permission_sign_messages": "Sign messages and typed data", + "wc_permission_switch_chains": "Switch and add EVM chains", + "wc_permission_view_balance": "View your wallet balance and activity", + "wc_scam_warning_message": "This request appears to be from a known scam. Please proceed with caution.", + "wc_scam_warning_title": "WARNING!", + "wc_scan_qr": "Scan QR", + "wc_sign_all_count": "Sign all ${count} messages", + "wc_signing_request_title": "Signing request", + "wc_swipe_to_approve": "Swipe to approve", + "wc_swipe_to_sign": "Swipe to sign", + "wc_verified": "Verified", + "wc_would_like_to_connect_to": "${name} would like to connect", + "wc_would_like_to_sign": "${name} would like to sign", "website": "Website", "welcome": "Welcome", "welcome_subtitle_new_wallet": "If you want to start fresh, tap Create New Wallet below and you'll be off to the races.", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index e820dc568..3fef8e0a1 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Se requiere permiso para usar la cámara.\nActívalo en la configuración de la aplicación.", "cancel": "Cancelar", "cannot_manage_accounts_during_sync": "No puedes administrar cuentas mientras la billetera aún se está sincronizando. Por favor, inténtalo de nuevo más tarde.", - "cannot_verify": "No se puede verificar", - "cannot_verify_description": "Este dominio no se puede verificar. Revisa la solicitud detenidamente antes de aprobar.", "card_address": "Dirección:", "card_order_reset_desc": "¿Restaurar el orden de las tarjetas a la configuración predeterminada?", "card_style": "Estilo de tarjeta", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Desactivar la optimización de batería", "disableBatteryOptimizationDescription": "¿Desea desactivar la optimización de la batería para que la sincronización en segundo plano se ejecute de forma más libre y fluida?", "disabled": "Desactivado", - "disconnect_session": "Desconectar sesión", "discount": "Ahorra ${value}%", "dismiss": "Descartar", "display": "Pantalla", @@ -362,8 +359,6 @@ "do_not_show_anymore": "No volver a mostrar esto", "do_not_show_me": "No me lo muestres de nuevo", "domain_looks_up": "Búsquedas de dominios", - "domain_mismatch": "Discordancia de dominio", - "domain_mismatch_description": "Este sitio web tiene un dominio que no coincide con el remitente de esta solicitud. Aprobarlo podría provocar la pérdida de fondos.", "donation": "Donación", "donation_link_details": "Detalles del enlace de donación", "done": "Listo", @@ -894,8 +889,6 @@ "second_intro_title": "Una dirección de emoji para gobernarlas a todas", "security": "Seguridad", "security_and_backup": "Seguridad y copia de seguridad", - "security_risk": "Riesgo de seguridad", - "security_risk_description": "Este dominio ha sido marcado como inseguro por varios proveedores de seguridad. Salga inmediatamente para proteger sus activos.", "seed_alert_back": "Volver", "seed_alert_content": "La semilla es la única forma de recuperar tu billetera. ¿La has escrito?", "seed_alert_title": "Atención", @@ -1260,7 +1253,6 @@ "unspent_coins_details_title": "Detalles de monedas sin gastar", "unspent_coins_title": "Monedas no gastadas", "unsupported_asset": "No se admite esta acción para este activo. Por favor, crea o cambia a una billetera de un tipo de activo compatible.", - "update_session": "Actualizar sesión", "uptime": "Tiempo de actividad", "upto": "hasta ${value}", "usb": "USB", @@ -1335,6 +1327,31 @@ "walletConnect": "WalletConnect", "wallets": "Carteras", "warning": "Advertencia", + "wc_action_approve": "Aprobar", + "wc_approve_request_title": "Aprobar solicitud", + "wc_connect_request_title": "Solicitud de conexión", + "wc_connected_to": "Conectado a ${name}", + "wc_max_network_fee": "Tarifa máxima de red", + "wc_message_to_sign": "Mensaje para firmar", + "wc_network_fee": "Tarifa de red", + "wc_not_verified": "No verificado", + "wc_pairing_list_header_subtitle": "Conecte su billetera con WalletConnect o administre aplicaciones existentes.", + "wc_paste_link": "Pegar enlace de WalletConnect", + "wc_permission_other": "Otro: ${method}", + "wc_permission_request_approval": "Solicitar aprobación para transacciones", + "wc_permission_sign_messages": "Firmar mensajes y datos escritos", + "wc_permission_switch_chains": "Cambie y agregue cadenas EVM", + "wc_permission_view_balance": "Ver el saldo y la actividad de su billetera", + "wc_scam_warning_message": "Esta solicitud parece provenir de una estafa conocida. Proceda con precaución.", + "wc_scam_warning_title": "¡ADVERTENCIA!", + "wc_scan_qr": "Escanear QR", + "wc_sign_all_count": "Firmar todos los mensajes de ${count}", + "wc_signing_request_title": "Solicitud de firma", + "wc_swipe_to_approve": "Desliza para aprobar", + "wc_swipe_to_sign": "Desliza para firmar", + "wc_verified": "Verificado", + "wc_would_like_to_connect_to": "${name} desea conectarse", + "wc_would_like_to_sign": "${name} quiere firmar", "website": "Sitio web", "welcome": "Bienvenido", "welcome_only": "Bienvenido", diff --git a/res/values/strings_fa.arb b/res/values/strings_fa.arb index ea3c0df67..64eb9f168 100644 --- a/res/values/strings_fa.arb +++ b/res/values/strings_fa.arb @@ -154,8 +154,6 @@ "camera_permission_is_required": "مجوز دسترسی به دوربین الزامی است.\nلطفاً آن را از تنظیمات برنامه فعال کنید.", "cancel": "لغو", "cannot_manage_accounts_during_sync": "در حالی که کیف پول هنوز در حال همگام‌سازی است، نمی‌توانید حساب‌ها را مدیریت کنید. لطفاً بعداً دوباره امتحان کنید.", - "cannot_verify": "امکان تأیید نیست", - "cannot_verify_description": "این دامنه قابل تأیید نیست.\nپیش از تأیید، درخواست را با دقت بررسی کنید.", "card_address": "آدرس:", "card_order_reset_desc": "ترتیب کارت‌ها به تنظیمات پیش‌فرض بازگردانده شود؟", "card_style": "سبک کارت", @@ -348,7 +346,6 @@ "disableBatteryOptimization": "غیرفعال کردن بهینه‌سازی باتری", "disableBatteryOptimizationDescription": "آیا می‌خواهید بهینه‌سازی باتری را غیرفعال کنید تا همگام‌سازی در پس‌زمینه آزادتر و روان‌تر اجرا شود؟", "disabled": "غیرفعال", - "disconnect_session": "قطع اتصال جلسه", "discount": "${value}% صرفه‌جویی", "dismiss": "بستن", "display": "نمایش", @@ -360,8 +357,6 @@ "do_not_show_anymore": "دیگر این را نشان نده", "do_not_show_me": "این را دوباره نشان ندهید", "domain_looks_up": "جستجوهای دامنه", - "domain_mismatch": "عدم تطابق دامنه", - "domain_mismatch_description": "این وب‌سایت دامنه‌ای دارد که با فرستندهٔ این درخواست مطابقت ندارد.\nتأیید آن ممکن است منجر به از دست رفتن دارایی‌ها شود.", "donation": "کمک مالی", "donation_link_details": "جزئیات لینک اهدایی", "done": "انجام شد", @@ -892,8 +887,6 @@ "second_intro_title": "یک آدرس ایموجی برای حکمرانی بر همه", "security": "امنیت", "security_and_backup": "امنیت و پشتیبان‌گیری", - "security_risk": "ریسک امنیتی", - "security_risk_description": "این دامنه توسط چندین ارائه‌دهندهٔ امنیتی به‌عنوان ناامن علامت‌گذاری شده است.\nبرای محافظت از دارایی‌های خود فوراً از آن خارج شوید.", "seed_alert_back": "بازگشت", "seed_alert_content": "عبارت بازیابی تنها راه بازیابی کیف پول شماست.\nآیا آن را یادداشت کرده‌اید؟", "seed_alert_title": "توجه", @@ -1257,7 +1250,6 @@ "unspent_coins_details_title": "جزئیات کوین‌های خرج‌نشده", "unspent_coins_title": "کوین‌های خرج‌نشده", "unsupported_asset": "ما از انجام این اقدام برای این دارایی پشتیبانی نمی‌کنیم.\nلطفاً یک کیف پول از نوع داراییِ پشتیبانی‌شده ایجاد کنید یا به کیف پولی با نوع داراییِ پشتیبانی‌شده تغییر دهید.", - "update_session": "به‌روزرسانی نشست", "uptime": "زمان فعالیت", "upto": "تا ${value}", "usb": "USB", @@ -1331,6 +1323,31 @@ "walletConnect": "WalletConnect", "wallets": "کیف پول‌ها", "warning": "هشدار", + "wc_action_approve": "تایید کنید", + "wc_approve_request_title": "درخواست را تایید کنید", + "wc_connect_request_title": "درخواست اتصال", + "wc_connected_to": "متصل به ${name}", + "wc_max_network_fee": "حداکثر هزینه شبکه", + "wc_message_to_sign": "برای امضا پیام دهید", + "wc_network_fee": "هزینه شبکه", + "wc_not_verified": "تایید نشده است", + "wc_pairing_list_header_subtitle": "کیف پول خود را با WalletConnect وصل کنید یا برنامه های موجود را مدیریت کنید.", + "wc_paste_link": "پیوند WalletConnect را جایگذاری کنید", + "wc_permission_other": "موارد دیگر: ${method}", + "wc_permission_request_approval": "درخواست تایید برای معاملات", + "wc_permission_sign_messages": "پیام ها و داده های تایپ شده را امضا کنید", + "wc_permission_switch_chains": "زنجیره های EVM را تغییر دهید و اضافه کنید", + "wc_permission_view_balance": "موجودی و فعالیت کیف پول خود را مشاهده کنید", + "wc_scam_warning_message": "به نظر می رسد این درخواست از یک کلاهبرداری شناخته شده باشد. لطفا با احتیاط ادامه دهید", + "wc_scam_warning_title": "هشدار!", + "wc_scan_qr": "اسکن QR", + "wc_sign_all_count": "همه پیام‌های ${count} را امضا کنید", + "wc_signing_request_title": "درخواست امضا", + "wc_swipe_to_approve": "برای تأیید، انگشت خود را بکشید", + "wc_swipe_to_sign": "برای امضا، انگشت خود را بکشید", + "wc_verified": "تایید شده است", + "wc_would_like_to_connect_to": "${name} مایل به اتصال است", + "wc_would_like_to_sign": "${name} می‌خواهد امضا کند", "website": "وب‌سایت", "welcome": "خوش آمدید", "welcome_subtitle_new_wallet": "اگر می‌خواهید از نو شروع کنید، روی «ایجاد کیف پول جدید» در پایین بزنید و آماده‌اید شروع کنید.", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index b5ef76528..89a3576b2 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "L'autorisation d'accéder à la caméra est requise.\nVeuillez l'activer dans les paramètres de l'application.", "cancel": "Annuler", "cannot_manage_accounts_during_sync": "Vous ne pouvez pas gérer les comptes tant que le portefeuille est en cours de synchronisation. Veuillez réessayer plus tard.", - "cannot_verify": "Impossible de vérifier", - "cannot_verify_description": "Ce domaine ne peut pas être vérifié. Vérifiez attentivement la demande avant d'approuver.", "card_address": "Adresse :", "card_order_reset_desc": "Rétablir l’ordre des cartes aux paramètres par défaut ?", "card_style": "Style de carte", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Désactiver l'optimisation de la batterie", "disableBatteryOptimizationDescription": "Voulez-vous désactiver l'optimisation de la batterie afin que la synchronisation en arrière-plan s'exécute plus librement et plus fluidement ?", "disabled": "Désactivé", - "disconnect_session": "Déconnecter la session", "discount": "Économisez ${value} %", "dismiss": "Ignorer", "display": "Affichage", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Ne plus afficher", "do_not_show_me": "Ne plus me montrer ceci", "domain_looks_up": "Recherches de domaine", - "domain_mismatch": "Incohérence de domaine", - "domain_mismatch_description": "Ce site Web utilise un domaine qui ne correspond pas à l'expéditeur de cette requête. L'approbation peut entraîner une perte de fonds.", "donation": "Don", "donation_link_details": "Détails du lien de don", "done": "Terminé", @@ -893,8 +888,6 @@ "second_intro_title": "Une adresse emoji pour toutes les gouverner", "security": "Sécurité", "security_and_backup": "Sécurité et sauvegarde", - "security_risk": "Risque de sécurité", - "security_risk_description": "Ce domaine est signalé comme non sécurisé par plusieurs fournisseurs de sécurité. Quittez-le immédiatement pour protéger vos actifs.", "seed_alert_back": "Retour", "seed_alert_content": "La phrase de récupération (seed) est le seul moyen de récupérer votre portefeuille (wallet). L'avez-vous notée ?", "seed_alert_title": "Attention", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Détails des coins non dépensés", "unspent_coins_title": "Coins non dépensées", "unsupported_asset": "Nous ne prenons pas en charge cette action pour cet actif. Veuillez créer ou passer à un portefeuille d’un type d’actif pris en charge.", - "update_session": "Mettre à jour la session", "uptime": "Temps de disponibilité", "upto": "jusqu’à ${value}", "usb": "USB", @@ -1334,6 +1326,31 @@ "walletConnect": "WalletConnect", "wallets": "Portefeuilles", "warning": "Avertissement", + "wc_action_approve": "Approuver", + "wc_approve_request_title": "Approuver la demande", + "wc_connect_request_title": "Demande de connexion", + "wc_connected_to": "Connecté à ${name}", + "wc_max_network_fee": "Frais de réseau maximum", + "wc_message_to_sign": "Message à signer", + "wc_network_fee": "Frais de réseau", + "wc_not_verified": "Non vérifié", + "wc_pairing_list_header_subtitle": "Connectez votre portefeuille avec WalletConnect ou gérez les applications existantes.", + "wc_paste_link": "Coller le lien WalletConnect", + "wc_permission_other": "Autre : ${method}", + "wc_permission_request_approval": "Demander l'approbation des transactions", + "wc_permission_sign_messages": "Signer les messages et les données saisies", + "wc_permission_switch_chains": "Changer et ajouter des chaînes EVM", + "wc_permission_view_balance": "Consultez le solde et l'activité de votre portefeuille", + "wc_scam_warning_message": "Cette demande semble provenir d'une arnaque connue. Veuillez procéder avec prudence.", + "wc_scam_warning_title": "AVERTISSEMENT!", + "wc_scan_qr": "Scanner le QR", + "wc_sign_all_count": "Signer tous les ${count} messages", + "wc_signing_request_title": "Demande de signature", + "wc_swipe_to_approve": "Glissez pour approuver", + "wc_swipe_to_sign": "Glissez pour signer", + "wc_verified": "Vérifié", + "wc_would_like_to_connect_to": "${name} souhaite se connecter", + "wc_would_like_to_sign": "${name} aimerait signer", "website": "Site web", "welcome": "Bienvenue", "welcome_subtitle_new_wallet": "Si vous souhaitez repartir de zéro, appuyez sur Créer un nouveau portefeuille ci-dessous et vous serez prêt à démarrer.", diff --git a/res/values/strings_gn.arb b/res/values/strings_gn.arb index c5e08c467..1d59ae3ed 100644 --- a/res/values/strings_gn.arb +++ b/res/values/strings_gn.arb @@ -1078,6 +1078,31 @@ "walletConnect": "WalletConnect", "wallets": "Billetera kuéra", "warning": "Kyhyje", + "wc_action_approve": "Hasapyre", + "wc_approve_request_title": "Omoneĩ mba’ejerure", + "wc_connect_request_title": "Ombojoaju mba’ejerure", + "wc_connected_to": "Oñembojoaju ${name} ndive.", + "wc_max_network_fee": "Max cuota red rehegua", + "wc_message_to_sign": "Marandu ofirma haguã", + "wc_network_fee": "Cuota de red rehegua", + "wc_not_verified": "Ndojehechái", + "wc_pairing_list_header_subtitle": "Embojoaju ne billetera WalletConnect ndive térã emohenda umi aplicación oĩmava.", + "wc_paste_link": "Pega WalletConnect enlace rehegua", + "wc_permission_other": "Ambue: ${method}.", + "wc_permission_request_approval": "Ojerure aprobación umi transacción rehegua", + "wc_permission_sign_messages": "Marandu ofirma ha dato ojehaipyréva", + "wc_permission_switch_chains": "Embohasa ha emoĩve umi cadena EVM rehegua", + "wc_permission_view_balance": "Ehecha nde billetera saldo ha actividad", + "wc_scam_warning_message": "Ko pedido ojekuaa oúha peteî estafa ojekuaávagui. Por favor, peprocede ñeñangareko reheve.", + "wc_scam_warning_title": "ÑEMONGYHYJE!", + "wc_scan_qr": "Escanear QR", + "wc_sign_all_count": "Efirma opaite ${count} marandu", + "wc_signing_request_title": "Ofirma pedido", + "wc_swipe_to_approve": "Emboguejy emoneĩ hag̃ua", + "wc_swipe_to_sign": "Emboguejy eñefirma hag̃ua", + "wc_verified": "Ojehecháma", + "wc_would_like_to_connect_to": "${name} oñembojoajuse", + "wc_would_like_to_sign": "${name} ofirmase", "welcome": "Tapeguahẽ porãite", "welcome_to_cakepay": "¡Eg̃uahẽporãite Cake Pay-pe!", "what_is_silent_payments": "¿Mbaʼépa hína Jehepymeʼẽ Kirirĩva?", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index af4797bfb..9611767a9 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Ana buƙatar izinin kyamara.\nDa fatan za a ba da izini daga saitunan manhaja.", "cancel": "Soke", "cannot_manage_accounts_during_sync": "Ba za ka iya sarrafa asusu ba yayin da walat ɗin ke ci gaba da daidaitawa. Da fatan za a sake gwadawa daga baya.", - "cannot_verify": "Ba a iya tabbatarwa", - "cannot_verify_description": "Ba za a iya tabbatar da wannan yanki ba. Duba buƙatar a hankali kafin amincewa.", "card_address": "Adireshi:", "card_order_reset_desc": "Mayar da jerin katin zuwa saitunan tsoho?", "card_style": "Salon kati", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Kashe inganta amfani da baturi", "disableBatteryOptimizationDescription": "Kana so ka kashe inganta amfani da baturi domin daidaitawar bayan-fage ta yi aiki cikin 'yanci kuma cikin santsi?", "disabled": "An kashe", - "disconnect_session": "Cire haɗin zaman", "discount": "Ajiye ${value}%", "dismiss": "Rufe", "display": "Nuni", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Kada a sake nuna wannan", "do_not_show_me": "Kada a sake nuna min wannan", "domain_looks_up": "Binciken yankuna", - "domain_mismatch": "Rashin Daidaiton Domain", - "domain_mismatch_description": "Wannan rukunin yanar gizon yana da yanki (domain) da bai dace da mai aiko da wannan buƙatar ba. Amincewa na iya haifar da asarar kuɗaɗe.", "donation": "Gudummawa", "donation_link_details": "Cikakkun bayanan hanyar bayar da gudummawa", "done": "An gama", @@ -895,8 +890,6 @@ "second_intro_title": "Adireshin emoji ɗaya don mulkin su duka", "security": "Tsaro", "security_and_backup": "Tsaro da ajiyar bayanai", - "security_risk": "Haɗarin Tsaro", - "security_risk_description": "Masu samar da tsaro da dama sun yi wa wannan yankin alama a matsayin mara aminci. Bar nan take don kare kadarorinka.", "seed_alert_back": "Komawa baya", "seed_alert_content": "Seed ɗin ita ce hanya ɗaya tilo ta dawo da walat ɗin ku. Kun rubuta ta?", "seed_alert_title": "Hankali", @@ -1261,7 +1254,6 @@ "unspent_coins_details_title": "Cikakkun bayanan tsabar kuɗi da ba a kashe ba", "unspent_coins_title": "Tsabar kuɗi da ba a kashe ba", "unsupported_asset": "Ba mu goyi bayan wannan aikin ga wannan kadara ba. Da fatan za a ƙirƙiri ko canza zuwa walat na nau'in kadara da ake tallafawa.", - "update_session": "Sabunta Zama", "uptime": "Lokacin aiki", "upto": "har zuwa ${value}", "usb": "USB", @@ -1336,6 +1328,31 @@ "walletConnect": "WalletConnect", "wallets": "Walati", "warning": "Gargaɗi", + "wc_action_approve": "Amincewa", + "wc_approve_request_title": "Amincewa da buƙata", + "wc_connect_request_title": "Buƙatar haɗin kai", + "wc_connected_to": "An haɗa zuwa ${name}", + "wc_max_network_fee": "Matsakaicin kuɗin hanyar sadarwa", + "wc_message_to_sign": "Saƙo don sa hannu", + "wc_network_fee": "Kudin hanyar sadarwa", + "wc_not_verified": "Ba a Tabbatarwa ba", + "wc_pairing_list_header_subtitle": "Haɗa walat ɗin ku tare da WalletConnect ko sarrafa ƙa'idodin da ke akwai.", + "wc_paste_link": "Manna hanyar haɗin WalletConnect", + "wc_permission_other": "Wani: ${method}", + "wc_permission_request_approval": "Nemi izini don ma'amaloli", + "wc_permission_sign_messages": "Sa hannu kan saƙonni da rubutattun bayanai", + "wc_permission_switch_chains": "Canja kuma ƙara sarƙoƙin EVM", + "wc_permission_view_balance": "Duba ma'auni na walat ɗin ku da aiki", + "wc_scam_warning_message": "Wannan buƙatar ta bayyana daga wata zamba da aka sani. Da fatan za a ci gaba da taka tsantsan.", + "wc_scam_warning_title": "GARGADI!", + "wc_scan_qr": "Duba QR", + "wc_sign_all_count": "Shiga duk saƙon ${count}", + "wc_signing_request_title": "Bukatar sa hannu", + "wc_swipe_to_approve": "Dokewa don amincewa", + "wc_swipe_to_sign": "Danna don sa hannu", + "wc_verified": "Tabbatarwa", + "wc_would_like_to_connect_to": "${name} yana son haɗi", + "wc_would_like_to_sign": "${name} na son sanya hannu", "website": "Yanar gizo", "welcome": "Barka da zuwa", "welcome_only": "Barka da zuwa", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index bfb674462..c3dd9f82a 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "कैमरा अनुमति आवश्यक है।\nकृपया इसे ऐप सेटिंग्स में सक्षम करें।", "cancel": "रद्द करें", "cannot_manage_accounts_during_sync": "जब वॉलेट अभी भी सिंक हो रहा हो, तब आप खातों का प्रबंधन नहीं कर सकते। कृपया बाद में फिर से प्रयास करें।", - "cannot_verify": "सत्यापित नहीं किया जा सकता", - "cannot_verify_description": "इस डोमेन को सत्यापित नहीं किया जा सकता। अनुमोदन करने से पहले अनुरोध को ध्यान से जाँचें।", "card_address": "पता:", "card_order_reset_desc": "कार्ड क्रम को डिफ़ॉल्ट सेटिंग्स पर रीसेट करें?", "card_style": "कार्ड शैली", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "बैटरी ऑप्टिमाइज़ेशन अक्षम करें", "disableBatteryOptimizationDescription": "क्या आप बैकग्राउंड सिंक को अधिक स्वतंत्र और सुचारू रूप से चलाने के लिए बैटरी ऑप्टिमाइज़ेशन को बंद करना चाहते हैं?", "disabled": "अक्षम", - "disconnect_session": "सत्र डिस्कनेक्ट करें", "discount": "${value}% बचाएं", "dismiss": "खारिज करें", "display": "डिस्प्ले", @@ -362,8 +359,6 @@ "do_not_show_anymore": "इसे फिर से न दिखाएँ", "do_not_show_me": "मुझे यह फिर से न दिखाएँ", "domain_looks_up": "डोमेन लुकअप", - "domain_mismatch": "डोमेन मेल नहीं खाता", - "domain_mismatch_description": "इस वेबसाइट का डोमेन इस अनुरोध के प्रेषक से मेल नहीं खाता। अनुमोदन करने पर धन की हानि हो सकती है।", "donation": "दान", "donation_link_details": "डोनेशन लिंक विवरण", "done": "पूर्ण", @@ -895,8 +890,6 @@ "second_intro_title": "सब पर राज करने के लिए एक इमोजी पता", "security": "सुरक्षा", "security_and_backup": "सुरक्षा और बैकअप", - "security_risk": "सुरक्षा जोखिम", - "security_risk_description": "इस डोमेन को कई सुरक्षा प्रदाताओं द्वारा असुरक्षित के रूप में चिह्नित किया गया है। अपनी संपत्तियों की सुरक्षा के लिए तुरंत यहाँ से निकल जाएँ।", "seed_alert_back": "वापस जाएँ", "seed_alert_content": "सीड आपके वॉलेट को रिकवर करने का एकमात्र तरीका है। क्या आपने इसे लिख लिया है?", "seed_alert_title": "ध्यान दें", @@ -1261,7 +1254,6 @@ "unspent_coins_details_title": "अव्ययित कॉइन्स का विवरण", "unspent_coins_title": "अव्ययित सिक्के", "unsupported_asset": "हम इस एसेट के लिए इस कार्रवाई का समर्थन नहीं करते हैं। कृपया समर्थित एसेट प्रकार का वॉलेट बनाएं या उस पर स्विच करें।", - "update_session": "सत्र अपडेट करें", "uptime": "अपटाइम", "upto": "${value} तक", "usb": "USB", @@ -1336,6 +1328,31 @@ "walletConnect": "WalletConnect", "wallets": "वॉलेट्स", "warning": "चेतावनी", + "wc_action_approve": "मंज़ूरी देना", + "wc_approve_request_title": "अनुरोध स्वीकृत करें", + "wc_connect_request_title": "कनेक्ट अनुरोध", + "wc_connected_to": "${name} से कनेक्टेड", + "wc_max_network_fee": "अधिकतम नेटवर्क शुल्क", + "wc_message_to_sign": "हस्ताक्षर करने के लिए संदेश", + "wc_network_fee": "नेटवर्क शुल्क", + "wc_not_verified": "सत्यापित नहीं है", + "wc_pairing_list_header_subtitle": "अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें या मौजूदा ऐप्स प्रबंधित करें।", + "wc_paste_link": "वॉलेटकनेक्ट लिंक चिपकाएँ", + "wc_permission_other": "अन्य: ${method}", + "wc_permission_request_approval": "लेन-देन के लिए अनुमोदन का अनुरोध करें", + "wc_permission_sign_messages": "संदेशों पर हस्ताक्षर करें और डेटा टाइप करें", + "wc_permission_switch_chains": "स्विच करें और ईवीएम चेन जोड़ें", + "wc_permission_view_balance": "अपना बटुआ शेष और गतिविधि देखें", + "wc_scam_warning_message": "ऐसा प्रतीत होता है कि यह अनुरोध किसी ज्ञात घोटाले से आया है। कृपया सावधानी से आगे बढ़ें.", + "wc_scam_warning_title": "चेतावनी!", + "wc_scan_qr": "QR स्कैन करें", + "wc_sign_all_count": "सभी ${count} संदेशों पर हस्ताक्षर करें", + "wc_signing_request_title": "अनुरोध पर हस्ताक्षर", + "wc_swipe_to_approve": "स्वीकृत करने के लिए स्वाइप करें", + "wc_swipe_to_sign": "हस्ताक्षर करने के लिए स्वाइप करें", + "wc_verified": "सत्यापित", + "wc_would_like_to_connect_to": "${name} कनेक्ट करना चाहेंगे", + "wc_would_like_to_sign": "${name} हस्ताक्षर करना चाहेंगे", "website": "वेबसाइट", "welcome": "स्वागत है", "welcome_subtitle_new_wallet": "यदि आप नए सिरे से शुरू करना चाहते हैं, तो नीचे \"नया वॉलेट बनाएं\" पर टैप करें और आप तुरंत शुरू कर देंगे।", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 5a71246df..985f360d6 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Potrebno je dopuštenje za kameru.\nOmogućite ga u postavkama aplikacije.", "cancel": "Odustani", "cannot_manage_accounts_during_sync": "Ne možete upravljati računima dok se novčanik još sinkronizira. Pokušajte ponovno kasnije.", - "cannot_verify": "Nije moguće provjeriti", - "cannot_verify_description": "Ovu domenu nije moguće provjeriti. Pažljivo provjerite zahtjev prije nego što ga odobrite.", "card_address": "Adresa:", "card_order_reset_desc": "Vratiti redoslijed kartica na zadane postavke?", "card_style": "Stil kartice", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Onemogući optimizaciju baterije", "disableBatteryOptimizationDescription": "Želite li onemogućiti optimizaciju baterije kako bi se pozadinska sinkronizacija odvijala slobodnije i glatko?", "disabled": "Onemogućeno", - "disconnect_session": "Odspoji sesiju", "discount": "Uštedite ${value}%", "dismiss": "Odbaci", "display": "Prikaz", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Ne prikazuj ovo više", "do_not_show_me": "Ne prikazuj mi ovo više", "domain_looks_up": "Pretrage domena", - "domain_mismatch": "Nepodudaranje domene", - "domain_mismatch_description": "Ova web-stranica ima domenu koja se ne podudara s pošiljateljem ovog zahtjeva. Odobravanje može dovesti do gubitka sredstava.", "donation": "Donacija", "donation_link_details": "Detalji poveznice za donacije", "done": "Gotovo", @@ -893,8 +888,6 @@ "second_intro_title": "Jedna emoji adresa koja će svima vladati", "security": "Sigurnost", "security_and_backup": "Sigurnost i sigurnosna kopija", - "security_risk": "Sigurnosni rizik", - "security_risk_description": "Ova je domena označena kao nesigurna od strane više pružatelja sigurnosnih usluga. Odmah napustite stranicu kako biste zaštitili svoju imovinu.", "seed_alert_back": "Natrag", "seed_alert_content": "Seed fraza jedini je način za oporavak vašeg novčanika. Jeste li je zapisali?", "seed_alert_title": "Pažnja", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Detalji nepotrošenih novčića", "unspent_coins_title": "Nepotrošeni novčići", "unsupported_asset": "Ne podržavamo ovu radnju za ovu imovinu. Izradite ili se prebacite na novčanik podržane vrste imovine.", - "update_session": "Ažuriraj sesiju", "uptime": "Vrijeme neprekidnog rada", "upto": "do ${value}", "usb": "USB", @@ -1334,6 +1326,31 @@ "walletConnect": "WalletConnect", "wallets": "Novčanici", "warning": "Upozorenje", + "wc_action_approve": "Odobriti", + "wc_approve_request_title": "Odobri zahtjev", + "wc_connect_request_title": "Zahtjev za povezivanje", + "wc_connected_to": "Povezan s ${name}", + "wc_max_network_fee": "Maksimalna mrežna naknada", + "wc_message_to_sign": "Poruka za potpisivanje", + "wc_network_fee": "Mrežna naknada", + "wc_not_verified": "Nije potvrđeno", + "wc_pairing_list_header_subtitle": "Povežite svoj novčanik s WalletConnectom ili upravljajte postojećim aplikacijama.", + "wc_paste_link": "Zalijepi vezu WalletConnect", + "wc_permission_other": "Ostalo: ${method}", + "wc_permission_request_approval": "Zatražite odobrenje za transakcije", + "wc_permission_sign_messages": "Potpisivati ​​poruke i upisane podatke", + "wc_permission_switch_chains": "Prebacite i dodajte EVM lance", + "wc_permission_view_balance": "Pogledajte svoj novčanik i aktivnost", + "wc_scam_warning_message": "Čini se da ovaj zahtjev potječe iz poznate prijevare. Molimo nastavite s oprezom.", + "wc_scam_warning_title": "UPOZORENJE!", + "wc_scan_qr": "Skeniraj QR", + "wc_sign_all_count": "Potpišite svih ${count} poruka", + "wc_signing_request_title": "Zahtjev za potpisivanje", + "wc_swipe_to_approve": "Prijeđite prstom za odobrenje", + "wc_swipe_to_sign": "Prijeđite prstom za potpis", + "wc_verified": "Provjereno", + "wc_would_like_to_connect_to": "${name} želi se povezati", + "wc_would_like_to_sign": "${name} želi potpisati", "website": "Web-stranica", "welcome": "Dobrodošli", "welcome_subtitle_new_wallet": "Ako želite krenuti ispočetka, dodirnite Create New Wallet u nastavku i spremni ste za početak.", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 75ae49fea..1961fa1b7 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Տեսախցիկի թույլտվությունը պահանջվում է։ \nԽնդրում ենք այն միացնել հավելվածի կարգավորումներից։", "cancel": "Չեղարկել", "cannot_manage_accounts_during_sync": "Դուք չեք կարող կառավարել հաշիվները, քանի դեռ դրամապանակը դեռ համաժամացվում է։ Խնդրում ենք կրկին փորձել ավելի ուշ։", - "cannot_verify": "Հնարավոր չէ ստուգել", - "cannot_verify_description": "Այս տիրույթը հնարավոր չէ ստուգել։ Հաստատելուց առաջ ուշադիր ստուգեք հարցումը։", "card_address": "Հասցե՝", "card_order_reset_desc": "Վերականգնե՞լ քարտերի դասավորությունը լռելյայն կարգավորումներին:", "card_style": "Քարտի ոճ", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Անջատել մարտկոցի օպտիմիզացիան", "disableBatteryOptimizationDescription": "Ցանկանո՞ւմ եք անջատել մարտկոցի օպտիմիզացիան, որպեսզի ֆոնային համաժամացումը աշխատի ավելի ազատ և սահուն:", "disabled": "Անջատված", - "disconnect_session": "Անջատել սեսիան", "discount": "Խնայեք ${value}%-ով", "dismiss": "Փակել", "display": "Ցուցադրում", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Այլևս չցուցադրել", "do_not_show_me": "Այլևս չցուցադրել", "domain_looks_up": "Դոմեյնի որոնումներ", - "domain_mismatch": "Դոմենի անհամապատասխանություն", - "domain_mismatch_description": "Այս կայքը ունի տիրույթ, որը չի համապատասխանում այս հարցման ուղարկողին։ Հաստատումը կարող է հանգեցնել միջոցների կորստի։", "donation": "Նվիրատվություն", "donation_link_details": "Նվիրատվության հղման մանրամասներ", "done": "Պատրաստ", @@ -892,8 +887,6 @@ "second_intro_title": "Մեկ էմոջի հասցե՝ բոլորին կառավարելու համար", "security": "Անվտանգություն", "security_and_backup": "Անվտանգություն և պահուստավորում", - "security_risk": "Անվտանգության ռիսկ", - "security_risk_description": "Այս տիրույթը մի քանի անվտանգության մատակարարների կողմից նշվել է որպես վտանգավոր։ Ձեր ակտիվները պաշտպանելու համար անմիջապես դուրս եկեք։", "seed_alert_back": "Վերադառնալ", "seed_alert_content": "Սերմը ձեր դրամապանակը վերականգնելու միակ միջոցն է։ Դուք այն գրի առե՞լ եք։", "seed_alert_title": "Ուշադրություն", @@ -1257,7 +1250,6 @@ "unspent_coins_details_title": "Չծախսված մետաղադրամների մանրամասները", "unspent_coins_title": "Չծախսված մետաղադրամներ", "unsupported_asset": "Մենք չենք աջակցում այս գործողությունը տվյալ ակտիվի համար։ Խնդրում ենք ստեղծել կամ անցնել աջակցվող ակտիվի տեսակի դրամապանակի։", - "update_session": "Թարմացնել նիստը", "uptime": "Աշխատանքի ժամանակ", "upto": "մինչև ${value}", "usb": "USB", @@ -1332,6 +1324,30 @@ "walletConnect": "WalletConnect", "wallets": "Դրամապանակներ", "warning": "Զգուշացում", + "wc_approve_request_title": "Հաստատել հարցումը", + "wc_connect_request_title": "Միացման հարցում", + "wc_connected_to": "Միացված է ${name}-ին", + "wc_max_network_fee": "Ցանցի առավելագույն վճար", + "wc_message_to_sign": "Հաղորդագրություն ստորագրելու համար", + "wc_network_fee": "Ցանցի վճար", + "wc_not_verified": "Ստուգված չէ", + "wc_pairing_list_header_subtitle": "Միացրեք ձեր դրամապանակը WalletConnect-ի հետ կամ կառավարեք առկա հավելվածները:", + "wc_paste_link": "Տեղադրեք WalletConnect հղումը", + "wc_permission_other": "Այլ՝ ${method}", + "wc_permission_request_approval": "Գործարքների համար հաստատման պահանջ", + "wc_permission_sign_messages": "Ստորագրեք հաղորդագրությունները և մուտքագրված տվյալները", + "wc_permission_switch_chains": "Միացրեք և ավելացրեք EVM շղթաներ", + "wc_permission_view_balance": "Դիտեք ձեր դրամապանակի մնացորդը և գործունեությունը", + "wc_scam_warning_message": "Այս հարցումը, կարծես, հայտնի խարդախությունից է: Խնդրում ենք շարունակել զգուշությամբ:", + "wc_scam_warning_title": "ԶԳՈՒՇԱՑՈՒՄ.", + "wc_scan_qr": "Scan QR", + "wc_sign_all_count": "Ստորագրեք բոլոր ${count} հաղորդագրությունները", + "wc_signing_request_title": "Ստորագրման հարցում", + "wc_swipe_to_approve": "Հաստատելու համար սահեցրեք", + "wc_swipe_to_sign": "Սահեցրեք՝ ստորագրելու համար", + "wc_verified": "Ստուգված է", + "wc_would_like_to_connect_to": "${name}-ը ցանկանում է միանալ", + "wc_would_like_to_sign": "${name}-ը ցանկանում է ստորագրել", "website": "Վեբկայք", "welcome": "Բարի գալուստ", "welcome_subtitle_new_wallet": "Եթե ցանկանում եք սկսել նորից, ներքևում հպեք «Ստեղծել նոր դրամապանակ», և կարող եք անմիջապես սկսել։", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 5bebc0548..d223330d2 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Izin kamera diperlukan.\nSilakan aktifkan melalui pengaturan aplikasi.", "cancel": "Batal", "cannot_manage_accounts_during_sync": "Anda tidak dapat mengelola akun saat wallet masih dalam proses sinkronisasi. Silakan coba lagi nanti.", - "cannot_verify": "Tidak dapat diverifikasi", - "cannot_verify_description": "Domain ini tidak dapat diverifikasi. Periksa permintaan dengan saksama sebelum menyetujui.", "card_address": "Alamat:", "card_order_reset_desc": "Pulihkan urutan kartu ke pengaturan bawaan?", "card_style": "Gaya kartu", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Nonaktifkan Optimasi Baterai", "disableBatteryOptimizationDescription": "Apakah Anda ingin menonaktifkan pengoptimalan baterai agar sinkronisasi latar belakang dapat berjalan lebih bebas dan lancar?", "disabled": "Dinonaktifkan", - "disconnect_session": "Putuskan Sesi", "discount": "Hemat ${value}%", "dismiss": "Tutup", "display": "Tampilan", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Jangan tampilkan lagi", "do_not_show_me": "Jangan tampilkan ini lagi", "domain_looks_up": "Pencarian domain", - "domain_mismatch": "Domain Tidak Cocok", - "domain_mismatch_description": "Situs web ini memiliki domain yang tidak sesuai dengan pengirim permintaan ini. Menyetujui dapat menyebabkan kehilangan dana.", "donation": "Donasi", "donation_link_details": "Detail tautan donasi", "done": "Selesai", @@ -896,8 +891,6 @@ "second_intro_title": "Satu alamat emoji untuk menguasai semuanya", "security": "Keamanan", "security_and_backup": "Keamanan dan pencadangan", - "security_risk": "Risiko Keamanan", - "security_risk_description": "Domain ini ditandai tidak aman oleh beberapa penyedia keamanan. Segera tinggalkan untuk melindungi aset Anda.", "seed_alert_back": "Kembali", "seed_alert_content": "Seed adalah satu-satunya cara untuk memulihkan dompet Anda. Apakah Anda sudah menuliskannya?", "seed_alert_title": "Perhatian", @@ -1262,7 +1255,6 @@ "unspent_coins_details_title": "Rincian koin yang belum dibelanjakan", "unspent_coins_title": "Koin yang belum dibelanjakan", "unsupported_asset": "Kami tidak mendukung tindakan ini untuk aset ini. Harap buat atau beralih ke dompet dengan jenis aset yang didukung.", - "update_session": "Perbarui Sesi", "uptime": "Waktu aktif", "upto": "hingga ${value}", "usb": "USB", @@ -1337,6 +1329,30 @@ "walletConnect": "WalletConnect", "wallets": "Dompet", "warning": "Peringatan", + "wc_approve_request_title": "Setujui permintaan", + "wc_connect_request_title": "Hubungkan permintaan", + "wc_connected_to": "Terhubung ke ${name}", + "wc_max_network_fee": "Biaya jaringan maksimal", + "wc_message_to_sign": "Pesan untuk ditandatangani", + "wc_network_fee": "Biaya jaringan", + "wc_not_verified": "Tidak Terverifikasi", + "wc_pairing_list_header_subtitle": "Hubungkan dompet Anda dengan WalletConnect atau kelola aplikasi yang ada.", + "wc_paste_link": "Tempel tautan WalletConnect", + "wc_permission_other": "Lainnya: ${method}", + "wc_permission_request_approval": "Minta persetujuan untuk transaksi", + "wc_permission_sign_messages": "Menandatangani pesan dan mengetik data", + "wc_permission_switch_chains": "Beralih dan tambahkan rantai EVM", + "wc_permission_view_balance": "Lihat saldo dan aktivitas dompet Anda", + "wc_scam_warning_message": "Permintaan ini tampaknya berasal dari penipuan yang diketahui. Silakan lanjutkan dengan hati-hati.", + "wc_scam_warning_title": "PERINGATAN!", + "wc_scan_qr": "Pindai QR", + "wc_sign_all_count": "Tanda tangani semua pesan ${count}", + "wc_signing_request_title": "Permintaan penandatanganan", + "wc_swipe_to_approve": "Geser untuk menyetujui", + "wc_swipe_to_sign": "Gesek untuk menandatangani", + "wc_verified": "Terverifikasi", + "wc_would_like_to_connect_to": "${name} ingin terhubung", + "wc_would_like_to_sign": "${name} ingin menandatangani", "website": "Situs web", "welcome": "Selamat datang", "welcome_subtitle_new_wallet": "Jika Anda ingin memulai dari awal, ketuk Buat Dompet Baru di bawah ini dan Anda siap melaju.", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index faaf12cac..8c0c9a1aa 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "È richiesta l'autorizzazione della fotocamera.\nAbilitala dalle impostazioni dell'app.", "cancel": "Annulla", "cannot_manage_accounts_during_sync": "Non puoi gestire gli account mentre il wallet è ancora in fase di sincronizzazione. Riprova più tardi.", - "cannot_verify": "Impossibile verificare", - "cannot_verify_description": "Questo dominio non può essere verificato. Controlla attentamente la richiesta prima di approvare.", "card_address": "Indirizzo:", "card_order_reset_desc": "Ripristinare l'ordine delle carte alle impostazioni predefinite?", "card_style": "Stile della scheda", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Disattiva l'ottimizzazione della batteria", "disableBatteryOptimizationDescription": "Vuoi disabilitare l'ottimizzazione della batteria per consentire alla sincronizzazione in background di funzionare in modo più libero e fluido?", "disabled": "Disabilitato", - "disconnect_session": "Disconnetti sessione", "discount": "Risparmia ${value}%", "dismiss": "Ignora", "display": "Schermo", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Non mostrarlo più", "do_not_show_me": "Non mostrarmelo più", "domain_looks_up": "Ricerche di dominio", - "domain_mismatch": "Dominio non corrispondente", - "domain_mismatch_description": "Questo sito web ha un dominio che non corrisponde al mittente di questa richiesta. L'approvazione potrebbe comportare la perdita di fondi.", "donation": "Donazione", "donation_link_details": "Dettagli del link di donazione", "done": "Fatto", @@ -893,8 +888,6 @@ "second_intro_title": "Un indirizzo emoji per domarli tutti", "security": "Sicurezza", "security_and_backup": "Sicurezza e backup", - "security_risk": "Rischio per la sicurezza", - "security_risk_description": "Questo dominio è segnalato come non sicuro da più provider di sicurezza. Esci immediatamente per proteggere i tuoi asset.", "seed_alert_back": "Torna indietro", "seed_alert_content": "Il seed è l'unico modo per recuperare il tuo wallet. L'hai scritto da qualche parte?", "seed_alert_title": "Attenzione", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Dettagli delle monete non spese", "unspent_coins_title": "Monete non spese", "unsupported_asset": "Non supportiamo questa azione per questo asset. Crea o passa a un wallet di un tipo di asset supportato.", - "update_session": "Aggiorna sessione", "uptime": "Tempo di attività", "upto": "fino a ${value}", "usb": "USB", @@ -1335,6 +1327,30 @@ "walletConnect": "WalletConnect", "wallets": "Portafogli", "warning": "Attenzione", + "wc_approve_request_title": "Approva la richiesta", + "wc_connect_request_title": "Richiesta di connessione", + "wc_connected_to": "Connesso a ${name}", + "wc_max_network_fee": "Tariffa di rete massima", + "wc_message_to_sign": "Messaggio da firmare", + "wc_network_fee": "Tariffa di rete", + "wc_not_verified": "Non verificato", + "wc_pairing_list_header_subtitle": "Collega il tuo portafoglio con WalletConnect o gestisci le app esistenti.", + "wc_paste_link": "Incolla il collegamento WalletConnect", + "wc_permission_other": "Altro: ${method}", + "wc_permission_request_approval": "Richiedere l'approvazione per le transazioni", + "wc_permission_sign_messages": "Firmare messaggi e dati digitati", + "wc_permission_switch_chains": "Cambia e aggiungi catene EVM", + "wc_permission_view_balance": "Visualizza il saldo e l'attività del tuo portafoglio", + "wc_scam_warning_message": "Questa richiesta sembra provenire da una truffa nota. Si prega di procedere con cautela.", + "wc_scam_warning_title": "AVVERTIMENTO!", + "wc_scan_qr": "Scansione QR", + "wc_sign_all_count": "Firma tutti i ${count} messaggi", + "wc_signing_request_title": "Richiesta di firma", + "wc_swipe_to_approve": "Scorri per approvare", + "wc_swipe_to_sign": "Scorri per firmare", + "wc_verified": "Verificato", + "wc_would_like_to_connect_to": "${name} vorrebbe connettersi", + "wc_would_like_to_sign": "${name} vorrebbe firmare", "website": "Sito web", "welcome": "Benvenuto", "welcome_only": "Benvenuto", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 40075ccde..3cae50928 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "カメラの許可が必要です。\nアプリの設定から有効にしてください。", "cancel": "キャンセル", "cannot_manage_accounts_during_sync": "ウォレットの同期が完了するまで、アカウントを管理できません。後でもう一度お試しください。", - "cannot_verify": "検証できません", - "cannot_verify_description": "このドメインは検証できません。承認する前に、リクエスト内容を注意深く確認してください。", "card_address": "アドレス:", "card_order_reset_desc": "カードの並び順をデフォルト設定に戻しますか?", "card_style": "カードスタイル", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "バッテリー最適化を無効にする", "disableBatteryOptimizationDescription": "バックグラウンド同期をより自由かつスムーズに実行するために、バッテリー最適化を無効にしますか?", "disabled": "無効", - "disconnect_session": "セッションを切断", "discount": "${value}%お得", "dismiss": "閉じる", "display": "表示", @@ -362,8 +359,6 @@ "do_not_show_anymore": "今後表示しない", "do_not_show_me": "今後このメッセージを表示しない", "domain_looks_up": "ドメイン検索", - "domain_mismatch": "ドメインの不一致", - "domain_mismatch_description": "このWebサイトのドメインが、このリクエストの送信元と一致しません。承認すると、資金を失う可能性があります。", "donation": "寄付", "donation_link_details": "寄付リンクの詳細", "done": "完了", @@ -893,8 +888,6 @@ "second_intro_title": "すべてを支配する1つの絵文字アドレス", "security": "セキュリティ", "security_and_backup": "セキュリティとバックアップ", - "security_risk": "セキュリティリスク", - "security_risk_description": "このドメインは、複数のセキュリティプロバイダーによって安全でないと判定されています。資産を保護するため、直ちに離れてください。", "seed_alert_back": "戻る", "seed_alert_content": "シードはウォレットを復元する唯一の方法です。書き留めましたか?", "seed_alert_title": "注意", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "未使用コインの詳細", "unspent_coins_title": "未使用コイン", "unsupported_asset": "このアセットではこの操作はサポートされていません。サポートされているアセットタイプのウォレットを作成するか、対応するウォレットに切り替えてください。", - "update_session": "セッションを更新", "uptime": "稼働時間", "upto": "${value}まで", "usb": "USB", @@ -1334,6 +1326,30 @@ "walletConnect": "WalletConnect", "wallets": "ウォレット", "warning": "警告", + "wc_approve_request_title": "リクエストの承認", + "wc_connect_request_title": "接続リクエスト", + "wc_connected_to": "${name} に接続しました", + "wc_max_network_fee": "最大ネットワーク料金", + "wc_message_to_sign": "署名するメッセージ", + "wc_network_fee": "ネットワーク料金", + "wc_not_verified": "未検証", + "wc_pairing_list_header_subtitle": "ウォレットを WalletConnect に接続するか、既存のアプリを管理します。", + "wc_paste_link": "WalletConnect リンクを貼り付けます", + "wc_permission_other": "その他: ${method}", + "wc_permission_request_approval": "取引の承認をリクエストする", + "wc_permission_sign_messages": "メッセージと入力されたデータに署名する", + "wc_permission_switch_chains": "EVMチェーンの切り替えと追加", + "wc_permission_view_balance": "ウォレットの残高とアクティビティを表示する", + "wc_scam_warning_message": "このリクエストは既知の詐欺によるものと思われます。慎重に進めてください。", + "wc_scam_warning_title": "警告!", + "wc_scan_qr": "QRをスキャン", + "wc_sign_all_count": "すべての ${count} メッセージに署名します", + "wc_signing_request_title": "署名リクエスト", + "wc_swipe_to_approve": "スワイプして承認します", + "wc_swipe_to_sign": "スワイプして署名", + "wc_verified": "確認済み", + "wc_would_like_to_connect_to": "${name} が接続を希望しています", + "wc_would_like_to_sign": "${name} は署名を希望しています", "website": "ウェブサイト", "welcome": "ようこそ", "welcome_subtitle_new_wallet": "新しく始めたい場合は、下の「新しいウォレットを作成」をタップすれば、すぐに始められます。", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index a91bb57c6..1975e9a90 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "카메라 권한이 필요합니다.\n앱 설정에서 활성화해 주세요.", "cancel": "취소", "cannot_manage_accounts_during_sync": "지갑이 아직 동기화 중인 동안에는 계정을 관리할 수 없습니다. 나중에 다시 시도해 주세요.", - "cannot_verify": "확인할 수 없음", - "cannot_verify_description": "이 도메인은 검증할 수 없습니다. 승인하기 전에 요청을 주의 깊게 확인하세요.", "card_address": "주소:", "card_order_reset_desc": "카드 순서를 기본 설정으로 복원하시겠습니까?", "card_style": "카드 스타일", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "배터리 최적화 끄기", "disableBatteryOptimizationDescription": "백그라운드 동기화가 더 자유롭고 원활하게 실행되도록 배터리 최적화를 비활성화하시겠습니까?", "disabled": "비활성화됨", - "disconnect_session": "세션 연결 끊기", "discount": "${value}% 절약", "dismiss": "닫기", "display": "디스플레이", @@ -362,8 +359,6 @@ "do_not_show_anymore": "다시 표시하지 않기", "do_not_show_me": "다시 보지 않기", "domain_looks_up": "도메인 조회", - "domain_mismatch": "도메인 불일치", - "domain_mismatch_description": "이 웹사이트의 도메인이 이 요청의 발신자와 일치하지 않습니다. 승인하면 자금이 손실될 수 있습니다.", "donation": "기부", "donation_link_details": "기부 링크 상세 정보", "done": "완료", @@ -893,8 +888,6 @@ "second_intro_title": "하나의 이모지 주소로 모두를 지배하세요", "security": "보안", "security_and_backup": "보안 및 백업", - "security_risk": "보안 위험", - "security_risk_description": "이 도메인은 여러 보안 제공업체에서 안전하지 않은 것으로 표시되었습니다. 자산을 보호하려면 즉시 떠나세요.", "seed_alert_back": "뒤로 가기", "seed_alert_content": "시드는 지갑을 복구할 수 있는 유일한 방법입니다. 적어두셨나요?", "seed_alert_title": "주의", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "미사용 코인 상세 정보", "unspent_coins_title": "미사용 코인", "unsupported_asset": "이 자산에서는 이 작업을 지원하지 않습니다. 지원되는 자산 유형의 지갑을 생성하거나 해당 지갑으로 전환하세요.", - "update_session": "세션 업데이트", "uptime": "가동 시간", "upto": "${value}까지", "usb": "USB", @@ -1334,6 +1326,30 @@ "walletConnect": "WalletConnect", "wallets": "지갑", "warning": "경고", + "wc_approve_request_title": "요청 승인", + "wc_connect_request_title": "연결 요청", + "wc_connected_to": "${name}에 연결됨", + "wc_max_network_fee": "최대 네트워크 수수료", + "wc_message_to_sign": "서명하라는 메시지", + "wc_network_fee": "네트워크 수수료", + "wc_not_verified": "확인되지 않음", + "wc_pairing_list_header_subtitle": "WalletConnect로 지갑을 연결하거나 기존 앱을 관리하세요.", + "wc_paste_link": "WalletConnect 링크 붙여넣기", + "wc_permission_other": "기타: ${method}", + "wc_permission_request_approval": "거래 승인 요청", + "wc_permission_sign_messages": "메시지 및 입력된 데이터에 서명", + "wc_permission_switch_chains": "EVM 체인 전환 및 추가", + "wc_permission_view_balance": "지갑 잔액 및 활동 보기", + "wc_scam_warning_message": "이 요청은 알려진 사기에서 나온 것 같습니다. 주의해서 진행하시기 바랍니다.", + "wc_scam_warning_title": "경고!", + "wc_scan_qr": "QR 스캔", + "wc_sign_all_count": "${count}개의 메시지 모두 서명", + "wc_signing_request_title": "서명 요청", + "wc_swipe_to_approve": "스와이프하여 승인", + "wc_swipe_to_sign": "스와이프하여 서명하세요", + "wc_verified": "확인됨", + "wc_would_like_to_connect_to": "${name}님이 연결하고 싶어합니다", + "wc_would_like_to_sign": "${name}님이 서명하고 싶어합니다", "website": "웹사이트", "welcome": "환영합니다", "welcome_subtitle_new_wallet": "새로 시작하려면 아래에서 새 지갑 만들기를 탭하면 바로 시작할 수 있습니다.", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index a3bd26e59..6a6584c4c 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "ကင်မရာအသုံးပြုခွင့် လိုအပ်ပါသည်။\nအက်ပ်ဆက်တင်များမှ ၎င်းကို ဖွင့်ပေးပါ။", "cancel": "ပယ်ဖျက်", "cannot_manage_accounts_during_sync": "ပိုက်ဆံအိတ်ကို စင့်ခ်လုပ်နေစဉ် အကောင့်များကို စီမံခန့်ခွဲလို့ မရပါ။ နောက်မှ ထပ်စမ်းကြည့်ပါ။", - "cannot_verify": "စစ်ဆေးအတည်ပြု၍မရပါ", - "cannot_verify_description": "ဤဒိုမိန်းကို အတည်ပြု၍ မရပါ။ အတည်ပြုမီ တောင်းဆိုချက်ကို သေချာစွာ စစ်ဆေးပါ။", "card_address": "လိပ်စာ:", "card_order_reset_desc": "ကတ်အစီအစဉ်ကို မူလဆက်တင်များသို့ ပြန်ထားမလား။", "card_style": "ကတ်ပုံစံ", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "ဘက်ထရီ အကောင်းဆုံးပြုလုပ်မှုကို ပိတ်ပါ", "disableBatteryOptimizationDescription": "နောက်ခံထပ်တူပြုခြင်းကို ပိုမိုလွတ်လပ်ပြီး ချောမွေ့စွာ လုပ်ဆောင်နိုင်ရန် ဘက်ထရီ အကောင်းဆုံးပြုလုပ်မှု (battery optimization) ကို ပိတ်လိုပါသလား။", "disabled": "ပိတ်ထားသည်", - "disconnect_session": "Session ကို ချိတ်ဆက်မှုဖြုတ်ရန်", "discount": "${value}% သက်သာ", "dismiss": "ပိတ်ရန်", "display": "ပြသမှု", @@ -362,8 +359,6 @@ "do_not_show_anymore": "ဒါကို နောက်ထပ် မပြပါနဲ့", "do_not_show_me": "ဒီကို နောက်တစ်ခါ မပြပါနဲ့", "domain_looks_up": "ဒိုမိန်းရှာဖွေမှုများ", - "domain_mismatch": "ဒိုမိန်း မကိုက်ညီ", - "domain_mismatch_description": "ဤဝဘ်ဆိုက်၏ ဒိုမိန်းသည် ဤတောင်းဆိုမှုကို ပေးပို့သူနှင့် မကိုက်ညီပါ။ အတည်ပြုပါက ရန်ပုံငွေများ ဆုံးရှုံးနိုင်သည်။", "donation": "လှူဒါန်းမှု", "donation_link_details": "လှူဒါန်းရန် လင့်ခ် အသေးစိတ်", "done": "ပြီးပြီ", @@ -892,8 +887,6 @@ "second_intro_title": "အားလုံးကို အုပ်စိုးမယ့် အီမိုဂျီလိပ်စာတစ်ခု", "security": "လုံခြုံရေး", "security_and_backup": "လုံခြုံရေးနှင့် အရန်သိမ်းဆည်းမှု", - "security_risk": "လုံခြုံရေးအန္တရာယ်", - "security_risk_description": "ဤဒိုမိန်းကို လုံခြုံရေးဝန်ဆောင်မှုပေးသူများအများအပြားက မလုံခြုံသောအဖြစ် သတ်မှတ်ထားသည်။ သင်၏ပိုင်ဆိုင်မှုများကို ကာကွယ်ရန် ချက်ချင်း ထွက်ခွာပါ။", "seed_alert_back": "နောက်သို့ ပြန်သွားပါ", "seed_alert_content": "Seed စကားဝှက်သည် သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူရန် တစ်ခုတည်းသောနည်းလမ်းဖြစ်သည်။ သင် ရေးမှတ်ပြီးပြီလား။", "seed_alert_title": "သတိပြုရန်", @@ -1258,7 +1251,6 @@ "unspent_coins_details_title": "မသုံးရသေးသော ကွိုင်များ၏ အသေးစိတ်", "unspent_coins_title": "မသုံးရသေးသော ကွိုင်များ", "unsupported_asset": "ဤပိုင်ဆိုင်မှုအတွက် ဤလုပ်ဆောင်ချက်ကို ကျွန်ုပ်တို့ မပံ့ပိုးပါ။ ကျေးဇူးပြု၍ ပံ့ပိုးထားသော ပိုင်ဆိုင်မှုအမျိုးအစားရှိ ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ထိုအမျိုးအစားရှိ ပိုက်ဆံအိတ်သို့ ပြောင်းပါ။", - "update_session": "စက်ရှင်ကို အပ်ဒိတ်လုပ်ပါ", "uptime": "လည်ပတ်ချိန်", "upto": "${value} အထိ", "usb": "USB", @@ -1333,6 +1325,30 @@ "walletConnect": "WalletConnect", "wallets": "ဝေါလက်များ", "warning": "သတိပေးချက်", + "wc_approve_request_title": "တောင်းဆိုချက်ကို အတည်ပြုပါ။", + "wc_connect_request_title": "တောင်းဆိုချက်ကို ချိတ်ဆက်ပါ။", + "wc_connected_to": "${name} သို့ ချိတ်ဆက်ထားသည်", + "wc_max_network_fee": "အများဆုံးကွန်ရက်အခကြေးငွေ", + "wc_message_to_sign": "လက်မှတ်ထိုးရန် မက်ဆေ့ချ်", + "wc_network_fee": "ကွန်ရက်ကြေး", + "wc_not_verified": "အတည်မပြုရသေးပါ။", + "wc_pairing_list_header_subtitle": "WalletConnect ဖြင့် သင့်ပိုက်ဆံအိတ်ကို ချိတ်ဆက်ပါ သို့မဟုတ် လက်ရှိအက်ပ်များကို စီမံခန့်ခွဲပါ။", + "wc_paste_link": "WalletConnect လင့်ခ်ကို ကူးထည့်ပါ။", + "wc_permission_other": "အခြား- ${method}", + "wc_permission_request_approval": "အရောင်းအ၀ယ်များအတွက် အတည်ပြုချက်တောင်းခံပါ။", + "wc_permission_sign_messages": "မက်ဆေ့ချ်များကို လက်မှတ်ထိုးပြီး ဒေတာကို ရိုက်ထည့်ပါ။", + "wc_permission_switch_chains": "ပြောင်းပြီး EVM ကြိုးများကို ထည့်ပါ။", + "wc_permission_view_balance": "သင့်ပိုက်ဆံအိတ်လက်ကျန်ငွေနှင့် လုပ်ဆောင်ချက်ကို ကြည့်ရှုပါ။", + "wc_scam_warning_message": "ဤတောင်းဆိုချက်သည် လူသိများသော လိမ်လည်မှုတစ်ခုမှ ဖြစ်ပုံရသည်။ ကျေးဇူးပြု၍ သတိဖြင့် ဆက်လုပ်ပါ။", + "wc_scam_warning_title": "သတိပေးချက်။", + "wc_scan_qr": "QR ကိုစကင်န်ဖတ်ပါ။", + "wc_sign_all_count": "${count} မက်ဆေ့ဂျ်များအားလုံးကို လက်မှတ်ထိုးပါ။", + "wc_signing_request_title": "လက်မှတ်ရေးထိုးတောင်းဆိုခြင်း။", + "wc_swipe_to_approve": "အတည်ပြုရန် ပွတ်ဆွဲပါ။", + "wc_swipe_to_sign": "လက်မှတ်ထိုးရန် ပွတ်ဆွဲပါ။", + "wc_verified": "စိစစ်ပြီး", + "wc_would_like_to_connect_to": "${name} ချိတ်ဆက်လိုပါသည်။", + "wc_would_like_to_sign": "${name} က လက်မှတ်ထိုးလိုပါသည်။", "website": "ဝဘ်ဆိုဒ်", "welcome": "ကြိုဆိုပါတယ်", "welcome_subtitle_new_wallet": "အသစ်စတင်လိုပါက အောက်က “ပိုက်ဆံအိတ်အသစ် ဖန်တီးရန်” ကိုနှိပ်ပါ။ ချက်ချင်း စတင်အသုံးပြုနိုင်ပါပြီ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index a45f5daf4..6919aa5de 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -155,8 +155,6 @@ "camera_permission_is_required": "Cameratoestemming is vereist.\nSchakel dit in via de app-instellingen.", "cancel": "Annuleren", "cannot_manage_accounts_during_sync": "Je kunt geen accounts beheren terwijl de wallet nog bezig is met synchroniseren. Probeer het later opnieuw.", - "cannot_verify": "Kan niet verifiëren", - "cannot_verify_description": "Dit domein kan niet worden geverifieerd. Controleer het verzoek zorgvuldig voordat je het goedkeurt.", "card_address": "Adres:", "card_order_reset_desc": "Kaartvolgorde herstellen naar de standaardinstellingen?", "card_style": "Kaartstijl", @@ -348,7 +346,6 @@ "disableBatteryOptimization": "Schakel de batterijoptimalisatie uit", "disableBatteryOptimizationDescription": "Wil je de batterijoptimalisatie uitschakelen zodat achtergrondsynchronisatie vrijer en soepeler kan werken?", "disabled": "Uitgeschakeld", - "disconnect_session": "Koppel de sessie los", "discount": "Bespaar ${value}%", "dismiss": "Sluiten", "display": "Weergave", @@ -360,8 +357,6 @@ "do_not_show_anymore": "Niet meer weergeven", "do_not_show_me": "Laat me dit niet opnieuw zien", "domain_looks_up": "Domein opzoeken", - "domain_mismatch": "Domein komt niet overeen", - "domain_mismatch_description": "Deze website heeft een domein dat niet overeenkomt met de afzender van dit verzoek. Goedkeuring kan leiden tot verlies van fondsen.", "donation": "Donatie", "donation_link_details": "Details van de donatielink", "done": "Klaar", @@ -889,8 +884,6 @@ "second_intro_title": "Eén emoji-adres om ze allemaal te beheren", "security": "Beveiliging", "security_and_backup": "Beveiliging en back-up", - "security_risk": "Beveiligingsrisico", - "security_risk_description": "Dit domein wordt door meerdere beveiligingsproviders gemarkeerd als onveilig. Vertrek onmiddellijk om je fondsen te beschermen.", "seed_alert_back": "Ga terug", "seed_alert_content": "Het seed is de enige manier om je wallet te herstellen. Heb je het opgeschreven?", "seed_alert_title": "Aandacht", @@ -1255,7 +1248,6 @@ "unspent_coins_details_title": "Details van niet-uitgegeven saldo", "unspent_coins_title": "Niet-uitgegeven saldo", "unsupported_asset": "We ondersteunen deze actie niet voor deze coin. Maak een wallet van een ondersteund activatype aan of schakel ernaar over.", - "update_session": "Updatesessie", "uptime": "Uptime", "upto": "tot ${value}", "usb": "USB", @@ -1331,6 +1323,30 @@ "walletConnect": "WalletConnect", "wallets": "Wallets", "warning": "Waarschuwing", + "wc_approve_request_title": "Aanvraag goedkeuren", + "wc_connect_request_title": "Verbind verzoek", + "wc_connected_to": "Verbonden met ${name}", + "wc_max_network_fee": "Maximale netwerkkosten", + "wc_message_to_sign": "Bericht om te ondertekenen", + "wc_network_fee": "Netwerk vergoeding", + "wc_not_verified": "Niet geverifieerd", + "wc_pairing_list_header_subtitle": "Verbind uw portemonnee met WalletConnect of beheer bestaande apps.", + "wc_paste_link": "Plak de WalletConnect-link", + "wc_permission_other": "Anders: ${method}", + "wc_permission_request_approval": "Goedkeuring aanvragen voor transacties", + "wc_permission_sign_messages": "Onderteken berichten en getypte gegevens", + "wc_permission_switch_chains": "Schakel over en voeg EVM-ketens toe", + "wc_permission_view_balance": "Bekijk uw portemonneesaldo en activiteit", + "wc_scam_warning_message": "Dit verzoek lijkt afkomstig te zijn van een bekende oplichting. Ga alstublieft voorzichtig te werk.", + "wc_scam_warning_title": "WAARSCHUWING!", + "wc_scan_qr": "QR scannen", + "wc_sign_all_count": "Onderteken alle ${count} berichten", + "wc_signing_request_title": "Ondertekeningsverzoek", + "wc_swipe_to_approve": "Veeg om goed te keuren", + "wc_swipe_to_sign": "Veeg om te ondertekenen", + "wc_verified": "Geverifieerd", + "wc_would_like_to_connect_to": "${name} wil graag verbinding maken", + "wc_would_like_to_sign": "${name} wil graag ondertekenen", "website": "Website", "welcome": "Welkom", "welcome_subtitle_new_wallet": "Als je opnieuw wilt beginnen, tik je op Nieuwe Wallet hieronder en je kunt meteen aan de slag.", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 757b6d4f9..1404187bb 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Wymagane jest uprawnienie do korzystania z aparatu. \nWłącz je w ustawieniach aplikacji.", "cancel": "Anuluj", "cannot_manage_accounts_during_sync": "Nie możesz zarządzać kontami, gdy portfel nadal się synchronizuje. Spróbuj ponownie później.", - "cannot_verify": "Nie można zweryfikować", - "cannot_verify_description": "Nie można zweryfikować tej domeny. Przed zatwierdzeniem dokładnie sprawdź żądanie.", "card_address": "Adres:", "card_order_reset_desc": "Przywrócić kolejność kart do ustawień domyślnych?", "card_style": "Styl karty", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Wyłącz optymalizację baterii", "disableBatteryOptimizationDescription": "Czy chcesz wyłączyć optymalizację baterii, aby synchronizacja w tle działała swobodniej i płynniej?", "disabled": "Wyłączone", - "disconnect_session": "Rozłącz sesję", "discount": "Oszczędź ${value}%", "dismiss": "Odrzuć", "display": "Wyświetlanie", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Nie pokazuj tego ponownie", "do_not_show_me": "Nie pokazuj tego ponownie", "domain_looks_up": "Wyszukiwanie domen", - "domain_mismatch": "Niezgodność domeny", - "domain_mismatch_description": "Ta witryna ma domenę, która nie pasuje do nadawcy tego żądania. Zatwierdzenie może prowadzić do utraty środków.", "donation": "Darowizna", "donation_link_details": "Szczegóły linku do darowizny", "done": "Gotowe", @@ -891,8 +886,6 @@ "second_intro_title": "Jeden emoji‑adres, by rządzić nimi wszystkimi", "security": "Bezpieczeństwo", "security_and_backup": "Bezpieczeństwo i kopia zapasowa", - "security_risk": "Ryzyko bezpieczeństwa", - "security_risk_description": "Ta domena została oznaczona jako niebezpieczna przez wielu dostawców usług bezpieczeństwa. Opuść ją natychmiast, aby chronić swoje aktywa.", "seed_alert_back": "Wróć", "seed_alert_content": "Seed to jedyny sposób na odzyskanie portfela. Czy zapisałeś go?", "seed_alert_title": "Uwaga", @@ -1257,7 +1250,6 @@ "unspent_coins_details_title": "Szczegóły niewydanych monet", "unspent_coins_title": "Niewydane monety", "unsupported_asset": "Nie obsługujemy tej czynności dla tego aktywa. Utwórz lub przełącz się na portfel obsługiwanego typu aktywów.", - "update_session": "Zaktualizuj sesję", "uptime": "Czas działania", "upto": "do ${value}", "usb": "USB", @@ -1332,6 +1324,30 @@ "walletConnect": "WalletConnect", "wallets": "Portfele", "warning": "Ostrzeżenie", + "wc_approve_request_title": "Zatwierdź żądanie", + "wc_connect_request_title": "Połącz żądanie", + "wc_connected_to": "Połączono z ${name}", + "wc_max_network_fee": "Maksymalna opłata sieciowa", + "wc_message_to_sign": "Wiadomość do podpisania", + "wc_network_fee": "Opłata sieciowa", + "wc_not_verified": "Nie zweryfikowano", + "wc_pairing_list_header_subtitle": "Połącz swój portfel z WalletConnect lub zarządzaj istniejącymi aplikacjami.", + "wc_paste_link": "Wklej link do WalletConnect", + "wc_permission_other": "Inne: ${method}", + "wc_permission_request_approval": "Poproś o zatwierdzenie transakcji", + "wc_permission_sign_messages": "Podpisuj wiadomości i wpisywane dane", + "wc_permission_switch_chains": "Przełącz i dodaj łańcuchy EVM", + "wc_permission_view_balance": "Sprawdź saldo i aktywność swojego portfela", + "wc_scam_warning_message": "Wygląda na to, że to żądanie pochodzi ze znanego oszustwa. Proszę postępować ostrożnie.", + "wc_scam_warning_title": "OSTRZEŻENIE!", + "wc_scan_qr": "Zeskanuj kod QR", + "wc_sign_all_count": "Podpisz wszystkie wiadomości ${count}", + "wc_signing_request_title": "Prośba o podpisanie", + "wc_swipe_to_approve": "Przesuń, aby zatwierdzić", + "wc_swipe_to_sign": "Przesuń, aby podpisać", + "wc_verified": "Zweryfikowano", + "wc_would_like_to_connect_to": "${name} chce się połączyć", + "wc_would_like_to_sign": "${name} chce się podpisać", "website": "Strona internetowa", "welcome": "Witamy", "welcome_subtitle_new_wallet": "Jeśli chcesz zacząć od nowa, stuknij poniżej „Utwórz nowy portfel” i gotowe.", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 751a4183c..a594749f8 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "A permissão da câmera é necessária.\nAtive-a nas configurações do aplicativo.", "cancel": "Cancelar", "cannot_manage_accounts_during_sync": "Você não pode gerenciar contas enquanto a carteira ainda estiver sincronizando. Tente novamente mais tarde.", - "cannot_verify": "Não é possível verificar", - "cannot_verify_description": "Este domínio não pode ser verificado. Verifique cuidadosamente a solicitação antes de aprovar.", "card_address": "Endereço:", "card_order_reset_desc": "Restaurar a ordem do cartão para as definições padrão?", "card_style": "Estilo do cartão", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Desativar a otimização da bateria", "disableBatteryOptimizationDescription": "Deseja desativar a otimização da bateria para que a sincronização em segundo plano funcione de forma mais livre e suave?", "disabled": "Desativado", - "disconnect_session": "Desconectar sessão", "discount": "Economize ${value}%", "dismiss": "Dispensar", "display": "Exibição", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Não mostrar novamente", "do_not_show_me": "Não me mostre isso novamente", "domain_looks_up": "Consultas de domínio", - "domain_mismatch": "Incompatibilidade de domínio", - "domain_mismatch_description": "Este site tem um domínio que não corresponde ao remetente desta solicitação. Aprovar pode levar à perda de fundos.", "donation": "Doação", "donation_link_details": "Detalhes do link de doação", "done": "Concluído", @@ -894,8 +889,6 @@ "second_intro_title": "Um endereço com emoji para governar todos", "security": "Segurança", "security_and_backup": "Segurança e backup", - "security_risk": "Risco de segurança", - "security_risk_description": "Este domínio foi sinalizado como inseguro por vários provedores de segurança. Saia imediatamente para proteger seus ativos.", "seed_alert_back": "Voltar", "seed_alert_content": "A seed é a única forma de recuperar sua carteira. Você a anotou?", "seed_alert_title": "Atenção", @@ -1260,7 +1253,6 @@ "unspent_coins_details_title": "Detalhes das moedas não gastas", "unspent_coins_title": "Moedas não gastas", "unsupported_asset": "Não oferecemos suporte a esta ação para este ativo. Crie ou alterne para uma carteira de um tipo de ativo compatível.", - "update_session": "Atualizar sessão", "uptime": "Tempo de atividade", "upto": "até ${value}", "usb": "USB", @@ -1336,6 +1328,30 @@ "walletConnect": "WalletConnect", "wallets": "Carteiras", "warning": "Aviso", + "wc_approve_request_title": "Aprovar solicitação", + "wc_connect_request_title": "Solicitação de conexão", + "wc_connected_to": "Conectado a ${name}", + "wc_max_network_fee": "Taxa máxima de rede", + "wc_message_to_sign": "Mensagem para assinar", + "wc_network_fee": "Taxa de rede", + "wc_not_verified": "Não verificado", + "wc_pairing_list_header_subtitle": "Conecte sua carteira ao WalletConnect ou gerencie aplicativos existentes.", + "wc_paste_link": "Colar link do WalletConnect", + "wc_permission_other": "Outro: ${method}", + "wc_permission_request_approval": "Solicitar aprovação para transações", + "wc_permission_sign_messages": "Assinar mensagens e dados digitados", + "wc_permission_switch_chains": "Alternar e adicionar cadeias EVM", + "wc_permission_view_balance": "Veja o saldo e a atividade da sua carteira", + "wc_scam_warning_message": "Esta solicitação parece ser de um golpe conhecido. Por favor, proceda com cautela.", + "wc_scam_warning_title": "AVISO!", + "wc_scan_qr": "Digitalize QR", + "wc_sign_all_count": "Assine todas as ${count} mensagens", + "wc_signing_request_title": "Solicitação de assinatura", + "wc_swipe_to_approve": "Deslize para aprovar", + "wc_swipe_to_sign": "Deslize para assinar", + "wc_verified": "Verificado", + "wc_would_like_to_connect_to": "${name} gostaria de se conectar", + "wc_would_like_to_sign": "${name} gostaria de assinar", "website": "Website", "welcome": "Bem-vindo", "welcome_subtitle_new_wallet": "Se você quiser começar do zero, toque em Criar Nova Carteira abaixo e pronto.", diff --git a/res/values/strings_pt_BR.arb b/res/values/strings_pt_BR.arb index 8c8e8083d..7faa03a42 100644 --- a/res/values/strings_pt_BR.arb +++ b/res/values/strings_pt_BR.arb @@ -155,8 +155,6 @@ "camera_permission_is_required": "É necessária a permissão da câmera.\nAtive-a nas configurações do aplicativo.", "cancel": "Cancelar", "cannot_manage_accounts_during_sync": "Você não pode gerenciar contas enquanto a carteira está sincronizando. Tente novamente mais tarde.", - "cannot_verify": "Não é possível verificar", - "cannot_verify_description": "Este domínio não pode ser verificado. Verifique a solicitação com cuidado antes de aprovar.", "card_address": "Endereço:", "card_order_reset_desc": "Restaurar a ordem dos cartões para as configurações padrão?", "card_style": "Estilo do cartão", @@ -348,7 +346,6 @@ "disableBatteryOptimization": "Desativar otimização de bateria", "disableBatteryOptimizationDescription": "Deseja desativar a otimização de bateria para que a sincronização em segundo plano funcione melhor?", "disabled": "Desativado", - "disconnect_session": "Desconectar sessão", "discount": "Economize ${value}%", "dismiss": "Dispensar", "display": "Exibição", @@ -360,8 +357,6 @@ "do_not_show_anymore": "Não mostrar novamente", "do_not_show_me": "Não mostrar isso novamente", "domain_looks_up": "Consultas de domínio", - "domain_mismatch": "Incompatibilidade de domínio", - "domain_mismatch_description": "Este site tem um domínio que não corresponde ao remetente desta solicitação. Aprovar pode resultar em perda de fundos.", "donation": "Doação", "donation_link_details": "Detalhes do link de doação", "done": "Concluído", @@ -888,8 +883,6 @@ "second_intro_title": "Um endereço de emoji para governar todos", "security": "Segurança", "security_and_backup": "Segurança e backup", - "security_risk": "Risco de segurança", - "security_risk_description": "Este domínio foi sinalizado como inseguro por vários provedores de segurança. Saia imediatamente para proteger seus ativos.", "seed_alert_back": "Voltar", "seed_alert_content": "A seed é a única forma de recuperar sua carteira. Você a anotou?", "seed_alert_title": "Atenção", @@ -1253,7 +1246,6 @@ "unspent_coins_details_title": "Detalhes de moedas não gastas", "unspent_coins_title": "Moedas não gastas", "unsupported_asset": "Não oferecemos suporte a esta ação para este ativo. Crie ou mude para uma carteira de um tipo de ativo compatível.", - "update_session": "Atualizar sessão", "uptime": "Tempo de atividade", "upto": "até ${value}", "usb": "USB", @@ -1328,6 +1320,30 @@ "walletConnect": "WalletConnect", "wallets": "Carteiras", "warning": "Aviso", + "wc_approve_request_title": "Aprovar solicitação", + "wc_connect_request_title": "Solicitação de conexão", + "wc_connected_to": "Conectado a ${name}", + "wc_not_verified": "Não verificado", + "wc_max_network_fee": "Taxa máxima de rede", + "wc_message_to_sign": "Mensagem para assinar", + "wc_network_fee": "Taxa de rede", + "wc_pairing_list_header_subtitle": "Conecte sua carteira ao WalletConnect ou gerencie aplicativos existentes.", + "wc_paste_link": "Colar link do WalletConnect", + "wc_permission_other": "Outro: ${method}", + "wc_permission_request_approval": "Solicitar aprovação para transações", + "wc_permission_sign_messages": "Assinar mensagens e dados digitados", + "wc_permission_switch_chains": "Alternar e adicionar cadeias EVM", + "wc_permission_view_balance": "Veja o saldo e a atividade da sua carteira", + "wc_scam_warning_message": "Esta solicitação parece ser de um golpe conhecido. Por favor, proceda com cautela.", + "wc_scam_warning_title": "AVISO!", + "wc_scan_qr": "Digitalize QR", + "wc_sign_all_count": "Assine todas as ${count} mensagens", + "wc_signing_request_title": "Solicitação de assinatura", + "wc_swipe_to_approve": "Deslize para aprovar", + "wc_swipe_to_sign": "Deslize para assinar", + "wc_verified": "Verificado", + "wc_would_like_to_connect_to": "${name} gostaria de se conectar", + "wc_would_like_to_sign": "${name} gostaria de assinar", "website": "Website", "welcome": "Bem-vindo", "welcome_subtitle_new_wallet": "Se deseja começar do zero, toque em Criar Nova Carteira abaixo.", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index df01fd237..577256929 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Требуется разрешение на использование камеры.\nПожалуйста, включите его в настройках приложения.", "cancel": "Отменить", "cannot_manage_accounts_during_sync": "Вы не можете управлять аккаунтами, пока кошелек все еще синхронизируется. Пожалуйста, повторите попытку позже.", - "cannot_verify": "Не удаётся проверить", - "cannot_verify_description": "Этот домен не может быть проверен. Внимательно проверьте запрос перед подтверждением.", "card_address": "Адрес:", "card_order_reset_desc": "Восстановить порядок карточек по умолчанию?", "card_style": "Стиль карточки", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Отключить оптимизацию батареи", "disableBatteryOptimizationDescription": "Вы хотите отключить оптимизацию батареи, чтобы фоновая синхронизация работала более свободно и плавно?", "disabled": "Отключено", - "disconnect_session": "Отключить сеанс", "discount": "Сэкономьте ${value}%", "dismiss": "Закрыть", "display": "Отображение", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Больше не показывать", "do_not_show_me": "Больше не показывать", "domain_looks_up": "Поиск доменов", - "domain_mismatch": "Несоответствие домена", - "domain_mismatch_description": "У этого веб-сайта домен не совпадает с отправителем этого запроса. Подтверждение может привести к потере средств.", "donation": "Пожертвование", "donation_link_details": "Детали ссылки для пожертвований", "done": "Готово", @@ -893,8 +888,6 @@ "second_intro_title": "Один адрес с эмодзи, чтобы править ими всеми", "security": "Безопасность", "security_and_backup": "Безопасность и резервное копирование", - "security_risk": "Риск безопасности", - "security_risk_description": "Этот домен помечен как небезопасный несколькими поставщиками услуг безопасности. Немедленно покиньте его, чтобы защитить свои активы.", "seed_alert_back": "Назад", "seed_alert_content": "Сид-фраза — единственный способ восстановить ваш кошелек. Вы записали ее?", "seed_alert_title": "Внимание", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Сведения о неизрасходованных монетах", "unspent_coins_title": "Непотраченные монеты", "unsupported_asset": "Мы не поддерживаем это действие для этого актива. Пожалуйста, создайте или переключитесь на кошелек поддерживаемого типа актива.", - "update_session": "Обновить сессию", "uptime": "Время работы", "upto": "до ${value}", "usb": "USB", @@ -1334,6 +1326,30 @@ "walletConnect": "WalletConnect", "wallets": "Кошельки", "warning": "Предупреждение", + "wc_approve_request_title": "Утвердить запрос", + "wc_connect_request_title": "Запрос на подключение", + "wc_connected_to": "Подключено к ${name}", + "wc_max_network_fee": "Максимальная плата за сеть", + "wc_message_to_sign": "Сообщение для подписи", + "wc_network_fee": "Сетевая плата", + "wc_not_verified": "Не проверено", + "wc_pairing_list_header_subtitle": "Подключите свой кошелек к WalletConnect или управляйте существующими приложениями.", + "wc_paste_link": "Вставьте ссылку на WalletConnect", + "wc_permission_other": "Другое: ${method}", + "wc_permission_request_approval": "Запросить одобрение транзакций", + "wc_permission_sign_messages": "Подписывать сообщения и вводимые данные", + "wc_permission_switch_chains": "Переключение и добавление цепочек EVM", + "wc_permission_view_balance": "Просматривайте баланс и активность своего кошелька", + "wc_scam_warning_message": "Судя по всему, этот запрос поступил от известного мошенника. Пожалуйста, действуйте осторожно.", + "wc_scam_warning_title": "ПРЕДУПРЕЖДЕНИЕ!", + "wc_scan_qr": "Сканировать QR-код", + "wc_sign_all_count": "Подпишите все сообщения (${count})", + "wc_signing_request_title": "Запрос на подпись", + "wc_swipe_to_approve": "Проведите пальцем, чтобы одобрить", + "wc_swipe_to_sign": "Проведите, чтобы подписать", + "wc_verified": "Проверено", + "wc_would_like_to_connect_to": "${name} хотел бы подключиться", + "wc_would_like_to_sign": "${name} хотел бы подписать", "website": "Веб-сайт", "welcome": "Добро пожаловать", "welcome_subtitle_new_wallet": "Если вы хотите начать с чистого листа, нажмите ниже «Создать новый кошелек», и вы будете готовы к старту.", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 913e2b4b9..8829e9647 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "ต้องได้รับอนุญาตให้เข้าถึงกล้อง \nโปรดเปิดใช้งานจากการตั้งค่าแอป", "cancel": "ยกเลิก", "cannot_manage_accounts_during_sync": "คุณไม่สามารถจัดการบัญชีได้ในขณะที่กระเป๋าสตางค์ยังซิงค์อยู่ โปรดลองอีกครั้งในภายหลัง", - "cannot_verify": "ไม่สามารถยืนยันได้", - "cannot_verify_description": "ไม่สามารถยืนยันโดเมนนี้ได้ โปรดตรวจสอบคำขออย่างละเอียดก่อนอนุมัติ", "card_address": "ที่อยู่:", "card_order_reset_desc": "คืนค่าลำดับบัตรเป็นค่าเริ่มต้นหรือไม่?", "card_style": "รูปแบบการ์ด", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "ปิดใช้งานการปรับแต่งแบตเตอรี่", "disableBatteryOptimizationDescription": "คุณต้องการปิดการเพิ่มประสิทธิภาพแบตเตอรี่เพื่อให้การซิงค์ในเบื้องหลังทำงานได้อย่างอิสระและราบรื่นมากขึ้นหรือไม่?", "disabled": "ปิดใช้งาน", - "disconnect_session": "ตัดการเชื่อมต่อเซสชัน", "discount": "ประหยัด ${value}%", "dismiss": "ปิด", "display": "การแสดงผล", @@ -362,8 +359,6 @@ "do_not_show_anymore": "ไม่ต้องแสดงอีก", "do_not_show_me": "ไม่ต้องแสดงอีก", "domain_looks_up": "การค้นหาโดเมน", - "domain_mismatch": "โดเมนไม่ตรงกัน", - "domain_mismatch_description": "เว็บไซต์นี้มีโดเมนที่ไม่ตรงกับผู้ส่งคำขอนี้ การอนุมัติอาจทำให้สูญเสียเงินทุนได้", "donation": "การบริจาค", "donation_link_details": "รายละเอียดลิงก์บริจาค", "done": "เสร็จสิ้น", @@ -892,8 +887,6 @@ "second_intro_title": "ที่อยู่อีโมจิเพียงหนึ่งเดียวที่ครองทั้งหมด", "security": "ความปลอดภัย", "security_and_backup": "ความปลอดภัยและการสำรองข้อมูล", - "security_risk": "ความเสี่ยงด้านความปลอดภัย", - "security_risk_description": "โดเมนนี้ถูกระบุว่าไม่ปลอดภัยโดยผู้ให้บริการด้านความปลอดภัยหลายราย ออกจากหน้านี้ทันทีเพื่อปกป้องสินทรัพย์ของคุณ", "seed_alert_back": "ย้อนกลับ", "seed_alert_content": "Seed เป็นวิธีเดียวในการกู้คืนกระเป๋าเงินของคุณ คุณได้จดไว้แล้วหรือยัง?", "seed_alert_title": "โปรดทราบ", @@ -1258,7 +1251,6 @@ "unspent_coins_details_title": "รายละเอียดเหรียญที่ยังไม่ได้ใช้", "unspent_coins_title": "เหรียญที่ยังไม่ได้ใช้", "unsupported_asset": "เราไม่รองรับการดำเนินการนี้สำหรับสินทรัพย์นี้ โปรดสร้างหรือสลับไปยังกระเป๋าเงินของประเภทสินทรัพย์ที่รองรับ", - "update_session": "อัปเดตเซสชัน", "uptime": "ระยะเวลาทำงาน", "upto": "สูงสุด ${value}", "usb": "USB", @@ -1333,6 +1325,30 @@ "walletConnect": "WalletConnect", "wallets": "กระเป๋าเงิน", "warning": "คำเตือน", + "wc_approve_request_title": "อนุมัติคำขอ", + "wc_connect_request_title": "เชื่อมต่อคำขอ", + "wc_connected_to": "เชื่อมต่อกับ ${name}", + "wc_max_network_fee": "ค่าธรรมเนียมเครือข่ายสูงสุด", + "wc_message_to_sign": "ข้อความที่จะลงนาม", + "wc_network_fee": "ค่าธรรมเนียมเครือข่าย", + "wc_not_verified": "ไม่ได้รับการยืนยัน", + "wc_pairing_list_header_subtitle": "เชื่อมต่อกระเป๋าเงินของคุณด้วย WalletConnect หรือจัดการแอพที่มีอยู่", + "wc_paste_link": "วางลิงก์ WalletConnect", + "wc_permission_other": "อื่นๆ: ${method}", + "wc_permission_request_approval": "ขออนุมัติการทำธุรกรรม", + "wc_permission_sign_messages": "เซ็นข้อความและพิมพ์ข้อมูล", + "wc_permission_switch_chains": "สลับและเพิ่มเชน EVM", + "wc_permission_view_balance": "ดูยอดคงเหลือในกระเป๋าสตางค์และกิจกรรมของคุณ", + "wc_scam_warning_message": "ดูเหมือนว่าคำขอนี้มาจากกลโกงที่ทราบ โปรดดำเนินการด้วยความระมัดระวัง", + "wc_scam_warning_title": "คำเตือน!", + "wc_scan_qr": "สแกนคิวอาร์", + "wc_sign_all_count": "ลงนามข้อความทั้งหมด ${count}", + "wc_signing_request_title": "คำขอลงนาม", + "wc_swipe_to_approve": "ปัดเพื่ออนุมัติ", + "wc_swipe_to_sign": "ปัดเพื่อลงนาม", + "wc_verified": "ตรวจสอบแล้ว", + "wc_would_like_to_connect_to": "${name} ต้องการเชื่อมต่อ", + "wc_would_like_to_sign": "${name} ต้องการลงนาม", "website": "เว็บไซต์", "welcome": "ยินดีต้อนรับ", "welcome_subtitle_new_wallet": "หากคุณต้องการเริ่มต้นใหม่ ให้แตะ “สร้างกระเป๋าเงินใหม่” ด้านล่าง แล้วคุณก็พร้อมลุยได้ทันที", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index be9395531..e08f506e5 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Kinakailangan ang pahintulot sa camera.\nPaki-enable ito sa mga setting ng app.", "cancel": "Kanselahin", "cannot_manage_accounts_during_sync": "Hindi mo mapapamahalaan ang mga account habang nagsi-sync pa ang wallet. Pakisubukang muli mamaya.", - "cannot_verify": "Hindi ma-verify", - "cannot_verify_description": "Hindi ma-verify ang domain na ito. Suriing mabuti ang kahilingan bago aprubahan.", "card_address": "Address:", "card_order_reset_desc": "Ibalik ang pagkakasunud-sunod ng card sa mga default na setting?", "card_style": "Estilo ng Card", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "I-disable ang Pag-optimize ng Baterya", "disableBatteryOptimizationDescription": "Gusto mo bang i-disable ang battery optimization para mas malaya at mas maayos na tumakbo ang background sync?", "disabled": "Naka-disable", - "disconnect_session": "Idiskonekta ang Session", "discount": "Makatipid ng ${value}%", "dismiss": "Isara", "display": "Display", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Huwag na itong ipakita muli", "do_not_show_me": "Huwag itong ipakita muli", "domain_looks_up": "Mga pag-lookup ng domain", - "domain_mismatch": "Hindi Tugma ang Domain", - "domain_mismatch_description": "Ang website na ito ay may domain na hindi tumutugma sa nagpadala ng kahilingang ito. Ang pag-apruba ay maaaring humantong sa pagkawala ng mga pondo.", "donation": "Donasyon", "donation_link_details": "Mga detalye ng link ng donasyon", "done": "Tapos", @@ -892,8 +887,6 @@ "second_intro_title": "Isang emoji address para mamuno sa kanilang lahat", "security": "Seguridad", "security_and_backup": "Seguridad at pag-backup", - "security_risk": "Panganib sa Seguridad", - "security_risk_description": "Ang domain na ito ay na-flag bilang hindi ligtas ng maraming security provider. Umalis kaagad upang maprotektahan ang iyong mga asset.", "seed_alert_back": "Bumalik", "seed_alert_content": "Ang seed ang tanging paraan para ma-recover ang iyong wallet. Naisulat mo na ba ito?", "seed_alert_title": "Paalala", @@ -1258,7 +1251,6 @@ "unspent_coins_details_title": "Mga detalye ng mga hindi nagastos na coin", "unspent_coins_title": "Mga hindi nagastos na coin", "unsupported_asset": "Hindi namin sinusuportahan ang aksyong ito para sa asset na ito. Mangyaring gumawa o lumipat sa isang wallet na may suportadong uri ng asset.", - "update_session": "I-update ang Session", "uptime": "Uptime", "upto": "hanggang ${value}", "usb": "USB", @@ -1333,6 +1325,30 @@ "walletConnect": "WalletConnect", "wallets": "Mga Wallet", "warning": "Babala", + "wc_approve_request_title": "Aprubahan ang kahilingan", + "wc_connect_request_title": "Ikonekta ang kahilingan", + "wc_connected_to": "Nakakonekta sa ${name}", + "wc_max_network_fee": "Pinakamataas na bayad sa network", + "wc_message_to_sign": "Mensahe para pirmahan", + "wc_network_fee": "Bayad sa network", + "wc_not_verified": "Hindi Na-verify", + "wc_pairing_list_header_subtitle": "Ikonekta ang iyong wallet sa WalletConnect o pamahalaan ang mga kasalukuyang app.", + "wc_paste_link": "I-paste ang link ng WalletConnect", + "wc_permission_other": "Iba pa: ${method}", + "wc_permission_request_approval": "Humiling ng pag-apruba para sa mga transaksyon", + "wc_permission_sign_messages": "Mag-sign ng mga mensahe at nai-type na data", + "wc_permission_switch_chains": "Lumipat at magdagdag ng mga EVM chain", + "wc_permission_view_balance": "Tingnan ang balanse at aktibidad ng iyong wallet", + "wc_scam_warning_message": "Mukhang mula sa isang kilalang scam ang kahilingang ito. Mangyaring magpatuloy nang may pag-iingat.", + "wc_scam_warning_title": "BABALA!", + "wc_scan_qr": "I-scan ang QR", + "wc_sign_all_count": "Lagdaan ang lahat ng ${count} na mensahe", + "wc_signing_request_title": "Kahilingan sa pagpirma", + "wc_swipe_to_approve": "Mag-swipe para aprubahan", + "wc_swipe_to_sign": "Mag-swipe para lagdaan", + "wc_verified": "Na-verify", + "wc_would_like_to_connect_to": "Gustong kumonekta ni ${name}.", + "wc_would_like_to_sign": "Gustong lagdaan ni ${name}.", "website": "Website", "welcome": "Maligayang pagdating", "welcome_subtitle_new_wallet": "Kung gusto mong magsimula muli, i-tap ang Lumikha ng Bagong Wallet sa ibaba at handa ka nang magsimula.", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 68b93fe38..f467fd2a9 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Kamera izni gereklidir.\nLütfen uygulama ayarlarından etkinleştirin.", "cancel": "İptal", "cannot_manage_accounts_during_sync": "Cüzdan hâlâ senkronize olurken hesapları yönetemezsiniz. Lütfen daha sonra tekrar deneyin.", - "cannot_verify": "Doğrulanamıyor", - "cannot_verify_description": "Bu alan adı doğrulanamıyor. Onaylamadan önce isteği dikkatlice kontrol edin.", "card_address": "Adres:", "card_order_reset_desc": "Kart sırası varsayılan ayarlara geri yüklensin mi?", "card_style": "Kart stili", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Pil optimizasyonunu devre dışı bırakın", "disableBatteryOptimizationDescription": "Arka plan senkronizasyonunun daha serbest ve sorunsuz çalışması için pil optimizasyonunu devre dışı bırakmak ister misiniz?", "disabled": "Devre dışı", - "disconnect_session": "Oturumu Sonlandır", "discount": "${value}% tasarruf et", "dismiss": "Kapat", "display": "Görüntü", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Bunu bir daha gösterme", "do_not_show_me": "Bunu bir daha gösterme", "domain_looks_up": "Alan adı sorgulamaları", - "domain_mismatch": "Alan Adı Uyuşmazlığı", - "domain_mismatch_description": "Bu web sitesinin alan adı, bu isteğin göndericisiyle eşleşmiyor. Onaylamak, fon kaybına yol açabilir.", "donation": "Bağış", "donation_link_details": "Bağış bağlantısı ayrıntıları", "done": "Bitti", @@ -892,8 +887,6 @@ "second_intro_title": "Hepsini yöneten tek bir emoji adresi", "security": "Güvenlik", "security_and_backup": "Güvenlik ve yedekleme", - "security_risk": "Güvenlik Riski", - "security_risk_description": "Bu alan adı, birden fazla güvenlik sağlayıcısı tarafından güvensiz olarak işaretlendi. Varlıklarınızı korumak için hemen ayrılın.", "seed_alert_back": "Geri dön", "seed_alert_content": "Seed, cüzdanınızı kurtarmanın tek yoludur. Seed'i yazdınız mı?", "seed_alert_title": "Dikkat", @@ -1258,7 +1251,6 @@ "unspent_coins_details_title": "Harcanmamış coin detayları", "unspent_coins_title": "Harcanmamış coinler", "unsupported_asset": "Bu varlık için bu eylemi desteklemiyoruz. Lütfen desteklenen bir varlık türüne sahip bir cüzdan oluşturun veya o cüzdana geçiş yapın.", - "update_session": "Oturumu Güncelle", "uptime": "Çalışma süresi", "upto": "${value} değerine kadar", "usb": "USB", @@ -1333,6 +1325,30 @@ "walletConnect": "WalletConnect", "wallets": "Cüzdanlar", "warning": "Uyarı", + "wc_approve_request_title": "İsteği onayla", + "wc_connect_request_title": "Bağlantı isteği", + "wc_connected_to": "${name} ile bağlantı kuruldu", + "wc_max_network_fee": "Maksimum ağ ücreti", + "wc_message_to_sign": "İmzalanacak mesaj", + "wc_network_fee": "Ağ ücreti", + "wc_not_verified": "Doğrulanmadı", + "wc_pairing_list_header_subtitle": "Cüzdanınızı WalletConnect'e bağlayın veya mevcut uygulamaları yönetin.", + "wc_paste_link": "WalletConnect bağlantısını yapıştırın", + "wc_permission_other": "Diğer: ${method}", + "wc_permission_request_approval": "İşlemler için onay isteyin", + "wc_permission_sign_messages": "Mesajları ve yazılan verileri imzalayın", + "wc_permission_switch_chains": "EVM zincirlerini değiştirin ve ekleyin", + "wc_permission_view_balance": "Cüzdan bakiyenizi ve etkinliğinizi görüntüleyin", + "wc_scam_warning_message": "Bu isteğin bilinen bir dolandırıcılıktan geldiği anlaşılıyor. Lütfen dikkatli bir şekilde ilerleyin.", + "wc_scam_warning_title": "UYARI!", + "wc_scan_qr": "QR'yi tara", + "wc_sign_all_count": "${count} mesajın tümünü imzala", + "wc_signing_request_title": "İmza isteği", + "wc_swipe_to_approve": "Onaylamak için kaydırın", + "wc_swipe_to_sign": "İmzalamak için kaydırın", + "wc_verified": "Doğrulandı", + "wc_would_like_to_connect_to": "${name} bağlanmak istiyor", + "wc_would_like_to_sign": "${name} imzalamak istiyor", "website": "Web sitesi", "welcome": "Hoş geldiniz", "welcome_subtitle_new_wallet": "Sıfırdan başlamak istiyorsanız, aşağıdaki Yeni Cüzdan Oluştur’a dokunun ve hemen başlayın.", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 0a869100c..425541d8b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Потрібен дозвіл на використання камери.\nУвімкніть його в налаштуваннях програми.", "cancel": "Скасувати", "cannot_manage_accounts_during_sync": "Ви не можете керувати обліковими записами, поки гаманець ще синхронізується. Будь ласка, спробуйте пізніше.", - "cannot_verify": "Не вдається перевірити", - "cannot_verify_description": "Цей домен неможливо перевірити. Ретельно перевірте запит перед схваленням.", "card_address": "Адреса:", "card_order_reset_desc": "Відновити порядок карток до налаштувань за замовчуванням?", "card_style": "Стиль картки", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Вимкнути оптимізацію батареї", "disableBatteryOptimizationDescription": "Ви хочете вимкнути оптимізацію акумулятора, щоб фонова синхронізація працювала вільніше та плавніше?", "disabled": "Вимкнено", - "disconnect_session": "Від’єднати сеанс", "discount": "Заощадьте ${value}%", "dismiss": "Закрити", "display": "Екран", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Більше не показувати", "do_not_show_me": "Більше не показувати це", "domain_looks_up": "Пошук доменів", - "domain_mismatch": "Невідповідність домену", - "domain_mismatch_description": "Цей вебсайт має домен, який не відповідає відправнику цього запиту. Підтвердження може призвести до втрати коштів.", "donation": "Пожертва", "donation_link_details": "Деталі посилання для донатів", "done": "Готово", @@ -893,8 +888,6 @@ "second_intro_title": "Одна емодзі-адреса, щоб керувати всіма", "security": "Безпека", "security_and_backup": "Безпека та резервне копіювання", - "security_risk": "Ризик безпеки", - "security_risk_description": "Цей домен позначено як небезпечний кількома постачальниками засобів безпеки. Негайно залиште його, щоб захистити свої активи.", "seed_alert_back": "Назад", "seed_alert_content": "Сід-фраза — єдиний спосіб відновити ваш гаманець. Ви записали її?", "seed_alert_title": "Увага", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Деталі невитрачених монет", "unspent_coins_title": "Невитрачені монети", "unsupported_asset": "Ми не підтримуємо цю дію для цього активу. Будь ласка, створіть або перемкніться на гаманець підтримуваного типу активів.", - "update_session": "Оновити сесію", "uptime": "Час безвідмовної роботи", "upto": "до ${value}", "usb": "USB", @@ -1334,6 +1326,30 @@ "walletConnect": "WalletConnect", "wallets": "Гаманці", "warning": "Попередження", + "wc_approve_request_title": "Підтвердити запит", + "wc_connect_request_title": "Запит на підключення", + "wc_connected_to": "Підключено до ${name}", + "wc_max_network_fee": "Максимальна мережева плата", + "wc_message_to_sign": "Повідомлення для підпису", + "wc_network_fee": "Плата за мережу", + "wc_not_verified": "Не перевірено", + "wc_pairing_list_header_subtitle": "Підключіть свій гаманець до WalletConnect або керуйте існуючими програмами.", + "wc_paste_link": "Вставте посилання WalletConnect", + "wc_permission_other": "Інше: ${method}", + "wc_permission_request_approval": "Запит на схвалення транзакцій", + "wc_permission_sign_messages": "Підписуйте повідомлення та введені дані", + "wc_permission_switch_chains": "Перемикайте та додавайте ланцюжки EVM", + "wc_permission_view_balance": "Перегляньте свій баланс гаманця та активність", + "wc_scam_warning_message": "Схоже, цей запит надійшов від відомого шахрая. Будь ласка, будьте обережні.", + "wc_scam_warning_title": "УВАГА!", + "wc_scan_qr": "Сканувати QR", + "wc_sign_all_count": "Підпишіть усі ${count} повідомлень", + "wc_signing_request_title": "Підписання запиту", + "wc_swipe_to_approve": "Проведіть пальцем, щоб підтвердити", + "wc_swipe_to_sign": "Проведіть пальцем, щоб підписати", + "wc_verified": "Перевірено", + "wc_would_like_to_connect_to": "${name} хоче підключитися", + "wc_would_like_to_sign": "${name} хоче підписати", "website": "Вебсайт", "welcome": "Ласкаво просимо", "welcome_subtitle_new_wallet": "Якщо ви хочете почати з чистого аркуша, торкніться «Створити новий гаманець» нижче — і вперед.", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index bdb0906b3..f05214f25 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "کیمرہ کی اجازت درکار ہے۔ \nبراہ کرم اسے ایپ کی سیٹنگز سے فعال کریں۔", "cancel": "منسوخ کریں", "cannot_manage_accounts_during_sync": "جب تک بٹوہ ہم آہنگ ہو رہا ہے، آپ اکاؤنٹس کا نظم نہیں کر سکتے۔ براہ کرم بعد میں دوبارہ کوشش کریں۔", - "cannot_verify": "تصدیق نہیں ہو سکتی", - "cannot_verify_description": "اس ڈومین کی تصدیق نہیں کی جا سکتی۔ منظوری دینے سے پہلے درخواست کو غور سے چیک کریں۔", "card_address": "پتہ:", "card_order_reset_desc": "کارڈ کی ترتیب کو پہلے سے طے شدہ ترتیبات پر بحال کریں؟", "card_style": "کارڈ کا انداز", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "بیٹری آپٹیمائزیشن کو غیر فعال کریں", "disableBatteryOptimizationDescription": "کیا آپ پس منظر میں ہم آہنگی کو زیادہ آزادانہ اور ہموار طریقے سے چلانے کے لیے بیٹری آپٹیمائزیشن کو غیر فعال کرنا چاہتے ہیں؟", "disabled": "غیر فعال", - "disconnect_session": "سیشن منقطع کریں", "discount": "${value}% بچائیں", "dismiss": "برخاست کریں", "display": "ڈسپلے", @@ -362,8 +359,6 @@ "do_not_show_anymore": "یہ دوبارہ نہ دکھائیں", "do_not_show_me": "یہ مجھے دوبارہ نہ دکھائیں", "domain_looks_up": "ڈومین تلاش", - "domain_mismatch": "ڈومین میں عدم مطابقت", - "domain_mismatch_description": "اس ویب سائٹ کا ڈومین اس درخواست کے بھیجنے والے سے مطابقت نہیں رکھتا۔ منظوری دینے سے فنڈز ضائع ہو سکتے ہیں۔", "donation": "عطیہ", "donation_link_details": "ڈونیشن لنک کی تفصیلات", "done": "ہو گیا", @@ -894,8 +889,6 @@ "second_intro_title": "سب پر حکمرانی کے لیے ایک ایموجی ایڈریس", "security": "سیکیورٹی", "security_and_backup": "سیکیورٹی اور بیک اپ", - "security_risk": "سیکیورٹی کا خطرہ", - "security_risk_description": "اس ڈومین کو متعدد سیکیورٹی فراہم کنندگان نے غیر محفوظ قرار دیا ہے۔ اپنے اثاثوں کی حفاظت کے لیے فوراً یہاں سے نکل جائیں۔", "seed_alert_back": "واپس جائیں", "seed_alert_content": "سیڈ آپ کے والیٹ کو بحال کرنے کا واحد طریقہ ہے۔ کیا آپ نے اسے لکھ لیا ہے؟", "seed_alert_title": "توجہ", @@ -1260,7 +1253,6 @@ "unspent_coins_details_title": "غیر خرچ شدہ کوائنز کی تفصیلات", "unspent_coins_title": "غیر خرچ شدہ سکے", "unsupported_asset": "ہم اس اثاثے کے لیے اس کارروائی کی حمایت نہیں کرتے۔ براہِ کرم کسی معاون اثاثہ قسم کا والیٹ بنائیں یا اس پر سوئچ کریں۔", - "update_session": "سیشن اپ ڈیٹ کریں", "uptime": "اپ ٹائم", "upto": "${value} تک", "usb": "USB", @@ -1335,6 +1327,30 @@ "walletConnect": "WalletConnect", "wallets": "والٹس", "warning": "انتباہ", + "wc_approve_request_title": "درخواست منظور کریں۔", + "wc_connect_request_title": "رابطہ کی درخواست", + "wc_connected_to": "${name} سے منسلک", + "wc_max_network_fee": "زیادہ سے زیادہ نیٹ ورک فیس", + "wc_message_to_sign": "دستخط کرنے کا پیغام", + "wc_network_fee": "نیٹ ورک فیس", + "wc_not_verified": "تصدیق شدہ نہیں۔", + "wc_pairing_list_header_subtitle": "اپنے بٹوے کو WalletConnect کے ساتھ مربوط کریں یا موجودہ ایپس کا نظم کریں۔", + "wc_paste_link": "WalletConnect لنک پیسٹ کریں۔", + "wc_permission_other": "دیگر: ${method}", + "wc_permission_request_approval": "لین دین کے لیے منظوری کی درخواست کریں۔", + "wc_permission_sign_messages": "پیغامات پر دستخط کریں اور ڈیٹا ٹائپ کریں۔", + "wc_permission_switch_chains": "ای وی ایم چینز کو تبدیل کریں اور شامل کریں۔", + "wc_permission_view_balance": "اپنے بٹوے کا بیلنس اور سرگرمی دیکھیں", + "wc_scam_warning_message": "یہ درخواست کسی معروف اسکام کی طرف سے معلوم ہوتی ہے۔ براہ کرم احتیاط کے ساتھ آگے بڑھیں۔", + "wc_scam_warning_title": "وارننگ!", + "wc_scan_qr": "QR اسکین کریں۔", + "wc_sign_all_count": "تمام ${count} پیغامات پر دستخط کریں۔", + "wc_signing_request_title": "درخواست پر دستخط کرنا", + "wc_swipe_to_approve": "منظور کرنے کے لیے سوائپ کریں۔", + "wc_swipe_to_sign": "دستخط کرنے کے لیے سوائپ کریں۔", + "wc_verified": "تصدیق شدہ", + "wc_would_like_to_connect_to": "${name} جڑنا چاہتا ہے۔", + "wc_would_like_to_sign": "${name} دستخط کرنا چاہیں گے۔", "website": "ویب سائٹ", "welcome": "خوش آمدید", "welcome_subtitle_new_wallet": "اگر آپ نئے سرے سے شروع کرنا چاہتے ہیں تو نیچے \"نیا والیٹ بنائیں\" پر تھپتھپائیں اور آپ فوراً شروع کر دیں گے۔", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 038e04b09..2d503a179 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "Cần có quyền truy cập máy ảnh. \nVui lòng bật quyền này trong cài đặt ứng dụng.", "cancel": "Hủy", "cannot_manage_accounts_during_sync": "Bạn không thể quản lý tài khoản khi ví vẫn đang đồng bộ. Vui lòng thử lại sau.", - "cannot_verify": "Không thể xác minh", - "cannot_verify_description": "Không thể xác minh miền này. Hãy kiểm tra kỹ yêu cầu trước khi phê duyệt.", "card_address": "Địa chỉ:", "card_order_reset_desc": "Khôi phục thứ tự thẻ về cài đặt mặc định?", "card_style": "Kiểu thẻ", @@ -349,7 +347,6 @@ "disableBatteryOptimization": "Tắt tối ưu hóa pin", "disableBatteryOptimizationDescription": "Bạn có muốn tắt tối ưu hóa pin để đồng bộ hóa nền chạy tự do và mượt mà hơn không?", "disabled": "Đã tắt", - "disconnect_session": "Ngắt kết nối phiên", "discount": "Tiết kiệm ${value}%", "dismiss": "Bỏ qua", "display": "Hiển thị", @@ -361,8 +358,6 @@ "do_not_show_anymore": "Không hiển thị lại nữa", "do_not_show_me": "Không hiển thị lại", "domain_looks_up": "Tra cứu tên miền", - "domain_mismatch": "Tên miền không khớp", - "domain_mismatch_description": "Trang web này có tên miền không khớp với người gửi yêu cầu này. Việc phê duyệt có thể dẫn đến mất tiền.", "donation": "Quyên góp", "donation_link_details": "Chi tiết liên kết quyên góp", "done": "Xong", @@ -890,8 +885,6 @@ "second_intro_title": "Một địa chỉ emoji thống trị tất cả", "security": "Bảo mật", "security_and_backup": "Bảo mật và sao lưu", - "security_risk": "Rủi ro bảo mật", - "security_risk_description": "Tên miền này đã bị nhiều nhà cung cấp bảo mật gắn cờ là không an toàn. Hãy rời khỏi ngay để bảo vệ tài sản của bạn.", "seed_alert_back": "Quay lại", "seed_alert_content": "Cụm từ khôi phục là cách duy nhất để khôi phục ví của bạn. Bạn đã ghi lại chưa?", "seed_alert_title": "Chú ý", @@ -1255,7 +1248,6 @@ "unspent_coins_details_title": "Chi tiết coin chưa chi tiêu", "unspent_coins_title": "Các coin chưa sử dụng", "unsupported_asset": "Chúng tôi không hỗ trợ hành động này cho tài sản này. Vui lòng tạo hoặc chuyển sang ví thuộc loại tài sản được hỗ trợ.", - "update_session": "Cập nhật phiên", "uptime": "Thời gian hoạt động", "upto": "tối đa ${value}", "usb": "USB", @@ -1330,6 +1322,30 @@ "walletConnect": "WalletConnect", "wallets": "Ví", "warning": "Cảnh báo", + "wc_approve_request_title": "Phê duyệt yêu cầu", + "wc_connect_request_title": "Yêu cầu kết nối", + "wc_connected_to": "Đã kết nối với ${name}", + "wc_max_network_fee": "Phí mạng tối đa", + "wc_message_to_sign": "Tin nhắn để ký", + "wc_network_fee": "Phí mạng", + "wc_not_verified": "Chưa được xác minh", + "wc_pairing_list_header_subtitle": "Kết nối ví của bạn với WalletConnect hoặc quản lý các ứng dụng hiện có.", + "wc_paste_link": "Dán liên kết WalletConnect", + "wc_permission_other": "Khác: ${method}", + "wc_permission_request_approval": "Yêu cầu phê duyệt giao dịch", + "wc_permission_sign_messages": "Ký tin nhắn và đánh máy dữ liệu", + "wc_permission_switch_chains": "Chuyển đổi và thêm chuỗi EVM", + "wc_permission_view_balance": "Xem số dư và hoạt động ví của bạn", + "wc_scam_warning_message": "Yêu cầu này có vẻ là từ một trò lừa đảo đã biết. Hãy tiến hành thận trọng.", + "wc_scam_warning_title": "CẢNH BÁO!", + "wc_scan_qr": "Quét QR", + "wc_sign_all_count": "Ký tất cả tin nhắn ${count}", + "wc_signing_request_title": "Yêu cầu ký", + "wc_swipe_to_approve": "Vuốt để phê duyệt", + "wc_swipe_to_sign": "Vuốt để ký", + "wc_verified": "Đã xác minh", + "wc_would_like_to_connect_to": "${name} muốn kết nối", + "wc_would_like_to_sign": "${name} muốn ký", "website": "Trang web", "welcome": "Chào mừng", "welcome_subtitle_new_wallet": "Nếu bạn muốn bắt đầu lại từ đầu, hãy nhấn Tạo ví mới bên dưới và bạn sẽ sẵn sàng bắt đầu.", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index a1f40d41e..0d97c86dc 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "A nilo igbanilaaye kamẹra.\nJọwọ mu un ṣiṣẹ lati inu eto app.", "cancel": "Fagilé", "cannot_manage_accounts_during_sync": "O ko le ṣakoso awọn akọọlẹ lakoko ti apamọwọ ṣi n ṣe ìbámu. Jọwọ gbiyanju lẹẹkansi nígbà míì.", - "cannot_verify": "Ko le jẹrisi", - "cannot_verify_description": "A ko le jẹrisi domain yii. Ṣayẹwo ìbéèrè náà dáadáa kí o tó fọwọ́sí.", "card_address": "Àdírẹ́sì:", "card_order_reset_desc": "Ṣe o fẹ́ tún ìtòlẹ́sẹẹsẹ kaadi padà sí àwọn eto aiyipada?", "card_style": "Ara kaadi", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "Pa Imudára Batiri Dúró", "disableBatteryOptimizationDescription": "Ṣe o fẹ́ pa ìmúdàgba batiri (battery optimization) kí ìbámuṣiṣẹ́pọ̀ abẹ́lẹ̀ lè ṣiṣẹ́ ní òmìnira àti láìsí ìdènà?", "disabled": "Ti wa ni pipa", - "disconnect_session": "Ge asopọ igba", "discount": "Fipamọ́ ${value}%", "dismiss": "Pa a", "display": "Ifihan", @@ -362,8 +359,6 @@ "do_not_show_anymore": "Ma ṣe fi eyi han mọ", "do_not_show_me": "Má ṣe fi èyí hàn mí mọ́", "domain_looks_up": "Ìwádìí domain", - "domain_mismatch": "Àìbámu Dómẹ́ẹ̀nì", - "domain_mismatch_description": "Oju opo wẹẹbu yii ni domain kan tí kò bá olùránṣẹ́ ìbéèrè yìí mu. Fífi àṣẹ sí i lè fa ìpàdánù owó.", "donation": "Àfikúnrẹ́rẹ́", "donation_link_details": "Awọn alaye ìjápọ̀ ẹ̀bùn", "done": "Ti parí", @@ -893,8 +888,6 @@ "second_intro_title": "Àdírẹ́sì emoji kan láti ṣàkóso gbogbo wọn", "security": "Aàbò", "security_and_backup": "Ààbò àti ẹ̀dà afẹ́yinti", - "security_risk": "Ewu Aabo", - "security_risk_description": "Aaye yii ni a ti samisi gẹ́gẹ́ bí aláìlábòóbo nípasẹ̀ ọ̀pọ̀ àwọn olùpèsè ààbò. Kúrò níbẹ̀ lẹ́sẹ̀kẹsẹ̀ láti dáàbò bo àwọn ohun-ini rẹ.", "seed_alert_back": "Padà sẹ́yìn", "seed_alert_content": "Gbolohun ìrùgbìn (seed) ni ọ̀nà kan ṣoṣo láti gba àpamọ́wọ́ rẹ padà. Ṣé o ti kọ ọ sílẹ̀?", "seed_alert_title": "Ìkìlọ̀", @@ -1259,7 +1252,6 @@ "unspent_coins_details_title": "Àlàyé àwọn owó ẹyọ tí a kò tíì ná", "unspent_coins_title": "Àwọn owó ẹyọ tí a kò tíì na", "unsupported_asset": "A ko ṣe atilẹyin ìṣe yìí fún dukia yìí. Jọwọ ṣẹda tàbí yípadà sí apamọwọ ti iru dukia tí a ṣe atilẹyin.", - "update_session": "Ṣàtúnṣe ìpàdé", "uptime": "Akoko tí ó ti ń ṣiṣẹ́", "upto": "títí dé ${value}", "usb": "USB", @@ -1334,6 +1326,30 @@ "walletConnect": "WalletConnect", "wallets": "Àwọn wálẹ́ẹ̀tì", "warning": "Ìkìlọ̀", + "wc_approve_request_title": "fọwọsi ìbéèrè", + "wc_connect_request_title": "Sopọ ìbéèrè", + "wc_connected_to": "Sopọ si ${name}", + "wc_max_network_fee": "Max owo nẹtiwọki", + "wc_message_to_sign": "Ifiranṣẹ lati wole", + "wc_network_fee": "Nẹtiwọki ọya", + "wc_not_verified": "Ko Ṣewadii", + "wc_pairing_list_header_subtitle": "So apamọwọ rẹ pọ pẹlu WalletConnect tabi ṣakoso awọn ohun elo to wa tẹlẹ.", + "wc_paste_link": "Lẹẹmọ WalletConnect ọna asopọ", + "wc_permission_other": "Omiiran: ${method}", + "wc_permission_request_approval": "Beere alakosile fun awọn idunadura", + "wc_permission_sign_messages": "Wole awọn ifiranṣẹ ati titẹ data", + "wc_permission_switch_chains": "Yipada ki o si fi EVM dè", + "wc_permission_view_balance": "Wo iwọntunwọnsi apamọwọ rẹ ati iṣẹ ṣiṣe", + "wc_scam_warning_message": "Ibeere yii han lati wa lati itanjẹ ti a mọ. Jọwọ tẹsiwaju pẹlu iṣọra.", + "wc_scam_warning_title": "IKILO!", + "wc_scan_qr": "Ṣayẹwo QR", + "wc_sign_all_count": "Wole gbogbo awọn ifiranṣẹ ${count}", + "wc_signing_request_title": "Ìbéèrè wíwọlé", + "wc_swipe_to_approve": "Ra lati fọwọsi", + "wc_swipe_to_sign": "Ra lati wole", + "wc_verified": "Jẹrisi", + "wc_would_like_to_connect_to": "${name} yoo fẹ lati sopọ", + "wc_would_like_to_sign": "${name} yoo fẹ lati fowo si", "website": "Oju opo wẹẹbu", "welcome": "Kaabọ", "welcome_subtitle_new_wallet": "Tí o bá fẹ́ bẹ̀rẹ̀ láti ìbẹ̀rẹ̀, tẹ Ṣẹda Apamọwọ Tuntun ní isalẹ, ìwọ yóò sì ti bẹ̀rẹ̀ lẹ́sẹ̀kẹsẹ̀.", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 22ed313bc..af8b72c00 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -156,8 +156,6 @@ "camera_permission_is_required": "需要相机权限。\n请在应用设置中启用。", "cancel": "取消", "cannot_manage_accounts_during_sync": "钱包仍在同步时,您无法管理账户。请稍后再试。", - "cannot_verify": "无法验证", - "cannot_verify_description": "无法验证此域名。批准前请仔细检查请求。", "card_address": "地址:", "card_order_reset_desc": "将卡片顺序恢复为默认设置?", "card_style": "卡片样式", @@ -350,7 +348,6 @@ "disableBatteryOptimization": "关闭电池优化", "disableBatteryOptimizationDescription": "您是否要禁用电池优化,以便让后台同步运行得更自由、更顺畅?", "disabled": "已禁用", - "disconnect_session": "断开会话", "discount": "节省 ${value}%", "dismiss": "关闭", "display": "显示", @@ -362,8 +359,6 @@ "do_not_show_anymore": "不再显示", "do_not_show_me": "不再显示此提示", "domain_looks_up": "域名查询", - "domain_mismatch": "域名不匹配", - "domain_mismatch_description": "该网站的域名与此请求的发送方不匹配。批准可能导致资金损失。", "donation": "捐赠", "donation_link_details": "捐赠链接详情", "done": "完成", @@ -892,8 +887,6 @@ "second_intro_title": "一个表情符号地址,统领所有地址", "security": "安全", "security_and_backup": "安全与备份", - "security_risk": "安全风险", - "security_risk_description": "该域名已被多家安全服务提供商标记为不安全。请立即离开以保护您的资产。", "seed_alert_back": "返回", "seed_alert_content": "助记词是恢复钱包的唯一方式。您是否已将其写下?", "seed_alert_title": "注意", @@ -1258,7 +1251,6 @@ "unspent_coins_details_title": "未花费币详情", "unspent_coins_title": "未花费币", "unsupported_asset": "我们不支持对该资产执行此操作。请创建或切换到受支持的资产类型的钱包。", - "update_session": "更新会话", "uptime": "正常运行时间", "upto": "最高可达 ${value}", "usb": "USB", @@ -1333,6 +1325,30 @@ "walletConnect": "WalletConnect", "wallets": "钱包", "warning": "警告", + "wc_approve_request_title": "批准请求", + "wc_connect_request_title": "连接请求", + "wc_connected_to": "连接到${name}", + "wc_max_network_fee": "最高网络费用", + "wc_message_to_sign": "留言要签名", + "wc_network_fee": "网络费", + "wc_not_verified": "未驗證", + "wc_pairing_list_header_subtitle": "将您的钱包与 WalletConnect 连接或管理现有应用程序。", + "wc_paste_link": "粘贴 WalletConnect 链接", + "wc_permission_other": "其他:${method}", + "wc_permission_request_approval": "请求批准交易", + "wc_permission_sign_messages": "签署消息和键入的数据", + "wc_permission_switch_chains": "切换并添加EVM链", + "wc_permission_view_balance": "查看您的钱包余额和活动", + "wc_scam_warning_message": "此请求似乎来自一个已知的骗局。请谨慎行事。", + "wc_scam_warning_title": "警告!", + "wc_scan_qr": "扫描二维码", + "wc_sign_all_count": "签署所有 ${count} 条消息", + "wc_signing_request_title": "签署请求", + "wc_swipe_to_approve": "滑动即可批准", + "wc_swipe_to_sign": "滑动即可签名", + "wc_verified": "已验证", + "wc_would_like_to_connect_to": "${name} 想要连接", + "wc_would_like_to_sign": "${name} 想要签名", "website": "网站", "welcome": "欢迎", "welcome_subtitle_new_wallet": "如果你想重新开始,请点击下方的【创建新钱包】,即可立即开始。", diff --git a/res/values/strings_zh_tw.arb b/res/values/strings_zh_tw.arb index c2ea4aa0c..6e8de98b0 100644 --- a/res/values/strings_zh_tw.arb +++ b/res/values/strings_zh_tw.arb @@ -132,8 +132,6 @@ "camera_consent": "您的相機將由 ${provider} 用於拍攝影像,以供身分識別之用。詳情請查看他們的隱私權政策。", "camera_permission_is_required": "需要相機權限。\n請在應用程式設定中啟用。", "cancel": "取消", - "cannot_verify": "無法驗證", - "cannot_verify_description": "此網域無法驗證。核准之前,請仔細檢查請求內容。", "card_address": "地址:", "cardholder_agreement": "持卡人協議", "cards": "卡片", @@ -301,7 +299,6 @@ "disableBatteryOptimization": "停用電池最佳化", "disableBatteryOptimizationDescription": "您是否要停用電池最佳化,以便讓背景同步更自由、更順暢地執行?", "disabled": "已停用", - "disconnect_session": "中斷連線階段", "discount": "省下 ${value}%", "display_settings": "顯示設定", "displayable": "可顯示", @@ -310,8 +307,6 @@ "do_not_share_warning_text": "請勿與任何人分享這些資訊,包括客服支援。\n\n您的資金可能而且一定會被盜!", "do_not_show_me": "不再顯示此訊息", "domain_looks_up": "網域查詢", - "domain_mismatch": "網域不符", - "domain_mismatch_description": "此網站的網域與此請求的發送者不相符。核准可能導致資金損失。", "donation_link_details": "捐款連結詳情", "done": "完成", "duress_pin_description": "這將設定胁迫 PIN,這是一項大多數使用者不應使用的進階功能。此 PIN 碼僅應在您身處危險時使用。使用此 PIN 碼後,您的所有錢包都將被刪除,因此請在使用前確保已備份所有助記詞。", @@ -771,8 +766,6 @@ "second_intro_title": "一個表情符號地址,統御所有地址", "security": "安全性", "security_and_backup": "安全性與備份", - "security_risk": "安全風險", - "security_risk_description": "此網域已被多家安全服務提供者標記為不安全。請立即離開以保護您的資產。", "seed_alert_back": "返回", "seed_alert_content": "助記詞是恢復錢包的唯一方式。您已經把它寫下來了嗎?", "seed_alert_title": "注意", @@ -1080,7 +1073,6 @@ "unspent_coins_details_title": "未花費幣詳細資訊", "unspent_coins_title": "未花費幣", "unsupported_asset": "我們不支援對此資產執行此操作。請建立或切換至支援的資產類型錢包。", - "update_session": "更新會話", "uptime": "正常運行時間", "upto": "最高可達 ${value}", "usb": "USB", @@ -1144,6 +1136,30 @@ "walletConnect": "WalletConnect", "wallets": "錢包", "warning": "警告", + "wc_approve_request_title": "批准請求", + "wc_connect_request_title": "連線請求", + "wc_connected_to": "已連線至 ${name}", + "wc_not_verified": "未驗證", + "wc_max_network_fee": "最高網路費用", + "wc_message_to_sign": "要簽署的訊息", + "wc_network_fee": "網路費用", + "wc_pairing_list_header_subtitle": "將您的錢包與 WalletConnect 連接或管理現有應用程式。", + "wc_paste_link": "貼上 WalletConnect 連結", + "wc_permission_other": "其他:${method}", + "wc_permission_request_approval": "請求批准交易", + "wc_permission_sign_messages": "簽署訊息和類型化資料", + "wc_permission_switch_chains": "切換並新增 EVM 鏈", + "wc_permission_view_balance": "查看您的錢包餘額和活動", + "wc_scam_warning_message": "此請求似乎來自已知的詐騙。請謹慎處理。", + "wc_scam_warning_title": "警告!", + "wc_scan_qr": "掃描 QR Code", + "wc_sign_all_count": "簽署全部 ${count} 條訊息", + "wc_signing_request_title": "簽署請求", + "wc_swipe_to_approve": "滑動以批准", + "wc_swipe_to_sign": "滑動以簽署", + "wc_verified": "已驗證", + "wc_would_like_to_connect_to": "${name} 想要連線", + "wc_would_like_to_sign": "${name} 想要簽署", "website": "網站", "welcome": "歡迎", "welcome_subtitle_new_wallet": "如果你想重新開始,點擊下方的【建立新錢包】,馬上就能開始。", From c90038feac6f768c52ae8b17cb3f4827f9946e03 Mon Sep 17 00:00:00 2001 From: rottenwheel <92872541+rottenwheel@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:29:19 -0500 Subject: [PATCH 3/6] help: update Discord server invite link (#3266) Updates Discord server invite link found within the application settings to redirect visitors to up-to-date invite link, that is found on main cakewallet.com site as well. Co-authored-by: Omar Hatem From cfe332a8783319eda553eca2a485df01e6013faa Mon Sep 17 00:00:00 2001 From: Serhii <17529954+serhii-bor@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:13:07 +0300 Subject: [PATCH 4/6] fix for Mastadon lookup (#3272) --- lib/entities/parse_address_from_domain.dart | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 0eea5a85d..7a6a47bc8 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -216,6 +216,14 @@ class AddressResolver { return emailRegex.hasMatch(address); } + String _stripHtmlTags(String value) { + return value + .replaceAll(RegExp(r'', caseSensitive: false), '\n') + .replaceAll(RegExp(r'', caseSensitive: false), '\n') + .replaceAll(RegExp(r'<[^>]*>'), '') + .trim(); + } + Future resolve(BuildContext context, String text, CryptoCurrency currency) async { final ticker = currency.title; try { @@ -348,7 +356,8 @@ class AddressResolver { await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName); if (mastodonUser != null) { - String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency, requireSurroundingWhitespaces: true); + final mastodonNote = _stripHtmlTags(mastodonUser.note); + String? addressFromBio = extractAddressByType(raw: mastodonNote, type: currency, requireSurroundingWhitespaces: true); if (addressFromBio != null && addressFromBio.isNotEmpty) { return ParsedAddress.fetchMastodonAddress( @@ -361,7 +370,7 @@ class AddressResolver { await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName); if (pinnedPosts.isNotEmpty) { - final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n'); + final userPinnedPostsText = pinnedPosts.map((item) => _stripHtmlTags(item.content)).join('\n'); String? addressFromPinnedPost = extractAddressByType(raw: userPinnedPostsText, type: currency, requireSurroundingWhitespaces: true); From 6c1ee1651349cf02ee05c1edc26137118f59bbf9 Mon Sep 17 00:00:00 2001 From: malik1004x Date: Mon, 1 Jun 2026 21:32:12 +0200 Subject: [PATCH 5/6] new-node-list-ui-on-dev (#3259) * node list new ui # Conflicts: # cw_core/lib/node_list.dart * increase touch target sizes * fix scroll behavior * fix concurrent modification * Update lib/src/screens/settings/manage_nodes_page.dart [skip ci] * Update lib/src/screens/nodes/node_create_or_edit_page.dart [skip ci] * run speed test in parallel not sequential --------- Co-authored-by: Omar Hatem --- cw_core/lib/node.dart | 26 ++- cw_core/lib/node_list.dart | 8 +- lib/di.dart | 16 +- .../action_row/coin_action_row.dart | 9 +- lib/new-ui/widgets/modal_header.dart | 9 +- .../nodes/node_create_or_edit_page.dart | 134 ++++++------ lib/src/screens/nodes/node_share_page.dart | 82 ++++++++ .../screens/nodes/widgets/node_list_row.dart | 170 +++++++-------- .../screens/settings/manage_nodes_page.dart | 197 +++++++++--------- .../node_create_or_edit_view_model.dart | 19 +- .../node_list/node_list_view_model.dart | 94 ++++++++- .../node_list/pow_node_list_view_model.dart | 83 -------- pubspec_base.yaml | 1 + res/pictures/default_node.svg | 3 + .../node_speed_badges/disconnected.svg | 4 + res/pictures/node_speed_badges/fast.svg | 3 + res/pictures/node_speed_badges/medium.svg | 4 + res/pictures/node_speed_badges/slow.svg | 7 + res/pictures/official_node.svg | 3 + res/values/strings_en.arb | 4 + 20 files changed, 472 insertions(+), 404 deletions(-) create mode 100644 lib/src/screens/nodes/node_share_page.dart create mode 100644 res/pictures/default_node.svg create mode 100644 res/pictures/node_speed_badges/disconnected.svg create mode 100644 res/pictures/node_speed_badges/fast.svg create mode 100644 res/pictures/node_speed_badges/medium.svg create mode 100644 res/pictures/node_speed_badges/slow.svg create mode 100644 res/pictures/official_node.svg diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 7550fc626..b7f2703b8 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -132,6 +132,26 @@ class Node { }; } + Node.fromUri(Uri uri, WalletType type) + : id = 0, + uriRaw = + "${uri.host}${uri.hasPort ? ':${uri.port}' : (uri.queryParameters['port']?.isNotEmpty == true ? ':${uri.queryParameters['port']}' : '')}", + path = uri.path, + login = uri.userInfo.contains(':') + ? uri.userInfo.split(':')[0] + : uri.queryParameters['username'], + password = uri.userInfo.contains(':') + ? uri.userInfo.split(':')[1] + : uri.queryParameters['password'], + useSSL = uri.queryParameters['protocol'] == 'https', + trusted = uri.queryParameters['trusted'] == 'true', + isPow = false, + isEnabledForAutoSwitching = false, + isOfficial = false, + isBuiltin = false, + isDefault = false, + typeRaw = serializeToInt(type); + Future delete() async { return await db!.delete(tableName, where: '${selfIdColumn} = ?', whereArgs: [id]); } @@ -378,14 +398,14 @@ class Node { final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName}; try { - final client = ProxyWrapper().getHttpIOClient(); final jsonBody = json.encode(body); - final response = await client.post( - rpcUri, + final response = await ProxyWrapper().post( + clearnetUri: rpcUri, headers: {'Content-Type': 'application/json'}, body: jsonBody, + allowMitmMoneroBypassSSLCheck: true ); // Check if we received a 401 Unauthorized response if (response.statusCode == 401) { diff --git a/cw_core/lib/node_list.dart b/cw_core/lib/node_list.dart index e46f0eee4..2c5007bea 100644 --- a/cw_core/lib/node_list.dart +++ b/cw_core/lib/node_list.dart @@ -1,5 +1,4 @@ import 'package:flutter/services.dart'; -import 'package:hive/hive.dart'; import "package:yaml/yaml.dart"; import 'package:cw_core/node.dart'; import 'package:cw_core/wallet_type.dart'; @@ -78,6 +77,7 @@ Future> loadDefaultNodes(WalletType type) async { map["isBuiltin"] = 1; map["isOfficial"] = ((map["isOfficial"] as bool? ?? false) ? 1 : 0); map["isDefault"] = ((map["isDefault"] as bool? ?? false) ? 1 : 0); + final node = Node.fromMap(Map.from(map)); node.type = type; nodes.add(node); @@ -94,7 +94,11 @@ Future> loadDefaultNanoPowNodes() async { for (final raw in loadedPowNodes) { if (raw is Map) { - final node = Node.fromMap(Map.from(raw)); + final map = Map.from(raw); + map["isBuiltin"] = 1; + map["isOfficial"] = ((map["isOfficial"] as bool? ?? false) ? 1 : 0); + map["isDefault"] = ((map["isDefault"] as bool? ?? false) ? 1 : 0); + final node = Node.fromMap(Map.from(map)); node.isPow = true; node.type = WalletType.nano; nodes.add(node); diff --git a/lib/di.dart b/lib/di.dart index aa42887d7..991e2f7ee 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1118,9 +1118,9 @@ Future setup({ getIt.registerFactory(() => AddressListPage(getIt.get())); - getIt.registerLazySingleton(() { + getIt.registerFactoryParam((isPow, _) { final appStore = getIt.get(); - return NodeListViewModel(appStore); + return NodeListViewModel(appStore, isPow); }); getIt.registerFactoryParam( @@ -1131,11 +1131,6 @@ Future setup({ ), ); - getIt.registerLazySingleton(() { - final appStore = getIt.get(); - return PowNodeListViewModel(appStore); - }); - getIt.registerFactory(() => ConnectionSyncViewModel(getIt.get(), getIt.get().wallet!)); getIt.registerFactory(() => ConnectionSyncPage(getIt.get())); @@ -1178,8 +1173,6 @@ Future setup({ final Node? editingNode = args['editingNode'] as Node?; return NodeCreateOrEditViewModel( isPow, - getIt.get(), - getIt.get(), type, getIt.get(), editingNode: editingNode @@ -1700,10 +1693,7 @@ Future setup({ ); getIt.registerFactoryParam((bool isPow, _) { - if (isPow) { - return ManageNodesPage(isPow, powNodeListViewModel: getIt.get(), dashboardViewModel: getIt.get()); - } - return ManageNodesPage(isPow, nodeListViewModel: getIt.get(), dashboardViewModel: getIt.get()); + return ManageNodesPage(isPow, nodeListViewModel: getIt.get(param1: isPow), dashboardViewModel: getIt.get()); }); getIt.registerFactory( diff --git a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart index 10a411c2f..b700f4aab 100644 --- a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart +++ b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart @@ -17,10 +17,10 @@ import 'package:cake_wallet/view_model/send/send_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/lnurl.dart'; +import 'package:cw_core/node.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import '../../../pages/receive_page.dart'; @@ -198,7 +198,12 @@ class CoinActionRow extends StatelessWidget { Navigator.of(context) .pushNamed(Routes.walletConnectConnectionsListing, arguments: Uri.parse(code)); return; - } else { + } else if(["http", "https", "tcp"].contains(Uri.tryParse(code)?.scheme)) { + Navigator.of(context).pushNamed(Routes.newNode, + arguments: {"editingNode": Node.fromUri(Uri.parse(code), walletType)}); + return; + + }else { final uri = Uri.tryParse(code); if (uri == null) return; req = PaymentRequest.fromUri(uri); diff --git a/lib/new-ui/widgets/modal_header.dart b/lib/new-ui/widgets/modal_header.dart index 46c292cea..6bef2bba5 100644 --- a/lib/new-ui/widgets/modal_header.dart +++ b/lib/new-ui/widgets/modal_header.dart @@ -4,11 +4,12 @@ import 'package:flutter_svg/svg.dart'; class ModalHeader extends StatelessWidget { const ModalHeader( - {super.key, required this.iconPath, required this.message, required this.title}); + {super.key, required this.iconPath, required this.message, required this.title, this.bottomWidget}); final String iconPath; final String title; final String message; + final Widget? bottomWidget; @override Widget build(BuildContext context) { @@ -41,7 +42,11 @@ class ModalHeader extends StatelessWidget { fontWeight: FontWeight.w400, color: Theme.of(context).colorScheme.onSurfaceVariant), textAlign: TextAlign.center, - )) + )), + if(bottomWidget != null) ...[ + Padding(padding: EdgeInsets.symmetric(horizontal: 18), child: Container(height:1, color: Theme.of(context).colorScheme.surfaceContainerHigh),), + bottomWidget! + ] ], ), ), 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 92bccb27a..0e45c51f4 100644 --- a/lib/src/screens/nodes/node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/node_create_or_edit_page.dart @@ -1,20 +1,20 @@ import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; +import 'package:cake_wallet/src/screens/nodes/node_share_page.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/alert_with_two_actions.dart'; -import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:mobx/mobx.dart'; class NodeCreateOrEditPage extends StatefulWidget { @@ -76,6 +76,14 @@ class _NodeCreateOrEditPageState extends State { }, ); + + reaction((_)=>widget.nodeCreateOrEditViewModel.state, (state) async { + if(state is ExecutedSuccessfullyState) { + await Future.delayed(Duration(milliseconds: 100)); + if (mounted) Navigator.of(context).pop(widget.nodeCreateOrEditViewModel.editingNode); + } + + }); } @override @@ -90,86 +98,62 @@ class _NodeCreateOrEditPageState extends State { title: widget.editingNode != null ? S.current.edit_node : S.current.node_new, leadingIcon: Icon(Icons.arrow_back_ios_new), onLeadingPressed: Navigator.of(context).pop, - trailingIcon: CakeImageWidget(imageUrl: - "assets/new-ui/scan.svg",width:24,height:24, - colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), - ), - onTrailingPressed: () => widget.nodeCreateOrEditViewModel.scanQRCodeForNewNode(context), + trailingIcon: Icon(Icons.check), + onTrailingPressed: () async { + if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) { + return; + } + + await widget.nodeCreateOrEditViewModel + .save(saveAsCurrent: widget.isSelected ?? false); + }, ), Expanded( child: KeyboardHideOverlay( child: Container( padding: const EdgeInsets.only(left: 18, right: 18), - child: ScrollableWithBottomSection( - contentPadding: const EdgeInsets.only(bottom: 24.0, top: 8), - content: NodeForm( + child: Column( + spacing: 18, + children: [NodeForm( key: _nodeFormKey, nodeViewModel: widget.nodeCreateOrEditViewModel, ), - bottomSectionPadding: const EdgeInsets.only(bottom: 24), - bottomSection: Observer( - builder: (_) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Container( - padding: const EdgeInsets.only(right: 8.0), - child: LoadingPrimaryButton( - onPressed: () async { - final confirmed = await showPopUp( - context: context, - builder: (context) => AlertWithTwoActions( - alertTitle: S.of(context).remove_node, - alertContent: S.of(context).remove_node_message, - rightButtonText: S.of(context).remove, - leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.pop(context, true), - actionLeftButton: () => Navigator.pop(context, false), - ), - ) ?? - false; + NewListSections(sections: {"":[ + if(widget.editingNode != null) - if (confirmed) { - await widget.nodeCreateOrEditViewModel.delete(editingNode: widget.editingNode!); - Navigator.of(context).pop(); - } - }, - text: S.of(context).delete, - isDisabled: widget.editingNode == null || - !widget.nodeCreateOrEditViewModel.isReady || - widget.editingNode!.isBuiltin || - (widget.isSelected ?? false), - color: Theme.of(context).colorScheme.errorContainer, - textColor: Theme.of(context).colorScheme.onErrorContainer, - ), - ), - ), - Flexible( - child: Container( - padding: const EdgeInsets.only(left: 8.0), - child: PrimaryButton( - onPressed: () async { - if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) { - return; - } + ListItemRegularRow(keyValue: "share", label: S.of(context).share_this_node, foregroundColor: Theme.of(context).colorScheme.primary, showArrow: false, onTap: (){ - await widget.nodeCreateOrEditViewModel.save( - saveAsCurrent: widget.isSelected ?? false); - if (context.mounted) { - Navigator.of(context).pop(); - } - }, - text: S.of(context).save, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - isDisabled: (!widget.nodeCreateOrEditViewModel.isReady) || - (widget.nodeCreateOrEditViewModel.connectionState is IsExecutingState), - ), - ), - ), - ], - ), - ), + Navigator.of(context).push(CupertinoPageRoute(builder: (context)=>NodeSharePage(uri: widget.editingNode!.uri, currency: walletTypeToCryptoCurrency(widget.type!)))); + + + }), + if(!(widget.editingNode == null || + !widget.nodeCreateOrEditViewModel.isReady || + widget.editingNode!.isBuiltin || + (widget.isSelected ?? false))) + ListItemRegularRow(keyValue: "delete", label: S.of(context).delete_this_node, foregroundColor: Theme.of(context).colorScheme.errorContainer, showArrow: false, onTap: ()async{ + final confirmed = await showPopUp( + context: context, + builder: (context) => AlertWithTwoActions( + alertTitle: S.of(context).remove_node, + alertContent: S.of(context).remove_node_message, + rightButtonText: S.of(context).remove, + leftButtonText: S.of(context).cancel, + actionRightButton: () => Navigator.pop(context, true), + actionLeftButton: () => Navigator.pop(context, false), + ), + ) ?? + false; + + if (confirmed) { + await widget.nodeCreateOrEditViewModel.delete(editingNode: widget.editingNode!); + Navigator.of(context).pop(); + } + + }) + ]}) + + ] ), ), ), diff --git a/lib/src/screens/nodes/node_share_page.dart b/lib/src/screens/nodes/node_share_page.dart new file mode 100644 index 000000000..1319d8426 --- /dev/null +++ b/lib/src/screens/nodes/node_share_page.dart @@ -0,0 +1,82 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; + +class NodeSharePage extends StatelessWidget { + const NodeSharePage({super.key, required this.uri, required this.currency}); + + final Uri uri; + final CryptoCurrency currency; + + @override + Widget build(BuildContext context) { + final qrSize = MediaQuery.of(context).size.width * 0.786; + final padding = 16.0; + return Container( + color: Theme.of(context).colorScheme.surface, + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + ModalTopBar( + title: S.of(context).share, + leadingIcon: Icon(Icons.arrow_back_ios_new), + onLeadingPressed: Navigator.of(context).pop, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 40, + children: [ + Stack(alignment: Alignment.center, children: [ + SizedBox( + width: qrSize, + height: qrSize, + child: ClipRRect( + borderRadius: BorderRadius.circular(18), + child: QrImage( + size: qrSize - padding * 2, + embeddedImagePath: "", + data: uri.toString())), + ), + SizedBox( + width: 60, + height: 60, + child: Stack( + children: [ + CakeImageWidget( + imageUrl: "assets/new-ui/settings_row_icons/nodes.svg", + width: 54, + height: 54, + ), + Align( + alignment: Alignment.bottomRight, + child: CakeImageWidget( + imageUrl: currency.iconPath, + width: 24, + height: 24, + )) + ], + ), + ), + ]), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 36.0), + child: Text(S.of(context).node_share_desc, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurfaceVariant)), + ) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/nodes/widgets/node_list_row.dart b/lib/src/screens/nodes/widgets/node_list_row.dart index 2e43ad3ff..5be027481 100644 --- a/lib/src/screens/nodes/widgets/node_list_row.dart +++ b/lib/src/screens/nodes/widgets/node_list_row.dart @@ -1,115 +1,55 @@ import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/screens/nodes/widgets/node_indicator.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; import 'package:cw_core/node.dart'; import 'package:flutter/material.dart'; -class NodeListRow extends StandardListRow { +class NodeListRow extends StatelessWidget { NodeListRow( - {required String title, - String? subtitle, - required this.node, - required void Function(BuildContext context) onTap, - required bool isSelected, - required this.isPow}) - : super(title: title, onTap: onTap, isSelected: isSelected, subtitle: subtitle); + {required this.node, + required this.onTap, + required this.onEditComplete, + required this.isSelected, + required this.isPow, this.speed}); final Node node; final bool isPow; + final VoidCallback onTap; + final Future Function(Object?) onEditComplete; + final bool isSelected; + final NodeSpeed? speed; @override Widget build(BuildContext context) { final leading = buildLeading(context); final trailing = buildTrailing(context); - return Container( - padding: EdgeInsets.only(left: 18, right: 18, top: 2, bottom: 2), - margin: EdgeInsets.only(top: 2, bottom: 2), - child: FilledButton( - onPressed: () => onTap?.call(context), - style: FilledButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.surfaceContainer, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20)), - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - leading, - buildCenter(context, hasLeftOffset: true), - trailing, - ], - ), - ), - ), - ); - } - @override - Widget buildLeading(BuildContext context) { - return FutureBuilder( - future: node.requestNode(), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.done: - return NodeIndicator(isLive: snapshot.data ?? false); - default: - return NodeIndicator(isLive: false); - } - }); - } - - @override - Widget buildTrailing(BuildContext context) { + final hasLabel = node.label != null && node.label!.isNotEmpty; + final uriText = "${node.uri.host}:${node.uri.port}"; return GestureDetector( - onTap: () => Navigator.of(context).pushNamed( - isPow ? Routes.newPowNode : Routes.newNode, - arguments: {'editingNode': node, 'isSelected': isSelected}, - ), - child: Container( - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.surface, - ), - child: Icon( - Icons.edit, - size: 14, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ); - } -} - -class NodeHeaderListRow extends StandardListRow { - NodeHeaderListRow({required String title, required void Function(BuildContext context) onTap}) - : super(title: title, onTap: onTap, isSelected: false); - - @override - Widget build(BuildContext context) { - final leading = buildLeading(context); - final trailing = buildTrailing(context); - return Container( - height: 56, - padding: EdgeInsets.only(left: 12, right: 12, top: 2, bottom: 2), - child: TextButton( - onPressed: () => onTap?.call(context), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.surface), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - ), - ), - ), + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 12), child: Row( + spacing: 12, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (leading != null) leading, - buildCenter(context, hasLeftOffset: leading != null), + leading, + Expanded( + child: Column(crossAxisAlignment:CrossAxisAlignment.start,spacing:4,children: [ + Row( + spacing: 4, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text(hasLabel ? node.label! : uriText, style: TextStyle(fontSize: 12),), + if(badgePath != null) + CakeImageWidget(imageUrl: badgePath, colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary,BlendMode.srcIn),) + ], + ), + if(hasLabel) Text(uriText, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),), + ],), + ), trailing, ], ), @@ -117,12 +57,44 @@ class NodeHeaderListRow extends StandardListRow { ); } - @override + Widget buildLeading(BuildContext context) => isSelected + ? Padding( + padding: const EdgeInsets.all(4.0), + child: Icon( + Icons.check, + size: 16, + color: Theme.of(context).colorScheme.primary, + ), + ) + : SizedBox(width: 24, height: 24); + Widget buildTrailing(BuildContext context) { - return SizedBox( - width: 20, - child: Icon(Icons.add, - color: Theme.of(context).colorScheme.onSurfaceVariant, size: 24.0), + return Row( + children: [ + if (speed != null) CakeImageWidget(imageUrl: speed!.iconPath, width: 24, height: 24), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () async { + final res = await Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode, + arguments: {'editingNode': node, 'isSelected': isSelected}); + await onEditComplete(res); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CakeImageWidget( + imageUrl: "assets/new-ui/3dots_vertical.svg", + colorFilter: + ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn), + ), + ), + ) + ], ); } + + String? get badgePath { + if(node.isOfficial) return "assets/new-ui/official_node.svg"; + if(node.isBuiltin) return "assets/new-ui/default_node.svg"; + return null; + } } diff --git a/lib/src/screens/settings/manage_nodes_page.dart b/lib/src/screens/settings/manage_nodes_page.dart index 02c2e4e6f..9f69c6587 100644 --- a/lib/src/screens/settings/manage_nodes_page.dart +++ b/lib/src/screens/settings/manage_nodes_page.dart @@ -1,42 +1,42 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/modal_header.dart'; import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart'; import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.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/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; -import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; +import 'package:cw_core/node.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; -class ManageNodesPage extends BasePage { - ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel, this.dashboardViewModel}) - : assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null)), - assert(powNodeListViewModel == null || nodeListViewModel == null); +class ManageNodesPage extends StatefulWidget { + ManageNodesPage(this.isPow, {required this.nodeListViewModel, this.dashboardViewModel}); final DashboardViewModel? dashboardViewModel; - final NodeListViewModel? nodeListViewModel; - final PowNodeListViewModel? powNodeListViewModel; + final NodeListViewModel nodeListViewModel; final bool isPow; @override - bool get hideAppBar => true; + State createState() => _ManageNodesPageState(); +} + +class _ManageNodesPageState extends State { + @override + void initState() { + super.initState(); + Future.delayed(Duration(milliseconds: 300)) + .then((_) => widget.nodeListViewModel.speedTestNodes()); + } @override - String get title => S.current.manage_nodes; - - @override - Widget body(BuildContext context) { + Widget build(BuildContext context) { return ModalPageWrapper( - horizontalPadding: 0, topBar: ModalTopBar( title: S.of(context).manage_nodes, leadingIcon: Icon(Icons.arrow_back_ios_new), @@ -44,87 +44,96 @@ class ManageNodesPage extends BasePage { trailingWidget: Row( spacing: 8, children: [ - if (dashboardViewModel?.hasRescan ?? true) - ModernButton( + Observer( + builder: (_) => ModernButton( size: 36, - icon: Icon(Icons.history), - onPressed: () => Navigator.of(context).pushNamed(Routes.rescan)), + icon: widget.nodeListViewModel.isTestingNodeSpeed + ? CupertinoActivityIndicator() + : Icon(Icons.refresh), + onPressed: () => widget.nodeListViewModel.speedTestNodes()), + ), ModernButton( size: 36, icon: Icon(Icons.add), - onPressed: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode)) + onPressed: ()async { + final res = await Navigator.of(context) + .pushNamed(widget.isPow ? Routes.newPowNode : Routes.newNode); + if(res != null && res is Node) { + widget.nodeListViewModel.nodes.add(res); + } + }) ], ), ), + header: ModalHeader( + iconPath: "assets/new-ui/settings_row_icons/nodes.svg", + message: S.of(context).nodes_desc, + title: S.of(context).nodes, + bottomWidget: Observer( + builder: (_) => NodeListRow( + node: widget.nodeListViewModel.currentNode, + speed: widget.nodeListViewModel.nodeSpeedFor(widget.nodeListViewModel.currentNode), + onEditComplete: (res)async{ + if(res != null && res is Node) { + widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id); + widget.nodeListViewModel.nodes.add(res); + } + + }, + onTap: () {}, + isSelected: true, + isPow: widget.isPow)), + ), content: Column( children: [ - if (FeatureFlag.isAutomaticNodeSwitchingEnabled) - 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); - } - }, - ), - ), - if (FeatureFlag.isAutomaticNodeSwitchingEnabled) SizedBox(height: 8), - Observer( - builder: (BuildContext context) { - int itemsCount = - nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length; - return SectionStandardList( - scrollController: ModalScrollController.of(context), - sectionCount: 1, - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - dividerPadding: EdgeInsets.symmetric(horizontal: 24), - itemCounter: (int sectionIndex) => itemsCount, - itemBuilder: (_, index) { - return Observer( - builder: (context) { - final node = - nodeListViewModel?.nodes[index] ?? powNodeListViewModel!.nodes[index]; - late bool isSelected; - if (isPow) { - isSelected = node.id == powNodeListViewModel!.currentNode.id; - } else { - isSelected = node.id == nodeListViewModel!.currentNode.id; - } - final nodeListRow = NodeListRow( - title: node.uriRaw, - subtitle: node.label ?? '', - node: node, - isSelected: isSelected, - isPow: isPow, - onTap: (_) async { - if (isSelected) { - return; - } + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(18)), + child: Observer( + builder: (BuildContext context) { + int itemsCount = widget.nodeListViewModel.nonCurrentNodes.length; + return ListView.separated( + controller: ModalScrollController.of(context), + shrinkWrap: true, + physics: BouncingScrollPhysics(), + itemCount: itemsCount, + separatorBuilder: (context, index) => Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: Container( + height: 1, + color: Theme.of(context).colorScheme.surfaceContainerHigh, + ), + ), + itemBuilder: (_, index) { + return Observer( + builder: (context) { + final node = widget.nodeListViewModel.nonCurrentNodes[index]; + final nodeListRow = NodeListRow( + node: node, + isSelected: false, + isPow: widget.isPow, + speed: widget.nodeListViewModel.nodeSpeedFor(node), + onEditComplete: (res) async { + if(res != null && res is Node) { + widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id); + widget.nodeListViewModel.nodes.add(res); + } + }, + onTap: () async { await showPopUp( context: context, builder: (BuildContext context) { return AlertWithTwoActions( alertTitle: S.of(context).change_current_node_title, - alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ?? - powNodeListViewModel!.getAlertContent(node.uriRaw), + alertContent: + widget.nodeListViewModel.getAlertContent(node.uriRaw), leftButtonText: S.of(context).cancel, rightButtonText: S.of(context).change, actionLeftButton: () => Navigator.of(context).pop(), actionRightButton: () async { - if (isPow) { - await powNodeListViewModel!.setAsCurrent(node); - } else { - await nodeListViewModel!.setAsCurrent(node); - } + await widget.nodeListViewModel.setAsCurrent(node); Navigator.of(context).pop(); }, ); @@ -132,32 +141,16 @@ class ManageNodesPage extends BasePage { ); }, ); - return nodeListRow; - }, - ); - }, - ); - }, + return nodeListRow; + }, + ); + }, + ); + }, + ), ), ], ), ); } - Future _presentReconnectAlert(BuildContext context) async { - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).reconnection, - alertContent: S.of(context).reconnect_alert_text, - rightButtonText: S.of(context).ok, - leftButtonText: S.of(context).cancel, - actionRightButton: () async { - Navigator.of(context).pop(); - await dashboardViewModel!.reconnect(); - }, - actionLeftButton: () => Navigator.of(context).pop()); - }, - ); - } } 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 774136ac4..bef64ef50 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 @@ -3,10 +3,7 @@ import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/permission_handler.dart'; -import 'package:collection/collection.dart'; import 'package:cw_core/node.dart'; -import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; -import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; @@ -20,7 +17,7 @@ class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase abstract class NodeCreateOrEditViewModelBase with Store { NodeCreateOrEditViewModelBase( - this.isPow, this.nodeListViewModel,this.powNodeListViewModel, this.walletType, this._settingsStore, {this.editingNode}) + this.isPow, this.walletType, this._settingsStore, {this.editingNode}) : state = InitialExecutionState(), connectionState = InitialExecutionState(), label = editingNode?.label ?? '', @@ -116,8 +113,6 @@ abstract class NodeCreateOrEditViewModelBase with Store { @observable bool isPow; - final NodeListViewModel? nodeListViewModel; - final PowNodeListViewModel? powNodeListViewModel; @computed bool get isReady => @@ -257,12 +252,6 @@ abstract class NodeCreateOrEditViewModelBase with Store { @action Future delete({required Node editingNode}) async { await editingNode.delete(); - if(nodeListViewModel != null) { - nodeListViewModel!.bindNodes(); - } - if(powNodeListViewModel != null) { - powNodeListViewModel!.bindNodes(); - } } @action @@ -287,12 +276,6 @@ abstract class NodeCreateOrEditViewModelBase with Store { } state = ExecutedSuccessfullyState(); - if(nodeListViewModel != null) { - nodeListViewModel!.bindNodes(); - } - if(powNodeListViewModel != null) { - powNodeListViewModel!.bindNodes(); - } } catch (e) { state = FailureState(e.toString()); diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index 55207f0df..cc91577af 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -1,12 +1,14 @@ +import 'dart:math'; + import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cw_core/node_list.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/node.dart'; -import 'package:cake_wallet/entities/node_list.dart'; import 'package:cake_wallet/entities/default_settings_migration.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/evm/evm.dart'; @@ -14,11 +16,34 @@ import 'package:cake_wallet/reactions/wallet_connect.dart'; part 'node_list_view_model.g.dart'; + +class NodeSpeed { + final String iconPath; + + // fraction of best node ping needed to be classified as this speed. + final double treshold; + + const NodeSpeed._(this.iconPath, this.treshold); + + static const disconnected = NodeSpeed._( + "assets/new-ui/node_speed_badges/disconnected.svg", 0); + static const slow = NodeSpeed._( + "assets/new-ui/node_speed_badges/slow.svg", 0); + static const medium = NodeSpeed._( + "assets/new-ui/node_speed_badges/medium.svg", 0.5); + static const fast = NodeSpeed._( + "assets/new-ui/node_speed_badges/fast.svg", 0.8); + + static const all = [fast, medium, slow, disconnected]; +} + + class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel; abstract class NodeListViewModelBase with Store { - NodeListViewModelBase(this._appStore) + NodeListViewModelBase(this._appStore, this.isPow) : nodes = ObservableList(), + _nodeSpeeds = ObservableMap(), settingsStore = _appStore.settingsStore { bindNodes(); @@ -37,6 +62,7 @@ abstract class NodeListViewModelBase with Store { bindNodes(); }); } + @computed Node get currentNode { @@ -52,7 +78,7 @@ abstract class NodeListViewModelBase with Store { return settingsStore.getCurrentNode(walletType, chainId: chainId); } - final node = settingsStore.nodes[walletType]; + final node = isPow ? settingsStore.powNodes[walletType] : settingsStore.nodes[walletType]; if (node == null) { throw Exception('No node for wallet type: $walletType'); } @@ -73,6 +99,59 @@ abstract class NodeListViewModelBase with Store { final ObservableList nodes; final SettingsStore settingsStore; final AppStore _appStore; + final bool isPow; + final ObservableMap _nodeSpeeds; + + + @computed + List get nonCurrentNodes => nodes.where((item)=>item != currentNode).toList(); + + @observable + bool isTestingNodeSpeed = false; + + Future speedTestNodes() async { + try { + isTestingNodeSpeed = true; + _nodeSpeeds.clear(); + final nodes = this.nodes.toList(); + await Future.wait(nodes.map((node) async { + final sw = Stopwatch()..start(); + final res = await node + .requestNode() + .timeout(const Duration(seconds: 10), onTimeout: () => false); + sw.stop(); + if (res) { + _nodeSpeeds[node.uriRaw] = sw.elapsedMilliseconds; + } + })); + } finally { + isTestingNodeSpeed = false; + } + } + + NodeSpeed? nodeSpeedFor(Node node) { + final bestSpeed = _bestNodeSpeed; + final currentSpeed = _nodeSpeeds[node.uriRaw]; + if (bestSpeed == null) { + return null; + } + if (currentSpeed == null) { + return NodeSpeed.disconnected; + } + for (final speed in NodeSpeed.all) { + if (currentSpeed < (bestSpeed / speed.treshold)) return speed; + } + + return null; + } + + int? get _bestNodeSpeed { + try { + printV(_nodeSpeeds); + return _nodeSpeeds.values.reduce(min); + } catch (e) {} + return null; + } Future reset() async { await resetToDefault(); @@ -125,7 +204,12 @@ abstract class NodeListViewModelBase with Store { } // For non-EVM wallets, use the wallet type directly - settingsStore.nodes[walletType] = node; + if(isPow) { + settingsStore.powNodes[walletType] = node; + } else { + settingsStore.nodes[walletType] = node; + } + } @action @@ -150,7 +234,7 @@ abstract class NodeListViewModelBase with Store { } // For non-EVM wallets, use the wallet type directly - nodes.addAll(await Node.getAllForWalletType(walletType)); + nodes.addAll(isPow ? await Node.getAllForWalletTypePow(walletType) : await Node.getAllForWalletType(walletType)); } } diff --git a/lib/view_model/node_list/pow_node_list_view_model.dart b/lib/view_model/node_list/pow_node_list_view_model.dart index b7e185e2b..e69de29bb 100644 --- a/lib/view_model/node_list/pow_node_list_view_model.dart +++ b/lib/view_model/node_list/pow_node_list_view_model.dart @@ -1,83 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/utils/mobx.dart'; -import 'package:cw_core/node_list.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:hive/hive.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cw_core/node.dart'; -import 'package:cake_wallet/entities/node_list.dart'; -import 'package:cake_wallet/entities/default_settings_migration.dart'; -import 'package:cw_core/wallet_type.dart'; - -part 'pow_node_list_view_model.g.dart'; - -class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewModel; - -abstract class PowNodeListViewModelBase with Store { - PowNodeListViewModelBase(this._appStore) - : nodes = ObservableList(), - settingsStore = _appStore.settingsStore { - bindNodes(); - - reaction((_) => _appStore.wallet, (WalletBase? _wallet) { - bindNodes(); - }); - } - - @computed - Node get currentNode { - final node = settingsStore.powNodes[_appStore.wallet!.type]; - - if (node == null) { - throw Exception('No node for wallet type: ${_appStore.wallet!.type}'); - } - - return node; - } - - String getAlertContent(String uri) => - 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 nodes; - final SettingsStore settingsStore; - final AppStore _appStore; - - Future reset() async { - await resetPowToDefault(); - - Node node; - - switch (_appStore.wallet!.type) { - case WalletType.nano: - node = (await getNanoDefaultPowNode())!; - break; - default: - throw Exception('Unexpected wallet type: ${_appStore.wallet!.type} for pow node list'); - } - - await setAsCurrent(node); - } - - @action - Future delete(Node node) async => node.delete(); - - Future setAsCurrent(Node node) async => - settingsStore.powNodes[_appStore.wallet!.type] = node; - - @action - Future bindNodes() async { - nodes.clear(); - nodes.addAll(await Node.getAllPow()); - } -} diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 416c6dea5..a04b093d6 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -290,6 +290,7 @@ flutter: - assets/new-ui/card_icons/outline_icons/ - assets/new-ui/card_icons/symbol_icons/ - assets/new-ui/crypto_full_icons/ + - assets/new-ui/node_speed_badges/ fonts: - family: Lato diff --git a/res/pictures/default_node.svg b/res/pictures/default_node.svg new file mode 100644 index 000000000..43d730b90 --- /dev/null +++ b/res/pictures/default_node.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/node_speed_badges/disconnected.svg b/res/pictures/node_speed_badges/disconnected.svg new file mode 100644 index 000000000..33b7d28ba --- /dev/null +++ b/res/pictures/node_speed_badges/disconnected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/pictures/node_speed_badges/fast.svg b/res/pictures/node_speed_badges/fast.svg new file mode 100644 index 000000000..4205def6f --- /dev/null +++ b/res/pictures/node_speed_badges/fast.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/node_speed_badges/medium.svg b/res/pictures/node_speed_badges/medium.svg new file mode 100644 index 000000000..398280251 --- /dev/null +++ b/res/pictures/node_speed_badges/medium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/pictures/node_speed_badges/slow.svg b/res/pictures/node_speed_badges/slow.svg new file mode 100644 index 000000000..fc4d0b200 --- /dev/null +++ b/res/pictures/node_speed_badges/slow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/pictures/official_node.svg b/res/pictures/official_node.svg new file mode 100644 index 000000000..75a09a0b9 --- /dev/null +++ b/res/pictures/official_node.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 70468bc30..f8828872e 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -300,6 +300,7 @@ "default_sell_provider": "Default Sell Provider", "delete": "Delete", "delete_account": "Delete Account", + "delete_this_node": "Delete this node", "delete_wallet": "Delete wallet", "delete_wallet_confirm_message": "Are you sure that you want to delete ${wallet_name} wallet?", "deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to", @@ -681,8 +682,10 @@ "node_new": "New Node", "node_port": "Node port", "node_reset_settings_title": "Reset settings", + "node_share_desc": "Scan this QR code from the Scan screen to import this node.", "node_test": "Test", "nodes": "Nodes", + "nodes_desc": "Manage existing nodes or add a custom one.", "nodes_list_reset_to_default_message": "Are you sure that you want to reset settings to default?", "none_of_selected_providers_can_exchange": "None of the selected providers can make this swap", "noNFTYet": "No NFTs yet", @@ -1022,6 +1025,7 @@ "share_address": "Share address", "shared_seed_wallet_groups": "Shared Seed Wallet Groups", "shared_seed_wallets": "Shared Seed Wallets", + "share_this_node": "Share this node", "show": "Show", "show_address_book_popup": "Show Address Book popup", "show_balance": "Long Press to Show Balance", From 5b2a8f67559046e89e64d34c841ab2ce0b87e0e5 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Tue, 2 Jun 2026 02:33:17 +0200 Subject: [PATCH 6/6] Cw 1379 add monero support to trezor rebase dev (#3273) * refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services * refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services * fix: correctly check for Ledger hardware wallet type in `isHardwareWallet` logic * refactor: unify `isConnected` method for hardware wallets, add Trezor-specific support, and update Monero sync logic * refactor: add Trezor transaction signing support, streamline hardware wallet handling, and update Monero dependency * refactor: improve Trezor transaction handling, adjust hardware wallet state logic, and update Monero dependency references * fix: non-hww tx commit() in xmr fix: prod monero_c fix: keyImage import workaround for Trezor/Cupcake chore: add kotlin's .salive to .gitignore * fix: universal_ble builds on iOS chore: remove prints fix: proper CI prebuilt for linux * new trezor connection ui * fix page title * typo * fix page title anim * refactor: enhance Trezor pairing flow, update state management, and simplify UI logic in hardware wallet integration * fix: correct widget indentation in Trezor pairing failure state UI * fix: extract Trezor pairing failure state UI to `_errorBox` widget and update Trezor dependency reference * refactor: simplify hardware wallet pairing error UI, optimize `_errorBox` layout, and streamline widget logic * Update cw_monero/lib/pending_monero_transaction.dart Co-authored-by: Omar Hatem * Update lib/buy/robinhood/robinhood_buy_provider.dart Co-authored-by: Omar Hatem * feat: add USB intent filters, device filter for hardware wallets, and update Trezor dependency reference * Update cw_monero/pubspec.yaml [skip ci] * Update cw_wownero/pubspec.yaml [skip ci] * Update cw_zano/pubspec.yaml [skip ci] * fix: use CryptoCurrency.title instead of amount-refactor-only .symbol getter new_wallet_type_page used curr.symbol, which only exists on the amount-refactor branch (String get symbol => title). On dev the equivalent is curr.title. Adapts the Trezor feature to dev's API without pulling in amount-refactor. Co-Authored-By: Claude Opus 4.8 (1M context) * Fix wallet list screen not scrollable minor fixes --------- Co-authored-by: Konstantin Ullrich Co-authored-by: Czarek Nakamoto Co-authored-by: Robert Malikowski Co-authored-by: Konstantin Ullrich Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/pr_test_build_linux.yml | 2 +- .gitignore | 2 + android/app/build.gradle | 2 +- android/app/src/main/AndroidManifestBase.xml | 8 + .../app/src/main/res/xml/device_filter.xml | 11 + .../device_ledger_nano_gen_5.svg | 3 - .../hardware_wallet/device_trezor_safe_5.svg | 3 - assets/images/hardware_wallet/ledger_flex.png | Bin 1777 -> 0 bytes .../images/hardware_wallet/ledger_nano_s.png | Bin 2639 -> 0 bytes assets/images/hardware_wallet/ledger_stax.png | Bin 1612 -> 0 bytes .../lib/hardware/device_connection_type.dart | 2 +- .../lib/hardware/hardware_wallet_service.dart | 3 +- cw_core/lib/wallet_info.dart | 2 +- cw_monero/lib/ledger.dart | 8 + cw_monero/lib/monero_wallet.dart | 35 ++ cw_monero/lib/monero_wallet_service.dart | 38 +- cw_monero/lib/pending_monero_transaction.dart | 41 ++- cw_monero/lib/trezor.dart | 53 +++ cw_monero/pubspec.lock | 56 ++- cw_monero/pubspec.yaml | 9 +- cw_wownero/pubspec.lock | 6 +- cw_wownero/pubspec.yaml | 4 +- cw_zano/pubspec.lock | 6 +- cw_zano/pubspec.yaml | 4 +- .../components/common_test_flows.dart | 5 +- .../robots/new_wallet_type_page_robot.dart | 6 +- ios/Podfile.lock | 1 + lib/buy/dfx/dfx_buy_provider.dart | 2 +- lib/buy/robinhood/robinhood_buy_provider.dart | 2 +- lib/di.dart | 6 +- .../hardware_wallet_device.dart | 55 ++- lib/monero/cw_monero.dart | 55 +-- lib/new-ui/pages/send_page.dart | 13 +- .../widgets/coins_page/wallet_info.dart | 6 +- lib/new-ui/widgets/digit_input.dart | 182 ++++++++++ .../proceed_on_device_message.dart | 60 ++++ .../proceed_on_device_sheet.dart | 338 ++++++++++++++++++ .../hardware_wallet/trezor_paring_sheet.dart | 106 ++++++ .../send_page/send_confirm_bottom_widget.dart | 61 +--- .../widgets/swap_page/swap_confirm_sheet.dart | 2 +- lib/router.dart | 2 +- .../connect_device/connect_device_page.dart | 172 +++++---- .../connect_device/widgets/device_tile.dart | 15 +- .../pages/balance/crypto_balance_widget.dart | 6 +- .../exchange_trade/exchange_trade_page.dart | 2 +- .../integrations/deuro/savings_page.dart | 2 +- .../new_wallet/new_wallet_type_page.dart | 224 ++++++------ lib/src/screens/send/send_page.dart | 2 +- .../hardware_wallet/bitbox_view_model.dart | 2 +- .../hardware_wallet_view_model.dart | 3 +- .../hardware_wallet/ledger_view_model.dart | 8 +- .../trezor_connect_view_model.dart | 258 +++++++++++++ .../hardware_wallet/trezor_view_model.dart | 79 ---- lib/view_model/send/send_view_model.dart | 8 +- .../wallet_hardware_restore_view_model.dart | 2 +- pubspec_base.yaml | 9 +- res/pictures/bluetooth.svg | 3 + res/pictures/cake_coin.svg | 60 ++++ res/pictures/hardware_wallet.svg | 191 ++++++++++ .../hardware_wallets}/device_bitbox.svg | 2 +- .../hardware_wallets/device_ledger_flex.svg | 3 + .../device_ledger_nano_gen_5.svg | 3 + .../hardware_wallets/device_ledger_nano_s.svg | 3 + .../device_ledger_nano_x.svg | 2 +- .../hardware_wallets/device_ledger_stax.svg | 3 + .../device_trezor_model_one.svg | 3 + .../device_trezor_model_t.svg | 3 + .../hardware_wallets/device_trezor_safe_3.svg | 3 + .../hardware_wallets/device_trezor_safe_5.svg | 3 + .../hardware_wallets/device_trezor_safe_7.svg | 3 + res/values/strings_en.arb | 12 +- scripts/prepare_moneroc.sh | 4 +- tool/configure.dart | 9 +- 73 files changed, 1851 insertions(+), 451 deletions(-) create mode 100644 android/app/src/main/res/xml/device_filter.xml delete mode 100644 assets/images/hardware_wallet/device_ledger_nano_gen_5.svg delete mode 100644 assets/images/hardware_wallet/device_trezor_safe_5.svg delete mode 100644 assets/images/hardware_wallet/ledger_flex.png delete mode 100644 assets/images/hardware_wallet/ledger_nano_s.png delete mode 100644 assets/images/hardware_wallet/ledger_stax.png create mode 100644 cw_monero/lib/trezor.dart create mode 100644 lib/new-ui/widgets/digit_input.dart create mode 100644 lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart create mode 100644 lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart create mode 100644 lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart create mode 100644 lib/view_model/hardware_wallet/trezor_connect_view_model.dart delete mode 100644 lib/view_model/hardware_wallet/trezor_view_model.dart create mode 100644 res/pictures/bluetooth.svg create mode 100644 res/pictures/cake_coin.svg create mode 100644 res/pictures/hardware_wallet.svg rename {assets/images/hardware_wallet => res/pictures/hardware_wallets}/device_bitbox.svg (87%) create mode 100644 res/pictures/hardware_wallets/device_ledger_flex.svg create mode 100644 res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg create mode 100644 res/pictures/hardware_wallets/device_ledger_nano_s.svg rename {assets/images/hardware_wallet => res/pictures/hardware_wallets}/device_ledger_nano_x.svg (94%) create mode 100644 res/pictures/hardware_wallets/device_ledger_stax.svg create mode 100644 res/pictures/hardware_wallets/device_trezor_model_one.svg create mode 100644 res/pictures/hardware_wallets/device_trezor_model_t.svg create mode 100644 res/pictures/hardware_wallets/device_trezor_safe_3.svg create mode 100644 res/pictures/hardware_wallets/device_trezor_safe_5.svg create mode 100644 res/pictures/hardware_wallets/device_trezor_safe_7.svg diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 36fb28a25..99a489bb1 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -118,7 +118,7 @@ jobs: pushd scripts/monero_c/release mkdir -p $(git describe --tags) pushd $(git describe --tags) - wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip + wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip unzip release-bundle.zip rm release-bundle.zip popd diff --git a/.gitignore b/.gitignore index 9fce14fcf..268167bc4 100644 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,5 @@ scripts/zcash_lib scripts/android/yttrium scripts/bitbox_flutter scripts/reown_flutter + +*.salive \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 6c1540222..26f5544c9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -60,7 +60,7 @@ android { defaultConfig { applicationId appProperties['id'] - minSdkVersion 24 + minSdkVersion 26 targetSdkVersion 36 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index a7364d670..0f15c75bc 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -128,6 +128,14 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg b/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg deleted file mode 100644 index 9d2fc8bbd..000000000 --- a/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/hardware_wallet/device_trezor_safe_5.svg b/assets/images/hardware_wallet/device_trezor_safe_5.svg deleted file mode 100644 index 14c0329be..000000000 --- a/assets/images/hardware_wallet/device_trezor_safe_5.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/hardware_wallet/ledger_flex.png b/assets/images/hardware_wallet/ledger_flex.png deleted file mode 100644 index fa39f241fccf109565a28f66c090bf57ca274fd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1777 zcmVPx*t4TybRCr$Poo$FzRT#(r&s~*jgAA8~ie9AXgwQf<` z8}8g$1C7X?NwLTX9r_R^>D=*~Nl*lS%A7l!(W{1qRG&;1F<6t<`^2uhIxqKS?mhR+ znVmiJbj;={ z0FtS#w>fa`137&Ls1M+))cSxX9Pnwdae6_I^ZhsbawprVr!5iC|J1fV_td!$ z4;|V(DNCs|0LfK_0cI|Nu~e3LS>x742H}|FAU#$#e~@8M3V>uLzm6I2%Mg5N%%&ic z8dc|r@m!^Bej4|jVh1pgDZE0=@5U{(6UHOLno7ys9jBI90W43A_B%w?V1ZIsYq0s~ zE&esw=|55)`8oO!^*v()a8GLU)xC7ZF92@x6*ud9##b=#BQc+8YVQCr;+o>jI38m% z(PB8b7laipT0CxB=3One4Qv)0fJC~m4dB-UES@C7rVA6l?;kr*oCs9c#s>GV-rBq9 z{A7keZvnW$w|r$Du9kCK6FTnTqFfMhzqlR-mn?|OCvfQ$3PQ2mWA zj~$GS2kr8(=hH#AUgA}d-hwXq)gj~dvC{C=bL5tP;sCn53=NTl&)Sw*^9Fcp3^ahC zTM>;5%%d9Rh%al21JGL#nE35#DfdQeYrejf1`reh0gogd#zv8E2gCu)=Nu5jy)F$P zC;}ueFbzNhh?2V6*T=UE)mxB!5E_7dANyL+05kx956E62iv17S^V3z3NEKvI(cUNa z=AQ;20-*D&AfsrPhP(IO(9Q?Y<*%h^0P<6`PQvnitgizU`&a{*Qvm8dLJ`k#x(w~^ zGE{tpsE&@yN!aCKu&#pS`v1tk}mIfCeDo0oi+`fV5b;%ATJtLviehu5E4L%bzH@wAa@GtvOMo)f|Zc8gxC3oAd*jAow~7>m(esM%4b@eG8hoFQjvT zd^mF?2h1GU)aZ{GR)~BT`&;OI00LjgJd`QiJ?UVDK~A3vM>}ks2JmIIoIUPk-|w)F zIKBdvnuH0?u8K9{dT8JDzcwLyM}^}XZ8FokYB~FXo0i%LXgsc_GXO-0fS}WMLqiEZ z#=-3>s_y^TTwuQA+j8ziW5YQe0r1EH1L?vh0&foOXsI1&#I~|c2hetY4)FPe$16k= z>HJiFjsfi&zehxF2i*3XEezu$IA+vs3J0l0I z$`|3x+=^)NunwsJ!XumG03syesl>+2Yn#I_;_{8?^xAHo+hv6Lg^?p=3%XRUc_iU5 z2jPX8j8I!It=;&Us!9N!M^9=1LhDz&^Be~77=im;JwQFxN@@5rm(uQH9~u8IEGmj1 zt#0Fb^a&$DI0u9=rrS1qKm$;`Dl`BMK(PlDo7WMb0Vwu>V)HrzGyugOP;CBx3ot01 ToY=F<00000NkvXXu0mjfR%KGE diff --git a/assets/images/hardware_wallet/ledger_nano_s.png b/assets/images/hardware_wallet/ledger_nano_s.png deleted file mode 100644 index 02777aeb69b2e436d2b9bd96c3b5f39a773b7def..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2639 zcmb7``9IT-1IORfFtag7h>siM@B-qbKl3vhvrDmx!%)c&eW(O=gg5KzGQNa z7KOH4$q{|X5jk^K`1bu1zCXNPugB~82fQAyWJ_}+BwQ2@008oavB9lB>G40oIR5x7 zIYQ%4KEn3QjgRe z7p;=o@(3R?lfp^2P&Ui5Z2EEm+F}Xb$!c!aP+0b^01Sm%=ee6IUCQQXOj7C63LlbD z@h{T<8){H|PKEJKbe*Xe(Y{hW)~XzFd$B&;_bQGRdlOlyzXK1SH!vVp)Gx9frl z0NWZ=3PhplaHvbn5=06FPr{n_Kb+79Ao8f-&0dQn=>Y>M}cUs*yT*M{du)o0M{rLq6(S<2PphkHx zoXC|5xUk9hm!BY@Az2Jv_?O!a%pUqMTqzaL^onF-T$qhVzH!hRq50C2GL{|r@DPVK zimzK&5ow;R`%&LYTr+EZSv8C#M#xn?g4dRj6p+a*#nJQ zmWxFNR_a%bh5zAq2R^~H?2A2+!v6^INdTy9o5c;KMYU(cEyuEcGb*`O?swV1~w}w5gWLrtq&^z^P3WDXPei z8aIh~Nx?M0T7+(y2)huse;q4=vJEgWc>?^zR= zszv)dlEN8)SDOvd&>pcGzm(3tIr2*|0gM&jYO`6lI2z{+pr!<~gYL8Ti4^ynjT_oT zT~3)zO|4e%W1Ou&J$jL*Tr*^IR7COc-=XAQPfJvq|dG@7nzuk zgTH-1(fl^!QN}Ei(%`VuWZlcM7K4jGOkE3y?fTK{((_{9J3=M`Y>i4H zlwV6h^>12K-Dve`@mv=H&$q$!l25bGJ&}B!nIEj;=Vcsu%c9CX^qQ$`lnN9r*zB=F zQI{Viig3$B>DwmOW|ma%O$p5EIFl>f%@J|>tg{h)<2cNy5@1zxeeWuc;%Q)R9N7(% zZ;Pu_jAG<1q|N)!tu7@zEDx@{m_?#X6t1 z0$}w;e9F?_v*AH|a<)&L{sxq;L5Mkm!6aTTS($NqW~Ec(@+~$HAgcX`vd;SHQ&fyK zk`1P~h@2CMLvILzt}w=@$D7`&@jFcSS4WH-#hL??J&B6p^bpX;ljn}ERrE8%V0fSc z42GcxN~+X^p*@wJQ+2zM6)kmg=>Oiyg(0+x6o{OKex|hjqjqxy9T+m~P>2NTV&IkRW=df^smR3ZYwwm8q7^tOF6a3@TtCd-a6}6Ilq40kIL^9-`chF@kXZ7=d`8~Knk`pZv2&{9NTAa zr9gI1IdEnExw-qGvg-_~J`4d8-TTW(cE9v}mX&<4j5RH#Dyo>X>5BOL?U@A!)-&ha~8})l=voeJ=2z<^YKYcgE9xuAo46@DJh%!`5#B zUKl^S$us5;?ATC+vc4lX`;{~~m7@!IAr3e+>mpn1WH;7Um*M zVP42GTFhUIUs5#!20DmlG`(h#{>e#ZM&VZFOSnm+wMB%EmE1zN0ZWbTu%v*FGgQt0 zK2gB+lsh*NFlZI(i4KyTokI*fmm1jHN&89lFUBNiq2isz0mbE0qoJ}zW&o5!_=SCb z20$R1(6o1_2Zb=|WP5xHCvPEUv@F@y%Eha#l*!W!YEn&)^IXTO1v@h3Bdb zKWlbAZ5knRGgbgiq#XtP!z_6pJ3^_{qJfOR@d&cFQ&PEV}xEDwmAI7$-qA(2vz|BQ%DX_uQ_ zjsFMBQ~Rz`674hC_TFmQeO@{%?wgNe+HgwuGFtN>&oHH)i$1@$Qn!HL!F3nXM|dyu zH1EXUF@-2hPyh0~pT5cymYX$@nC>5FdtFn4Bl*4UqO;){#k^RjEM{Hb2SISy^mXVK roB(`%xy85){gPb0mGj{LDO<Px*21!IgRCr$Pon43I}_78*`QruT<7~(O#p^{uMC_A;~WhcKbIC#6~BG-ECNOo+54F2Fo0X_2W7dy z!sG7R=Dz?=7#C^Z;?dMOVW(P}^8`R_JohLuj`=YFsXnYJuqH+o-ZC?#D#g^hQuf=~ z5eG0jHob#489xEo;mmnwvvpk{XI6V|Ww~YItBWunKV6*oqi1_<42T1WC1xH2aZ%c^ zC;(^7tCBJEnN8So*Vo0Dt@I@Rk=6&rug|k80B!l8!R7v6hMw7Ys7BcPw!Izk_t4~@;ilU1SkLxr*jvkH3GU2P4bfP0~nu~ z1M}WiqV#(eWceJo@gKlv2Ak={;>7n-(B;>F@!2`B8C3vP-5Hpz6w)aP2=V|zdMCW0 z40W`Z#o%Wuh0F)NLXZa#?G&_8@88RX^fd~gR|Oi&Q2RBYuJw??H-58_`K1>ZJdc2l z4^kqq2-j)N2H##;$eiwL02`fC0L`d8i2!Ea+sd#3%q{luW?XX_YX3B?KO-QSn`dB8 zOS0kEjB`{4S+ES|*kUoW)L$uRK+4c6fD0U3Jf;A8nSf2#+HOTOwAKg^m43}w_yG*g zjTriJ5w?3PA`E4yo&QY%$Hr~t;=ylw2}Joav|$mhd!tROpe9!G00trjt=%82GT(fv zm^s_m5m4t`v?5@+Tu5*2MSSa8`3M-0`v78krBqD4(SHC2AO#UHT`Hs}dllcC2&e;4 zzn2n*D#-qkBp+uoCoB2M?CJq6%WFzMzBKw43k_Syp=Yu`ZpD^^+s4Nev0%?xrm!81LE2+#=dc1F9k9*IS`?GLwV8LDMyr-+D71n6d5C)U;f z>muxU5hfUfFh7t9&s!0Gu3;JhO%aei4$vdb`J*b;;c_9h-%)OH*hn5g*uSEb%vnEO zd~Iv}cz?N&vChR2`^W>RJ(LgOBHZcl31+&#QcQg*roJx1Yf|3()YQn|%OkS{ykMir z%3tR%-*)!oODp{iz$OLti5?1$w-`e{k=Jt;$7{nkQPqp|-LtFBBs zxX4^JZ&~_u;ztKfT{RJ314fhCDF%uD#=ff&dC-`MkCgJ6Cp|DNe4t70)Lkbkv^!(gnzhR2L!mmz%@Qx3$UX?7 zlb3{S8n+W62n^nMyr2kA$3_Sr51X5x^=jtV{19IQYKH$$MehT+%JHCGH*^BhrBTL7 z8lu;{I5Vu3C!PVYI@>k&=G7O5&D{q0dJxwgjI(~ytaaY3^;fVTlZ`(K^hvpR@Ham_ z@f3ic0|{$i3Ls!|3P1q_tU$osYXm5OfE5V1dyN1E5U>IPcmE$44V#X~Kndvp0000< KMNUMnLSTZg0^5E7 diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart index 39e0400c9..3b1dd9349 100644 --- a/cw_core/lib/hardware/device_connection_type.dart +++ b/cw_core/lib/hardware/device_connection_type.dart @@ -29,7 +29,7 @@ enum DeviceConnectionType { break; case HardwareWalletType.trezor: isSupported = [ - // WalletType.monero, + WalletType.monero, WalletType.bitcoin, WalletType.litecoin, WalletType.ethereum, diff --git a/cw_core/lib/hardware/hardware_wallet_service.dart b/cw_core/lib/hardware/hardware_wallet_service.dart index 19125ba84..94cbdc443 100644 --- a/cw_core/lib/hardware/hardware_wallet_service.dart +++ b/cw_core/lib/hardware/hardware_wallet_service.dart @@ -4,7 +4,8 @@ import 'dart:typed_data'; import 'package:cw_core/hardware/hardware_account_data.dart'; abstract class HardwareWalletService { - Future> getAvailableAccounts({int index = 0, int limit = 5}); + Future> getAvailableAccounts({int index = 0, int limit = 5}) => + throw UnimplementedError(); Future signTransaction({required String transaction}) => throw UnimplementedError(); diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index 1ce397267..5877ad2da 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -44,7 +44,7 @@ enum HardwareWalletType { coldcard, seedsigner, keystone, - trezor, + trezor; } enum WalletInfoAddressType { diff --git a/cw_monero/lib/ledger.dart b/cw_monero/lib/ledger.dart index 7707acd48..7c1742ada 100644 --- a/cw_monero/lib/ledger.dart +++ b/cw_monero/lib/ledger.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:typed_data'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; @@ -118,3 +119,10 @@ void _logLedgerCommand(Uint8List command, [bool isResponse = true]) { "> ${_ledgerMoneroCommands[command[1]]} ${toHexString(command.sublist(2))}"); } } + +class MoneroLedgerService extends HardwareWalletService { + final LedgerConnection connection; + + MoneroLedgerService(this.connection); +} + diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index e315f24fa..30c550721 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -37,6 +37,7 @@ import 'package:cw_monero/monero_unspent.dart'; import 'package:cw_monero/monero_wallet_addresses.dart'; import 'package:cw_monero/monero_wallet_service.dart'; import 'package:cw_monero/pending_monero_transaction.dart'; +import 'package:cw_monero/trezor.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; @@ -385,8 +386,42 @@ abstract class MoneroWalletBase extends WalletBase syncTrezor() async { + if (trezorService == null) throw Exception("Trezor not connected"); + + final ptr = Pointer.fromAddress(currentWallet!.ffiAddress()); + final tdis = monero.Wallet_exportTrezorTdis(ptr); + + final response = await Trezor(trezorService!).keyImageSync(tdis); + + final success = monero.Wallet_importTrezorEncryptedKeyImagesJson(ptr, response); + + if (!success) throw Exception(monero.Wallet_errorString(ptr)); + } + + Future signTrezorTransaction(String json) async { + if (trezorService == null) throw Exception("Trezor not connected"); + return Trezor(trezorService!).signTransaction(json); + } + @override Future createTransaction(Object credentials) async { + if (hardwareWalletType == HardwareWalletType.trezor) { + for (int i = 0; i < 2; i++) { + try { + return await _createTransaction(credentials); + } catch (e) { + printV(e); + } + await save(); + await Future.delayed(Duration(seconds: i)); + } + } + return await _createTransaction(credentials); + } + + Future _createTransaction(Object credentials) async { final _credentials = credentials as MoneroTransactionCreationCredentials; final inputs = []; final outputs = _credentials.outputs; diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 2a153c5e1..1b0e9e347 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -3,8 +3,8 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; -import 'package:collection/collection.dart'; import 'package:cw_core/get_height_by_date.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/monero_wallet_utils.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -20,8 +20,8 @@ import 'package:cw_monero/api/wallet_manager.dart'; import 'package:cw_monero/bip39_seed.dart'; import 'package:cw_monero/ledger.dart'; import 'package:cw_monero/monero_wallet.dart'; +import 'package:cw_monero/trezor.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:monero/monero.dart' as monero; import 'package:polyseed/polyseed.dart'; @@ -46,11 +46,11 @@ class MoneroNewWalletCredentials extends WalletCredentials { class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials { MoneroRestoreWalletFromHardwareCredentials( {required String name, - required this.ledgerConnection, + required this.hardwareWalletService, int height = 0, String? password}) : super(name: name, password: password, height: height); - LedgerConnection ledgerConnection; + HardwareWalletService hardwareWalletService; } class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials { @@ -193,7 +193,7 @@ class MoneroWalletService extends WalletService< unspentCoinsInfo: unspentCoinsInfoSource, password: password); - if (wallet.isHardwareWallet) { + if (wallet.hardwareWalletType == HardwareWalletType.ledger) { wallet.setLedgerConnection(gLedger!); gLedger = null; } @@ -301,15 +301,29 @@ class MoneroWalletService extends WalletService< try { final path = await pathForWallet(name: credentials.name, type: getType()); final password = credentials.password; - final height = credentials.height; - enableLedgerExchange(credentials.ledgerConnection); + if (credentials.hardwareWalletService case MoneroLedgerService service) { + enableLedgerExchange(service.connection); - await monero_wallet_manager.restoreWalletFromHardwareWallet( + await monero_wallet_manager.restoreWalletFromHardwareWallet( path: path, password: password!, - restoreHeight: height!, - deviceName: 'Ledger'); + restoreHeight: credentials.height!, + deviceName: 'Ledger', + ); + } else if (credentials.hardwareWalletService case MoneroTrezorService service) { + final watchCredentials = await Trezor(service).getWatchCredentials(); + + monero_wallet_manager.restoreWalletFromKeys( + path: path, + password: credentials.password!, + language: "English", + restoreHeight: credentials.height!, + address: watchCredentials.address, + viewKey: watchCredentials.watchKey, + spendKey: "", + ); + } final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, @@ -567,7 +581,7 @@ class MoneroWalletService extends WalletService< if (walletInfo == null) { return false; } - return walletInfo.isHardwareWallet; + return walletInfo.hardwareWalletType == HardwareWalletType.ledger; } } @@ -585,4 +599,4 @@ Future closeWalletAwaitIfShould(int wmaddr, int waddr) async { monero.WalletManager_errorString(Pointer.fromAddress(wmaddr)); })); } -} \ No newline at end of file +} diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart index e1d191eac..c32a2cc9c 100644 --- a/cw_monero/lib/pending_monero_transaction.dart +++ b/cw_monero/lib/pending_monero_transaction.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'dart:ffi'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/transaction_history.dart' @@ -10,6 +13,7 @@ import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/monero_wallet.dart'; +import 'package:monero/monero.dart' as monero; class DoubleSpendException implements Exception { DoubleSpendException(); @@ -43,22 +47,39 @@ class PendingMoneroTransaction with PendingTransaction { CryptoCurrency.xmr, pendingTransactionDescription.fee); @override - bool shouldCommitUR() => isViewOnly; + bool shouldCommitUR() => isViewOnly && wallet.hardwareWalletType != HardwareWalletType.trezor; @override Future commit() async { - try { - await monero_transaction_history.commitTransactionFromPointerAddress( - address: pendingTransactionDescription.pointerAddress, - useUR: false); - } catch (e) { - final message = e.toString(); + if (wallet.hardwareWalletType == HardwareWalletType.trezor) { + final ptr = Pointer.fromAddress(pendingTransactionDescription.pointerAddress); + final ret = await monero.PendingTransaction_commitTrezor(ptr, 0); - if (message.contains('Reason: double spend')) { - throw DoubleSpendException(); + final json = await wallet.signTrezorTransaction(ret); + + final wptr = Pointer.fromAddress(currentWallet!.ffiAddress()); + + final suc = await monero.Wallet_submitTransactionHex(wptr, json); + + if (!suc) { + final err = monero.UnsignedTransaction_errorString(ptr); + printV(err); + throw err; } + } else { + try { + await monero_transaction_history.commitTransactionFromPointerAddress( + address: pendingTransactionDescription.pointerAddress, + useUR: false); + } catch (e) { + final message = e.toString(); - rethrow; + if (message.contains('Reason: double spend')) { + throw DoubleSpendException(); + } + + rethrow; + } } storeSync(force: true); unawaited(() async { diff --git a/cw_monero/lib/trezor.dart b/cw_monero/lib/trezor.dart new file mode 100644 index 000000000..f2651d062 --- /dev/null +++ b/cw_monero/lib/trezor.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:trezor_flutter/trezor_flutter.dart'; + +class MoneroTrezorService extends HardwareWalletService { + final TrezorClient client; + + MoneroTrezorService(this.client); +} + +class MoneroTrezorWatchCredentials { + final String watchKey; + final String address; + + const MoneroTrezorWatchCredentials(this.watchKey, this.address); +} + +class Trezor { + final MoneroTrezorService service; + + Trezor(this.service); + + Future getWatchCredentials() async { + final credentials = await TrezorMonero(service.client).getWatchCredentials(); + + return MoneroTrezorWatchCredentials(credentials.$1, credentials.$2); + } + + Future keyImageSync(String tdis) async { + final tdisMap = jsonDecode(tdis) as Map; + final tdisList = tdisMap["tdis"] as List; + + final txIds = []; + for (final tdi in tdisList) { + txIds.add(MoneroKeyImageTxData( + outKey: tdi["out_key"], + txPubKey: tdi["tx_pub_key"], + internalOutputIndex: tdi["internal_output_index"], + subAddrMajor: tdi["sub_addr_major"], + subAddrMinor: tdi["sub_addr_minor"], + additionalTxPubKeys: (tdi["additional_tx_pub_keys"] as List?)?.map((e) => e as String).toList() ?? [] + )); + } + final keyImages = await TrezorMonero(service.client).syncKeyImages(txIds); + + return jsonEncode(keyImages.toMap()); + } + + Future signTransaction(String json) async { + return TrezorMonero(service.client).signTransaction(jsonDecode(json)); + } +} diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 6f6174d08..d00f2149e 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -296,6 +296,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + fast_base58: + dependency: transitive + description: + name: fast_base58 + sha256: "611f65633b734f27a850b51371b3eba993a5165650e12e8e7b02959f3768ba06" + url: "https://pub.dev" + source: hosted + version: "0.2.1" ffi: dependency: "direct main" description: @@ -342,10 +350,10 @@ packages: dependency: transitive description: name: flutter_web_bluetooth - sha256: "1363831def5eed1e1064d1eca04e8ccb35446e8f758579c3c519e156b77926da" + sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" frontend_server_client: dependency: transitive description: @@ -494,18 +502,18 @@ packages: dependency: "direct main" description: name: ledger_flutter_plus - sha256: "1c03f3c4a9754b5f0170a9eb9552ec54fa86e985f8ee71a255ee2c5629b53d31" + sha256: "59c5a6062505c721c2ca909fa085c52cffe93de995174b8418b54a6acf52eb70" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.6" ledger_usb_plus: dependency: transitive description: name: ledger_usb_plus - sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87" + sha256: "911df2d1d55cdae612ff2c9db140cf71a9b28a67ba16adfdc4983cd9f02c531e" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.8" logging: dependency: transitive description: @@ -582,9 +590,9 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 - resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 - url: "https://github.com/mrcyjanek/monero_c" + ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d" + resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d" + url: "http://github.com/mrcyjanek/monero_c.git" source: git version: "0.0.0" mutex: @@ -724,6 +732,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" provider: dependency: transitive description: @@ -962,6 +978,24 @@ packages: relative: true source: path version: "0.0.1" + trezor_flutter: + dependency: "direct main" + description: + path: trezor-flutter + ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107" + resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107" + url: "https://github.com/cake-tech/trezor-flutter.git" + source: git + version: "0.1.0" + trezor_usb_transport: + dependency: transitive + description: + path: trezor_usb_transport + ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107" + resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107" + url: "https://github.com/cake-tech/trezor-flutter.git" + source: git + version: "0.0.1" tuple: dependency: transitive description: @@ -982,10 +1016,10 @@ packages: dependency: transitive description: name: universal_ble - sha256: "1fad089150a29db82b3b7d60327e18c5ad6b3a5bb509defc1c690b0a76b9c098" + sha256: "8325ca9f5cce2fba2b4efef74a4fcfe7144f303b409d6fc6ed16197b00a1a241" url: "https://pub.dev" source: hosted - version: "0.15.0" + version: "1.2.0" universal_platform: dependency: transitive description: diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 7f38c0ea0..4c10e94d7 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -25,11 +25,16 @@ dependencies: path: ../cw_core monero: git: - url: https://github.com/mrcyjanek/monero_c - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 + url: https://github.com/mrcyjanek/monero_c.git + ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d path: impls/monero.dart mutex: ^3.1.0 ledger_flutter_plus: ^1.4.1 + trezor_flutter: + git: + url: https://github.com/cake-tech/trezor-flutter.git + ref: 4bf54339a1caefd4c53a4a8873a9686ec13a9107 + path: trezor-flutter dev_dependencies: flutter_test: diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 734af9338..55cfb8fdf 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -502,9 +502,9 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 - resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 - url: "https://github.com/mrcyjanek/monero_c" + ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d" + resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d" + url: "http://github.com/mrcyjanek/monero_c.git" source: git version: "0.0.0" mutex: diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml index a23fc3955..ca9c70bea 100644 --- a/cw_wownero/pubspec.yaml +++ b/cw_wownero/pubspec.yaml @@ -23,8 +23,8 @@ dependencies: path: ../cw_core monero: git: - url: https://github.com/mrcyjanek/monero_c - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash + url: https://github.com/mrcyjanek/monero_c.git + ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d path: impls/monero.dart mutex: ^3.1.0 diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index 662ccded6..6e2916d71 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -507,9 +507,9 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 - resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 - url: "https://github.com/mrcyjanek/monero_c" + ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d" + resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d" + url: "http://github.com/mrcyjanek/monero_c.git" source: git version: "0.0.0" nested: diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml index 8eb5d3e14..16585b715 100644 --- a/cw_zano/pubspec.yaml +++ b/cw_zano/pubspec.yaml @@ -24,8 +24,8 @@ dependencies: fluttertoast: ^8.2.12 monero: git: - url: https://github.com/mrcyjanek/monero_c - ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash + url: https://github.com/mrcyjanek/monero_c.git + ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d path: impls/monero.dart dev_dependencies: flutter_test: diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index d885e3e14..888c2ce03 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -231,12 +231,9 @@ class CommonTestFlows { await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type); + // Tapping the wallet type navigates directly (no separate Next button). _tester.printToConsole('Tapping wallet type: ${type.name}'); await _newWalletTypePageRobot.selectWalletType(type); - await _tester.pumpAndSettle(Duration(milliseconds: 500)); - - _tester.printToConsole('Pressing next button'); - await _newWalletTypePageRobot.onNextButtonPressed(); await _tester.pumpAndSettle(Duration(milliseconds: 2000)); diff --git a/integration_test/robots/new_wallet_type_page_robot.dart b/integration_test/robots/new_wallet_type_page_robot.dart index fc3e94f59..11785b67d 100644 --- a/integration_test/robots/new_wallet_type_page_robot.dart +++ b/integration_test/robots/new_wallet_type_page_robot.dart @@ -77,11 +77,9 @@ class NewWalletTypePageRobot { tester.printToConsole('Wallet type ${type.name} is now visible and tappable'); } + // Tapping a wallet type now navigates directly (the page no longer has a + // separate "Next" button — selection happens on tap). Future selectWalletType(WalletType type) async { await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key'); } - - Future onNextButtonPressed() async { - await commonTestCases.tapItemByKey('new_wallet_type_next_button_key'); - } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e661632c8..2d32cb833 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -295,6 +295,7 @@ SPEC CHECKSUMS: universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e + warp_api: 997d8fb3e777669bc6260d0c12e56cd40b9db1d1 YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index f927b702d..582cccd5d 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -336,7 +336,7 @@ class DFXBuyProvider extends BuyProvider { required String cryptoCurrencyAddress, String? countryCode}) async { if (wallet.isHardwareWallet) { - if (!hardwareWalletVM!.isConnected) { + if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: wallet.walletInfo.type, diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 7cd992812..2c26fa4ca 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -173,7 +173,7 @@ class RobinhoodBuyProvider extends BuyProvider { required String cryptoCurrencyAddress, String? countryCode}) async { if (wallet.isHardwareWallet) { - if (!hardwareWalletVM!.isConnected) { + if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: wallet.walletInfo.type, diff --git a/lib/di.dart b/lib/di.dart index 991e2f7ee..cd7ee62d1 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -240,7 +240,7 @@ import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; -import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart'; import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; @@ -414,7 +414,7 @@ Future setup({ switch(type) { case HardwareWalletType.bitbox: return getIt(); case HardwareWalletType.ledger: return getIt(); - case HardwareWalletType.trezor: return getIt(); + case HardwareWalletType.trezor: return getIt(); case HardwareWalletType.cupcake: case HardwareWalletType.coldcard: case HardwareWalletType.seedsigner: @@ -429,7 +429,7 @@ Future setup({ getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect", appName: "Cake Wallet")); - getIt.registerLazySingleton(() => TrezorViewModel(getIt())); + getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt())); getIt.registerFactory(() => KeyService(getIt.get())); diff --git a/lib/entities/hardware_wallet/hardware_wallet_device.dart b/lib/entities/hardware_wallet/hardware_wallet_device.dart index 646f5a2c2..757e68099 100644 --- a/lib/entities/hardware_wallet/hardware_wallet_device.dart +++ b/lib/entities/hardware_wallet/hardware_wallet_device.dart @@ -1,5 +1,6 @@ import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:trezor_flutter/trezor_flutter.dart' as trezor; abstract class HardwareWalletDevice { String get name; @@ -41,6 +42,21 @@ class BitboxHardwareWalletDevice extends HardwareWalletDevice { HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb; } +class TrezorHardwareWalletDevice extends HardwareWalletDevice { + final trezor.TrezorDevice device; + + TrezorHardwareWalletDevice(this.device); + + @override + String get name => device.name; + + @override + HardwareWalletDeviceType get type => device.deviceInfo.toGeneric(); + + @override + HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric(); +} + enum HardwareWalletDeviceType { ledgerBlue, ledgerNanoS, @@ -50,7 +66,12 @@ enum HardwareWalletDeviceType { ledgerStax, ledgerFlex, BitBox02, - BitBox02Nova; + BitBox02Nova, + trezorModelOne, + trezorModelT, + trezorSafe3, + trezorSafe5, + trezorSafe7; } enum HardwareWalletConnectionType { @@ -60,7 +81,7 @@ enum HardwareWalletConnectionType { qr; } -extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType { +extension LedgerToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType { HardwareWalletDeviceType toGeneric() { switch (this) { case ledger.LedgerDeviceType.blue: @@ -81,7 +102,24 @@ extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType { } } -extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType { +extension TrezorToGenericHardwareWalletDeviceType on trezor.TrezorDeviceType { + HardwareWalletDeviceType toGeneric() { + switch (this) { + case trezor.TrezorDeviceType.modelOne: + return HardwareWalletDeviceType.trezorModelOne; + case trezor.TrezorDeviceType.modelT: + return HardwareWalletDeviceType.trezorModelT; + case trezor.TrezorDeviceType.safe3: + return HardwareWalletDeviceType.trezorSafe3; + case trezor.TrezorDeviceType.safe5: + return HardwareWalletDeviceType.trezorSafe5; + case trezor.TrezorDeviceType.safe7: + return HardwareWalletDeviceType.trezorSafe7; + } + } +} + +extension LedgerToGenericHardwareWalletConnectionType on ledger.ConnectionType { HardwareWalletConnectionType toGeneric() { switch (this) { case ledger.ConnectionType.usb: @@ -91,3 +129,14 @@ extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType { } } } + +extension TrezorToGenericHardwareWalletConnectionType on trezor.ConnectionType { + HardwareWalletConnectionType toGeneric() { + switch (this) { + case trezor.ConnectionType.usb: + return HardwareWalletConnectionType.usb; + case trezor.ConnectionType.ble: + return HardwareWalletConnectionType.ble; + } + } +} diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 08143b3b3..0a378e219 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -236,13 +236,14 @@ class CWMonero extends Monero { required String name, required String password, required int height, - required ledger.LedgerConnection ledgerConnection, + required HardwareWalletService hardwareWalletService, }) => MoneroRestoreWalletFromHardwareCredentials( - name: name, - password: password, - height: height, - ledgerConnection: ledgerConnection); + name: name, + password: password, + height: height, + hardwareWalletService: hardwareWalletService, + ); @override WalletCredentials createMoneroRestoreWalletFromSeedCredentials( @@ -416,9 +417,7 @@ class CWMonero extends Monero { } @override - void monerocCheck() { - checkIfMoneroCIsFine(); - } + void monerocCheck() => checkIfMoneroCIsFine(); @override Future setLedgerConnection(Object wallet, ledger.LedgerConnection connection) async { @@ -427,29 +426,41 @@ class CWMonero extends Monero { } @override - void resetLedgerConnection() { - disableLedgerExchange(); - } + void resetLedgerConnection() => disableLedgerExchange(); @override - void setGlobalLedgerConnection(ledger.LedgerConnection connection) { - gLedger = connection; - } + void setGlobalLedgerConnection(ledger.LedgerConnection connection) => gLedger = connection; @override String? getLastLedgerCommand() => latestLedgerCommand; - bool isViewOnly() { - return isViewOnlyBySpendKey(null); + @override + void setHardwareWalletService(Object wallet, HardwareWalletService service) { + final moneroWallet = wallet as MoneroWallet; + moneroWallet.trezorService = service as MoneroTrezorService; } - @override - Map> debugCallLength() { - return monero_wallet_api.debugCallLength(); - } @override - Map getWalletCacheDebug() { - return monero_wallet_api.getWalletCacheDebug(); + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) => + MoneroLedgerService(connection); + + @override + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client) => + MoneroTrezorService(client); + + @override + Future syncTrezor(Object wallet) async { + final moneroWallet = wallet as MoneroWallet; + + return moneroWallet.syncTrezor(); } + + bool isViewOnly() => isViewOnlyBySpendKey(null); + + @override + Map> debugCallLength() => monero_wallet_api.debugCallLength(); + + @override + Map getWalletCacheDebug() => monero_wallet_api.getWalletCacheDebug(); } diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart index c3f5da957..9292d1af2 100644 --- a/lib/new-ui/pages/send_page.dart +++ b/lib/new-ui/pages/send_page.dart @@ -726,12 +726,13 @@ class _NewSendPageState extends State { } if (widget.sendViewModel.wallet.isHardwareWallet) { - if (!widget.sendViewModel.hardwareWalletViewModel!.isConnected) { + if (!widget.sendViewModel.hardwareWalletViewModel! + .isConnected(widget.sendViewModel.walletType)) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: widget.sendViewModel.walletType, hardwareWalletType: widget.sendViewModel.wallet.walletInfo.hardwareWalletType!, - onConnectDevice: (BuildContext context, _) { + onConnectDevice: (_, __) { widget.sendViewModel.hardwareWalletViewModel! .initWallet(widget.sendViewModel.wallet); Navigator.of(context).pop(); @@ -748,8 +749,12 @@ class _NewSendPageState extends State { amount += item.formattedCryptoAmount; } if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) { - await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, - arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet)); + if (widget.sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor) { + await monero!.syncTrezor(widget.sendViewModel.wallet); + } else { + await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, + arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet)); + } await Future.delayed(Duration(seconds: 1)); // wait for monero to refresh the state } if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) { diff --git a/lib/new-ui/widgets/coins_page/wallet_info.dart b/lib/new-ui/widgets/coins_page/wallet_info.dart index 21c83db44..4963513fc 100644 --- a/lib/new-ui/widgets/coins_page/wallet_info.dart +++ b/lib/new-ui/widgets/coins_page/wallet_info.dart @@ -86,11 +86,11 @@ class WalletInfoBar extends StatelessWidget { case null: return null; case HardwareWalletType.bitbox: - return "assets/images/hardware_wallet/device_bitbox.svg"; + return "assets/new-ui/hardware_wallets/device_bitbox.svg"; case HardwareWalletType.ledger: - return "assets/images/hardware_wallet/device_ledger_nano_x.svg"; + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg"; case HardwareWalletType.trezor: - return "assets/images/hardware_wallet/device_trezor_safe_5.svg"; + return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg"; case HardwareWalletType.cupcake: return "assets/images/cupcake.svg"; case HardwareWalletType.coldcard: diff --git a/lib/new-ui/widgets/digit_input.dart b/lib/new-ui/widgets/digit_input.dart new file mode 100644 index 000000000..d6889db67 --- /dev/null +++ b/lib/new-ui/widgets/digit_input.dart @@ -0,0 +1,182 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class DigitInputController { + String _text = ""; + + String get text => _text; + + set text(String newText) { + _text = newText; + for (final listener in _listeners) { + listener.call(); + } + } + + final List _listeners = []; + + void addListener(void Function() listener) => _listeners.add(listener); +} + +class DigitInputPill extends StatelessWidget { + const DigitInputPill({super.key, this.digit, required this.highlighted}); + + final String? digit; + final bool highlighted; + + @override + Widget build(BuildContext context) { + return Container( + width: 51, + height: 84, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(16), + border: + highlighted ? Border.all(color: Theme.of(context).colorScheme.primary, width: 2) : null, + ), + child: Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Text(digit ?? " ", style: TextStyle(fontSize: 40)), + ), + ), + ); + } +} + +class DigitInput extends StatefulWidget { + const DigitInput({ + super.key, + required this.controller, + required this.desiredLength, + this.breakAt = 3, + }); + + final DigitInputController controller; + final int desiredLength; + final int breakAt; + + @override + State createState() => _DigitInputState(); +} + +class _DigitInputState extends State implements TextInputClient { + TextInputConnection? _textInputConnection; + + @override + void initState() { + super.initState(); + _openVirtualKeyboard(); + } + + void _openVirtualKeyboard() { + if (_textInputConnection == null || !_textInputConnection!.attached) { + _textInputConnection = TextInput.attach( + this, + const TextInputConfiguration( + inputType: TextInputType.numberWithOptions( + signed: false, + decimal: false, + ), + inputAction: TextInputAction.none, + ), + ); + } + + _textInputConnection!.setEditingState( + TextEditingValue( + text: widget.controller.text, + selection: TextSelection.collapsed(offset: widget.controller.text.length), + ), + ); + _textInputConnection!.show(); + } + + void _closeVirtualKeyboard() => _textInputConnection?.close(); + + @override + void dispose() { + _closeVirtualKeyboard(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _openVirtualKeyboard, + child: SizedBox( + height: 84, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => DigitInputPill( + digit: widget.controller.text.length > index ? widget.controller.text[index] : null, + highlighted: widget.controller.text.length == index, + ), + separatorBuilder: (context, index) => + SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4), + itemCount: widget.desiredLength, + ), + ), + ); + } + + @override + void updateEditingValue(TextEditingValue value) { + final RegExp numberRegExp = RegExp(r'^\d{0,6}$'); + + if (numberRegExp.hasMatch(value.text)) { + setState(() => widget.controller.text = value.text); + } else { + // flutter keeps the text editing state regardless of whether you actually used it or not. + // so, if validation fails, we gotta set it back to what it was explicitly + _textInputConnection?.setEditingState( + TextEditingValue( + text: widget.controller.text, + selection: TextSelection.collapsed(offset: widget.controller.text.length), + ), + ); + } + } + + @override + void performAction(TextInputAction action) {} + + @override + TextEditingValue? get currentTextEditingValue => TextEditingValue(text: widget.controller.text); + + @override + void connectionClosed() => _textInputConnection = null; + + @override + AutofillScope? get currentAutofillScope => null; + + @override + void performPrivateCommand(String action, Map data) {} + + @override + void showAutocorrectionPromptRect(int start, int end) {} + + @override + void updateFloatingCursor(RawFloatingCursorPoint point) {} + + @override + void insertTextPlaceholder(Size size) {} + + @override + void removeTextPlaceholder() {} + + @override + void showToolbar() {} + + @override + void performSelector(String selectorName) {} + + @override + void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {} + + @override + void insertContent(KeyboardInsertedContent content) {} +} diff --git a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart new file mode 100644 index 000000000..100325dbe --- /dev/null +++ b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart @@ -0,0 +1,60 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:flutter/material.dart'; + +class HardwareWalletProceedOnDeviceMessage extends StatelessWidget { + const HardwareWalletProceedOnDeviceMessage({super.key, required this.hardwareWalletType}); + + final HardwareWalletType hardwareWalletType; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.primary, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column(spacing: 8, children: [ + if (hardwareWalletIcon != null) + CakeImageWidget( + imageUrl: hardwareWalletIcon!, + width: 36, + height: 36, + colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), + ), + Text( + S.of(context).proceed_on_device, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface, + ), + ) + ]), + ), + ); + } + + String? get hardwareWalletIcon { + switch (hardwareWalletType) { + case HardwareWalletType.bitbox: + return "assets/new-ui/hardware_wallets/device_bitbox.svg"; + case HardwareWalletType.ledger: + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg"; + case HardwareWalletType.trezor: + return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg"; + case HardwareWalletType.cupcake: + return "assets/images/cupcake.svg"; + case HardwareWalletType.coldcard: + case HardwareWalletType.seedsigner: + case HardwareWalletType.keystone: + return "assets/images/hardware_wallet/device_qr.svg"; + } + } +} diff --git a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart new file mode 100644 index 000000000..2a06f37e6 --- /dev/null +++ b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart @@ -0,0 +1,338 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/digit_input.dart'; +import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; +import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; +import 'package:cake_wallet/new-ui/widgets/send_page/directional_switcher.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/src/widgets/base_alert_dialog.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; + +class HardwareWalletProceedOnDeviceSheet extends StatefulWidget { + const HardwareWalletProceedOnDeviceSheet({ + super.key, + required this.hardwareWalletType, + required this.trezorConnectVM, + required this.onRetry, + }); + + final HardwareWalletType hardwareWalletType; + final TrezorConnectViewModelBase trezorConnectVM; + final void Function() onRetry; + + @override + State createState() => + _HardwareWalletProceedOnDeviceSheetState(); +} + +class _HardwareWalletProceedOnDeviceSheetState extends State { + TrezorParingState _paringState = TrezorParingState.initial; + + final DigitInputController _controller = DigitInputController(); + + static const pinOpenDuration = Duration(milliseconds: 300); + + // you should probably check if there can be other pin lengths + static const pinLength = 6; + + ReactionDisposer? paringStateReaction; + + @override + void initState() { + super.initState(); + _controller.addListener(() => setState(() {})); + + _paringState = widget.trezorConnectVM.paringState; + paringStateReaction = reaction((_) => widget.trezorConnectVM.paringState, (paringState) { + if (paringState is SuccessTrezorParingState) { + if (mounted) Navigator.of(context).pop(); + return; + } + setState(() => _paringState = paringState); + }); + } + + @override + void dispose() { + super.dispose(); + paringStateReaction?.reaction.dispose(); + } + + bool get _isAwaitingPin => _paringState == TrezorParingState.enterPin; + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + child: SafeArea( + bottom: false, + child: Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + children: [ + ModalTopBar( + title: "", + leadingWidget: AnimatedSwitcher( + layoutBuilder: (currentChild, previousChildren) { + return Stack( + alignment: Alignment.centerLeft, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, + duration: pinOpenDuration, + child: Text( + key: ValueKey(pageTitle), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + pageTitle, + ), + ), + trailingIcon: Icon( + Icons.close, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + onTrailingPressed: () => showPopUp( + context: context, + builder: (context) => AlertWithTwoActions( + alertTitle: S.of(context).are_you_sure_exit, + alertContent: S.of(context).hww_exit_desc, + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).yes_exit, + rightAlertButtonStyle: AlertButtonStyle.error(context), + actionLeftButton: Navigator.of(context).pop, + actionRightButton: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ), + ), + ), + Expanded( + child: DirectionalAnimatedSwitcher( + duration: Duration(milliseconds: 400), + child: content, + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 24, right: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + AnimatedOpacity( + curve: Curves.easeOutQuad, + opacity: hasFullPin ? 1 : 0, + duration: pinOpenDuration, + child: ModernButton( + backgroundColor: Theme.of(context).colorScheme.primary, + iconColor: Theme.of(context).colorScheme.onPrimary, + size: 36, + icon: Icon(Icons.arrow_forward), + onPressed: () => + widget.trezorConnectVM.setParingPin(_controller.text.trim()), + ), + ), + ], + ), + ) + ], + ), + )), + ), + ), + ); + } + + Widget get content { + if (_paringState is InitialTrezorParingState || _paringState is EnterPinTrezorParingState) { + return _enterPinCode(); + } + + if (_paringState is VerifyingPinTrezorParingState) return _verifyingCode(); + + if (_paringState is FailTrezorParingState) + return _errorBox((_paringState as FailTrezorParingState).message); + + return SizedBox.shrink(key: ValueKey(3)); + } + + void retry() { + _controller.text = ""; + widget.onRetry(); + } + + String get pageTitle { + if (_paringState is InitialTrezorParingState) return S.of(context).device_confirmation; + if (_isAwaitingPin) return S.of(context).pairing_code; + return ""; + } + + bool get hasFullPin => _controller.text.length == pinLength && _isAwaitingPin; + + String? get hardwareWalletIcon { + switch (widget.hardwareWalletType) { + case HardwareWalletType.bitbox: + return "assets/new-ui/hardware_wallets/device_bitbox.svg"; + case HardwareWalletType.ledger: + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg"; + case HardwareWalletType.trezor: + return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg"; + case HardwareWalletType.cupcake: + return "assets/images/cupcake.svg"; + case HardwareWalletType.coldcard: + case HardwareWalletType.seedsigner: + case HardwareWalletType.keystone: + return "assets/images/hardware_wallet/device_qr.svg"; + } + } + + Widget _verifyingCode() => Container( + key: ValueKey(1), + width: MediaQuery.of(context).size.width, + child: Column( + spacing: 12, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CupertinoActivityIndicator(radius: 36), + const SizedBox(), + Text( + "${S.of(context).verifying_code}...", + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), + ), + Text( + S.of(context).this_can_take_few_seconds, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + ); + + Widget _errorBox(String errorText) => Container( + key: ValueKey(2), + width: MediaQuery.of(context).size.width, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 12, + children: [ + const Spacer(), + Icon( + Icons.error_outline, + size: 80, + color: Theme.of(context).colorScheme.error, + ), + Text( + S.of(context).pairing_error, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, + ), + textAlign: TextAlign.center, + ), + Text( + errorText, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + const Spacer(), + NewPrimaryButton( + onPressed: retry, + text: S.of(context).try_again, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ) + ], + ), + ), + ); + + Widget _enterPinCode() => Column( + key: ValueKey(0), + spacing: 12, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedContainer( + duration: pinOpenDuration, + curve: Curves.easeOutCubic, + width: _isAwaitingPin ? 40 : 100, + height: _isAwaitingPin ? 40 : 100, + child: CakeImageWidget( + imageUrl: hardwareWalletIcon, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onSurfaceVariant, + BlendMode.srcIn, + ), + ), + ), + AnimatedSwitcher( + duration: pinOpenDuration, + switchInCurve: Curves.easeInCubic, + switchOutCurve: Curves.easeOutCubic, + child: _isAwaitingPin + ? Column( + spacing: 24, + key: ValueKey(1), + children: [ + Text( + textAlign: TextAlign.center, + "${S.of(context).pairing_code_desc_1}\n${S.of(context).pairing_code_desc_2}", + ), + DigitInput( + controller: _controller, + desiredLength: pinLength, + ) + ], + ) + : Column( + key: ValueKey(0), + spacing: 12, + children: [ + Text( + S.of(context).proceed_on_device, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 20, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Text( + S.of(context).proceed_on_device_description, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ], + ), + ) + ], + ); +} diff --git a/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart new file mode 100644 index 000000000..9b429edc4 --- /dev/null +++ b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart @@ -0,0 +1,106 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; +import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; +import 'package:flutter/material.dart'; + +class HardwareWalletTrezorParingSheet extends StatefulWidget { + const HardwareWalletTrezorParingSheet({super.key}); + + @override + State createState() => _HardwareWalletTrezorParingSheetState(); +} + +class _HardwareWalletTrezorParingSheetState extends State { + final TextEditingController _paringCodeController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: SafeArea( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical(top: Radius.circular(18)), + color: Theme.of(context).colorScheme.surface, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ModalTopBar( + title: "Paring code", + onLeadingPressed: Navigator.of(context).pop, + onTrailingPressed: () {}, + leadingIcon: Icon(Icons.close), + ), + Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, + children: [ + SizedBox(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + flex: 75, + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + height: 60, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + bottomLeft: Radius.circular(16), + ), + border: Border.all( + color: Theme.of(context).colorScheme.surfaceContainer, + width: 2, + ), + ), + child: TextField( + textAlign: TextAlign.left, + textAlignVertical: TextAlignVertical.center, + controller: _paringCodeController, + keyboardType: + TextInputType.numberWithOptions(signed: false, decimal: false), + decoration: InputDecoration( + hint: Text( + "000 000", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.5), + ), + ), + border: InputBorder.none, + filled: true, + fillColor: Colors.transparent, + ), + style: TextStyle(color: Theme.of(context).colorScheme.onSurface), + ), + ), + ), + ], + ), + SizedBox(), + NewPrimaryButton( + text: S.of(context).continue_text, + onPressed: () => Navigator.of(context).pop(_paringCodeController.text), + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ) + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart b/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart index 68f8c6889..671579914 100644 --- a/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart +++ b/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart @@ -1,16 +1,14 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/widgets/confirm_swiper.dart'; +import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart'; import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart'; import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; -import 'package:cw_core/wallet_info.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:vector_graphics/vector_graphics.dart'; class SendConfirmBottomWidget extends StatelessWidget { const SendConfirmBottomWidget({super.key, required this.sendViewModel}); @@ -61,7 +59,7 @@ class SendConfirmBottomWidget extends StatelessWidget { text: "${S.of(context).signing_transaction}...", ); case IsAwaitingDeviceResponseState: - return HardwareWalletConfirmationMessage( + return HardwareWalletProceedOnDeviceMessage( hardwareWalletType: sendViewModel.wallet.hardwareWalletType!); case TransactionCommitting: return LoadingBottomWidget( @@ -173,58 +171,3 @@ class TransactionErrorActions extends StatelessWidget { ); } } - -class HardwareWalletConfirmationMessage extends StatelessWidget { - const HardwareWalletConfirmationMessage({super.key, required this.hardwareWalletType}); - - final HardwareWalletType hardwareWalletType; - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.primary, - ), - borderRadius: BorderRadius.circular(16), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column(spacing: 8, children: [ - if (hardwareWalletIcon != null) - CakeImageWidget(imageUrl: - hardwareWalletIcon!, - width: 36, - height: 36, - colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), - ), - Text( - S.of(context).proceed_on_device, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.onSurface), - ) - ]), - ), - ); - } - - String? get hardwareWalletIcon { - switch (hardwareWalletType) { - case HardwareWalletType.bitbox: - return "assets/images/hardware_wallet/device_bitbox.svg"; - case HardwareWalletType.ledger: - return "assets/images/hardware_wallet/device_ledger_nano_x.svg"; - case HardwareWalletType.trezor: - return "assets/images/hardware_wallet/device_trezor_safe_5.svg"; - case HardwareWalletType.cupcake: - return "assets/images/cupcake.svg"; - case HardwareWalletType.coldcard: - case HardwareWalletType.seedsigner: - case HardwareWalletType.keystone: - return "assets/images/hardware_wallet/device_qr.svg"; - } - } -} diff --git a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart index 9ccfb1453..3676cd6e4 100644 --- a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart +++ b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart @@ -41,7 +41,7 @@ class _SwapConfirmSheetState extends State { final sendVM = widget.exchangeTradeViewModel.sendViewModel; if (sendVM.wallet.isHardwareWallet) { - if (!sendVM.hardwareWalletViewModel!.isConnected) { + if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: sendVM.walletType, diff --git a/lib/router.dart b/lib/router.dart index a5af558eb..89365171a 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -371,7 +371,7 @@ Route createRoute(RouteSettings settings) { (_) => getIt.get( param1: NewWalletTypeArguments( onTypeSelected: (BuildContext context, WalletType type) { - if (hardwareWalletType == HardwareWalletType.trezor) { + if (hardwareWalletType == HardwareWalletType.trezor && type != WalletType.monero) { Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type, hardwareWalletType]); return; diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index 2122db44a..22e466cf3 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -3,10 +3,13 @@ import 'dart:io'; import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/main.dart'; +import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/themes/core/material_base_theme.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; @@ -91,6 +94,8 @@ class ConnectDevicePageBodyState extends State { var bleDevices = []; var usbDevices = []; + List get allDevices => [...bleDevices, ...usbDevices]; + late Timer? _usbRefreshTimer = null; late Timer? _bleRefreshTimer = null; late Timer? _bleStateTimer = null; @@ -158,24 +163,38 @@ class ConnectDevicePageBodyState extends State { Future _connectToDevice(HardwareWalletDevice device) async { final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType); - if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (isConnected) widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM); + }); } String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) { switch (deviceType) { case HardwareWalletDeviceType.ledgerNanoX: - return 'assets/images/hardware_wallet/ledger_nano_x.png'; + return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg'; case HardwareWalletDeviceType.ledgerNanoGen5: - return 'assets/images/hardware_wallet/device_ledger_nano_gen_5.svg'; + return 'assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg'; case HardwareWalletDeviceType.ledgerStax: - return 'assets/images/hardware_wallet/ledger_stax.png'; + return 'assets/new-ui/hardware_wallets/ledger_stax.svg'; case HardwareWalletDeviceType.ledgerFlex: - return 'assets/images/hardware_wallet/ledger_flex.png'; + return 'assets/new-ui/hardware_wallets/ledger_flex.svg'; case HardwareWalletDeviceType.BitBox02: - return 'assets/images/hardware_wallet/device_bitbox.svg'; + case HardwareWalletDeviceType.BitBox02Nova: + return 'assets/new-ui/hardware_wallets/device_bitbox.svg'; + case HardwareWalletDeviceType.trezorModelOne: + return 'assets/new-ui/hardware_wallets/device_trezor_model_one.svg'; + case HardwareWalletDeviceType.trezorModelT: + return 'assets/new-ui/hardware_wallets/device_trezor_model_t.svg'; + case HardwareWalletDeviceType.trezorSafe3: + return 'assets/new-ui/hardware_wallets/device_trezor_safe_3.svg'; + case HardwareWalletDeviceType.trezorSafe5: + return 'assets/new-ui/hardware_wallets/device_trezor_safe_5.svg'; + case HardwareWalletDeviceType.trezorSafe7: + return 'assets/new-ui/hardware_wallets/device_trezor_safe_7.svg'; default: - return 'assets/images/hardware_wallet/ledger_nano_x.png'; + return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg'; } } @@ -197,25 +216,15 @@ class ConnectDevicePageBodyState extends State { Expanded( child: SingleChildScrollView( child: Column( + spacing: 24, children: [ - Padding( - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), - child: Text( - description, - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), + CakeImageWidget( + imageUrl: "assets/new-ui/hardware_wallet.svg", ), - Offstage( - offstage: !longWait, - child: Padding( - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), - child: Text( - S.of(context).if_you_dont_see_your_device, - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), - ), + Text( + description, + style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant), + textAlign: TextAlign.center, ), Observer( builder: (_) => Offstage( @@ -231,56 +240,69 @@ class ConnectDevicePageBodyState extends State { ), ), ), - if (bleDevices.length > 0) ...[ - Padding( - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), - child: Container( - width: double.infinity, - child: Text( - S.of(context).bluetooth, - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ), - ...bleDevices - .map( - (device) => Padding( - padding: EdgeInsets.only(bottom: 20), - child: DeviceTile( - onPressed: () => _connectToDevice(device), - title: device.name, - leading: _getDeviceTileLeading(device.type), - connectionType: device.connectionType, + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(18)), + child: ListView.separated( + shrinkWrap: true, + itemBuilder: (context, index) { + final item = allDevices[index]; + return GestureDetector( + onTap: () => _connectToDevice(item), + child: SizedBox( + height: 48, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + spacing: 12, + children: [ + CakeImageWidget( + imageUrl: _getDeviceTileLeading(item.type), + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onSurfaceVariant, + BlendMode.srcIn), + ), + Text(item.name) + ], + ), + Row( + spacing: 12, + children: [ + if (item.connectionType == + HardwareWalletConnectionType.ble) + CakeImageWidget( + imageUrl: "assets/new-ui/bluetooth.svg", + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.primary, + BlendMode.srcIn), + ), + CakeImageWidget( + imageUrl: "assets/new-ui/arrow_forward.svg", + height: 16, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onSurfaceVariant, + BlendMode.srcIn)) + ], + ) + ], + ), + ), ), - ), - ) - .toList() - ], - if (usbDevices.length > 0) ...[ - Padding( - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), - child: Container( - width: double.infinity, - child: Text( - S.of(context).usb, - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ), - ...usbDevices - .map( - (device) => Padding( - padding: EdgeInsets.only(bottom: 20), - child: DeviceTile( - onPressed: () => _connectToDevice(device), - title: device.name, - leading: _getDeviceTileLeading(device.type), - connectionType: device.connectionType, - ), - ), - ) - .toList(), - ], + ); + }, + separatorBuilder: (context, index) => Container( + height: 1, + color: Theme.of(context).colorScheme.surfaceContainerHigh), + itemCount: allDevices.length), + ), if (widget.allowChangeWallet) ...[ PrimaryButton( text: S.of(context).wallets, @@ -293,10 +315,10 @@ class ConnectDevicePageBodyState extends State { ), ), ), - PrimaryButton( + NewPrimaryButton( text: S.of(context).how_to_connect, color: Theme.of(context).colorScheme.surfaceContainer, - textColor: Theme.of(context).colorScheme.onSecondaryContainer, + textColor: Theme.of(context).colorScheme.primary, onPressed: () => _onHowToConnect(context), ) ], diff --git a/lib/src/screens/connect_device/widgets/device_tile.dart b/lib/src/screens/connect_device/widgets/device_tile.dart index 803a648bb..4014e5040 100644 --- a/lib/src/screens/connect_device/widgets/device_tile.dart +++ b/lib/src/screens/connect_device/widgets/device_tile.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -43,14 +44,12 @@ class DeviceTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (leading != null && !leading!.endsWith(".svg")) - Image.asset( - leading!, - height: 30, - color: Theme.of(context).colorScheme.onSurface, - ), - if (leading != null && leading!.endsWith(".svg")) - SvgPicture.asset(leading!, height: 30), + CakeImageWidget( + height: 30, + imageUrl: leading, + colorFilter: + ColorFilter.mode(Theme.of(context).colorScheme.onSurface, BlendMode.srcIn), + ), Expanded( child: Padding( padding: EdgeInsets.only(left: 16), diff --git a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart index a6ea7b340..de05bccf4 100644 --- a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart +++ b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart @@ -34,11 +34,11 @@ class CryptoBalanceWidget extends StatelessWidget { case null: return null; case HardwareWalletType.bitbox: - return "assets/images/hardware_wallet/device_bitbox.svg"; + return "assets/new-ui/hardware_wallets/device_bitbox.svg"; case HardwareWalletType.ledger: - return "assets/images/hardware_wallet/device_ledger_nano_x.svg"; + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg"; case HardwareWalletType.trezor: - return "assets/images/hardware_wallet/device_trezor_safe_5.svg"; + return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg"; case HardwareWalletType.cupcake: return "assets/images/cupcake.svg"; case HardwareWalletType.coldcard: diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index d7fd3196e..6ebd2526f 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -213,7 +213,7 @@ class ExchangeTradeState extends State { final sendVM = widget.exchangeTradeViewModel.sendViewModel; if (sendVM.wallet.isHardwareWallet) { - if (!sendVM.hardwareWalletViewModel!.isConnected) { + if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: sendVM.walletType, diff --git a/lib/src/screens/integrations/deuro/savings_page.dart b/lib/src/screens/integrations/deuro/savings_page.dart index 4a5d62a02..a08437822 100644 --- a/lib/src/screens/integrations/deuro/savings_page.dart +++ b/lib/src/screens/integrations/deuro/savings_page.dart @@ -160,7 +160,7 @@ class DEuroSavingsPage extends BasePage { Future _requireHardwareWallet(BuildContext context) async { if (_dEuroViewModel.wallet.isHardwareWallet) { - if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected) { + if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected(_dEuroViewModel.wallet.type)) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: _dEuroViewModel.wallet.type, diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index fff206836..4b170dc03 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -6,14 +6,8 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_utils.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; -import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart'; -import 'package:cake_wallet/src/widgets/search_bar_widget.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/hardware/device_connection_type.dart'; @@ -21,10 +15,38 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; +class NewWalletTypePageHeader extends StatelessWidget { + const NewWalletTypePageHeader({super.key}); + + @override + Widget build(BuildContext context) => const SizedBox( + width: 138, + height: 75, + child: Stack( + children: [ + Positioned( + left: 0, + child: CakeImageWidget( + imageUrl: "assets/new-ui/hardware_wallet.svg", + width: 75, + height: 75, + ), + ), + Positioned( + left: 63, + child: CakeImageWidget( + imageUrl: "assets/new-ui/cake_coin.svg", + width: 75, + height: 75, + ), + ), + ], + ), + ); +} + class NewWalletTypePage extends BasePage { - NewWalletTypePage({ - required this.newWalletTypeArguments, - }); + NewWalletTypePage({required this.newWalletTypeArguments}); final NewWalletTypeArguments newWalletTypeArguments; @@ -38,23 +60,21 @@ class NewWalletTypePage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + final currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) currentFocus.focusedChild?.unfocus(); + }; @override Widget body(BuildContext context) => WalletTypeForm( - walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage, - isCreate: newWalletTypeArguments.isCreate, - onTypeSelected: newWalletTypeArguments.onTypeSelected, - hardwareWalletType: newWalletTypeArguments.hardwareWalletType, - ); + walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage, + isCreate: newWalletTypeArguments.isCreate, + onTypeSelected: newWalletTypeArguments.onTypeSelected, + hardwareWalletType: newWalletTypeArguments.hardwareWalletType, + ); } class WalletTypeForm extends StatefulWidget { - WalletTypeForm({ + const WalletTypeForm({ required this.walletImage, required this.isCreate, this.onTypeSelected, @@ -77,9 +97,6 @@ class WalletTypeFormState extends State { static const aspectRatioImage = 1.22; - final TextEditingController searchController = TextEditingController(); - - WalletType? selected; List types; List filteredTypes = []; @@ -87,21 +104,12 @@ class WalletTypeFormState extends State { void initState() { types = filteredTypes = availableWalletTypes .where((element) => - !widget.isHardwareWallet || - DeviceConnectionType.supportedConnectionTypes( - element, widget.hardwareWalletType!, Platform.isIOS) - .isNotEmpty) + !widget.isHardwareWallet || + DeviceConnectionType.supportedConnectionTypes( + element, widget.hardwareWalletType!, Platform.isIOS) + .isNotEmpty) .toList(); super.initState(); - - searchController.addListener(() { - setState(() { - filteredTypes = List.from(types.where((type) => walletTypeToDisplayName(type) - .toLowerCase() - .contains(searchController.text.toLowerCase()))); - return; - }); - }); } @override @@ -110,96 +118,102 @@ class WalletTypeFormState extends State { child: ConstrainedBox( constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: Column( + spacing: 24, children: [ - Padding( - padding: EdgeInsets.only(top: 48), - child: Text( - S.of(context).choose_wallet_currency, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), + const NewWalletTypePageHeader(), + Text( + S.of(context).choose_wallet_currency, + textAlign: TextAlign.center, ), - Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 12), - child: SearchBarWidget(searchController: searchController), - ), - Expanded( - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - scrollableKey: ValueKey('new_wallet_type_scrollable_key'), - content: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ...filteredTypes.map( - (type) => Padding( - padding: EdgeInsets.only(top: 12), - child: SelectButton( - key: ValueKey('new_wallet_type_${type.name}_button_key'), - image: CakeImageWidget( - imageUrl: getCryptoCurrencyIconForWalletListItem(type), - height: 24, - width: 24, + Flexible( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(18), + ), + child: ListView.separated( + key: const ValueKey('new_wallet_type_scrollable_key'), + itemCount: filteredTypes.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final item = filteredTypes[index]; + final curr = getCryptoCurrencyForWalletListItem(item); + + return GestureDetector( + key: ValueKey('new_wallet_type_${item.name}_button_key'), + behavior: HitTestBehavior.opaque, + onTap: () { + onTypeSelected(item); + }, + child: SizedBox( + height: 48, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + CakeImageWidget( + height: 24, + width: 24, + imageUrl: curr.iconPath, + ), + const SizedBox(width: 12), + Text(curr.fullName ?? curr.name), + const SizedBox(width: 4), + Text( + curr.title, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + ], + ), + CakeImageWidget( + imageUrl: "assets/new-ui/arrow_forward.svg", + height: 16, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onSurfaceVariant, + BlendMode.srcIn, + ), + ) + ], + ), ), - text: walletTypeToDisplayName(type), - showTrailingIcon: false, - height: 54, - isSelected: selected == type, - onTap: () => setState(() => selected = type), - deviceConnectionTypes: widget.isHardwareWallet - ? DeviceConnectionType.supportedConnectionTypes( - type, widget.hardwareWalletType!, Platform.isIOS) - : [], ), - ), + ); + }, + separatorBuilder: (context, index) => Container( + height: 1, + color: Theme.of(context).colorScheme.surfaceContainerHigh, ), - ], - ), - bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - bottomSection: PrimaryButton( - key: ValueKey('new_wallet_type_next_button_key'), - onPressed: () => onTypeSelected(), - text: S.of(context).seed_language_next, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - isDisabled: selected == null, + ), ), ), - ), + ) ], ), ), ); } - Future onTypeSelected() async { - if (selected == null) throw Exception('Wallet Type is not selected yet.'); - - if (selected == WalletType.haven && widget.isCreate) { - return await showPopUp( - context: context, - builder: (BuildContext context) => PopUpCancellableAlertDialog( - contentText: S.of(context).pause_wallet_creation, - actionButtonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ), - ); - } - + Future onTypeSelected(WalletType selected) async { // If it's a restore flow, trigger the external callback // If it's not a BIP39 Wallet or if there are no other wallets, route to the newWallet page // Any other scenario, route to pre-existing seed page final walletList = await WalletInfo.getAll(); if (!widget.isCreate) { - widget.onTypeSelected!(context, selected!); - } else if (!isBIP39Wallet(selected!) || walletList.isEmpty) { + widget.onTypeSelected!(context, selected); + } else if (!isBIP39Wallet(selected) || walletList.isEmpty) { Navigator.of(context).pushNamed( Routes.newWallet, - arguments: NewWalletArguments(type: selected!), + arguments: NewWalletArguments(type: selected), ); } else { - Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected!); + Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected); } } -} \ No newline at end of file +} diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 7e44e413d..db0d5e883 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -426,7 +426,7 @@ class SendPage extends BasePage { } if (sendViewModel.wallet.isHardwareWallet) { - if (!sendViewModel.hardwareWalletViewModel!.isConnected) { + if (!sendViewModel.hardwareWalletViewModel!.isConnected(sendViewModel.walletType)) { await Navigator.of(context).pushNamed(Routes.connectDevices, arguments: ConnectDevicePageParams( walletType: sendViewModel.walletType, diff --git a/lib/view_model/hardware_wallet/bitbox_view_model.dart b/lib/view_model/hardware_wallet/bitbox_view_model.dart index 07a412cc2..b51815451 100644 --- a/lib/view_model/hardware_wallet/bitbox_view_model.dart +++ b/lib/view_model/hardware_wallet/bitbox_view_model.dart @@ -78,7 +78,7 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store { bool _isConnecting = false; @override - bool get isConnected => false; + bool isConnected(WalletType type) => false; @override HardwareWalletService getHardwareWalletService(WalletType type) { diff --git a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart index 4c79eda71..de64079b5 100644 --- a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart +++ b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart @@ -6,10 +6,11 @@ import 'package:cw_core/wallet_type.dart'; abstract class HardwareWalletViewModel { HardwareWalletType get hardwareWalletType; - bool get isConnected; bool get isBleEnabled; bool get hasBluetooth; + bool isConnected(WalletType type); + Future updateBleState(); Stream scanForBleDevices(); diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 793f0df84..c0746223a 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -114,7 +114,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store { if (_isConnecting) return false; _isConnecting = true; _connectingWalletType = type; - if (isConnected) { + if (isConnected(type)) { try { await _connection!.disconnect().catchError((_) {}); } catch (_) {} @@ -126,7 +126,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store { if (_connectionChangeSubscription == null) { _connectionChangeSubscription = ledger - .deviceStateChanges + .deviceStateChanges(device.device.id) .listen(_connectionChangeListener); } @@ -172,7 +172,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store { } @override - bool get isConnected => _connection != null && !(_connection!.isDisconnected); + bool isConnected(WalletType type) => _connection != null && !(_connection!.isDisconnected); sdk.LedgerConnection get connection => _connection!; @@ -196,6 +196,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store { @override HardwareWalletService getHardwareWalletService(WalletType type) { switch (type) { + case WalletType.monero: + return monero!.getLedgerHardwareWalletService(connection); case WalletType.bitcoin: return bitcoin!.getLedgerHardwareWalletService(connection, true); case WalletType.litecoin: diff --git a/lib/view_model/hardware_wallet/trezor_connect_view_model.dart b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart new file mode 100644 index 000000000..3fd4514e6 --- /dev/null +++ b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart @@ -0,0 +1,258 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; +import 'package:cake_wallet/evm/evm.dart'; +import 'package:cake_wallet/main.dart'; +import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart'; +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:cw_core/hardware/device_connection_type.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:trezor_connect/trezor_connect.dart' as connect_sdk; +import 'package:trezor_flutter/trezor_flutter.dart' as sdk; + +part 'trezor_connect_view_model.g.dart'; + +class TrezorConnectViewModel = TrezorConnectViewModelBase with _$TrezorConnectViewModel; + +abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with Store { + final connect_sdk.TrezorConnect trezorConnect; + + late final sdk.TrezorInterface trezorBLE; + late final sdk.TrezorInterface trezorUSB; + + sdk.ThpState _state = sdk.ThpState(); + + TrezorConnectViewModelBase(this.trezorConnect) { + if (_doesSupportHardwareWallets) { + reaction((_) => isBleEnabled, (_) { + if (isBleEnabled) _initBLE(); + }); + updateBleState(); + + if (!Platform.isIOS) { + trezorUSB = sdk.TrezorInterface.usb(); + } + } + } + + bool get _doesSupportHardwareWallets { + if (isMoneroOnly) { + return DeviceConnectionType.supportedConnectionTypes( + WalletType.monero, HardwareWalletType.trezor, Platform.isIOS) + .isNotEmpty; + } + + return true; + } + + bool _bleIsInitialized = false; + + Future _initBLE() async { + if (isBleEnabled && !_bleIsInitialized) { + trezorBLE = sdk.TrezorInterface.ble( + onPermissionRequest: (_) async { + if (Platform.isMacOS) return true; + + Map statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.bluetoothAdvertise, + ].request(); + + return statuses.values.where((status) => status.isDenied).isEmpty; + }, + bleOptions: sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)), + ); + _bleIsInitialized = true; + } + } + + @override + HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor; + + @override + @observable + bool isBleEnabled = false; + + @override + bool get hasBluetooth => true; + + @override + Future updateBleState() async { + final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState(); + + final newState = bleState == sdk.AvailabilityState.poweredOn; + + if (newState != isBleEnabled) isBleEnabled = newState; + } + + @override + Stream scanForBleDevices() => + trezorBLE.scan().map((d) => TrezorHardwareWalletDevice(d)); + + @override + Future> getAllUsbDevices() => trezorUSB.devices + .then((devices) => devices.map((d) => TrezorHardwareWalletDevice(d)).toList()); + + @override + Future stopScanning() async { + if (_bleIsInitialized) await trezorBLE.stopScanning(); + if (!Platform.isIOS) await trezorUSB.stopScanning(); + } + + bool _isConnecting = false; + sdk.TrezorClient? _client; + + Completer? _pinCompleter; + + @observable + TrezorParingState paringState = TrezorParingState.initial; + + void setParingPin(String pin) => _pinCompleter?.complete(pin); + + @override + Future connectDevice(HardwareWalletDevice device, WalletType type, + [bool isRetry = false]) async { + if (!(device is TrezorHardwareWalletDevice)) return false; + if (_isConnecting) return false; + _isConnecting = true; + paringState = TrezorParingState.initial; + + try { + final trezorInterface = + device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB; + final connection = await trezorInterface.connect(device.device); + + if (!isRetry) { + showModalBottomSheet( + context: navigatorKey.currentContext!, + isScrollControlled: true, + isDismissible: false, + enableDrag: false, + useSafeArea: true, + builder: (context) => + HardwareWalletProceedOnDeviceSheet( + hardwareWalletType: hardwareWalletType, + trezorConnectVM: this, + onRetry: () => connectDevice(device, type, true) + ), + ); + } + + Future onPinCode() async { + _pinCompleter = Completer(); + paringState = TrezorParingState.enterPin; + + final res = await _pinCompleter!.future; + paringState = TrezorParingState.verifyingPin; + if (res == null) throw Exception(); + return res; + } + + final deviceInfo = await _deviceName; + + _client = sdk.TrezorClient.getClientForConnection( + connection, + _state, + "Cake Wallet", + deviceInfo, + onPinCode, + ); + + await _client!.createChannel(); + + paringState = TrezorParingState.success; + return true; + } catch (e) { + await _client?.connection.disconnect(); + _client = null; + _state = sdk.ThpState(); + // rethrow; + paringState = TrezorParingState.fail(e.toString()); + printV(e); + return false; + } finally { + _isConnecting = false; + } + } + + Future get _deviceName async { + final deviceInfo = DeviceInfoPlugin(); + if (Platform.isAndroid) return (await deviceInfo.androidInfo).model; + if (Platform.isIOS) return (await deviceInfo.iosInfo).name; + return "Computer"; + } + + @override + bool isConnected(WalletType type) => type == WalletType.monero + ? _client != null && _client?.connection.isDisconnected == false + : true; + + @override + HardwareWalletService getHardwareWalletService(WalletType type) { + switch (type) { + case WalletType.monero: + return monero!.getTrezorHardwareWalletService(_client!); + case WalletType.bitcoin: + return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true); + case WalletType.litecoin: + return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false); + case WalletType.ethereum: + case WalletType.polygon: + return evm!.getTrezorHardwareWalletService(trezorConnect); + default: + throw UnimplementedError(); + } + } + + @override + Future initWallet(WalletBase wallet) async { + switch (wallet.type) { + case WalletType.monero: + return monero!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); + case WalletType.bitcoin: + case WalletType.litecoin: + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); + case WalletType.ethereum: + case WalletType.polygon: + return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type)); + default: + throw Exception('Unexpected wallet type: ${wallet.type} for trezor'); + } + } +} + + +abstract class TrezorParingState { + static TrezorParingState initial = InitialTrezorParingState(); + static TrezorParingState enterPin = EnterPinTrezorParingState(); + static TrezorParingState verifyingPin = VerifyingPinTrezorParingState(); + static TrezorParingState success = SuccessTrezorParingState(); + static TrezorParingState fail(String message) => FailTrezorParingState(message); +} + +class InitialTrezorParingState extends TrezorParingState {} + +class EnterPinTrezorParingState extends TrezorParingState {} + +class VerifyingPinTrezorParingState extends TrezorParingState {} + +class SuccessTrezorParingState extends TrezorParingState {} + +class FailTrezorParingState extends TrezorParingState { + final String message; + + FailTrezorParingState(this.message); +} diff --git a/lib/view_model/hardware_wallet/trezor_view_model.dart b/lib/view_model/hardware_wallet/trezor_view_model.dart deleted file mode 100644 index 8a56fb1a4..000000000 --- a/lib/view_model/hardware_wallet/trezor_view_model.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:async'; - -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart'; -import 'package:cake_wallet/evm/evm.dart'; -import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; -import 'package:cw_core/hardware/hardware_wallet_service.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:trezor_connect/trezor_connect.dart' as sdk; -import 'package:mobx/mobx.dart'; - -part 'trezor_view_model.g.dart'; - -class TrezorViewModel = TrezorViewModelBase with _$TrezorViewModel; - -abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store { - final sdk.TrezorConnect trezorConnect; - - TrezorViewModelBase(this.trezorConnect); - - @override - HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor; - - @override - @observable - bool isBleEnabled = false; - - @override - bool get hasBluetooth => false; - - @override - Future updateBleState() async {} - - @override - Stream scanForBleDevices() => throw UnimplementedError(); - - @override - Future> getAllUsbDevices() async => []; - - @override - Future stopScanning() async {} - - @override - Future connectDevice(HardwareWalletDevice device, WalletType type) async => true; - - @override - bool get isConnected => true; - - @override - HardwareWalletService getHardwareWalletService(WalletType type) { - switch (type) { - case WalletType.bitcoin: - return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true); - case WalletType.litecoin: - return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false); - case WalletType.ethereum: - case WalletType.polygon: - return evm!.getTrezorHardwareWalletService(trezorConnect); - default: - throw UnimplementedError(); - } - } - - @override - Future initWallet(WalletBase wallet) async { - switch (wallet.type) { - case WalletType.bitcoin: - case WalletType.litecoin: - return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type)); - case WalletType.ethereum: - case WalletType.polygon: - return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type)); - default: - throw Exception('Unexpected wallet type: ${wallet.type} for trezor'); - } - } -} diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 35883f502..a51423ec5 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -619,7 +619,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor if (!(state is IsExecutingState)) state = IsExecutingState(); if (wallet.isHardwareWallet) { - state = IsAwaitingDeviceResponseState(); if (walletType == WalletType.monero) { _ledgerTxStateTimer = Timer.periodic(Duration(seconds: 1), (timer) { if (monero!.getLastLedgerCommand() == "INS_CLSAG") { @@ -627,6 +626,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor state = IsDeviceSigningResponseState(); } }); + } else { + state = IsAwaitingDeviceResponseState(); } } @@ -970,8 +971,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor throw Exception("Pending transaction doesn't exist. It should not be happened."); } + try { - state = TransactionCommitting(); + state = wallet.isHardwareWallet && walletType == WalletType.monero + ? IsAwaitingDeviceResponseState() + : TransactionCommitting(); if (ocpRequest != null) { await _handleOcpRequest(); diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index ac89b2adc..1baabd1b3 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -89,7 +89,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials( name: name, - ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection, + hardwareWalletService: hardwareWalletVM.getHardwareWalletService(type), password: password, height: _options['height'] as int? ?? 0, ); diff --git a/pubspec_base.yaml b/pubspec_base.yaml index a04b093d6..fb35f3582 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -125,7 +125,7 @@ dependencies: ledger_flutter_plus: git: url: https://github.com/vespr-wallet/ledger-flutter-plus - ref: 60817d4b20144f9da9029f5034790272795b9d38 + ref: 0ccfbe26298e8974dfd4e891f34719c8348562f9 trezor_connect: git: url: https://github.com/cake-tech/trezor_connect @@ -203,7 +203,7 @@ dependency_overrides: ledger_flutter_plus: git: url: https://github.com/cake-tech/ledger-flutter-plus - ref: 5237e5b3d5f07696eefd0aaee9c32478ed399d20 + ref: 7bcb00380783f8611f68f5cd63fec96261fea2c2 ledger_usb_plus: git: url: https://github.com/konstantinullrich/ledger-usb-plus @@ -227,6 +227,10 @@ dependency_overrides: decimal: ^2.3.3 flutter_rust_bridge: 2.11.1 flutter_secure_storage_windows: 3.1.2 + universal_ble: + git: + url: https://github.com/cake-tech/universal_ble.git + ref: f7044558ad111a156987614971e08c583c80175c flutter_icons: image_path: "assets/images/app_logo.png" @@ -290,6 +294,7 @@ flutter: - assets/new-ui/card_icons/outline_icons/ - assets/new-ui/card_icons/symbol_icons/ - assets/new-ui/crypto_full_icons/ + - assets/new-ui/hardware_wallets/ - assets/new-ui/node_speed_badges/ fonts: diff --git a/res/pictures/bluetooth.svg b/res/pictures/bluetooth.svg new file mode 100644 index 000000000..ddee93cf7 --- /dev/null +++ b/res/pictures/bluetooth.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/cake_coin.svg b/res/pictures/cake_coin.svg new file mode 100644 index 000000000..03d468a3c --- /dev/null +++ b/res/pictures/cake_coin.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/pictures/hardware_wallet.svg b/res/pictures/hardware_wallet.svg new file mode 100644 index 000000000..56f7c8998 --- /dev/null +++ b/res/pictures/hardware_wallet.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/hardware_wallet/device_bitbox.svg b/res/pictures/hardware_wallets/device_bitbox.svg similarity index 87% rename from assets/images/hardware_wallet/device_bitbox.svg rename to res/pictures/hardware_wallets/device_bitbox.svg index 52368aaec..fe41717d5 100644 --- a/assets/images/hardware_wallet/device_bitbox.svg +++ b/res/pictures/hardware_wallets/device_bitbox.svg @@ -1,3 +1,3 @@ - + diff --git a/res/pictures/hardware_wallets/device_ledger_flex.svg b/res/pictures/hardware_wallets/device_ledger_flex.svg new file mode 100644 index 000000000..9c8e9f7d4 --- /dev/null +++ b/res/pictures/hardware_wallets/device_ledger_flex.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg b/res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg new file mode 100644 index 000000000..fe1124e33 --- /dev/null +++ b/res/pictures/hardware_wallets/device_ledger_nano_gen_5.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/hardware_wallets/device_ledger_nano_s.svg b/res/pictures/hardware_wallets/device_ledger_nano_s.svg new file mode 100644 index 000000000..a8032746a --- /dev/null +++ b/res/pictures/hardware_wallets/device_ledger_nano_s.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/hardware_wallet/device_ledger_nano_x.svg b/res/pictures/hardware_wallets/device_ledger_nano_x.svg similarity index 94% rename from assets/images/hardware_wallet/device_ledger_nano_x.svg rename to res/pictures/hardware_wallets/device_ledger_nano_x.svg index c605249dc..538bb3fa3 100644 --- a/assets/images/hardware_wallet/device_ledger_nano_x.svg +++ b/res/pictures/hardware_wallets/device_ledger_nano_x.svg @@ -1,3 +1,3 @@ - + diff --git a/res/pictures/hardware_wallets/device_ledger_stax.svg b/res/pictures/hardware_wallets/device_ledger_stax.svg new file mode 100644 index 000000000..270457001 --- /dev/null +++ b/res/pictures/hardware_wallets/device_ledger_stax.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/hardware_wallets/device_trezor_model_one.svg b/res/pictures/hardware_wallets/device_trezor_model_one.svg new file mode 100644 index 000000000..5d1c779c5 --- /dev/null +++ b/res/pictures/hardware_wallets/device_trezor_model_one.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/hardware_wallets/device_trezor_model_t.svg b/res/pictures/hardware_wallets/device_trezor_model_t.svg new file mode 100644 index 000000000..fb437c9ea --- /dev/null +++ b/res/pictures/hardware_wallets/device_trezor_model_t.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/hardware_wallets/device_trezor_safe_3.svg b/res/pictures/hardware_wallets/device_trezor_safe_3.svg new file mode 100644 index 000000000..338c7275b --- /dev/null +++ b/res/pictures/hardware_wallets/device_trezor_safe_3.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/hardware_wallets/device_trezor_safe_5.svg b/res/pictures/hardware_wallets/device_trezor_safe_5.svg new file mode 100644 index 000000000..5ee539f39 --- /dev/null +++ b/res/pictures/hardware_wallets/device_trezor_safe_5.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/hardware_wallets/device_trezor_safe_7.svg b/res/pictures/hardware_wallets/device_trezor_safe_7.svg new file mode 100644 index 000000000..8d442f4b6 --- /dev/null +++ b/res/pictures/hardware_wallets/device_trezor_safe_7.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index f8828872e..f0b425652 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -74,6 +74,7 @@ "approve_tokens": "Approve tokens", "apps": "Apps", "arbiscan_history": "ArbiScan History", + "are_you_sure_exit": "Are you sure you want to exit?", "arrive_in_this_address": "${currency} ${tag}will arrive in this address", "ascending": "Ascending", "ask_each_time": "Ask each time", @@ -330,6 +331,7 @@ "deuro_savings_welcome_description": "dEURO is a fully decentralized, collateralized stablecoin pegged to the Euro — secure, versatile, and censorship-resistant without having to rely on oracles. 1 dEURO = 1 EURO.", "deuro_tooltip_no_eth": "In order to enable dEURO, you must have ETH in your wallet.", "deuro_tx_commited_content": "It might take a couple of seconds for the transaction to confirm and be reflected on screen", + "device_confirmation": "Device Confirmation", "device_is_signing": "Device is signing", "dfx_option_description": "Buy crypto with EUR & CHF. For retail and corporate customers in Europe", "did_you_back_up_seeds": "Did you back up all your seeds?", @@ -529,6 +531,7 @@ "how_to_connect": "How to Connect", "how_to_use": "How to use", "how_to_use_card": "How to use this card", + "hww_exit_desc": "This will ed the ongoing pairing process with your hardware device", "i_understand": "I understand", "icon_style": "Icon style", "id": "ID: ", @@ -722,6 +725,10 @@ "overshot": "Overshot", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Pairing Invalid Event", + "pairing_code": "Pairing Code", + "pairing_code_desc_1": "Your device will show a pairing code.", + "pairing_code_desc_2": "Input here to continue", + "pairing_error": "Pairing Error", "passphrase": "Passphrase (Optional)", "passphrase_view_keys": "Passphrase", "passphrases_doesnt_match": "Passphrases do not match, please try again", @@ -1138,6 +1145,7 @@ "testnet_coins_no_value": "Testnet coins have no value", "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!", "third_intro_title": "Yat plays nicely with others", + "this_can_take_few_seconds": "This can take a few seconds", "this_pair_is_not_supported_warning": "This pair is not supported with the currently selected exchange(s). Please select another exchange.", "thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address", "thorchain_taproot_address_not_supported": "The THORChain provider does not support Taproot addresses. Please change the address or select a different provider.", @@ -1287,6 +1295,7 @@ "value_type": "Value Type", "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges", "verification": "Verification", + "verifying_code": "Verifying Code", "verify_message": "Verify Message", "verify_seed": "Verify Seed", "verify_with_2fa": "Verify with Cake 2FA", @@ -1394,6 +1403,7 @@ "yat_popup_title": "Your wallet address can be emojified.", "yes": "Yes", "yesterday": "Yesterday", + "yes_exit": "Yes. Exit", "you_now_have_debit_card": "You now have a debit card", "you_pay": "You Pay", "you_will_get": "Convert to", @@ -1408,4 +1418,4 @@ "zcash_card_missing_funds": "Missing funds?", "zcash_card_scan": "Scan", "zcash_card_warning": "Do not close the app until the procedure completes, if you do so this process will need to restart from scratch." -} \ No newline at end of file +} diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index 2d7b06f0c..0a328f5f3 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -14,9 +14,9 @@ else fi # NOTE: Make sure to update monero_c prebuilds link in workflow files -# https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip +# https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip git fetch -a -git checkout bc8d1a0b75b97156d71579581b4cdfe58c777ed2 +git checkout 3bfb3856a838f2bf6b729501837bb0295dedf25d git reset --hard git submodule update --init --force --recursive diff --git a/tool/configure.dart b/tool/configure.dart index 80856320d..6a5385f86 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -334,8 +334,10 @@ import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:trezor_flutter/trezor_flutter.dart' as trezor; import 'package:polyseed/polyseed.dart';"""; const moneroCWHeaders = """ +import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/account.dart' as monero_account; import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/monero_amount_format.dart'; @@ -345,6 +347,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet_api; import 'package:cw_monero/ledger.dart'; import 'package:cw_monero/monero_unspent.dart'; import 'package:cw_monero/api/account_list.dart'; +import 'package:cw_monero/trezor.dart'; import 'package:cw_monero/monero_wallet_service.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_transaction_info.dart'; @@ -463,7 +466,7 @@ abstract class Monero { HardwareWalletType? hardwareWalletType, required int height}); WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); - WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection}); + WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService}); WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic}); Map getKeys(Object wallet); int? getRestoreHeight(Object wallet); @@ -484,6 +487,10 @@ WalletCredentials createMoneroNewWalletCredentials({required String name, requir void resetLedgerConnection(); void setGlobalLedgerConnection(ledger.LedgerConnection connection); String? getLastLedgerCommand(); + void setHardwareWalletService(Object wallet, HardwareWalletService service); + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection); + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client); + Future syncTrezor(Object wallet); Map> debugCallLength(); Map getWalletCacheDebug(); }