diff --git a/lib/di.dart b/lib/di.dart index 78a9b7802..1667fd1d2 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -53,6 +53,8 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; @@ -320,6 +322,13 @@ Future setup( type: type, language: language); }); + getIt + .registerFactoryParam((WalletType type, _) { + return WalletRestorationFromQRVM(getIt.get(), + getIt.get(param1: type), + _walletInfoSource, type); + }); + getIt.registerFactory(() => WalletAddressListViewModel( appStore: getIt.get(), yatStore: getIt.get(), @@ -743,7 +752,9 @@ Future setup( getIt.registerFactory( () => EditBackupPasswordPage(getIt.get())); - getIt.registerFactory(() => RestoreOptionsPage()); + getIt.registerFactoryParam((bool isNewInstall, _) => + RestoreOptionsPage(isNewInstall: isNewInstall)); + getIt.registerFactory( () => RestoreFromBackupViewModel(getIt.get())); diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 0aefa5afe..8ac9bb51f 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -33,7 +33,7 @@ class AddressResolver { final addressPattern = AddressValidator.getAddressFromStringPattern(type); if (addressPattern == null) { - throw 'Unexpected token: $type for getAddressFromStringPattern'; + throw Exception('Unexpected token: $type for getAddressFromStringPattern'); } final match = RegExp(addressPattern).firstMatch(raw); diff --git a/lib/router.dart b/lib/router.dart index 103a0889e..5a657a9ca 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; import 'package:cake_wallet/src/screens/buy/payfura_page.dart'; import 'package:cake_wallet/src/screens/buy/pre_order_page.dart'; +import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart'; @@ -40,6 +41,8 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -158,8 +161,9 @@ Route createRoute(RouteSettings settings) { param2: false)); case Routes.restoreOptions: + final isNewInstall = settings.arguments as bool; return CupertinoPageRoute( - builder: (_) => getIt.get()); + builder: (_) => getIt.get(param1: isNewInstall)); case Routes.restoreWalletOptions: final type = WalletType.monero; //settings.arguments as WalletType; @@ -189,12 +193,18 @@ Route createRoute(RouteSettings settings) { })); case Routes.restoreWalletOptionsFromWelcome: - return CupertinoPageRoute( + final isNewInstall = settings.arguments as bool; + return isNewInstall ? CupertinoPageRoute( builder: (_) => getIt.get( param1: (PinCodeState context, dynamic _) => Navigator.pushNamed( context.context, Routes.restoreWalletType)), - fullscreenDialog: true); + fullscreenDialog: true) : CupertinoPageRoute( + builder: (_) => getIt.get( + param1: (BuildContext context, WalletType type) => + Navigator.of(context) + .pushNamed(Routes.restoreWallet, arguments: type), + param2: false)); case Routes.seed: return MaterialPageRoute( @@ -224,6 +234,10 @@ Route createRoute(RouteSettings settings) { builder: (_) => RestoreWalletFromKeysPage( walletRestorationFromKeysVM: walletRestorationFromKeysVM)); + case Routes.sweepingWalletPage: + return CupertinoPageRoute( + builder: (_) => getIt.get()); + case Routes.dashboard: return CupertinoPageRoute( builder: (_) => getIt.get()); diff --git a/lib/routes.dart b/lib/routes.dart index 295b55ae0..823febe78 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -84,6 +84,7 @@ class Routes { static const displaySettingsPage = '/display_settings_page'; static const otherSettingsPage = '/other_settings_page'; static const advancedPrivacySettings = '/advanced_privacy_settings'; + static const sweepingWalletPage = '/sweeping_wallet_page'; static const anonPayInvoicePage = '/anon_pay_invoice_page'; static const anonPayReceivePage = '/anon_pay_receive_page'; static const anonPayDetailsPage = '/anon_pay_details_page'; diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index a7eb03778..8025ebd85 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -1,3 +1,11 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/language_list.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; +import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -7,15 +15,16 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/generated/i18n.dart'; class RestoreOptionsPage extends BasePage { - RestoreOptionsPage(); - - static const _aspectRatioImage = 2.086; + RestoreOptionsPage({required this.isNewInstall}); @override String get title => S.current.restore_restore_wallet; + + final bool isNewInstall; final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png'); final imageBackup = Image.asset('assets/images/backup.png'); + final qrCode = Image.asset('assets/images/qr_code_icon.png'); @override Widget body(BuildContext context) { @@ -28,24 +37,69 @@ class RestoreOptionsPage extends BasePage { child: Column( children: [ RestoreButton( - onPressed: () => - Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome), + onPressed: () => Navigator.pushNamed( + context, Routes.restoreWalletOptionsFromWelcome, + arguments: isNewInstall), image: imageSeedKeys, title: S.of(context).restore_title_from_seed_keys, - description: - S.of(context).restore_description_from_seed_keys), + description: S.of(context).restore_description_from_seed_keys), + if (isNewInstall) + Padding( + padding: EdgeInsets.only(top: 24), + child: RestoreButton( + onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup), + image: imageBackup, + title: S.of(context).restore_title_from_backup, + description: S.of(context).restore_description_from_backup), + ), Padding( padding: EdgeInsets.only(top: 24), child: RestoreButton( - onPressed: () => - Navigator.pushNamed(context, Routes.restoreFromBackup), - image: imageBackup, - title: S.of(context).restore_title_from_backup, - description: S.of(context).restore_description_from_backup), + onPressed: () async { + bool isPinSet = false; + if (isNewInstall) { + await Navigator.pushNamed(context, Routes.setupPin, + arguments: (PinCodeState setupPinContext, String _) { + setupPinContext.close(); + isPinSet = true; + }); + } + if (!isNewInstall || isPinSet) { + try { + final restoreWallet = + await WalletRestoreFromQRCode.scanQRCodeForRestoring(context); + + final restoreFromQRViewModel = getIt.get(param1: restoreWallet.type); + + await restoreFromQRViewModel.create(restoreWallet: restoreWallet); + if (restoreFromQRViewModel.state is FailureState) { + _onWalletCreateFailure(context, + 'Create wallet state: ${restoreFromQRViewModel.state.runtimeType.toString()}'); + } + } catch (e) { + _onWalletCreateFailure(context, e.toString()); + } + } + }, + image: qrCode, + title: S.of(context).scan_qr_code, + description: S.of(context).cold_or_recover_wallet), ) ], ), )), ); } + + void _onWalletCreateFailure(BuildContext context, String error) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.current.error, + alertContent: error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } } diff --git a/lib/src/screens/restore/sweeping_wallet_page.dart b/lib/src/screens/restore/sweeping_wallet_page.dart new file mode 100644 index 000000000..a7828b385 --- /dev/null +++ b/lib/src/screens/restore/sweeping_wallet_page.dart @@ -0,0 +1,123 @@ +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/scheduler.dart'; + +class SweepingWalletPage extends BasePage { + SweepingWalletPage(); + + static const aspectRatioImage = 1.25; + final welcomeImageLight = Image.asset('assets/images/welcome_light.png'); + final welcomeImageDark = Image.asset('assets/images/welcome.png'); + + + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).backgroundColor, + resizeToAvoidBottomInset: false, + body: body(context)); + } + + @override + Widget body(BuildContext context) { + final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight; + + return SweepingWalletWidget( + aspectRatioImage: aspectRatioImage, + welcomeImage: welcomeImage, + ); + } +} + +class SweepingWalletWidget extends StatefulWidget { + const SweepingWalletWidget({ + required this.aspectRatioImage, + required this.welcomeImage, + }); + + final double aspectRatioImage; + final Image welcomeImage; + + @override + State createState() => _SweepingWalletWidgetState(); +} + +class _SweepingWalletWidgetState extends State { + @override + void initState() { + SchedulerBinding.instance.addPostFrameCallback((_) async { + + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: Container( + padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 2, + child: AspectRatio( + aspectRatio: widget.aspectRatioImage, + child: FittedBox(child: widget.welcomeImage, fit: BoxFit.fill))), + Flexible( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 24), + child: Text( + S.of(context).please_wait, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.color!, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + S.of(context).sweeping_wallet, + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryTextTheme!.headline6!.color!, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + S.of(context).sweeping_wallet_alert, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.color!, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + )) + ], + ))); + } +} + + diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 7288d624b..aa9d31f1b 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -21,6 +21,7 @@ import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/core/seed_validator.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; class WalletRestorePage extends BasePage { WalletRestorePage(this.walletRestoreViewModel) diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 316203ddd..e1c4c48e5 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -178,7 +178,7 @@ class WalletListBodyState extends State { Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: widget.walletListViewModel.currentWalletType); } else { - Navigator.of(context).pushNamed(Routes.restoreWalletType); + Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); } }, image: restoreWalletImage, diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index 86e1cbcf1..0249093bd 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -148,7 +148,8 @@ class WelcomePage extends BasePage { padding: EdgeInsets.only(top: 10), child: PrimaryImageButton( onPressed: () { - Navigator.pushNamed(context, Routes.restoreOptions); + Navigator.pushNamed(context, Routes.restoreOptions, + arguments: true); }, image: restoreWalletImage, text: S.of(context).restore_wallet, diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart new file mode 100644 index 000000000..7efb92e69 --- /dev/null +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -0,0 +1,106 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cake_wallet/core/generate_wallet_password.dart'; +import 'package:cake_wallet/core/wallet_creation_service.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; +import 'package:cw_core/wallet_info.dart'; + +part 'restore_from_qr_vm.g.dart'; + +class WalletRestorationFromQRVM = WalletRestorationFromQRVMBase with _$WalletRestorationFromQRVM; + +abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store { + WalletRestorationFromQRVMBase(AppStore appStore, WalletCreationService walletCreationService, + Box walletInfoSource, WalletType type) + : height = 0, + viewKey = '', + spendKey = '', + wif = '', + address = '', + super(appStore, walletInfoSource, walletCreationService, + type: type, isRecovery: true); + + @observable + int height; + + @observable + String viewKey; + + @observable + String spendKey; + + @observable + String wif; + + @observable + String address; + + bool get hasRestorationHeight => type == WalletType.monero; + + @override + WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) { + final password = generateWalletPassword(); + + switch (restoreWallet.restoreMode) { + case WalletRestoreMode.keys: + switch (restoreWallet.type) { + case WalletType.monero: + return monero!.createMoneroRestoreWalletFromKeysCredentials( + name: name, + password: password, + language: 'English', + address: restoreWallet.address ?? '', + viewKey: restoreWallet.viewKey ?? '', + spendKey: restoreWallet.spendKey ?? '', + height: restoreWallet.height ?? 0); + case WalletType.bitcoin: + case WalletType.litecoin: + return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( + name: name, password: password, wif: wif); + default: + throw Exception('Unexpected type: ${restoreWallet.type.toString()}'); + } + case WalletRestoreMode.seed: + switch (restoreWallet.type) { + case WalletType.monero: + return monero!.createMoneroRestoreWalletFromSeedCredentials( + name: name, + height: restoreWallet.height ?? 0, + mnemonic: restoreWallet.mnemonicSeed ?? '', + password: password); + case WalletType.bitcoin: + case WalletType.litecoin: + return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); + default: + throw Exception('Unexpected type: ${type.toString()}'); + } + default: + throw Exception('Unexpected type: ${type.toString()}'); + } + } + + @override + Future processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) async { + try { + switch (restoreWallet.restoreMode) { + case WalletRestoreMode.keys: + return walletCreationService.restoreFromKeys(credentials); + case WalletRestoreMode.seed: + return walletCreationService.restoreFromSeed(credentials); + default: + throw Exception('Unexpected restore mode: ${restoreWallet.restoreMode.toString()}'); + } + } catch (e) { + throw Exception('Unexpected restore mode: ${e.toString()}'); + } + } +} diff --git a/lib/view_model/restore/restore_mode.dart b/lib/view_model/restore/restore_mode.dart new file mode 100644 index 000000000..d8344841d --- /dev/null +++ b/lib/view_model/restore/restore_mode.dart @@ -0,0 +1 @@ +enum WalletRestoreMode { seed, keys, txids } \ No newline at end of file diff --git a/lib/view_model/restore/restore_wallet.dart b/lib/view_model/restore/restore_wallet.dart new file mode 100644 index 000000000..0f872d8cc --- /dev/null +++ b/lib/view_model/restore/restore_wallet.dart @@ -0,0 +1,66 @@ +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cw_core/wallet_type.dart'; + +class RestoredWallet { + RestoredWallet( + {required this.restoreMode, + required this.type, + required this.address, + this.txId, + this.spendKey, + this.viewKey, + this.mnemonicSeed, + this.txAmount, + this.txDescription, + this.recipientName, + this.height}); + + final WalletRestoreMode restoreMode; + final WalletType type; + final String? address; + final String? txId; + final String? spendKey; + final String? viewKey; + final String? mnemonicSeed; + final String? txAmount; + final String? txDescription; + final String? recipientName; + final int? height; + + factory RestoredWallet.fromKey(Map json) { + final height = json['height'] as String?; + return RestoredWallet( + restoreMode: json['mode'] as WalletRestoreMode, + type: json['type'] as WalletType, + address: json['address'] as String?, + spendKey: json['spend_key'] as String?, + viewKey: json['view_key'] as String?, + height: height != null ? int.parse(height) : 0, + ); + } + + factory RestoredWallet.fromSeed(Map json) { + final height = json['height'] as String?; + final mnemonic_seed = json['mnemonic_seed'] as String?; + final seed = json['seed'] as String?; + return RestoredWallet( + restoreMode: json['mode'] as WalletRestoreMode, + type: json['type'] as WalletType, + address: json['address'] as String?, + mnemonicSeed: mnemonic_seed ?? seed, + height: height != null ? int.parse(height) : 0, + ); + } + + factory RestoredWallet.fromTxIds(Map json) { + return RestoredWallet( + restoreMode: json['mode'] as WalletRestoreMode, + type: json['type'] as WalletType, + address: json['address'] as String?, + txId: json['tx_payment_id'] as String, + txAmount: json['tx_amount'] as String, + txDescription: json['tx_description'] as String?, + recipientName: json['recipient_name'] as String?, + ); + } +} diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart new file mode 100644 index 000000000..241b2d3fd --- /dev/null +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -0,0 +1,159 @@ +import 'package:cake_wallet/core/address_validator.dart'; +import 'package:cake_wallet/core/seed_validator.dart'; +import 'package:cake_wallet/entities/mnemonic_item.dart'; +import 'package:cake_wallet/entities/parse_address_from_domain.dart'; +import 'package:cake_wallet/entities/qr_scanner.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/cupertino.dart'; + +class WalletRestoreFromQRCode { + WalletRestoreFromQRCode(); + + static Future scanQRCodeForRestoring(BuildContext context) async { + String code = await presentQRScanner(); + Map credentials = {}; + + if (code.isEmpty) { + throw Exception('Unexpected scan QR code value: value is empty'); + } + final formattedUri = getFormattedUri(code); + final uri = Uri.parse(formattedUri); + final queryParameters = uri.queryParameters; + credentials['type'] = getWalletTypeFromUrl(uri.scheme); + + final address = getAddressFromUrl( + type: credentials['type'] as WalletType, + rawString: queryParameters.toString(), + ); + if (address != null) { + credentials['address'] = address; + } + + final seed = + getSeedPhraseFromUrl(queryParameters.toString(), credentials['type'] as WalletType); + if (seed != null) { + credentials['seed'] = seed; + } + credentials.addAll(queryParameters); + credentials['mode'] = getWalletRestoreMode(credentials); + + switch (credentials['mode']) { + case WalletRestoreMode.txids: + return RestoredWallet.fromTxIds(credentials); + case WalletRestoreMode.seed: + return RestoredWallet.fromSeed(credentials); + case WalletRestoreMode.keys: + return RestoredWallet.fromKey(credentials); + default: + throw Exception('Unexpected restore mode: ${credentials['mode']}'); + } + } + + static String getFormattedUri(String code) { + final index = code.indexOf(':'); + final scheme = code.substring(0, index).replaceAll('_', '-'); + final query = code.substring(index + 1).replaceAll('?', '&'); + final formattedUri = '$scheme:?$query'; + return formattedUri; + } + + static WalletType getWalletTypeFromUrl(String scheme) { + switch (scheme) { + case 'monero': + case 'monero-wallet': + return WalletType.monero; + case 'bitcoin': + case 'bitcoin-wallet': + return WalletType.bitcoin; + case 'litecoin': + case 'litecoin-wallet': + return WalletType.litecoin; + default: + throw Exception('Unexpected wallet type: ${scheme.toString()}'); + } + } + + static String? getAddressFromUrl({required WalletType type, required String rawString}) { + return AddressResolver.extractAddressByType( + raw: rawString, type: walletTypeToCryptoCurrency(type)); + } + + static String? getSeedPhraseFromUrl(String rawString, WalletType walletType) { + switch (walletType) { + case WalletType.monero: + RegExp regex25 = RegExp(r'\b(\S+\b\s+){24}\S+\b'); + RegExp regex14 = RegExp(r'\b(\S+\b\s+){13}\S+\b'); + RegExp regex13 = RegExp(r'\b(\S+\b\s+){12}\S+\b'); + + if (regex25.firstMatch(rawString) == null) { + if (regex14.firstMatch(rawString) == null) { + if (regex13.firstMatch(rawString) == null) { + return null; + } else { + return regex13.firstMatch(rawString)!.group(0)!; + } + } else { + return regex14.firstMatch(rawString)!.group(0)!; + } + } else { + return regex25.firstMatch(rawString)!.group(0)!; + } + case WalletType.bitcoin: + case WalletType.litecoin: + RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b'); + RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b'); + RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b'); + + if (regex24.firstMatch(rawString) == null) { + if (regex18.firstMatch(rawString) == null) { + if (regex12.firstMatch(rawString) == null) { + return null; + } else { + return regex12.firstMatch(rawString)!.group(0)!; + } + } else { + return regex18.firstMatch(rawString)!.group(0)!; + } + } else { + return regex24.firstMatch(rawString)!.group(0)!; + } + default: + return null; + } + } + + static WalletRestoreMode getWalletRestoreMode(Map credentials) { + final type = credentials['type'] as WalletType; + if (credentials.containsKey('tx_payment_id')) { + final txIdValue = credentials['tx_payment_id'] as String? ?? ''; + return txIdValue.isNotEmpty + ? WalletRestoreMode.txids + : throw Exception('Unexpected restore mode: tx_payment_id is invalid'); + } + + if (credentials.containsKey('seed')) { + final seedValue = credentials['seed'] as String; + final words = SeedValidator.getWordList(type: type, language: 'english'); + seedValue.split(' ').forEach((element) { + if (!words.contains(element)) { + throw Exception('Unexpected restore mode: mnemonic_seed is invalid'); + } + }); + return WalletRestoreMode.seed; + } + + if (credentials.containsKey('spend_key') || credentials.containsKey('view_key')) { + final spendKeyValue = credentials['spend_key'] as String? ?? ''; + final viewKeyValue = credentials['view_key'] as String? ?? ''; + + return spendKeyValue.isNotEmpty || viewKeyValue.isNotEmpty + ? WalletRestoreMode.keys + : throw Exception('Unexpected restore mode: spend_key or view_key is invalid'); + } + + throw Exception('Unexpected restore mode: restore params are invalid'); + } +} diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index 2609aed7d..323d1f911 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -1,5 +1,5 @@ import 'package:cake_wallet/core/wallet_creation_service.dart'; -import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/execution_state.dart'; @@ -39,7 +39,8 @@ abstract class WalletCreationVMBase with Store { bool typeExists(WalletType type) => walletCreationService.typeExists(type); - Future create({dynamic options}) async { + Future create({dynamic options, RestoredWallet? restoreWallet}) async { + final type = restoreWallet?.type ?? this.type; try { state = IsExecutingState(); if (name.isEmpty) { @@ -49,7 +50,9 @@ abstract class WalletCreationVMBase with Store { walletCreationService.checkIfExists(name); final dirPath = await pathForWalletDir(name: name, type: type); final path = await pathForWallet(name: name, type: type); - final credentials = getCredentials(options); + final credentials = restoreWallet != null + ? getCredentialsFromRestoredWallet(options, restoreWallet) + : getCredentials(options); final walletInfo = WalletInfo.external( id: WalletBase.idFor(name, type), name: name, @@ -62,7 +65,9 @@ abstract class WalletCreationVMBase with Store { address: '', showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven); credentials.walletInfo = walletInfo; - final wallet = await process(credentials); + final wallet = restoreWallet != null + ? await processFromRestoredWallet(credentials, restoreWallet) + : await process(credentials); walletInfo.address = wallet.walletAddresses.address; await _walletInfoSource.add(walletInfo); _appStore.changeCurrentWallet(wallet); @@ -72,10 +77,15 @@ abstract class WalletCreationVMBase with Store { state = FailureState(e.toString()); } } - WalletCredentials getCredentials(dynamic options) => throw UnimplementedError(); Future process(WalletCredentials credentials) => throw UnimplementedError(); + + WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) => + throw UnimplementedError(); + + Future processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) => + throw UnimplementedError(); } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index e20089915..0a9ae60a7 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -88,16 +88,17 @@ abstract class WalletKeysViewModelBase with Store { return null; } - String get _path { + + String get _scheme { switch (_appStore.wallet!.type) { case WalletType.monero: - return 'monero_wallet:'; + return 'monero-wallet'; case WalletType.bitcoin: - return 'bitcoin_wallet:'; + return 'bitcoin-wallet'; case WalletType.litecoin: - return 'litecoin_wallet:'; + return 'litecoin-wallet'; case WalletType.haven: - return 'haven_wallet:'; + return 'haven-wallet'; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}'); } @@ -124,7 +125,7 @@ abstract class WalletKeysViewModelBase with Store { Future get url async { return Uri( - path: _path, + scheme: _scheme, queryParameters: await _queryParams, ); } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index e505cff77..dcd57b3ff 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; diff --git a/lib/view_model/wallet_restoration_from_keys_vm.dart b/lib/view_model/wallet_restoration_from_keys_vm.dart index f7195c240..97cb8d519 100644 --- a/lib/view_model/wallet_restoration_from_keys_vm.dart +++ b/lib/view_model/wallet_restoration_from_keys_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -66,7 +67,7 @@ abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( name: name, password: password, wif: wif); default: - throw Exception('Unexpected type: ${type.toString()}');; + throw Exception('Unexpected type: ${type.toString()}'); } } diff --git a/lib/view_model/wallet_restoration_from_seed_vm.dart b/lib/view_model/wallet_restoration_from_seed_vm.dart index ef584db08..0caa7f37d 100644 --- a/lib/view_model/wallet_restoration_from_seed_vm.dart +++ b/lib/view_model/wallet_restoration_from_seed_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index f122da3ba..7af653cf1 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/mnemonic_length.dart'; +import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -13,10 +14,10 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; part 'wallet_restore_view_model.g.dart'; -enum WalletRestoreMode { seed, keys } class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 23686e451..0caa02094 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -150,7 +150,7 @@ "receive_amount":"المقدار", "subaddresses":"العناوين الفرعية", "addresses":"عناوين", - "scan_qr_code":"امسح ال QR للحصول على العنوان", + "scan_qr_code_to_get_address":"امسح ال QR للحصول على العنوان", "qr_fullscreen":"انقر لفتح ال QR بملء الشاشة", "rename":"إعادة تسمية", "choose_account":"اختر حساب", @@ -683,6 +683,11 @@ "arrive_in_this_address" : "سيصل ${currency} ${tag}إلى هذا العنوان", "do_not_send": "لا ترسل", "error_dialog_content": "عفوًا ، لقد حصلنا على بعض الخطأ.\n\nيرجى إرسال تقرير التعطل إلى فريق الدعم لدينا لتحسين التطبيق.", + "scan_qr_code": "امسح رمز QR ضوئيًا", + "cold_or_recover_wallet": "أضف محفظة باردة أو استعد محفظة ورقية", + "please_wait": "انتظر من فضلك", + "sweeping_wallet": "كنس المحفظة", + "sweeping_wallet_alert": "لن يستغرق هذا وقتًا طويلاً. لا تترك هذه الشاشة وإلا فقد يتم فقد أموال سويبت", "decimal_places_error": "عدد كبير جدًا من المنازل العشرية", "edit_node": "تحرير العقدة", "frozen_balance": "الرصيد المجمد", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 9fd3432bd..930ed4bac 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -150,7 +150,7 @@ "receive_amount" : "Betrag", "subaddresses" : "Unteradressen", "addresses" : "Adressen", - "scan_qr_code" : "Scannen Sie den QR-Code, um die Adresse zu erhalten", + "scan_qr_code_to_get_address" : "Scannen Sie den QR-Code, um die Adresse zu erhalten", "qr_fullscreen" : "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen", "rename" : "Umbenennen", "choose_account" : "Konto auswählen", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}wird an dieser Adresse ankommen", "do_not_send": "Nicht senden", "error_dialog_content": "Hoppla, wir haben einen Fehler.\n\nBitte senden Sie den Absturzbericht an unser Support-Team, um die Anwendung zu verbessern.", + "scan_qr_code": "QR-Code scannen", + "cold_or_recover_wallet": "Fügen Sie eine Cold Wallet hinzu oder stellen Sie eine Paper Wallet wieder her", + "please_wait": "Warten Sie mal", + "sweeping_wallet": "Kehre Geldbörse", + "sweeping_wallet_alert": "Das sollte nicht lange dauern. VERLASSEN SIE DIESEN BILDSCHIRM NICHT, ANDERNFALLS KÖNNEN DIE SWEPT-GELDER VERLOREN GEHEN", "decimal_places_error": "Zu viele Nachkommastellen", "edit_node": "Knoten bearbeiten", "frozen_balance": "Gefrorenes Gleichgewicht", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 7f03b96e9..22cc9b950 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -150,7 +150,7 @@ "receive_amount" : "Amount", "subaddresses" : "Subaddresses", "addresses" : "Addresses", - "scan_qr_code" : "Scan the QR code to get the address", + "scan_qr_code_to_get_address" : "Scan the QR code to get the address", "qr_fullscreen" : "Tap to open full screen QR code", "rename" : "Rename", "choose_account" : "Choose account", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}will arrive in this address", "do_not_send": "Don't send", "error_dialog_content": "Oops, we got some error.\n\nPlease send the crash report to our support team to make the application better.", + "scan_qr_code": "Scan QR code", + "cold_or_recover_wallet": "Add a cold wallet or recover a paper wallet", + "please_wait": "Please wait", + "sweeping_wallet": "Sweeping wallet", + "sweeping_wallet_alert": "This shouldn’t take long. DO NOT LEAVE THIS SCREEN OR THE SWEPT FUNDS MAY BE LOST.", "invoice_details": "Invoice details", "donation_link_details": "Donation link details", "anonpay_description": "Generate ${type}. The recipient can ${method} with any supported cryptocurrency, and you will receive funds in this wallet.", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 4b649e12a..9159d7a12 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -150,7 +150,7 @@ "receive_amount" : "Cantidad", "subaddresses" : "Subdirecciones", "addresses" : "Direcciones", - "scan_qr_code" : "Escanee el código QR para obtener la dirección", + "scan_qr_code_to_get_address" : "Escanee el código QR para obtener la dirección", "qr_fullscreen" : "Toque para abrir el código QR en pantalla completa", "rename" : "Rebautizar", "choose_account" : "Elegir cuenta", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}llegará a esta dirección", "do_not_send": "no enviar", "error_dialog_content": "Vaya, tenemos un error.\n\nEnvíe el informe de bloqueo a nuestro equipo de soporte para mejorar la aplicación.", + "scan_qr_code": "Escanear código QR", + "cold_or_recover_wallet": "Agregue una billetera fría o recupere una billetera de papel", + "please_wait": "Espere por favor", + "sweeping_wallet": "Billetera de barrido", + "sweeping_wallet_alert": "Esto no debería llevar mucho tiempo. NO DEJES ESTA PANTALLA O SE PUEDEN PERDER LOS FONDOS BARRIDOS", "decimal_places_error": "Demasiados lugares decimales", "edit_node": "Editar nodo", "frozen_balance": "Balance congelado", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 5250ed88d..983fc5f46 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -150,7 +150,7 @@ "receive_amount" : "Montant", "subaddresses" : "Sous-adresses", "addresses" : "Adresses", - "scan_qr_code" : "Scannez le QR code pour obtenir l'adresse", + "scan_qr_code_to_get_address" : "Scannez le QR code pour obtenir l'adresse", "qr_fullscreen" : "Appuyez pour ouvrir le QR code en mode plein écran", "rename" : "Renommer", "choose_account" : "Choisir le compte", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}arrivera à cette adresse", "do_not_send": "Ne pas envoyer", "error_dialog_content": "Oups, nous avons rencontré une erreur.\n\nMerci d'envoyer le rapport d'erreur à notre équipe d'assistance afin de nous permettre d'améliorer l'application.", + "scan_qr_code": "Scannez le code QR", + "cold_or_recover_wallet": "Ajoutez un cold wallet ou récupérez un paper wallet", + "please_wait": "S'il vous plaît, attendez", + "sweeping_wallet": "Portefeuille de balayage", + "sweeping_wallet_alert": "Cela ne devrait pas prendre longtemps. NE QUITTEZ PAS CET ÉCRAN OU LES FONDS BALAYÉS POURRAIENT ÊTRE PERDUS", "decimal_places_error": "Trop de décimales", "edit_node": "Modifier le nœud", "frozen_balance": "Équilibre gelé", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 6a9a97880..8b505cff2 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -150,7 +150,7 @@ "receive_amount" : "रकम", "subaddresses" : "उप पते", "addresses" : "पतों", - "scan_qr_code" : "पता प्राप्त करने के लिए QR कोड स्कैन करें", + "scan_qr_code_to_get_address" : "पता प्राप्त करने के लिए QR कोड स्कैन करें", "qr_fullscreen" : "फ़ुल स्क्रीन क्यूआर कोड खोलने के लिए टैप करें", "rename" : "नाम बदलें", "choose_account" : "खाता चुनें", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}इस पते पर पहुंचेंगे", "do_not_send": "मत भेजो", "error_dialog_content": "ओह, हमसे कुछ गड़बड़ी हुई है.\n\nएप्लिकेशन को बेहतर बनाने के लिए कृपया क्रैश रिपोर्ट हमारी सहायता टीम को भेजें।", + "scan_qr_code": "स्कैन क्यू आर कोड", + "cold_or_recover_wallet": "कोल्ड वॉलेट जोड़ें या पेपर वॉलेट पुनर्प्राप्त करें", + "please_wait": "कृपया प्रतीक्षा करें", + "sweeping_wallet": "स्वीपिंग वॉलेट", + "sweeping_wallet_alert": "इसमें अधिक समय नहीं लगना चाहिए। इस स्क्रीन को न छोड़ें या स्वैप्ट फंड खो सकते हैं", "decimal_places_error": "बहुत अधिक दशमलव स्थान", "edit_node": "नोड संपादित करें", "frozen_balance": "जमे हुए संतुलन", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 10974efa2..f9b5fd0f2 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -150,7 +150,7 @@ "receive_amount" : "Iznos", "subaddresses" : "Podadrese", "addresses" : "Adrese", - "scan_qr_code" : "Skeniraj QR kod za dobivanje adrese", + "scan_qr_code_to_get_address" : "Skeniraj QR kod za dobivanje adrese", "qr_fullscreen" : "Dodirnite za otvaranje QR koda preko cijelog zaslona", "rename" : "Preimenuj", "choose_account" : "Odaberi račun", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}će stići na ovu adresu", "do_not_send": "Ne šalji", "error_dialog_content": "Ups, imamo grešku.\n\nPošaljite izvješće o padu našem timu za podršku kako bismo poboljšali aplikaciju.", + "scan_qr_code": "Skenirajte QR kod", + "cold_or_recover_wallet": "Dodajte hladni novčanik ili povratite papirnati novčanik", + "please_wait": "Molimo pričekajte", + "sweeping_wallet": "Čisti novčanik", + "sweeping_wallet_alert": "Ovo ne bi trebalo dugo trajati. NE NAPUŠTAJTE OVAJ ZASLON INAČE SE POBREŠENA SREDSTVA MOGU IZGUBITI", "decimal_places_error": "Previše decimalnih mjesta", "edit_node": "Uredi čvor", "frozen_balance": "Zamrznuti saldo", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b8881e2f6..95ce251bd 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -150,7 +150,7 @@ "receive_amount" : "Ammontare", "subaddresses" : "Sottoindirizzi", "addresses" : "Indirizzi", - "scan_qr_code" : "Scansiona il codice QR per ottenere l'indirizzo", + "scan_qr_code_to_get_address" : "Scansiona il codice QR per ottenere l'indirizzo", "qr_fullscreen" : "Tocca per aprire il codice QR a schermo intero", "rename" : "Rinomina", "choose_account" : "Scegli account", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}arriverà a questo indirizzo", "do_not_send": "Non inviare", "error_dialog_content": "Spiacenti, abbiamo riscontrato un errore.\n\nSi prega di inviare il rapporto sull'arresto anomalo al nostro team di supporto per migliorare l'applicazione.", + "scan_qr_code": "Scansiona il codice QR", + "cold_or_recover_wallet": "Aggiungi un cold wallet o recupera un paper wallet", + "please_wait": "Attendere prego", + "sweeping_wallet": "Portafoglio ampio", + "sweeping_wallet_alert": "Questo non dovrebbe richiedere molto tempo. NON LASCIARE QUESTA SCHERMATA O I FONDI SPAZZATI POTREBBERO ANDARE PERSI", "decimal_places_error": "Troppe cifre decimali", "edit_node": "Modifica nodo", "frozen_balance": "Equilibrio congelato", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 97baf1281..c1b78cc64 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -150,7 +150,7 @@ "receive_amount" : "量", "subaddresses" : "サブアドレス", "addresses" : "住所", - "scan_qr_code" : "QRコードをスキャンして住所を取得します", + "scan_qr_code_to_get_address" : "QRコードをスキャンして住所を取得します", "qr_fullscreen" : "タップして全画面QRコードを開く", "rename" : "リネーム", "choose_account" : "アカウントを選択", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}はこの住所に到着します", "do_not_send": "送信しない", "error_dialog_content": "エラーが発生しました。\n\nアプリケーションを改善するために、クラッシュ レポートをサポート チームに送信してください。", + "scan_qr_code": "QRコードをスキャン", + "cold_or_recover_wallet": "コールド ウォレットを追加するか、ペーパー ウォレットを復元する", + "please_wait": "お待ちください", + "sweeping_wallet": "スイープウォレット", + "sweeping_wallet_alert": "これには時間がかかりません。この画面から離れないでください。そうしないと、スイープ ファンドが失われる可能性があります", "decimal_places_error": "小数点以下の桁数が多すぎる", "edit_node": "ノードを編集", "frozen_balance": "冷凍残高", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 50994a752..f2e03ed55 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -150,7 +150,7 @@ "receive_amount" : "양", "subaddresses" : "하위 주소", "addresses" : "구애", - "scan_qr_code" : "QR 코드를 스캔하여 주소를 얻습니다.", + "scan_qr_code_to_get_address" : "QR 코드를 스캔하여 주소를 얻습니다.", "qr_fullscreen" : "전체 화면 QR 코드를 열려면 탭하세요.", "rename" : "이름 바꾸기", "choose_account" : "계정을 선택하십시오", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}이(가) 이 주소로 도착합니다", "do_not_send": "보내지 마세요", "error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n응용 프로그램을 개선하려면 지원 팀에 충돌 보고서를 보내주십시오.", + "scan_qr_code": "QR 코드 스캔", + "cold_or_recover_wallet": "콜드 지갑 추가 또는 종이 지갑 복구", + "please_wait": "기다리세요", + "sweeping_wallet": "스위핑 지갑", + "sweeping_wallet_alert": "오래 걸리지 않습니다. 이 화면을 떠나지 마십시오. 그렇지 않으면 스웹트 자금이 손실될 수 있습니다.", "decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.", "edit_node": "노드 편집", "frozen_balance": "얼어붙은 균형", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index e93b8a0b9..c314dfb65 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -150,7 +150,7 @@ "receive_amount" : "ပမာဏ", "subaddresses" : "လိပ်စာများ", "addresses" : "လိပ်စာများ", - "scan_qr_code" : "လိပ်စာရယူရန် QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", + "scan_qr_code_to_get_address" : "လိပ်စာရယူရန် QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", "qr_fullscreen" : "မျက်နှာပြင်အပြည့် QR ကုဒ်ကိုဖွင့်ရန် တို့ပါ။", "rename" : "အမည်ပြောင်းပါ။", "choose_account" : "အကောင့်ကို ရွေးပါ။", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။", "do_not_send": "မပို့ပါနှင့်", "error_dialog_content": "အိုး၊ ကျွန်ုပ်တို့တွင် အမှားအယွင်းအချို့ရှိသည်။\n\nအပလီကေးရှင်းကို ပိုမိုကောင်းမွန်စေရန်အတွက် ပျက်စီးမှုအစီရင်ခံစာကို ကျွန်ုပ်တို့၏ပံ့ပိုးကူညီရေးအဖွဲ့ထံ ပေးပို့ပါ။", + "scan_qr_code": "QR ကုဒ်ကို စကင်န်ဖတ်ပါ။", + "cold_or_recover_wallet": "အေးသောပိုက်ဆံအိတ်ထည့်ပါ သို့မဟုတ် စက္ကူပိုက်ဆံအိတ်ကို ပြန်ယူပါ။", + "please_wait": "ကျေးဇူးပြုပြီးခဏစောင့်ပါ", + "sweeping_wallet": "ိုက်ဆံအိတ် တံမြက်လှည်း", + "sweeping_wallet_alert": "ဒါက ကြာကြာမခံသင့်ပါဘူး။ ဤစခရင်ကို ချန်မထားပါနှင့် သို့မဟုတ် ထုတ်ယူထားသော ရန်ပုံငွေများ ဆုံးရှုံးနိုင်သည်", "decimal_places_error": "ဒဿမနေရာများ များလွန်းသည်။", "edit_node": "Node ကို တည်းဖြတ်ပါ။", "frozen_balance": "ေးခဲမှူ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 1561bb3d4..734630c96 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -151,7 +151,7 @@ "subaddresses" : "Subadressen", "rename" : "Hernoemen", "addresses" : "Adressen", - "scan_qr_code" : "Scan de QR-code om het adres te krijgen", + "scan_qr_code_to_get_address" : "Scan de QR-code om het adres te krijgen", "qr_fullscreen" : "Tik om de QR-code op volledig scherm te openen", "choose_account" : "Kies account", "create_new_account" : "Creëer een nieuw account", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}komt aan op dit adres", "do_not_send": "Niet sturen", "error_dialog_content": "Oeps, er is een fout opgetreden.\n\nStuur het crashrapport naar ons ondersteuningsteam om de applicatie te verbeteren.", + "scan_qr_code": "Scan QR-code", + "cold_or_recover_wallet": "Voeg een cold wallet toe of herstel een paper wallet", + "please_wait": "Even geduld aub", + "sweeping_wallet": "Vegende portemonnee", + "sweeping_wallet_alert": "Dit duurt niet lang. VERLAAT DIT SCHERM NIET, ANDERS KAN HET SWEPT-GELD VERLOREN WORDEN", "decimal_places_error": "Te veel decimalen", "edit_node": "Knooppunt bewerken", "frozen_balance": "Bevroren saldo", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index ea20d4da7..23b7bbb61 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -150,7 +150,7 @@ "receive_amount" : "Ilość", "subaddresses" : "Podadresy", "addresses" : "Adresy", - "scan_qr_code" : "Zeskanuj kod QR, aby uzyskać adres", + "scan_qr_code_to_get_address" : "Zeskanuj kod QR, aby uzyskać adres", "qr_fullscreen" : "Dotknij, aby otworzyć pełnoekranowy kod QR", "rename" : "Zmień nazwę", "choose_account" : "Wybierz konto", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}dotrze na ten adres", "do_not_send": "Nie wysyłaj", "error_dialog_content": "Ups, wystąpił błąd.\n\nPrześlij raport o awarii do naszego zespołu wsparcia, aby ulepszyć aplikację.", + "scan_qr_code": "Skanowania QR code", + "cold_or_recover_wallet": "Dodaj zimny portfel lub odzyskaj portfel papierowy", + "please_wait": "Proszę czekać", + "sweeping_wallet": "Zamiatanie portfela", + "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", "decimal_places_error": "Za dużo miejsc dziesiętnych", "edit_node": "Edytuj węzeł", "frozen_balance": "Zamrożona równowaga", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 9f088fcef..0346b1ff4 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -150,7 +150,7 @@ "receive_amount" : "Quantia", "subaddresses" : "Sub-endereços", "addresses" : "Endereços", - "scan_qr_code" : "Digitalize o código QR para obter o endereço", + "scan_qr_code_to_get_address" : "Digitalize o código QR para obter o endereço", "qr_fullscreen" : "Toque para abrir o código QR em tela cheia", "rename" : "Renomear", "choose_account" : "Escolha uma conta", @@ -684,6 +684,11 @@ "arrive_in_this_address" : "${currency} ${tag}chegará neste endereço", "do_not_send": "não envie", "error_dialog_content": "Ops, houve algum erro.\n\nPor favor, envie o relatório de falha para nossa equipe de suporte para melhorar o aplicativo.", + "scan_qr_code": "Escanear código QR", + "cold_or_recover_wallet": "Adicione uma cold wallet ou recupere uma paper wallet", + "please_wait": "Por favor, aguarde", + "sweeping_wallet": "Carteira varrendo", + "sweeping_wallet_alert": "To nie powinno zająć dużo czasu. NIE WYCHODŹ Z TEGO EKRANU, W PRZECIWNYM WYPADKU MOŻE ZOSTAĆ UTRACONA ŚRODKI", "decimal_places_error": "Muitas casas decimais", "edit_node": "Editar nó", "frozen_balance": "Saldo Congelado", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 53dc31b21..33058a13e 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -150,7 +150,7 @@ "receive_amount" : "Сумма", "subaddresses" : "Субадреса", "addresses" : "Адреса", - "scan_qr_code" : "Отсканируйте QR-код для получения адреса", + "scan_qr_code_to_get_address" : "Отсканируйте QR-код для получения адреса", "qr_fullscreen" : "Нажмите, чтобы открыть полноэкранный QR-код", "rename" : "Переименовать", "choose_account" : "Выберите аккаунт", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}придет на этот адрес", "do_not_send": "Не отправлять", "error_dialog_content": "Ой, у нас какая-то ошибка.\n\nПожалуйста, отправьте отчет о сбое в нашу службу поддержки, чтобы сделать приложение лучше.", + "scan_qr_code": "Сканировать QR-код", + "cold_or_recover_wallet": "Добавьте холодный кошелек или восстановите бумажный кошелек", + "please_wait": "Пожалуйста, подождите", + "sweeping_wallet": "Подметание кошелька", + "sweeping_wallet_alert": "Это не должно занять много времени. НЕ ПОКИДАЙТЕ ЭТОТ ЭКРАН, ИНАЧЕ ВЫЧИСЛЕННЫЕ СРЕДСТВА МОГУТ БЫТЬ ПОТЕРЯНЫ", "decimal_places_error": "Слишком много десятичных знаков", "edit_node": "Редактировать узел", "frozen_balance": "Замороженный баланс", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index ea7b2afde..f1cc320ac 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -150,7 +150,7 @@ "receive_amount" : "จำนวน", "subaddresses" : "ที่อยู่ย่อย", "addresses" : "ที่อยู่", - "scan_qr_code" : "สแกน QR code เพื่อรับที่อยู่", + "scan_qr_code_to_get_address" : "สแกน QR code เพื่อรับที่อยู่", "qr_fullscreen" : "แตะเพื่อเปิดหน้าจอ QR code แบบเต็มจอ", "rename" : "เปลี่ยนชื่อ", "choose_account" : "เลือกบัญชี", @@ -683,6 +683,11 @@ "arrive_in_this_address" : "${currency} ${tag}จะมาถึงที่อยู่นี้", "do_not_send": "อย่าส่ง", "error_dialog_content": "อ๊ะ เราพบข้อผิดพลาดบางอย่าง\n\nโปรดส่งรายงานข้อขัดข้องไปยังทีมสนับสนุนของเราเพื่อปรับปรุงแอปพลิเคชันให้ดียิ่งขึ้น", + "scan_qr_code": "สแกนรหัส QR", + "cold_or_recover_wallet": "เพิ่มกระเป๋าเงินเย็นหรือกู้คืนกระเป๋าเงินกระดาษ", + "please_wait": "โปรดรอ", + "sweeping_wallet": "กวาดกระเป๋าสตางค์", + "sweeping_wallet_alert": "การดำเนินการนี้ใช้เวลาไม่นาน อย่าออกจากหน้าจอนี้ มิฉะนั้นเงินที่กวาดไปอาจสูญหาย", "decimal_places_error": "ทศนิยมมากเกินไป", "edit_node": "แก้ไขโหนด", "frozen_balance": "ยอดคงเหลือแช่แข็ง", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 7cb72e57a..675fbb5aa 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -150,7 +150,7 @@ "receive_amount" : "Miktar", "subaddresses" : "Alt adresler", "addresses" : "Adresler", - "scan_qr_code" : "Adresi getirmek için QR kodunu tara", + "scan_qr_code_to_get_address" : "Adresi getirmek için QR kodunu tara", "qr_fullscreen" : "QR kodunu tam ekranda açmak için dokun", "rename" : "Yeniden adlandır", "choose_account" : "Hesabı seç", @@ -685,6 +685,11 @@ "arrive_in_this_address" : "${currency} ${tag}bu adrese ulaşacak", "do_not_send": "Gönderme", "error_dialog_content": "Hay aksi, bir hatamız var.\n\nUygulamayı daha iyi hale getirmek için lütfen kilitlenme raporunu destek ekibimize gönderin.", + "scan_qr_code": "QR kodunu tarayın", + "cold_or_recover_wallet": "Soğuk bir cüzdan ekleyin veya bir kağıt cüzdanı kurtarın", + "please_wait": "Lütfen bekleyin", + "sweeping_wallet": "Süpürme cüzdanı", + "sweeping_wallet_alert": "Bu uzun sürmemeli. BU EKRANDAN BIRAKMAYIN YOKSA SÜPÜRÜLEN FONLAR KAYBOLABİLİR", "decimal_places_error": "Çok fazla ondalık basamak", "edit_node": "Düğümü Düzenle", "frozen_balance": "Dondurulmuş Bakiye", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index ac3d32dfd..affdda645 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -150,7 +150,7 @@ "receive_amount" : "Сума", "subaddresses" : "Субадреси", "addresses" : "Адреси", - "scan_qr_code" : "Скануйте QR-код для одержання адреси", + "scan_qr_code_to_get_address" : "Скануйте QR-код для одержання адреси", "qr_fullscreen" : "Торкніться, щоб відкрити QR-код на весь екран", "rename" : "Перейменувати", "choose_account" : "Оберіть акаунт", @@ -684,6 +684,11 @@ "arrive_in_this_address" : "${currency} ${tag}надійде на цю адресу", "do_not_send": "Не надсилайте", "error_dialog_content": "На жаль, ми отримали помилку.\n\nБудь ласка, надішліть звіт про збій нашій команді підтримки, щоб покращити додаток.", + "scan_qr_code": "Відскануйте QR-код", + "cold_or_recover_wallet": "Додайте холодний гаманець або відновіть паперовий гаманець", + "please_wait": "Будь ласка, зачекайте", + "sweeping_wallet": "Підмітаня гаманця", + "sweeping_wallet_alert": "Це не повинно зайняти багато часу. НЕ ЗАЛИШАЙТЕ ЦЬОГО ЕКРАНУ, АБО КОШТИ МОЖУТЬ БУТИ ВТРАЧЕНІ", "decimal_places_error": "Забагато знаків після коми", "edit_node": "Редагувати вузол", "frozen_balance": "Заморожений баланс", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index ea1804b58..10a7d89f2 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -150,7 +150,7 @@ "receive_amount" : "金额", "subaddresses" : "子地址", "addresses" : "地址", - "scan_qr_code" : "扫描二维码获取地址", + "scan_qr_code_to_get_address" : "扫描二维码获取地址", "qr_fullscreen" : "点击打开全屏二维码", "rename" : "重命名", "choose_account" : "选择账户", @@ -684,6 +684,11 @@ "arrive_in_this_address" : "${currency} ${tag}将到达此地址", "do_not_send": "不要发送", "error_dialog_content": "糟糕,我们遇到了一些错误。\n\n请将崩溃报告发送给我们的支持团队,以改进应用程序。", + "scan_qr_code": "扫描二维码", + "cold_or_recover_wallet": "添加冷钱包或恢复纸钱包", + "please_wait": "请稍等", + "sweeping_wallet": "扫一扫钱包", + "sweeping_wallet_alert": "\n这应该不会花很长时间。请勿离开此屏幕,否则可能会丢失所掠取的资金", "decimal_places_error": "小数位太多", "edit_node": "编辑节点", "frozen_balance": "冻结余额",