Cw-263-TOTP-2FA-In-Security-Settings (#892)

* CW-263-TOTP-2FA-in-security-settings WIP * Implement TOTP 2FA WIP * Implement TOTP 2FA Authentication * chore: Remove unneeded formatting * revert formatting * fixes * CW-263-TOTP-2FA-in-security-settings WIP * Setup TOTP Complete, left with Modify TOTF * CW-263-TOTP-2FA-in-security-settings * CW-263-TOTP-2FA-in-security-settings * CW-263-TOTP-2FA-in-security-settings * fix: Add copy-to-clipboard for qr secret key * fix: Translation * chore: Move strings into translation files * feat: End to end flow for TOTP * hotfix: Switch totp to use sha512 * Update strings; 8 digits and error explanation * fix: Totp 2fa implementation feedback * hotfix: same action for button and alert close * feat: App should show both normal and totp auths when totp is enabled * hotfix: prevent barrier from dismissing * fix: Changes requested during PR review * - Minor Enhancements - Minor UI fixes --------- Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com> Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>

Adegoke David committed May 17, 2023 at 15:43 UTC 43e062d1ac319ed127a0f656b68958de9e453107
43 files changed +1959 -502
lib/core/auth_service.dart
+37 -5
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/core/totp_request_details.dart';
2 import 'package:cake_wallet/routes.dart';
3 import 'package:cake_wallet/src/screens/auth/auth_page.dart';
4 import 'package:flutter/material.dart';
@@ -9,6 +10,8 @@ import 'package:cake_wallet/entities/secret_store_key.dart';
10 import 'package:cake_wallet/entities/encrypt.dart';
11 import 'package:cake_wallet/store/settings_store.dart';
12
13 +import '../src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
14 +
15 class AuthService with Store {
16 AuthService({
17 required this.secureStorage,
@@ -20,6 +23,8 @@ class AuthService with Store {
23 Routes.showKeys,
24 Routes.backup,
25 Routes.setupPin,
26 + Routes.setup_2faPage,
27 + Routes.modify2FAPage,
28 ];
29
30 final FlutterSecureStorage secureStorage;
@@ -79,6 +84,7 @@ class AuthService with Store {
84 {Function(bool)? onAuthSuccess, String? route, Object? arguments}) async {
85 assert(route != null || onAuthSuccess != null,
86 'Either route or onAuthSuccess param must be passed.');
87 +
88 if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) {
89 if (onAuthSuccess != null) {
90 onAuthSuccess(true);
@@ -90,17 +96,43 @@ class AuthService with Store {
96 }
97 return;
98 }
99 +
100 +
101 Navigator.of(context).pushNamed(Routes.auth,
102 arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
103 if (!isAuthenticatedSuccessfully) {
104 onAuthSuccess?.call(false);
105 return;
98 - }
99 - if (onAuthSuccess != null) {
100 - auth.close().then((value) => onAuthSuccess.call(true));
106 } else {
102 - auth.close(route: route, arguments: arguments);
107 + if (settingsStore.useTOTP2FA) {
108 + auth.close(
109 + route: Routes.totpAuthCodePage,
110 + arguments: TotpAuthArgumentsModel(
111 + isForSetup: !settingsStore.useTOTP2FA,
112 + onTotpAuthenticationFinished:
113 + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) async {
114 + if (!isAuthenticatedSuccessfully) {
115 + onAuthSuccess?.call(false);
116 + return;
117 + }
118 + if (onAuthSuccess != null) {
119 + totpAuth.close().then((value) => onAuthSuccess.call(true));
120 + } else {
121 + totpAuth.close(route: route, arguments: arguments);
122 + }
123 + },
124 + ),
125 + );
126 + } else {
127 + if (onAuthSuccess != null) {
128 + auth.close().then((value) => onAuthSuccess.call(true));
129 + } else {
130 + auth.close(route: route, arguments: arguments);
131 + }
132 + }
133 }
104 - });
134 +
135 + });
136 +
137 }
138 }
lib/core/totp_request_details.dart new
+13
@@ -0,0 +1,13 @@
1 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
2 +
3 +class TotpAuthArgumentsModel {
4 + final bool? isForSetup;
5 + final bool? isClosable;
6 + final OnTotpAuthenticationFinished? onTotpAuthenticationFinished;
7 +
8 + TotpAuthArgumentsModel({
9 + this.isForSetup,
10 + this.isClosable,
11 + this.onTotpAuthenticationFinished,
12 + });
13 +}
lib/di.dart
+328 -297
@@ -10,6 +10,7 @@ import 'package:cake_wallet/entities/receive_page_option.dart';
10 import 'package:cake_wallet/ionia/ionia_anypay.dart';
11 import 'package:cake_wallet/ionia/ionia_gift_card.dart';
12 import 'package:cake_wallet/ionia/ionia_tip.dart';
13 +import 'package:cake_wallet/routes.dart';
14 import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
15 import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
16 import 'package:cake_wallet/src/screens/buy/payfura_page.dart';
@@ -27,6 +28,10 @@ import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dar
28 import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
29 import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
30 import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
31 +import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
32 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
33 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
34 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
35 import 'package:cake_wallet/themes/theme_list.dart';
36 import 'package:cake_wallet/utils/device_info.dart';
37 import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
@@ -53,8 +58,8 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
58 import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
59 import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
60 import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
61 +import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
62 import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
57 -import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
63 import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
64 import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
65 import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
@@ -187,6 +192,8 @@ import 'package:cake_wallet/core/wallet_loading_service.dart';
192 import 'package:cw_core/crypto_currency.dart';
193 import 'package:cake_wallet/entities/qr_view_data.dart';
194
195 +import 'core/totp_request_details.dart';
196 +
197 final getIt = GetIt.instance;
198
199 var _isSetupFinished = false;
@@ -201,18 +208,18 @@ late Box<Order> _ordersSource;
208 late Box<UnspentCoinsInfo>? _unspentCoinsInfoSource;
209 late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
210
204 -Future setup(
205 - {required Box<WalletInfo> walletInfoSource,
206 - required Box<Node> nodeSource,
207 - required Box<Contact> contactSource,
208 - required Box<Trade> tradesSource,
209 - required Box<Template> templates,
210 - required Box<ExchangeTemplate> exchangeTemplates,
211 - required Box<TransactionDescription> transactionDescriptionBox,
212 - required Box<Order> ordersSource,
213 - Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
214 - required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
215 - }) async {
211 +Future setup({
212 + required Box<WalletInfo> walletInfoSource,
213 + required Box<Node> nodeSource,
214 + required Box<Contact> contactSource,
215 + required Box<Trade> tradesSource,
216 + required Box<Template> templates,
217 + required Box<ExchangeTemplate> exchangeTemplates,
218 + required Box<TransactionDescription> transactionDescriptionBox,
219 + required Box<Order> ordersSource,
220 + Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
221 + required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
222 +}) async {
223 _walletInfoSource = walletInfoSource;
224 _nodeSource = nodeSource;
225 _contactSource = contactSource;
@@ -225,8 +232,7 @@ Future setup(
232 _anonpayInvoiceInfoSource = anonpayInvoiceInfoSource;
233
234 if (!_isSetupFinished) {
228 - getIt.registerSingletonAsync<SharedPreferences>(
229 - () => SharedPreferences.getInstance());
235 + getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
236 }
237
238 final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty ?? false) &&
@@ -256,84 +262,73 @@ Future setup(
262 walletList: getIt.get<WalletListStore>(),
263 settingsStore: getIt.get<SettingsStore>(),
264 nodeListStore: getIt.get<NodeListStore>()));
259 - getIt.registerSingleton<TradesStore>(TradesStore(
260 - tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
261 - getIt.registerSingleton<OrdersStore>(OrdersStore(
262 - ordersSource: _ordersSource, settingsStore: getIt.get<SettingsStore>()));
265 + getIt.registerSingleton<TradesStore>(
266 + TradesStore(tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
267 + getIt.registerSingleton<OrdersStore>(
268 + OrdersStore(ordersSource: _ordersSource, settingsStore: getIt.get<SettingsStore>()));
269 getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
270 getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore());
271 getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
266 - getIt.registerSingleton<SendTemplateStore>(
267 - SendTemplateStore(templateSource: _templates));
272 + getIt.registerSingleton<SendTemplateStore>(SendTemplateStore(templateSource: _templates));
273 getIt.registerSingleton<ExchangeTemplateStore>(
274 ExchangeTemplateStore(templateSource: _exchangeTemplates));
270 - getIt.registerSingleton<YatStore>(YatStore(
271 - appStore: getIt.get<AppStore>(),
272 - secureStorage: getIt.get<FlutterSecureStorage>())
273 - ..init());
274 - getIt.registerSingleton<AnonpayTransactionsStore>(AnonpayTransactionsStore(
275 - anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
275 + getIt.registerSingleton<YatStore>(
276 + YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<FlutterSecureStorage>())
277 + ..init());
278 + getIt.registerSingleton<AnonpayTransactionsStore>(
279 + AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
280
277 - final secretStore =
278 - await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
281 + final secretStore = await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
282
283 getIt.registerSingleton<SecretStore>(secretStore);
284
282 - getIt.registerFactory<KeyService>(
283 - () => KeyService(getIt.get<FlutterSecureStorage>()));
285 + getIt.registerFactory<KeyService>(() => KeyService(getIt.get<FlutterSecureStorage>()));
286
285 - getIt.registerFactoryParam<WalletCreationService, WalletType, void>(
286 - (type, _) => WalletCreationService(
287 + getIt.registerFactoryParam<WalletCreationService, WalletType, void>((type, _) =>
288 + WalletCreationService(
289 initialType: type,
290 keyService: getIt.get<KeyService>(),
291 secureStorage: getIt.get<FlutterSecureStorage>(),
292 sharedPreferences: getIt.get<SharedPreferences>(),
293 walletInfoSource: _walletInfoSource));
294
293 - getIt.registerFactory<WalletLoadingService>(
294 - () => WalletLoadingService(
295 + getIt.registerFactory<WalletLoadingService>(() => WalletLoadingService(
296 getIt.get<SharedPreferences>(),
297 getIt.get<KeyService>(),
298 (WalletType type) => getIt.get<WalletService>(param1: type)));
299
299 - getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
300 - WalletNewVM(getIt.get<AppStore>(),
301 - getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
302 - type: type));
300 + getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) => WalletNewVM(
301 + getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
302 + type: type));
303
304 - getIt
305 - .registerFactoryParam<WalletRestorationFromSeedVM, List, void>((args, _) {
304 + getIt.registerFactoryParam<WalletRestorationFromSeedVM, List, void>((args, _) {
305 final type = args.first as WalletType;
306 final language = args[1] as String;
307 final mnemonic = args[2] as String;
308
310 - return WalletRestorationFromSeedVM(getIt.get<AppStore>(),
311 - getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
309 + return WalletRestorationFromSeedVM(
310 + getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
311 type: type, language: language, seed: mnemonic);
312 });
313
315 - getIt
316 - .registerFactoryParam<WalletRestorationFromKeysVM, List, void>((args, _) {
314 + getIt.registerFactoryParam<WalletRestorationFromKeysVM, List, void>((args, _) {
315 final type = args.first as WalletType;
316 final language = args[1] as String;
317
320 - return WalletRestorationFromKeysVM(getIt.get<AppStore>(),
321 - getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
318 + return WalletRestorationFromKeysVM(
319 + getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
320 type: type, language: language);
321 });
322
325 - getIt
326 - .registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) {
323 + getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) {
324 return WalletRestorationFromQRVM(getIt.get<AppStore>(),
328 - getIt.get<WalletCreationService>(param1: type),
329 - _walletInfoSource, type);
325 + getIt.get<WalletCreationService>(param1: type), _walletInfoSource, type);
326 });
327
332 - getIt.registerFactory<WalletAddressListViewModel>(() =>
333 - WalletAddressListViewModel(
334 - appStore: getIt.get<AppStore>(), yatStore: getIt.get<YatStore>(),
335 - fiatConversionStore: getIt.get<FiatConversionStore>()
336 - ));
328 + getIt.registerFactory<WalletAddressListViewModel>(() => WalletAddressListViewModel(
329 + appStore: getIt.get<AppStore>(),
330 + yatStore: getIt.get<YatStore>(),
331 + fiatConversionStore: getIt.get<FiatConversionStore>()));
332
333 getIt.registerFactory(() => BalanceViewModel(
334 appStore: getIt.get<AppStore>(),
@@ -349,65 +344,108 @@ Future setup(
344 settingsStore: settingsStore,
345 yatStore: getIt.get<YatStore>(),
346 ordersStore: getIt.get<OrdersStore>(),
352 - anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>())
353 - );
347 + anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>()));
348
355 - getIt.registerFactory<AuthService>(() => AuthService(
349 + getIt.registerFactory<AuthService>(
350 + () => AuthService(
351 secureStorage: getIt.get<FlutterSecureStorage>(),
352 sharedPreferences: getIt.get<SharedPreferences>(),
353 settingsStore: getIt.get<SettingsStore>(),
359 - ),
360 - );
354 + ),
355 + );
356
362 - getIt.registerFactory<AuthViewModel>(() => AuthViewModel(
363 - getIt.get<AuthService>(),
364 - getIt.get<SharedPreferences>(),
357 + getIt.registerFactory<AuthViewModel>(() => AuthViewModel(getIt.get<AuthService>(),
358 + getIt.get<SharedPreferences>(), getIt.get<SettingsStore>(), BiometricAuth()));
359 +
360 + getIt.registerFactoryParam<AuthPage, void Function(bool, AuthPageState), bool>(
361 + (onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
362 + onAuthenticationFinished: onAuthFinished, closable: closable));
363 +
364 + getIt.registerFactory<Setup2FAViewModel>(
365 + () => Setup2FAViewModel(
366 getIt.get<SettingsStore>(),
366 - BiometricAuth()));
367 -
368 - getIt.registerFactory<AuthPage>(
369 - () => AuthPage(getIt.get<AuthViewModel>(), onAuthenticationFinished:
370 - (isAuthenticated, AuthPageState authPageState) {
371 - if (!isAuthenticated) {
372 - return;
373 - }
374 - final authStore = getIt.get<AuthenticationStore>();
375 - final appStore = getIt.get<AppStore>();
376 -
377 - if (appStore.wallet != null) {
378 - authStore.allowed();
379 - return;
380 - }
381 -
382 - authPageState.changeProcessText('Loading the wallet');
383 -
384 - if (loginError != null) {
385 - authPageState
386 - .changeProcessText('ERROR: ${loginError.toString()}');
387 - }
388 -
389 - ReactionDisposer? _reaction;
390 - _reaction = reaction((_) => appStore.wallet, (Object? _) {
391 - _reaction?.reaction.dispose();
392 - authStore.allowed();
393 - });
394 - }, closable: false),
395 - instanceName: 'login');
396 -
397 - getIt
398 - .registerFactoryParam<AuthPage, void Function(bool, AuthPageState), bool>(
399 - (onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
400 - onAuthenticationFinished: onAuthFinished,
401 - closable: closable));
367 + getIt.get<SharedPreferences>(),
368 + getIt.get<AuthService>(),
369 + ),
370 + );
371
403 - getIt.registerFactory(() =>
404 - BalancePage(dashboardViewModel: getIt.get<DashboardViewModel>(), settingsStore: getIt.get<SettingsStore>()));
372 + getIt.registerFactoryParam<TotpAuthCodePage, TotpAuthArgumentsModel, void>(
373 + (totpAuthPageArguments, _) => TotpAuthCodePage(
374 + getIt.get<Setup2FAViewModel>(),
375 + totpArguments: totpAuthPageArguments,
376 + ),
377 + );
378
406 - getIt.registerFactory<DashboardPage>(() => DashboardPage(
407 - balancePage: getIt.get<BalancePage>(),
379 + getIt.registerFactory<AuthPage>(() {
380 + return AuthPage(getIt.get<AuthViewModel>(),
381 + onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) {
382 + if (!isAuthenticated) {
383 + return;
384 + } else {
385 + final authStore = getIt.get<AuthenticationStore>();
386 + final appStore = getIt.get<AppStore>();
387 + final useTotp = appStore.settingsStore.useTOTP2FA;
388 + if (useTotp) {
389 + authPageState.close(
390 + route: Routes.totpAuthCodePage,
391 + arguments: TotpAuthArgumentsModel(
392 + isForSetup: false,
393 + isClosable: false,
394 + onTotpAuthenticationFinished: (bool isAuthenticatedSuccessfully,
395 + TotpAuthCodePageState totpAuthPageState) async {
396 + if (!isAuthenticatedSuccessfully) {
397 + return;
398 + }
399 + if (appStore.wallet != null) {
400 + authStore.allowed();
401 + return;
402 + }
403 +
404 + totpAuthPageState.changeProcessText('Loading the wallet');
405 +
406 + if (loginError != null) {
407 + totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}');
408 + }
409 +
410 + ReactionDisposer? _reaction;
411 + _reaction = reaction((_) => appStore.wallet, (Object? _) {
412 + _reaction?.reaction.dispose();
413 + authStore.allowed();
414 + });
415 + },
416 + ),
417 + );
418 + } else {
419 + if (appStore.wallet != null) {
420 + authStore.allowed();
421 + return;
422 + }
423 +
424 + authPageState.changeProcessText('Loading the wallet');
425 +
426 + if (loginError != null) {
427 + authPageState.changeProcessText('ERROR: ${loginError.toString()}');
428 + }
429 +
430 + ReactionDisposer? _reaction;
431 + _reaction = reaction((_) => appStore.wallet, (Object? _) {
432 + _reaction?.reaction.dispose();
433 + authStore.allowed();
434 + });
435 + }
436 + }
437 + }, closable: false);
438 + }, instanceName: 'login');
439 +
440 + getIt.registerFactory(() => BalancePage(
441 dashboardViewModel: getIt.get<DashboardViewModel>(),
409 - addressListViewModel: getIt.get<WalletAddressListViewModel>(),
410 - ));
442 + settingsStore: getIt.get<SettingsStore>()));
443 +
444 + getIt.registerFactory<DashboardPage>(() => DashboardPage(
445 + balancePage: getIt.get<BalancePage>(),
446 + dashboardViewModel: getIt.get<DashboardViewModel>(),
447 + addressListViewModel: getIt.get<WalletAddressListViewModel>(),
448 + ));
449
450 getIt.registerFactory<DesktopSidebarWrapper>(() {
451 final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
@@ -420,16 +458,26 @@ Future setup(
458 });
459 getIt.registerFactoryParam<DesktopDashboardPage, GlobalKey<NavigatorState>, void>(
460 (desktopKey, _) => DesktopDashboardPage(
423 - balancePage: getIt.get<BalancePage>(),
424 - dashboardViewModel: getIt.get<DashboardViewModel>(),
425 - addressListViewModel: getIt.get<WalletAddressListViewModel>(),
426 - desktopKey: desktopKey,
427 - ));
461 + balancePage: getIt.get<BalancePage>(),
462 + dashboardViewModel: getIt.get<DashboardViewModel>(),
463 + addressListViewModel: getIt.get<WalletAddressListViewModel>(),
464 + desktopKey: desktopKey,
465 + ));
466 +
467 + getIt.registerFactory<TransactionsPage>(
468 + () => TransactionsPage(dashboardViewModel: getIt.get<DashboardViewModel>()));
469 +
470 + getIt.registerFactory<Setup2FAPage>(
471 + () => Setup2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
472 +
473 + getIt.registerFactory<Setup2FAQRPage>(
474 + () => Setup2FAQRPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
475
429 - getIt.registerFactory<TransactionsPage>(() => TransactionsPage(dashboardViewModel: getIt.get<DashboardViewModel>()));
476 + getIt.registerFactory<Modify2FAPage>(
477 + () => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
478
431 - getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>((pageOption, _) => ReceiveOptionViewModel(
432 - getIt.get<AppStore>().wallet!, pageOption));
479 + getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
480 + (pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
481
482 getIt.registerFactoryParam<AnonInvoicePageViewModel, List<dynamic>, void>((args, _) {
483 final address = args.first as String;
@@ -443,28 +491,27 @@ Future setup(
491 getIt.get<SharedPreferences>(),
492 pageOption,
493 );
446 - });
494 + });
495
496 getIt.registerFactoryParam<AnonPayInvoicePage, List<dynamic>, void>((List<dynamic> args, _) {
497 final pageOption = args.last as ReceivePageOption;
450 - return AnonPayInvoicePage(
451 - getIt.get<AnonInvoicePageViewModel>(param1: args),
452 - getIt.get<ReceiveOptionViewModel>(param1: pageOption));
453 - });
498 + return AnonPayInvoicePage(getIt.get<AnonInvoicePageViewModel>(param1: args),
499 + getIt.get<ReceiveOptionViewModel>(param1: pageOption));
500 + });
501
455 - getIt.registerFactory<ReceivePage>(() => ReceivePage(
456 - addressListViewModel: getIt.get<WalletAddressListViewModel>()));
502 + getIt.registerFactory<ReceivePage>(
503 + () => ReceivePage(addressListViewModel: getIt.get<WalletAddressListViewModel>()));
504 getIt.registerFactory<AddressPage>(() => AddressPage(
505 addressListViewModel: getIt.get<WalletAddressListViewModel>(),
506 dashboardViewModel: getIt.get<DashboardViewModel>(),
507 receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>()));
508
509 getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, WalletAddressListItem?, void>(
463 - (WalletAddressListItem? item, _) => WalletAddressEditOrCreateViewModel(
464 - wallet: getIt.get<AppStore>().wallet!, item: item));
510 + (WalletAddressListItem? item, _) =>
511 + WalletAddressEditOrCreateViewModel(wallet: getIt.get<AppStore>().wallet!, item: item));
512
466 - getIt.registerFactoryParam<AddressEditOrCreatePage, dynamic, void>(
467 - (dynamic item, _) => AddressEditOrCreatePage(
513 + getIt.registerFactoryParam<AddressEditOrCreatePage, dynamic, void>((dynamic item, _) =>
514 + AddressEditOrCreatePage(
515 addressEditOrCreateViewModel:
516 getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
517
@@ -484,15 +531,16 @@ Future setup(
531
532 getIt.registerFactoryParam<SendPage, PaymentRequest?, void>(
533 (PaymentRequest? initialPaymentRequest, _) => SendPage(
487 - sendViewModel: getIt.get<SendViewModel>(),
488 - initialPaymentRequest: initialPaymentRequest,
489 - ));
534 + sendViewModel: getIt.get<SendViewModel>(),
535 + initialPaymentRequest: initialPaymentRequest,
536 + ));
537
491 - getIt.registerFactory(() => SendTemplatePage(
492 - sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
538 + getIt.registerFactory(
539 + () => SendTemplatePage(sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
540
541 if (DeviceInfo.instance.isMobile) {
495 - getIt.registerFactory(() => WalletListViewModel(
542 + getIt.registerFactory(
543 + () => WalletListViewModel(
544 _walletInfoSource,
545 getIt.get<AppStore>(),
546 getIt.get<WalletLoadingService>(),
@@ -502,7 +550,8 @@ Future setup(
550 } else {
551 // register wallet list view model as singleton on desktop since it can be accessed
552 // from multiple places at the same time (Wallets DropDown, Wallets List in settings)
505 - getIt.registerLazySingleton(() => WalletListViewModel(
553 + getIt.registerLazySingleton(
554 + () => WalletListViewModel(
555 _walletInfoSource,
556 getIt.get<AppStore>(),
557 getIt.get<WalletLoadingService>(),
@@ -511,8 +560,10 @@ Future setup(
560 );
561 }
562
514 - getIt.registerFactory(() =>
515 - WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>(), authService: getIt.get<AuthService>(),));
563 + getIt.registerFactory(() => WalletListPage(
564 + walletListViewModel: getIt.get<WalletListViewModel>(),
565 + authService: getIt.get<AuthService>(),
566 + ));
567
568 getIt.registerFactory(() {
569 final wallet = getIt.get<AppStore>().wallet!;
@@ -521,11 +572,12 @@ Future setup(
572 return MoneroAccountListViewModel(wallet);
573 }
574
524 - throw Exception('Unexpected wallet type: ${wallet.type} for generate MoneroAccountListViewModel');
575 + throw Exception(
576 + 'Unexpected wallet type: ${wallet.type} for generate MoneroAccountListViewModel');
577 });
578
527 - getIt.registerFactory(() => MoneroAccountListPage(
528 - accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
579 + getIt.registerFactory(
580 + () => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
581
582 /*getIt.registerFactory(() {
583 final wallet = getIt.get<AppStore>().wallet;
@@ -542,16 +594,14 @@ Future setup(
594 moneroAccountCreationViewModel:
595 getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
596
545 - getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
546 - AccountListItem?, void>(
597 + getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel, AccountListItem?, void>(
598 (AccountListItem? account, _) => MoneroAccountEditOrCreateViewModel(
599 monero!.getAccountList(getIt.get<AppStore>().wallet!),
600 haven?.getAccountList(getIt.get<AppStore>().wallet!),
601 wallet: getIt.get<AppStore>().wallet!,
602 accountListItem: account));
603
553 - getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem?,
554 - void>(
604 + getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem?, void>(
605 (AccountListItem? account, _) => MoneroAccountEditOrCreatePage(
606 moneroAccountCreationViewModel:
607 getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
@@ -572,41 +622,37 @@ Future setup(
622 return SecuritySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AuthService>());
623 });
624
575 - getIt
576 - .registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!));
625 + getIt.registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!));
626
578 - getIt.registerFactoryParam<WalletSeedPage, bool, void>(
579 - (bool isWalletCreated, _) => WalletSeedPage(
580 - getIt.get<WalletSeedViewModel>(),
581 - isNewWalletCreated: isWalletCreated));
627 + getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
628 + WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
629
583 - getIt
584 - .registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>()));
630 + getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>()));
631
632 getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>()));
633
634 getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
589 - (ContactRecord? contact, _) =>
590 - ContactViewModel(_contactSource, contact: contact));
635 + (ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
636
637 getIt.registerFactoryParam<ContactListViewModel, CryptoCurrency?, void>(
638 (CryptoCurrency? cur, _) => ContactListViewModel(_contactSource, _walletInfoSource, cur));
639
595 - getIt.registerFactoryParam<ContactListPage, CryptoCurrency?, void>((CryptoCurrency? cur, _)
596 - => ContactListPage(getIt.get<ContactListViewModel>(param1: cur)));
640 + getIt.registerFactoryParam<ContactListPage, CryptoCurrency?, void>(
641 + (CryptoCurrency? cur, _) => ContactListPage(getIt.get<ContactListViewModel>(param1: cur)));
642
643 getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
599 - (ContactRecord? contact, _) =>
600 - ContactPage(getIt.get<ContactViewModel>(param1: contact)));
644 + (ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
645
646 getIt.registerFactory(() {
647 final appStore = getIt.get<AppStore>();
648 return NodeListViewModel(_nodeSource, appStore);
649 });
650
607 - getIt.registerFactory(() => ConnectionSyncPage(getIt.get<NodeListViewModel>(), getIt.get<DashboardViewModel>()));
651 + getIt.registerFactory(
652 + () => ConnectionSyncPage(getIt.get<NodeListViewModel>(), getIt.get<DashboardViewModel>()));
653
609 - getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
654 + getIt.registerFactory(
655 + () => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
656
657 getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
658
@@ -614,41 +660,38 @@ Future setup(
660
661 getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
662
617 - getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>(
618 - (WalletType? type, _) => NodeCreateOrEditViewModel(
619 - _nodeSource,
620 - type ?? getIt.get<AppStore>().wallet!.type,
621 - getIt.get<SettingsStore>()
622 - ));
663 + getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>((WalletType? type, _) =>
664 + NodeCreateOrEditViewModel(
665 + _nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
666
667 getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
625 - (Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
668 + (Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
669 nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(),
670 editingNode: editingNode,
671 isSelected: isSelected));
672
673 getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
631 - settingsStore: getIt.get<AppStore>().settingsStore,
632 - wallet: getIt.get<AppStore>().wallet!,
633 - ));
674 + settingsStore: getIt.get<AppStore>().settingsStore,
675 + wallet: getIt.get<AppStore>().wallet!,
676 + ));
677
678 getIt.registerFactory(() => OnRamperPage(getIt.get<OnRamperBuyProvider>()));
679
680 getIt.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider(
638 - settingsStore: getIt.get<AppStore>().settingsStore,
639 - wallet: getIt.get<AppStore>().wallet!,
640 - ));
681 + settingsStore: getIt.get<AppStore>().settingsStore,
682 + wallet: getIt.get<AppStore>().wallet!,
683 + ));
684
685 getIt.registerFactory(() => PayFuraPage(getIt.get<PayfuraBuyProvider>()));
686
687 getIt.registerFactory(() => ExchangeViewModel(
645 - getIt.get<AppStore>().wallet!,
646 - _tradesSource,
647 - getIt.get<ExchangeTemplateStore>(),
648 - getIt.get<TradesStore>(),
649 - getIt.get<AppStore>().settingsStore,
650 - getIt.get<SharedPreferences>(),
651 - ));
688 + getIt.get<AppStore>().wallet!,
689 + _tradesSource,
690 + getIt.get<ExchangeTemplateStore>(),
691 + getIt.get<TradesStore>(),
692 + getIt.get<AppStore>().settingsStore,
693 + getIt.get<SharedPreferences>(),
694 + ));
695
696 getIt.registerFactory(() => ExchangeTradeViewModel(
697 wallet: getIt.get<AppStore>().wallet!,
@@ -658,40 +701,34 @@ Future setup(
701
702 getIt.registerFactory(() => ExchangePage(getIt.get<ExchangeViewModel>()));
703
661 - getIt.registerFactory(
662 - () => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
663 -
664 - getIt.registerFactory(() => ExchangeTradePage(
665 - exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
704 + getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
705
706 getIt.registerFactory(
668 - () => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
707 + () => ExchangeTradePage(exchangeTradeViewModel: getIt.get<ExchangeTradeViewModel>()));
708 +
709 + getIt.registerFactory(() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
710
670 - getIt.registerFactoryParam<WalletService, WalletType, void>(
671 - (WalletType param1, __) {
711 + getIt.registerFactoryParam<WalletService, WalletType, void>((WalletType param1, __) {
712 switch (param1) {
713 case WalletType.haven:
714 return haven!.createHavenWalletService(_walletInfoSource);
715 case WalletType.monero:
716 return monero!.createMoneroWalletService(_walletInfoSource);
717 case WalletType.bitcoin:
678 - return bitcoin!.createBitcoinWalletService(
679 - _walletInfoSource, _unspentCoinsInfoSource!);
718 + return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
719 case WalletType.litecoin:
681 - return bitcoin!.createLitecoinWalletService(
682 - _walletInfoSource, _unspentCoinsInfoSource!);
720 + return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
721 default:
722 throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
723 }
724 });
725
688 - getIt.registerFactory<SetupPinCodeViewModel>(() => SetupPinCodeViewModel(
689 - getIt.get<AuthService>(), getIt.get<SettingsStore>()));
726 + getIt.registerFactory<SetupPinCodeViewModel>(
727 + () => SetupPinCodeViewModel(getIt.get<AuthService>(), getIt.get<SettingsStore>()));
728
691 - getIt.registerFactoryParam<SetupPinCodePage,
692 - void Function(PinCodeState<PinCodeWidget>, String), void>(
693 - (onSuccessfulPinSetup, _) => SetupPinCodePage(
694 - getIt.get<SetupPinCodeViewModel>(),
729 + getIt.registerFactoryParam<SetupPinCodePage, void Function(PinCodeState<PinCodeWidget>, String),
730 + void>(
731 + (onSuccessfulPinSetup, _) => SetupPinCodePage(getIt.get<SetupPinCodeViewModel>(),
732 onSuccessfulPinSetup: onSuccessfulPinSetup));
733
734 getIt.registerFactory(() => RescanViewModel(getIt.get<AppStore>().wallet!));
@@ -700,17 +737,16 @@ Future setup(
737
738 getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
739
703 - getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, void>(
704 - (type, _) => WalletRestoreViewModel(getIt.get<AppStore>(),
705 - getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
740 + getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, void>((type, _) =>
741 + WalletRestoreViewModel(
742 + getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
743 type: type));
744
708 - getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
709 - WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
745 + getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
746 + (type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
747
711 - getIt
712 - .registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
713 - (TransactionInfo transactionInfo, _) {
748 + getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
749 + (TransactionInfo transactionInfo, _) {
750 final wallet = getIt.get<AppStore>().wallet!;
751 return TransactionDetailsViewModel(
752 transactionInfo: transactionInfo,
@@ -724,54 +760,48 @@ Future setup(
760 transactionDetailsViewModel:
761 getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
762
727 - getIt.registerFactoryParam<NewWalletTypePage,
728 - void Function(BuildContext, WalletType), void>(
763 + getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), void>(
764 (param1, _) => NewWalletTypePage(onTypeSelected: param1));
765
766 getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
767 (WalletType type, _) => PreSeedPage(type));
768
769 getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
735 - TradeDetailsViewModel(tradeForDetails: trade, trades: _tradesSource,
770 + TradeDetailsViewModel(
771 + tradeForDetails: trade,
772 + trades: _tradesSource,
773 settingsStore: getIt.get<SettingsStore>()));
774
738 - getIt.registerFactory(() => BackupService(
739 - getIt.get<FlutterSecureStorage>(),
740 - _walletInfoSource,
741 - getIt.get<KeyService>(),
742 - getIt.get<SharedPreferences>()));
775 + getIt.registerFactory(() => BackupService(getIt.get<FlutterSecureStorage>(), _walletInfoSource,
776 + getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
777
744 - getIt.registerFactory(() => BackupViewModel(getIt.get<FlutterSecureStorage>(),
745 - getIt.get<SecretStore>(), getIt.get<BackupService>()));
778 + getIt.registerFactory(() => BackupViewModel(
779 + getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>(), getIt.get<BackupService>()));
780
781 getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
782
749 - getIt.registerFactory(
750 - () => EditBackupPasswordViewModel(getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>()));
751 -
752 - getIt.registerFactory(
753 - () => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
783 + getIt.registerFactory(() =>
784 + EditBackupPasswordViewModel(getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>()));
785
755 - getIt.registerFactoryParam<RestoreOptionsPage, bool, void>((bool isNewInstall, _) =>
756 - RestoreOptionsPage(isNewInstall: isNewInstall));
786 + getIt.registerFactory(() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
787
788 + getIt.registerFactoryParam<RestoreOptionsPage, bool, void>(
789 + (bool isNewInstall, _) => RestoreOptionsPage(isNewInstall: isNewInstall));
790
759 - getIt.registerFactory(
760 - () => RestoreFromBackupViewModel(getIt.get<BackupService>()));
791 + getIt.registerFactory(() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
792
762 - getIt.registerFactory(
763 - () => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
793 + getIt.registerFactory(() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
794
765 - getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
766 - TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
795 + getIt.registerFactoryParam<TradeDetailsPage, Trade, void>(
796 + (Trade trade, _) => TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
797
798 getIt.registerFactory(() => BuyAmountViewModel());
799
800 getIt.registerFactory(() {
801 final wallet = getIt.get<AppStore>().wallet;
802
773 - return BuyViewModel(_ordersSource, getIt.get<OrdersStore>(),
774 - getIt.get<SettingsStore>(), getIt.get<BuyAmountViewModel>(),
803 + return BuyViewModel(_ordersSource, getIt.get<OrdersStore>(), getIt.get<SettingsStore>(),
804 + getIt.get<BuyAmountViewModel>(),
805 wallet: wallet!);
806 });
807
@@ -783,7 +813,8 @@ Future setup(
813 final url = args.first as String;
814 final buyViewModel = args[1] as BuyViewModel;
815
786 - return BuyWebViewPage(buyViewModel: buyViewModel, ordersStore: getIt.get<OrdersStore>(), url: url);
816 + return BuyWebViewPage(
817 + buyViewModel: buyViewModel, ordersStore: getIt.get<OrdersStore>(), url: url);
818 });
819
820 getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) {
@@ -792,8 +823,8 @@ Future setup(
823 return OrderDetailsViewModel(wallet: wallet!, orderForDetails: order);
824 });
825
795 - getIt.registerFactoryParam<OrderDetailsPage, Order, void>((Order order, _) =>
796 - OrderDetailsPage(getIt.get<OrderDetailsViewModel>(param1: order)));
826 + getIt.registerFactoryParam<OrderDetailsPage, Order, void>(
827 + (Order order, _) => OrderDetailsPage(getIt.get<OrderDetailsViewModel>(param1: order)));
828
829 getIt.registerFactory(() => SupportViewModel());
830
@@ -802,20 +833,18 @@ Future setup(
833 getIt.registerFactory(() {
834 final wallet = getIt.get<AppStore>().wallet;
835
805 - return UnspentCoinsListViewModel(
806 - wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource!);
836 + return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource!);
837 });
838
809 - getIt.registerFactory(() => UnspentCoinsListPage(
810 - unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()));
839 + getIt.registerFactory(() =>
840 + UnspentCoinsListPage(unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()));
841
842 getIt.registerFactoryParam<UnspentCoinsDetailsViewModel, UnspentCoinsItem,
843 UnspentCoinsListViewModel>(
814 - (item, model) => UnspentCoinsDetailsViewModel(
815 - unspentCoinsItem: item, unspentCoinsListViewModel: model));
844 + (item, model) =>
845 + UnspentCoinsDetailsViewModel(unspentCoinsItem: item, unspentCoinsListViewModel: model));
846
817 - getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>(
818 - (List args, _) {
847 + getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>((List args, _) {
848 final item = args.first as UnspentCoinsItem;
849 final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel;
850
@@ -826,11 +855,11 @@ Future setup(
855
856 getIt.registerFactory(() => YatService());
857
829 - getIt.registerFactory(() => AddressResolver(yatService: getIt.get<YatService>(),
830 - walletType: getIt.get<AppStore>().wallet!.type));
858 + getIt.registerFactory(() => AddressResolver(
859 + yatService: getIt.get<YatService>(), walletType: getIt.get<AppStore>().wallet!.type));
860
861 getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
833 - (QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
862 + (QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
863
864 getIt.registerFactory(() => IoniaApi());
865
@@ -839,26 +868,24 @@ Future setup(
868 getIt.registerFactory<IoniaService>(
869 () => IoniaService(getIt.get<FlutterSecureStorage>(), getIt.get<IoniaApi>()));
870
842 - getIt.registerFactory<IoniaAnyPay>(
843 - () => IoniaAnyPay(
844 - getIt.get<IoniaService>(),
845 - getIt.get<AnyPayApi>(),
846 - getIt.get<AppStore>().wallet!));
871 + getIt.registerFactory<IoniaAnyPay>(() => IoniaAnyPay(
872 + getIt.get<IoniaService>(), getIt.get<AnyPayApi>(), getIt.get<AppStore>().wallet!));
873
874 getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
875
876 getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));
877
852 - getIt.registerFactoryParam<IoniaMerchPurchaseViewModel, double, IoniaMerchant>((double amount, merchant) {
878 + getIt.registerFactoryParam<IoniaMerchPurchaseViewModel, double, IoniaMerchant>(
879 + (double amount, merchant) {
880 return IoniaMerchPurchaseViewModel(
854 - ioniaAnyPayService: getIt.get<IoniaAnyPay>(),
855 - amount: amount,
856 - ioniaMerchant: merchant,
857 - sendViewModel: getIt.get<SendViewModel>()
858 - );
881 + ioniaAnyPayService: getIt.get<IoniaAnyPay>(),
882 + amount: amount,
883 + ioniaMerchant: merchant,
884 + sendViewModel: getIt.get<SendViewModel>());
885 });
886
861 - getIt.registerFactoryParam<IoniaBuyCardViewModel, IoniaMerchant, void>((IoniaMerchant merchant, _) {
887 + getIt.registerFactoryParam<IoniaBuyCardViewModel, IoniaMerchant, void>(
888 + (IoniaMerchant merchant, _) {
889 return IoniaBuyCardViewModel(ioniaMerchant: merchant);
890 });
891
@@ -886,43 +913,45 @@ Future setup(
913 getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List, void>((List args, _) {
914 final amount = args.first as double;
915 final merchant = args.last as IoniaMerchant;
889 - return IoniaBuyGiftCardDetailPage(getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
916 + return IoniaBuyGiftCardDetailPage(
917 + getIt.get<IoniaMerchPurchaseViewModel>(param1: amount, param2: merchant));
918 });
919
892 - getIt.registerFactoryParam<IoniaGiftCardDetailsViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) {
893 - return IoniaGiftCardDetailsViewModel(
894 - ioniaService: getIt.get<IoniaService>(),
895 - giftCard: giftCard);
920 + getIt.registerFactoryParam<IoniaGiftCardDetailsViewModel, IoniaGiftCard, void>(
921 + (IoniaGiftCard giftCard, _) {
922 + return IoniaGiftCardDetailsViewModel(
923 + ioniaService: getIt.get<IoniaService>(), giftCard: giftCard);
924 });
925
898 - getIt.registerFactoryParam<IoniaCustomTipViewModel, List, void>((List args, _) {
899 - final amount = args[0] as double;
900 - final merchant = args[1] as IoniaMerchant;
901 - final tip = args[2] as IoniaTip;
926 + getIt.registerFactoryParam<IoniaCustomTipViewModel, List, void>((List args, _) {
927 + final amount = args[0] as double;
928 + final merchant = args[1] as IoniaMerchant;
929 + final tip = args[2] as IoniaTip;
930
903 - return IoniaCustomTipViewModel(amount: amount, tip: tip, ioniaMerchant: merchant);
931 + return IoniaCustomTipViewModel(amount: amount, tip: tip, ioniaMerchant: merchant);
932 });
933
906 - getIt.registerFactoryParam<IoniaGiftCardDetailPage, IoniaGiftCard, void>((IoniaGiftCard giftCard, _) {
907 - return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
934 + getIt.registerFactoryParam<IoniaGiftCardDetailPage, IoniaGiftCard, void>(
935 + (IoniaGiftCard giftCard, _) {
936 + return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
937 });
938
910 - getIt.registerFactoryParam<IoniaMoreOptionsPage, List, void>((List args, _){
939 + getIt.registerFactoryParam<IoniaMoreOptionsPage, List, void>((List args, _) {
940 final giftCard = args.first as IoniaGiftCard;
941
942 return IoniaMoreOptionsPage(giftCard);
943 });
944
916 - getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _)
917 - => IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get<IoniaService>()));
945 + getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>(
946 + (IoniaGiftCard giftCard, _) =>
947 + IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get<IoniaService>()));
948
919 - getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _){
949 + getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _) {
950 final giftCard = args.first as IoniaGiftCard;
951
922 - return IoniaCustomRedeemPage(getIt.get<IoniaCustomRedeemViewModel>(param1: giftCard) );
952 + return IoniaCustomRedeemPage(getIt.get<IoniaCustomRedeemViewModel>(param1: giftCard));
953 });
954
925 -
955 getIt.registerFactoryParam<IoniaCustomTipPage, List, void>((List args, _) {
956 return IoniaCustomTipPage(getIt.get<IoniaCustomTipViewModel>(param1: args));
957 });
@@ -937,42 +966,44 @@ Future setup(
966
967 getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
968
940 - getIt.registerFactory(() => AnonPayApi(useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
941 - wallet: getIt.get<AppStore>().wallet!)
942 - );
969 + getIt.registerFactory(() => AnonPayApi(
970 + useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
971 + wallet: getIt.get<AppStore>().wallet!));
972
944 - getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>(), getIt.get<AuthService>()));
973 + getIt.registerFactory(() =>
974 + DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>(), getIt.get<AuthService>()));
975
976 getIt.registerFactory(() => DesktopSidebarViewModel());
977
978 getIt.registerFactoryParam<AnonpayDetailsViewModel, AnonpayInvoiceInfo, void>(
949 - (AnonpayInvoiceInfo anonpayInvoiceInfo, _)
950 - => AnonpayDetailsViewModel(
951 - anonPayApi: getIt.get<AnonPayApi>(),
952 - anonpayInvoiceInfo: anonpayInvoiceInfo,
953 - settingsStore: getIt.get<SettingsStore>(),
954 - ));
979 + (AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsViewModel(
980 + anonPayApi: getIt.get<AnonPayApi>(),
981 + anonpayInvoiceInfo: anonpayInvoiceInfo,
982 + settingsStore: getIt.get<SettingsStore>(),
983 + ));
984
985 getIt.registerFactoryParam<AnonPayReceivePage, AnonpayInfoBase, void>(
957 - (AnonpayInfoBase anonpayInvoiceInfo, _) => AnonPayReceivePage(invoiceInfo: anonpayInvoiceInfo));
986 + (AnonpayInfoBase anonpayInvoiceInfo, _) =>
987 + AnonPayReceivePage(invoiceInfo: anonpayInvoiceInfo));
988
989 getIt.registerFactoryParam<AnonpayDetailsPage, AnonpayInvoiceInfo, void>(
960 - (AnonpayInvoiceInfo anonpayInvoiceInfo, _)
961 - => AnonpayDetailsPage(anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
990 + (AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
991 + anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
992
963 - getIt.registerFactoryParam<IoniaPaymentStatusViewModel, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
964 - (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)
965 - => IoniaPaymentStatusViewModel(
966 - getIt.get<IoniaService>(),
967 - paymentInfo: paymentInfo,
968 - committedInfo: committedInfo));
993 + getIt.registerFactoryParam<IoniaPaymentStatusViewModel, IoniaAnyPayPaymentInfo,
994 + AnyPayPaymentCommittedInfo>(
995 + (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo) =>
996 + IoniaPaymentStatusViewModel(getIt.get<IoniaService>(),
997 + paymentInfo: paymentInfo, committedInfo: committedInfo));
998
970 - getIt.registerFactoryParam<IoniaPaymentStatusPage, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
971 - (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)
972 - => IoniaPaymentStatusPage(getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo)));
999 + getIt.registerFactoryParam<IoniaPaymentStatusPage, IoniaAnyPayPaymentInfo,
1000 + AnyPayPaymentCommittedInfo>(
1001 + (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo) =>
1002 + IoniaPaymentStatusPage(
1003 + getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo)));
1004
974 - getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>((type, _) =>
975 - AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
1005 + getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
1006 + (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
1007
1008 _isSetupFinished = true;
978 -}
\ No newline at end of file
1009 +}
lib/entities/preferences_key.dart
+3
@@ -15,6 +15,9 @@ class PreferencesKey {
15 static const currentFiatApiModeKey = 'current_fiat_api_mode';
16 static const allowBiometricalAuthenticationKey =
17 'allow_biometrical_authentication';
18 + static const useTOTP2FA = 'use_totp_2fa';
19 + static const failedTotpTokenTrials = 'failed_token_trials';
20 + static const totpSecretKey = 'totp_qr_secret_key';
21 static const disableExchangeKey = 'disable_exchange';
22 static const exchangeStatusKey = 'exchange_status';
23 static const currentTheme = 'current_theme';
lib/router.dart
+40 -8
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
2 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 +import 'package:cake_wallet/core/totp_request_details.dart';
4 import 'package:cake_wallet/entities/contact_record.dart';
5 import 'package:cake_wallet/buy/order.dart';
6 import 'package:cake_wallet/entities/qr_view_data.dart';
@@ -33,6 +34,10 @@ import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
34 import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
35 import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
36 import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
37 +import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
38 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
39 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
40 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
41 import 'package:cake_wallet/src/screens/support/support_page.dart';
42 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
43 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
@@ -149,6 +154,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
154 case Routes.restoreOptions:
155 final isNewInstall = settings.arguments as bool;
156 return CupertinoPageRoute<void>(
157 + fullscreenDialog: true,
158 builder: (_) => getIt.get<RestoreOptionsPage>(param1: isNewInstall));
159
160 case Routes.restoreWalletFromSeedKeys:
@@ -262,6 +268,26 @@ Route<dynamic> createRoute(RouteSettings settings) {
268 param1: settings.arguments as OnAuthenticationFinished,
269 param2: true));
270
271 + case Routes.totpAuthCodePage:
272 + final args = settings.arguments as TotpAuthArgumentsModel;
273 + return MaterialPageRoute<void>(
274 + fullscreenDialog: true,
275 + builder: (_) => getIt.get<TotpAuthCodePage>(
276 + param1: args,
277 + ),
278 + );
279 +
280 + case Routes.login:
281 + return CupertinoPageRoute<void>(
282 + builder: (context) => WillPopScope(
283 + child: getIt.get<AuthPage>(instanceName: 'login'),
284 + onWillPop: () async =>
285 + // FIX-ME: Additional check does it works correctly
286 + (await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
287 + false),
288 + ),
289 + fullscreenDialog: true);
290 +
291 case Routes.unlock:
292 return MaterialPageRoute<void>(
293 fullscreenDialog: true,
@@ -303,14 +329,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
329 param1: args?['editingNode'] as Node?,
330 param2: args?['isSelected'] as bool?));
331
306 - case Routes.login:
307 - return CupertinoPageRoute<void>(
308 - builder: (context) => WillPopScope(
309 - child: getIt.get<AuthPage>(instanceName: 'login'),
310 - onWillPop: () async =>
311 - // FIX-ME: Additional check does it works correctly
312 - (await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ?? false)),
313 - fullscreenDialog: true);
332 +
333
334 case Routes.accountCreation:
335 return CupertinoPageRoute<String>(
@@ -346,6 +365,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
365
366 case Routes.tradeDetails:
367 return MaterialPageRoute<void>(
368 + fullscreenDialog: true,
369 builder: (_) =>
370 getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
371
@@ -363,6 +383,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
383 final args = settings.arguments as List;
384
385 return MaterialPageRoute<void>(
386 + fullscreenDialog: true,
387 builder: (_) =>
388 getIt.get<BuyWebViewPage>(param1: args));
389
@@ -372,6 +393,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
393 getIt.get<WalletRestorationFromSeedVM>(param1: args);
394
395 return CupertinoPageRoute<void>(
396 + fullscreenDialog: true,
397 builder: (_) => RestoreWalletFromSeedDetailsPage(
398 walletRestorationFromSeedVM: walletRestorationFromSeedVM));
399
@@ -405,6 +427,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
427
428 case Routes.restoreFromBackup:
429 return CupertinoPageRoute<void>(
430 + fullscreenDialog: true,
431 builder: (_) => getIt.get<RestoreFromBackupPage>());
432
433 case Routes.support:
@@ -543,6 +566,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
566 fullscreenDialog: true,
567 builder: (_) => getIt.get<TransactionsPage>());
568
569 + case Routes.setup_2faPage:
570 + return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAPage>());
571 +
572 + case Routes.setup_2faQRPage:
573 + return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAQRPage>());
574 +
575 + case Routes.modify2FAPage:
576 + return MaterialPageRoute<void>(builder: (_) => getIt.get<Modify2FAPage>());
577 +
578 default:
579 return MaterialPageRoute<void>(
580 builder: (_) => Scaffold(
lib/routes.dart
+4
@@ -84,4 +84,8 @@ class Routes {
84 static const payfuraPage = '/pay_fura_page';
85 static const desktop_actions = '/desktop_actions';
86 static const transactionsPage = '/transactions_page';
87 + static const setup_2faPage = '/setup_2fa_page';
88 + static const setup_2faQRPage = '/setup_2fa_qr_page';
89 + static const totpAuthCodePage = '/totp_auth_code_page';
90 + static const modify2FAPage = '/modify_2fa_page';
91 }
lib/src/screens/root/root.dart
+45 -12
@@ -1,5 +1,6 @@
1 import 'dart:async';
2 import 'package:cake_wallet/core/auth_service.dart';
3 +import 'package:cake_wallet/core/totp_request_details.dart';
4 import 'package:cake_wallet/utils/device_info.dart';
5 import 'package:cake_wallet/utils/payment_request.dart';
6 import 'package:flutter/material.dart';
@@ -10,6 +11,8 @@ import 'package:cake_wallet/store/authentication_store.dart';
11 import 'package:cake_wallet/entities/qr_scanner.dart';
12 import 'package:uni_links/uni_links.dart';
13
14 +import '../setup_2fa/setup_2fa_enter_code_page.dart';
15 +
16 class Root extends StatefulWidget {
17 Root({
18 required Key key,
@@ -114,19 +117,49 @@ class RootState extends State<Root> with WidgetsBindingObserver {
117 if (_isInactive && !_postFrameCallback && _requestAuth) {
118 _postFrameCallback = true;
119 WidgetsBinding.instance.addPostFrameCallback((_) {
117 - widget.navigatorKey.currentState?.pushNamed(Routes.unlock,
118 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
119 - if (!isAuthenticatedSuccessfully) {
120 - return;
121 - }
122 -
123 - _reset();
124 - auth.close(
125 - route: launchUri != null ? Routes.send : null,
126 - arguments: PaymentRequest.fromUri(launchUri),
120 + widget.navigatorKey.currentState?.pushNamed(
121 + Routes.unlock,
122 + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
123 + if (!isAuthenticatedSuccessfully) {
124 + return;
125 + } else {
126 + final useTotp = widget.appStore.settingsStore.useTOTP2FA;
127 + if (useTotp) {
128 + _reset();
129 + auth.close(
130 + route: Routes.totpAuthCodePage,
131 + arguments: TotpAuthArgumentsModel(
132 + onTotpAuthenticationFinished:
133 + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) {
134 + if (!isAuthenticatedSuccessfully) {
135 + return;
136 + }
137 + _reset();
138 + totpAuth.close(
139 + route: launchUri != null ? Routes.send : null,
140 + arguments: PaymentRequest.fromUri(launchUri),
141 + );
142 + launchUri = null;
143 + },
144 + isForSetup: false,
145 + isClosable: false,
146 + ),
147 + );
148 + } else {
149 + _reset();
150 + auth.close(
151 + route: launchUri != null ? Routes.send : null,
152 + arguments: PaymentRequest.fromUri(launchUri),
153 + );
154 + launchUri = null;
155 + }
156 + }
157 +
158 +
159 + },
160 );
128 - launchUri = null;
129 - });
161 +
162 +
163 });
164 } else if (launchUri != null) {
165 widget.navigatorKey.currentState?.pushNamed(
lib/src/screens/settings/security_backup_page.dart
+69 -52
@@ -26,64 +26,81 @@ class SecurityBackupPage extends BasePage {
26 @override
27 Widget body(BuildContext context) {
28 return Container(
29 - padding: EdgeInsets.only(top: 10),
30 - child: Column(mainAxisSize: MainAxisSize.min, children: [
31 - SettingsCellWithArrow(
32 - title: S.current.show_keys,
33 - handler: (_) => _authService.authenticateAction(context, route: Routes.showKeys),
34 - ),
35 - StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
36 - SettingsCellWithArrow(
37 - title: S.current.create_backup,
38 - handler: (_) => _authService.authenticateAction(context, route: Routes.backup),
39 - ),
40 - StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
41 - SettingsCellWithArrow(
42 - title: S.current.settings_change_pin,
43 - handler: (_) => _authService.authenticateAction(
44 - context,
45 - route: Routes.setupPin,
46 - arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
47 - setupPinContext.close();
48 - },
29 + padding: EdgeInsets.only(top: 10),
30 + child: Column(mainAxisSize: MainAxisSize.min, children: [
31 + SettingsCellWithArrow(
32 + title: S.current.show_keys,
33 + handler: (_) => _authService.authenticateAction(context, route: Routes.showKeys),
34 ),
50 - ),
51 - StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
52 - if (DeviceInfo.instance.isMobile)
53 - Observer(builder: (_) {
54 - return SettingsSwitcherCell(
55 - title: S.current.settings_allow_biometrical_authentication,
56 - value: _securitySettingsViewModel.allowBiometricalAuthentication,
57 - onValueChange: (BuildContext context, bool value) {
58 - if (value) {
59 - _authService.authenticateAction(context,
60 - onAuthSuccess: (isAuthenticatedSuccessfully) async {
61 - if (isAuthenticatedSuccessfully) {
62 - if (await _securitySettingsViewModel.biometricAuthenticated()) {
35 + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
36 + SettingsCellWithArrow(
37 + title: S.current.create_backup,
38 + handler: (_) => _authService.authenticateAction(context, route: Routes.backup),
39 + ),
40 + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
41 + SettingsCellWithArrow(
42 + title: S.current.settings_change_pin,
43 + handler: (_) => _authService.authenticateAction(
44 + context,
45 + route: Routes.setupPin,
46 + arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
47 + setupPinContext.close();
48 + },
49 + ),
50 + ),
51 + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
52 + if (DeviceInfo.instance.isMobile)
53 + Observer(builder: (_) {
54 + return SettingsSwitcherCell(
55 + title: S.current.settings_allow_biometrical_authentication,
56 + value: _securitySettingsViewModel.allowBiometricalAuthentication,
57 + onValueChange: (BuildContext context, bool value) {
58 + if (value) {
59 + _authService.authenticateAction(context,
60 + onAuthSuccess: (isAuthenticatedSuccessfully) async {
61 + if (isAuthenticatedSuccessfully) {
62 + if (await _securitySettingsViewModel.biometricAuthenticated()) {
63 + _securitySettingsViewModel
64 + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
65 + }
66 + } else {
67 _securitySettingsViewModel
68 .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
69 }
66 - } else {
67 - _securitySettingsViewModel
68 - .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
69 - }
70 - });
71 - } else {
72 - _securitySettingsViewModel.setAllowBiometricalAuthentication(value);
73 - }
74 - });
70 + });
71 + } else {
72 + _securitySettingsViewModel.setAllowBiometricalAuthentication(value);
73 + }
74 + });
75 + }),
76 + Observer(builder: (_) {
77 + return SettingsPickerCell<PinCodeRequiredDuration>(
78 + title: S.current.require_pin_after,
79 + items: PinCodeRequiredDuration.values,
80 + selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration,
81 + onItemSelected: (PinCodeRequiredDuration code) {
82 + _securitySettingsViewModel.setPinCodeRequiredDuration(code);
83 + },
84 + );
85 }),
76 - Observer(builder: (_) {
77 - return SettingsPickerCell<PinCodeRequiredDuration>(
78 - title: S.current.require_pin_after,
79 - items: PinCodeRequiredDuration.values,
80 - selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration,
81 - onItemSelected: (PinCodeRequiredDuration code) {
82 - _securitySettingsViewModel.setPinCodeRequiredDuration(code);
86 + Observer(
87 + builder: (context) {
88 + return SettingsCellWithArrow(
89 + title: _securitySettingsViewModel.useTotp2FA
90 + ? S.current.modify_2fa
91 + : S.current.setup_2fa,
92 + handler: (_) => _authService.authenticateAction(
93 + context,
94 + route: _securitySettingsViewModel.useTotp2FA
95 + ? Routes.modify2FAPage
96 + : Routes.setup_2faPage,
97 + ),
98 + );
99 },
84 - );
85 - }),
86 - ]),
100 + ),
101 + ],
102 + ),
103 );
104 +
105 }
106 }
lib/src/screens/setup_2fa/modify_2fa_page.dart new
+55
@@ -0,0 +1,55 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
3 +import 'package:cake_wallet/utils/show_pop_up.dart';
4 +import 'package:flutter/cupertino.dart';
5 +import 'package:flutter/material.dart';
6 +import 'package:cake_wallet/src/screens/base_page.dart';
7 +import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
8 +import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
9 +import 'package:cake_wallet/src/widgets/standard_list.dart';
10 +
11 +import '../../../routes.dart';
12 +
13 +class Modify2FAPage extends BasePage {
14 + Modify2FAPage({required this.setup2FAViewModel});
15 +
16 + final Setup2FAViewModel setup2FAViewModel;
17 +
18 + @override
19 + String get title => S.current.modify_2fa;
20 +
21 + @override
22 + Widget body(BuildContext context) {
23 + return SingleChildScrollView(
24 + child: Column(
25 + crossAxisAlignment: CrossAxisAlignment.start,
26 + children: [
27 + SettingsCellWithArrow(
28 + title: S.current.disable_cake_2fa,
29 + handler: (_) async {
30 + await showPopUp<void>(
31 + context: context,
32 + builder: (BuildContext context) {
33 + return AlertWithTwoActions(
34 + alertTitle: S.current.disable_cake_2fa,
35 + alertContent: S.current.question_to_disable_2fa,
36 + leftButtonText: S.current.cancel,
37 + rightButtonText: S.current.disable,
38 + actionLeftButton: () {
39 + Navigator.of(context).pop();
40 + },
41 + actionRightButton: () {
42 + setup2FAViewModel.setUseTOTP2FA(false);
43 + Navigator.pushNamedAndRemoveUntil(
44 + context, Routes.dashboard, (route) => false);
45 + },
46 + );
47 + },
48 + );
49 + }),
50 + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
51 + ],
52 + ),
53 + );
54 + }
55 +}
lib/src/screens/setup_2fa/setup_2fa.dart new
+62
@@ -0,0 +1,62 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +import 'package:cake_wallet/routes.dart';
5 +import 'package:cake_wallet/src/screens/base_page.dart';
6 +import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
7 +import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
8 +
9 +import '../../widgets/standard_list.dart';
10 +
11 +class Setup2FAPage extends BasePage {
12 + Setup2FAPage({required this.setup2FAViewModel});
13 +
14 + final Setup2FAViewModel setup2FAViewModel;
15 +
16 + @override
17 + String get title => S.current.setup_2fa;
18 +
19 + @override
20 + Widget body(BuildContext context) {
21 + return SingleChildScrollView(
22 + child: Column(
23 + crossAxisAlignment: CrossAxisAlignment.start,
24 + children: [
25 + Padding(
26 + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
27 + child: Column(
28 + crossAxisAlignment: CrossAxisAlignment.start,
29 + children: [
30 + Text(
31 + S.current.important_note,
32 + style: TextStyle(
33 + fontWeight: FontWeight.w700,
34 + fontSize: 14,
35 + height: 1.571,
36 + color: Theme.of(context).primaryTextTheme.headline6!.color!,
37 + ),
38 + ),
39 + SizedBox(height: 16),
40 + Text(
41 + S.current.setup_2fa_text,
42 + style: TextStyle(
43 + fontWeight: FontWeight.w400,
44 + fontSize: 14,
45 + height: 1.571,
46 + color: Theme.of(context).primaryTextTheme.headline6!.color!,
47 + ),
48 + ),
49 + ],
50 + ),
51 + ),
52 + SizedBox(height: 86),
53 + SettingsCellWithArrow(
54 + title: S.current.setup_totp_recommended,
55 + handler: (_) => Navigator.of(context).pushNamed(Routes.setup_2faQRPage),
56 + ),
57 + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
58 + ],
59 + ),
60 + );
61 + }
62 +}
lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart new
+221
@@ -0,0 +1,221 @@
1 +import 'package:another_flushbar/flushbar.dart';
2 +import 'package:cake_wallet/core/execution_state.dart';
3 +import 'package:cake_wallet/core/totp_request_details.dart';
4 +import 'package:cake_wallet/utils/show_bar.dart';
5 +import 'package:cake_wallet/view_model/auth_state.dart';
6 +import 'package:flutter/material.dart';
7 +
8 +import 'package:cake_wallet/generated/i18n.dart';
9 +import 'package:cake_wallet/src/screens/base_page.dart';
10 +import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
11 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
12 +import 'package:cake_wallet/src/widgets/primary_button.dart';
13 +import 'package:cake_wallet/utils/show_pop_up.dart';
14 +import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
15 +import 'package:flutter_mobx/flutter_mobx.dart';
16 +import 'package:mobx/mobx.dart';
17 +
18 +import '../../../palette.dart';
19 +import '../../../routes.dart';
20 +
21 +typedef OnTotpAuthenticationFinished = void Function(bool, TotpAuthCodePageState);
22 +
23 +class TotpAuthCodePage extends StatefulWidget {
24 + TotpAuthCodePage(
25 + this.setup2FAViewModel, {
26 + required this.totpArguments,
27 + });
28 +
29 + final Setup2FAViewModel setup2FAViewModel;
30 +
31 + final TotpAuthArgumentsModel totpArguments;
32 +
33 + @override
34 + TotpAuthCodePageState createState() => TotpAuthCodePageState();
35 +}
36 +
37 +class TotpAuthCodePageState extends State<TotpAuthCodePage> {
38 + final _key = GlobalKey<ScaffoldState>();
39 +
40 + ReactionDisposer? _reaction;
41 + Flushbar<void>? _authBar;
42 + Flushbar<void>? _progressBar;
43 +
44 + @override
45 + void initState() {
46 + _reaction ??= reaction((_) => widget.setup2FAViewModel.state, (ExecutionState state) {
47 + if (state is ExecutedSuccessfullyState) {
48 + WidgetsBinding.instance.addPostFrameCallback((_) {
49 + widget.totpArguments.onTotpAuthenticationFinished!(true, this);
50 + });
51 + }
52 +
53 + if (state is FailureState) {
54 + print(state.error);
55 + WidgetsBinding.instance.addPostFrameCallback((_) {
56 + widget.totpArguments.onTotpAuthenticationFinished!(false, this);
57 + });
58 + }
59 +
60 + if (state is AuthenticationBanned) {
61 + WidgetsBinding.instance.addPostFrameCallback((_) {
62 + widget.totpArguments.onTotpAuthenticationFinished!(false, this);
63 + });
64 + }
65 + });
66 +
67 + super.initState();
68 + }
69 +
70 + @override
71 + void dispose() {
72 + _reaction?.reaction.dispose();
73 + super.dispose();
74 + }
75 +
76 + void changeProcessText(String text) {
77 + dismissFlushBar(_authBar);
78 + _progressBar = createBar<void>(text, duration: null)..show(_key.currentContext!);
79 + }
80 +
81 + Future<void> close({String? route, dynamic arguments}) async {
82 + if (_key.currentContext == null) {
83 + throw Exception('Key context is null. Should be not happened');
84 + }
85 + await Future<void>.delayed(Duration(milliseconds: 50));
86 + if (route != null) {
87 + Navigator.of(_key.currentContext!).pushReplacementNamed(route, arguments: arguments);
88 + } else {
89 + Navigator.of(_key.currentContext!).pop();
90 + }
91 + }
92 +
93 + @override
94 + Widget build(BuildContext context) {
95 + return Scaffold(
96 + key: _key,
97 + resizeToAvoidBottomInset: false,
98 + body: TOTPEnterCode(
99 + setup2FAViewModel: widget.setup2FAViewModel,
100 + isForSetup: widget.totpArguments.isForSetup ?? false,
101 + isClosable: widget.totpArguments.isClosable ?? true,
102 + ),
103 + );
104 + }
105 +
106 + void dismissFlushBar(Flushbar<dynamic>? bar) {
107 + WidgetsBinding.instance.addPostFrameCallback((_) async {
108 + await bar?.dismiss();
109 + });
110 + }
111 +}
112 +
113 +class TOTPEnterCode extends BasePage {
114 + TOTPEnterCode({
115 + required this.setup2FAViewModel,
116 + required this.isForSetup,
117 + required this.isClosable,
118 + }) : totpController = TextEditingController() {
119 + totpController.addListener(() {
120 + setup2FAViewModel.enteredOTPCode = totpController.text;
121 + });
122 + }
123 +
124 + @override
125 + String get title => isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa;
126 +
127 + Widget? leading(BuildContext context) {
128 + return isClosable ? super.leading(context) : null;
129 + }
130 +
131 + final TextEditingController totpController;
132 + final Setup2FAViewModel setup2FAViewModel;
133 + final bool isForSetup;
134 + final bool isClosable;
135 +
136 + @override
137 + Widget body(BuildContext context) {
138 + return Padding(
139 + padding: const EdgeInsets.symmetric(
140 + horizontal: 24,
141 + ),
142 + child: Column(
143 + children: [
144 + BaseTextFormField(
145 + textAlign: TextAlign.left,
146 + hintText: S.current.totp_code,
147 + controller: totpController,
148 + keyboardType: TextInputType.number,
149 + placeholderTextStyle: TextStyle(
150 + fontSize: 16,
151 + fontWeight: FontWeight.w600,
152 + ),
153 + ),
154 + SizedBox(height: 16),
155 + Text(
156 + S.current.please_fill_totp,
157 + style: TextStyle(
158 + fontSize: 12,
159 + fontWeight: FontWeight.w400,
160 + height: 1.2,
161 + color: Palette.darkGray,
162 + ),
163 + textAlign: TextAlign.center,
164 + ),
165 + Spacer(),
166 + Observer(
167 + builder: (context) {
168 + return PrimaryButton(
169 + isDisabled: setup2FAViewModel.enteredOTPCode.length != 8,
170 + onPressed: () async {
171 + final result =
172 + await setup2FAViewModel.totp2FAAuth(totpController.text, isForSetup);
173 + final bannedState = setup2FAViewModel.state is AuthenticationBanned;
174 +
175 + await showPopUp<void>(
176 + context: context,
177 + builder: (BuildContext context) {
178 + return PopUpCancellableAlertDialog(
179 + contentText: _textDisplayedInPopupOnResult(result, bannedState, context),
180 + actionButtonText: S.of(context).ok,
181 + buttonAction: () {
182 + result ? setup2FAViewModel.success() : null;
183 + if (isForSetup && result) {
184 + Navigator.pushNamedAndRemoveUntil(
185 + context, Routes.dashboard, (route) => false);
186 + } else {
187 + Navigator.of(context).pop(result);
188 + }
189 + },
190 + );
191 + },
192 + );
193 + },
194 + text: S.of(context).continue_text,
195 + color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
196 + textColor: Colors.white,
197 + );
198 + },
199 + ),
200 + SizedBox(height: 24),
201 + ],
202 + ),
203 + );
204 + }
205 +
206 + String _textDisplayedInPopupOnResult(bool result, bool bannedState, BuildContext context) {
207 + switch (result) {
208 + case true:
209 + return isForSetup ? S.current.totp_2fa_success : S.current.totp_verification_success;
210 + case false:
211 + if (bannedState) {
212 + final state = setup2FAViewModel.state as AuthenticationBanned;
213 + return S.of(context).failed_authentication(state.error);
214 + } else {
215 + return S.current.totp_2fa_failure;
216 + }
217 + default:
218 + return S.current.enter_totp_code;
219 + }
220 + }
221 +}
lib/src/screens/setup_2fa/setup_2fa_qr_page.dart new
+145
@@ -0,0 +1,145 @@
1 +import 'package:cake_wallet/core/totp_request_details.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:flutter/services.dart';
4 +import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/routes.dart';
6 +import 'package:cake_wallet/src/screens/base_page.dart';
7 +import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
8 +import 'package:cake_wallet/utils/show_bar.dart';
9 +import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
10 +import 'package:qr_flutter/qr_flutter.dart' as qr;
11 +import '../../../palette.dart';
12 +import '../../widgets/primary_button.dart';
13 +import '../../widgets/standard_list.dart';
14 +
15 +class Setup2FAQRPage extends BasePage {
16 + Setup2FAQRPage({required this.setup2FAViewModel});
17 +
18 + final Setup2FAViewModel setup2FAViewModel;
19 +
20 + @override
21 + String get title => S.current.setup_2fa;
22 +
23 + @override
24 + Widget body(BuildContext context) {
25 +
26 + final copyImage = Image.asset(
27 + 'assets/images/copy_content.png',
28 + height: 12,
29 + width: 12,
30 + color: Color(0xFF355688),
31 + );
32 + return Padding(
33 + padding: const EdgeInsets.symmetric(horizontal: 24),
34 + child: Column(
35 + children: [
36 + SizedBox(height: 58),
37 + Text(
38 + S.current.add_secret_code,
39 + style: TextStyle(
40 + fontSize: 14,
41 + fontWeight: FontWeight.w700,
42 + height: 1.5714,
43 + color: Palette.darkBlueCraiola,
44 + ),
45 + ),
46 + SizedBox(height: 10),
47 + AspectRatio(
48 + aspectRatio: 1.0,
49 + child: Container(
50 + padding: EdgeInsets.all(5),
51 + decoration: BoxDecoration(
52 + border: Border.all(
53 + width: 3,
54 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
55 + ),
56 + ),
57 + child: Container(
58 + decoration: BoxDecoration(
59 + border: Border.all(
60 + width: 3,
61 + color: Colors.white,
62 + ),
63 + ),
64 + child: QrImage(
65 + data: setup2FAViewModel.totpVersionOneLink,
66 + version: qr.QrVersions.auto,
67 + )),
68 + ),
69 + ),
70 + SizedBox(height: 13),
71 + Row(
72 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
73 + crossAxisAlignment: CrossAxisAlignment.center,
74 + children: [
75 + Expanded(
76 + flex: 2,
77 + child: Column(
78 + crossAxisAlignment: CrossAxisAlignment.start,
79 + children: [
80 + Text(
81 + S.current.totp_secret_code,
82 + style: TextStyle(
83 + fontSize: 12,
84 + fontWeight: FontWeight.w500,
85 + color: Palette.darkGray,
86 + height: 1.8333,
87 + ),
88 + ),
89 + SizedBox(height: 8),
90 + Text(
91 + '${setup2FAViewModel.secretKey}',
92 + style: TextStyle(
93 + fontSize: 16,
94 + fontWeight: FontWeight.w700,
95 + height: 1.375,
96 + ),
97 + maxLines: 1,
98 + overflow: TextOverflow.ellipsis,
99 + ),
100 + ],
101 + ),
102 + ),
103 + SizedBox(width: 8),
104 + Container(
105 + width: 32,
106 + height: 32,
107 + child: InkWell(
108 + onTap: () {
109 + Clipboard.setData(ClipboardData(text: '${setup2FAViewModel.secretKey}'));
110 + showBar<void>(context, S.of(context).copied_to_clipboard);
111 + },
112 + child: Container(
113 + decoration: BoxDecoration(
114 + borderRadius: BorderRadius.circular(6),
115 + color: Color(0xFFF2F0FA),
116 + ),
117 + child: copyImage,
118 + ),
119 + ),
120 + )
121 + ],
122 + ),
123 + SizedBox(height: 8),
124 + StandardListSeparator(),
125 + Spacer(),
126 + PrimaryButton(
127 + onPressed: () {
128 + Navigator.of(context).pushReplacementNamed(
129 + Routes.totpAuthCodePage,
130 + arguments: TotpAuthArgumentsModel(
131 + isForSetup: true,
132 + )
133 +
134 + );
135 + },
136 + text: S.current.continue_text,
137 + color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
138 + textColor: Colors.white,
139 + ),
140 + SizedBox(height: 24),
141 + ],
142 + ),
143 + );
144 + }
145 +}
lib/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart new
+92
@@ -0,0 +1,92 @@
1 +import 'package:cake_wallet/src/widgets/alert_close_button.dart';
2 +import 'package:cake_wallet/src/widgets/primary_button.dart';
3 +import 'package:flutter/material.dart';
4 +import 'package:cake_wallet/palette.dart';
5 +
6 +import 'package:cake_wallet/src/widgets/alert_background.dart';
7 +
8 +class PopUpCancellableAlertDialog extends StatelessWidget {
9 + final String contentText;
10 + final String actionButtonText;
11 + final VoidCallback? buttonAction;
12 + final bool sameActionForButtonAndClose;
13 +
14 + const PopUpCancellableAlertDialog({
15 + super.key,
16 + this.contentText = '',
17 + this.actionButtonText = '',
18 + this.buttonAction,
19 + this.sameActionForButtonAndClose = true,
20 + });
21 + bool get barrierDismissible => false;
22 + Color? get actionButtonTextColor => null;
23 + Color? get actionButtonColor => null;
24 +
25 + Widget content(BuildContext context) {
26 + return Text(
27 + contentText,
28 + textAlign: TextAlign.center,
29 + style: TextStyle(
30 + fontSize: 16,
31 + fontWeight: FontWeight.normal,
32 + fontFamily: 'Lato',
33 + color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
34 + decoration: TextDecoration.none,
35 + ),
36 + );
37 + }
38 +
39 + @override
40 + Widget build(BuildContext context) {
41 + return GestureDetector(
42 + onTap: () => barrierDismissible ? Navigator.of(context).pop() : null,
43 + child: AlertBackground(
44 + child: Stack(
45 + alignment: AlignmentDirectional.center,
46 + children: [
47 + Positioned(
48 + top: 280,
49 + child: Column(
50 + children: [
51 + Padding(
52 + padding: EdgeInsets.only(left: 24, right: 24, top: 24),
53 + child: ClipRRect(
54 + borderRadius: BorderRadius.all(Radius.circular(30)),
55 + child: Container(
56 + width: 340,
57 + padding: EdgeInsets.all(10),
58 + color: Theme.of(context).accentTextTheme.titleLarge!.decorationColor!,
59 + child: Column(
60 + mainAxisSize: MainAxisSize.min,
61 + children: <Widget>[
62 + Column(
63 + children: [
64 + Padding(
65 + padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
66 + child: content(context),
67 + ),
68 + PrimaryButton(
69 + onPressed: buttonAction,
70 + text: actionButtonText,
71 + color: Color(0xffE9F2FC),
72 + textColor: Palette.darkBlueCraiola,
73 + ),
74 + ],
75 + ),
76 + ],
77 + ),
78 + ),
79 + ),
80 + ),
81 + ],
82 + ),
83 + ),
84 + AlertCloseButton(
85 + onTap: sameActionForButtonAndClose ? buttonAction : null,
86 + ),
87 + ],
88 + ),
89 + ),
90 + );
91 + }
92 +}
lib/src/widgets/alert_close_button.dart
+3 -2
@@ -2,7 +2,8 @@ import 'package:cake_wallet/palette.dart';
2 import 'package:flutter/material.dart';
3
4 class AlertCloseButton extends StatelessWidget {
5 - AlertCloseButton({this.image, this.bottom});
5 + AlertCloseButton({this.image, this.bottom, this.onTap});
6 + final VoidCallback? onTap;
7
8 final Image? image;
9 final double? bottom;
@@ -17,7 +18,7 @@ class AlertCloseButton extends StatelessWidget {
18 return Positioned(
19 bottom: bottom ?? 60,
20 child: GestureDetector(
20 - onTap: () => Navigator.of(context).pop(),
21 + onTap: onTap ?? () => Navigator.of(context).pop(),
22 child: Container(
23 height: 42,
24 width: 42,
lib/store/settings_store.dart
+175 -124
@@ -1,3 +1,5 @@
1 +import 'dart:io';
2 +
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/entities/exchange_api_mode.dart';
5 import 'package:cake_wallet/entities/pin_code_required_duration.dart';
@@ -5,6 +7,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
7 import 'package:cw_core/transaction_priority.dart';
8 import 'package:cake_wallet/themes/theme_base.dart';
9 import 'package:cake_wallet/themes/theme_list.dart';
10 +import 'package:device_info_plus/device_info_plus.dart';
11 import 'package:flutter/material.dart';
12 import 'package:hive/hive.dart';
13 import 'package:mobx/mobx.dart';
@@ -38,12 +41,16 @@ abstract class SettingsStoreBase with Store {
41 required bool initialDisableSell,
42 required FiatApiMode initialFiatMode,
43 required bool initialAllowBiometricalAuthentication,
44 + required String initialTotpSecretKey,
45 + required bool initialUseTOTP2FA,
46 + required int initialFailedTokenTrial,
47 required ExchangeApiMode initialExchangeStatus,
48 required ThemeBase initialTheme,
49 required int initialPinLength,
50 required String initialLanguageCode,
51 // required String initialCurrentLocale,
52 required this.appVersion,
53 + required this.deviceName,
54 required Map<WalletType, Node> nodes,
55 required this.shouldShowYatPopup,
56 required this.isBitcoinBuyEnabled,
@@ -53,38 +60,41 @@ abstract class SettingsStoreBase with Store {
60 TransactionPriority? initialMoneroTransactionPriority,
61 TransactionPriority? initialHavenTransactionPriority,
62 TransactionPriority? initialLitecoinTransactionPriority})
56 - : nodes = ObservableMap<WalletType, Node>.of(nodes),
57 - _sharedPreferences = sharedPreferences,
58 - fiatCurrency = initialFiatCurrency,
59 - balanceDisplayMode = initialBalanceDisplayMode,
60 - shouldSaveRecipientAddress = initialSaveRecipientAddress,
63 + : nodes = ObservableMap<WalletType, Node>.of(nodes),
64 + _sharedPreferences = sharedPreferences,
65 + fiatCurrency = initialFiatCurrency,
66 + balanceDisplayMode = initialBalanceDisplayMode,
67 + shouldSaveRecipientAddress = initialSaveRecipientAddress,
68 + fiatApiMode = initialFiatMode,
69 + allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
70 + totpSecretKey = initialTotpSecretKey,
71 + useTOTP2FA = initialUseTOTP2FA,
72 + numberOfFailedTokenTrials = initialFailedTokenTrial,
73 isAppSecure = initialAppSecure,
62 - disableBuy = initialDisableBuy,
63 - disableSell = initialDisableSell,
64 - fiatApiMode = initialFiatMode,
65 - allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
74 + disableBuy = initialDisableBuy,
75 + disableSell = initialDisableSell,
76 shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
67 - exchangeStatus = initialExchangeStatus,
68 - currentTheme = initialTheme,
69 - pinCodeLength = initialPinLength,
70 - languageCode = initialLanguageCode,
71 - priority = ObservableMap<WalletType, TransactionPriority>() {
77 + exchangeStatus = initialExchangeStatus,
78 + currentTheme = initialTheme,
79 + pinCodeLength = initialPinLength,
80 + languageCode = initialLanguageCode,
81 + priority = ObservableMap<WalletType, TransactionPriority>() {
82 //this.nodes = ObservableMap<WalletType, Node>.of(nodes);
83
84 if (initialMoneroTransactionPriority != null) {
75 - priority[WalletType.monero] = initialMoneroTransactionPriority;
85 + priority[WalletType.monero] = initialMoneroTransactionPriority;
86 }
87
88 if (initialBitcoinTransactionPriority != null) {
79 - priority[WalletType.bitcoin] = initialBitcoinTransactionPriority;
89 + priority[WalletType.bitcoin] = initialBitcoinTransactionPriority;
90 }
91
92 if (initialHavenTransactionPriority != null) {
83 - priority[WalletType.haven] = initialHavenTransactionPriority;
93 + priority[WalletType.haven] = initialHavenTransactionPriority;
94 }
95
96 if (initialLitecoinTransactionPriority != null) {
87 - priority[WalletType.litecoin] = initialLitecoinTransactionPriority;
97 + priority[WalletType.litecoin] = initialLitecoinTransactionPriority;
98 }
99
100 reaction(
@@ -94,8 +104,8 @@ abstract class SettingsStoreBase with Store {
104
105 reaction(
106 (_) => shouldShowYatPopup,
97 - (bool shouldShowYatPopup) => sharedPreferences
98 - .setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
107 + (bool shouldShowYatPopup) =>
108 + sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
109
110 priority.observe((change) {
111 final String? key;
@@ -124,8 +134,7 @@ abstract class SettingsStoreBase with Store {
134 reaction(
135 (_) => shouldSaveRecipientAddress,
136 (bool shouldSaveRecipientAddress) => sharedPreferences.setBool(
127 - PreferencesKey.shouldSaveRecipientAddressKey,
128 - shouldSaveRecipientAddress));
137 + PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress));
138
139 reaction((_) => isAppSecure, (bool isAppSecure) {
140 sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure);
@@ -149,40 +158,46 @@ abstract class SettingsStoreBase with Store {
158 }
159
160 reaction(
152 - (_) => fiatApiMode,
153 - (FiatApiMode mode) => sharedPreferences.setInt(
154 - PreferencesKey.currentFiatApiModeKey, mode.serialize()));
161 + (_) => fiatApiMode,
162 + (FiatApiMode mode) =>
163 + sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, mode.serialize()));
164
156 - reaction(
157 - (_) => currentTheme,
158 - (ThemeBase theme) =>
159 - sharedPreferences.setInt(PreferencesKey.currentTheme, theme.raw));
165 + reaction((_) => currentTheme,
166 + (ThemeBase theme) => sharedPreferences.setInt(PreferencesKey.currentTheme, theme.raw));
167
168 reaction(
169 (_) => allowBiometricalAuthentication,
170 (bool biometricalAuthentication) => sharedPreferences.setBool(
164 - PreferencesKey.allowBiometricalAuthenticationKey,
165 - biometricalAuthentication));
171 + PreferencesKey.allowBiometricalAuthenticationKey, biometricalAuthentication));
172 +
173 + reaction(
174 + (_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use));
175 +
176 + reaction(
177 + (_) => numberOfFailedTokenTrials,
178 + (int failedTokenTrail) =>
179 + sharedPreferences.setInt(PreferencesKey.failedTotpTokenTrials, failedTokenTrail));
180 +
181 + reaction((_) => totpSecretKey,
182 + (String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey));
183
184 reaction(
185 (_) => shouldShowMarketPlaceInDashboard,
186 (bool value) =>
187 sharedPreferences.setBool(PreferencesKey.shouldShowMarketPlaceInDashboard, value));
188
172 - reaction(
173 - (_) => pinCodeLength,
174 - (int pinLength) => sharedPreferences.setInt(
175 - PreferencesKey.currentPinLength, pinLength));
189 + reaction((_) => pinCodeLength,
190 + (int pinLength) => sharedPreferences.setInt(PreferencesKey.currentPinLength, pinLength));
191
192 reaction(
193 (_) => languageCode,
179 - (String languageCode) => sharedPreferences.setString(
180 - PreferencesKey.currentLanguageCode, languageCode));
194 + (String languageCode) =>
195 + sharedPreferences.setString(PreferencesKey.currentLanguageCode, languageCode));
196
197 reaction(
198 (_) => pinTimeOutDuration,
184 - (PinCodeRequiredDuration pinCodeInterval) => sharedPreferences.setInt(
185 - PreferencesKey.pinTimeOutDuration, pinCodeInterval.value));
199 + (PinCodeRequiredDuration pinCodeInterval) =>
200 + sharedPreferences.setInt(PreferencesKey.pinTimeOutDuration, pinCodeInterval.value));
201
202 reaction(
203 (_) => balanceDisplayMode,
@@ -190,17 +205,15 @@ abstract class SettingsStoreBase with Store {
205 PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
206
207 reaction(
193 - (_) => exchangeStatus,
194 - (ExchangeApiMode mode) => sharedPreferences.setInt(
195 - PreferencesKey.exchangeStatusKey, mode.serialize()));
196 -
197 - this
198 - .nodes
199 - .observe((change) {
200 - if (change.newValue != null && change.key != null) {
201 - _saveCurrentNode(change.newValue!, change.key!);
202 - }
203 - });
208 + (_) => exchangeStatus,
209 + (ExchangeApiMode mode) =>
210 + sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, mode.serialize()));
211 +
212 + this.nodes.observe((change) {
213 + if (change.newValue != null && change.key != null) {
214 + _saveCurrentNode(change.newValue!, change.key!);
215 + }
216 + });
217 }
218
219 static const defaultPinLength = 4;
@@ -240,6 +253,20 @@ abstract class SettingsStoreBase with Store {
253 @observable
254 bool allowBiometricalAuthentication;
255
256 + @observable
257 + String totpSecretKey;
258 +
259 + @computed
260 + String get totpVersionOneLink {
261 + return 'otpauth://totp/Cake%20Wallet:$deviceName?secret=$totpSecretKey&issuer=Cake%20Wallet&algorithm=SHA512&digits=8&period=30';
262 + }
263 +
264 + @observable
265 + bool useTOTP2FA;
266 +
267 + @observable
268 + int numberOfFailedTokenTrials;
269 +
270 @observable
271 ExchangeApiMode exchangeStatus;
272
@@ -263,6 +290,8 @@ abstract class SettingsStoreBase with Store {
290
291 String appVersion;
292
293 + String deviceName;
294 +
295 SharedPreferences _sharedPreferences;
296
297 ObservableMap<WalletType, Node> nodes;
@@ -271,7 +300,7 @@ abstract class SettingsStoreBase with Store {
300 final node = nodes[walletType];
301
302 if (node == null) {
274 - throw Exception('No node found for wallet type: ${walletType.toString()}');
303 + throw Exception('No node found for wallet type: ${walletType.toString()}');
304 }
305
306 return node;
@@ -280,10 +309,10 @@ abstract class SettingsStoreBase with Store {
309 bool isBitcoinBuyEnabled;
310
311 bool get shouldShowReceiveWarning =>
283 - _sharedPreferences.getBool(PreferencesKey.shouldShowReceiveWarning) ?? true;
312 + _sharedPreferences.getBool(PreferencesKey.shouldShowReceiveWarning) ?? true;
313
314 Future<void> setShouldShowReceiveWarning(bool value) async =>
286 - _sharedPreferences.setBool(PreferencesKey.shouldShowReceiveWarning, value);
315 + _sharedPreferences.setBool(PreferencesKey.shouldShowReceiveWarning, value);
316
317 static Future<SettingsStore> load(
318 {required Box<Node> nodeSource,
@@ -291,18 +320,15 @@ abstract class SettingsStoreBase with Store {
320 FiatCurrency initialFiatCurrency = FiatCurrency.usd,
321 BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
322 ThemeBase? initialTheme}) async {
294 -
323 final sharedPreferences = await getIt.getAsync<SharedPreferences>();
296 - final currentFiatCurrency = FiatCurrency.deserialize(raw:
297 - sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
324 + final currentFiatCurrency = FiatCurrency.deserialize(
325 + raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
326
299 - TransactionPriority? moneroTransactionPriority =
300 - monero?.deserializeMoneroTransactionPriority(
301 - raw: sharedPreferences
302 - .getInt(PreferencesKey.moneroTransactionPriority)!);
327 + TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority(
328 + raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!);
329 TransactionPriority? bitcoinTransactionPriority =
304 - bitcoin?.deserializeBitcoinTransactionPriority(sharedPreferences
305 - .getInt(PreferencesKey.bitcoinTransactionPriority)!);
330 + bitcoin?.deserializeBitcoinTransactionPriority(
331 + sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!);
332
333 TransactionPriority? havenTransactionPriority;
334 TransactionPriority? litecoinTransactionPriority;
@@ -322,8 +348,7 @@ abstract class SettingsStoreBase with Store {
348 litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
349
350 final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
325 - raw: sharedPreferences
326 - .getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
351 + raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
352 // FIX-ME: Check for which default value we should have here
353 final shouldSaveRecipientAddress =
354 sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false;
@@ -334,29 +359,29 @@ abstract class SettingsStoreBase with Store {
359 final disableSell =
360 sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
361 final currentFiatApiMode = FiatApiMode.deserialize(
337 - raw: sharedPreferences
338 - .getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw);
339 - final allowBiometricalAuthentication = sharedPreferences
340 - .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
341 - false;
362 + raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ??
363 + FiatApiMode.enabled.raw);
364 + final allowBiometricalAuthentication =
365 + sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false;
366 + final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
367 + final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
368 + final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
369 final shouldShowMarketPlaceInDashboard =
370 sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
371 final exchangeStatus = ExchangeApiMode.deserialize(
345 - raw: sharedPreferences
346 - .getInt(PreferencesKey.exchangeStatusKey) ?? ExchangeApiMode.enabled.raw);
347 - final legacyTheme =
348 - (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
349 - ? ThemeType.dark.index
350 - : ThemeType.bright.index;
351 - final savedTheme = initialTheme ?? ThemeList.deserialize(
352 - raw: sharedPreferences.getInt(PreferencesKey.currentTheme) ??
353 - legacyTheme);
372 + raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
373 + ExchangeApiMode.enabled.raw);
374 + final legacyTheme = (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
375 + ? ThemeType.dark.index
376 + : ThemeType.bright.index;
377 + final savedTheme = initialTheme ??
378 + ThemeList.deserialize(
379 + raw: sharedPreferences.getInt(PreferencesKey.currentTheme) ?? legacyTheme);
380 final actionListDisplayMode = ObservableList<ActionListDisplayMode>();
381 actionListDisplayMode.addAll(deserializeActionlistDisplayModes(
356 - sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ??
357 - defaultActionsMode));
382 + sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode));
383 var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength);
359 - final timeOutDuration = sharedPreferences.getInt(PreferencesKey.pinTimeOutDuration);
384 + final timeOutDuration = sharedPreferences.getInt(PreferencesKey.pinTimeOutDuration);
385 final pinCodeTimeOutDuration = timeOutDuration != null
386 ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
387 : defaultPinCodeTimeOutDuration;
@@ -366,40 +391,38 @@ abstract class SettingsStoreBase with Store {
391 pinLength = defaultPinLength;
392 }
393
369 - final savedLanguageCode =
370 - sharedPreferences.getString(PreferencesKey.currentLanguageCode) ??
371 - await LanguageService.localeDetection();
394 + final savedLanguageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ??
395 + await LanguageService.localeDetection();
396 final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
373 - final bitcoinElectrumServerId = sharedPreferences
374 - .getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
375 - final litecoinElectrumServerId = sharedPreferences
376 - .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
377 - final havenNodeId = sharedPreferences
378 - .getInt(PreferencesKey.currentHavenNodeIdKey);
397 + final bitcoinElectrumServerId =
398 + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
399 + final litecoinElectrumServerId =
400 + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
401 + final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
402 final moneroNode = nodeSource.get(nodeId);
403 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
404 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
405 final havenNode = nodeSource.get(havenNodeId);
406 final packageInfo = await PackageInfo.fromPlatform();
384 - final shouldShowYatPopup =
385 - sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
407 + final deviceName = await _getDeviceName() ?? '';
408 + final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
409
410 final nodes = <WalletType, Node>{};
411
412 if (moneroNode != null) {
390 - nodes[WalletType.monero] = moneroNode;
413 + nodes[WalletType.monero] = moneroNode;
414 }
415
416 if (bitcoinElectrumServer != null) {
394 - nodes[WalletType.bitcoin] = bitcoinElectrumServer;
417 + nodes[WalletType.bitcoin] = bitcoinElectrumServer;
418 }
419
420 if (litecoinElectrumServer != null) {
398 - nodes[WalletType.litecoin] = litecoinElectrumServer;
421 + nodes[WalletType.litecoin] = litecoinElectrumServer;
422 }
423
424 if (havenNode != null) {
402 - nodes[WalletType.haven] = havenNode;
425 + nodes[WalletType.haven] = havenNode;
426 }
427
428 return SettingsStore(
@@ -407,6 +430,7 @@ abstract class SettingsStoreBase with Store {
430 initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
431 nodes: nodes,
432 appVersion: packageInfo.version,
433 + deviceName: deviceName,
434 isBitcoinBuyEnabled: isBitcoinBuyEnabled,
435 initialFiatCurrency: currentFiatCurrency,
436 initialBalanceDisplayMode: currentBalanceDisplayMode,
@@ -416,6 +440,9 @@ abstract class SettingsStoreBase with Store {
440 initialDisableSell: disableSell,
441 initialFiatMode: currentFiatApiMode,
442 initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
443 + initialTotpSecretKey: totpSecretKey,
444 + initialUseTOTP2FA: useTOTP2FA,
445 + initialFailedTokenTrial: tokenTrialNumber,
446 initialExchangeStatus: exchangeStatus,
447 initialTheme: savedTheme,
448 actionlistDisplayMode: actionListDisplayMode,
@@ -430,34 +457,38 @@ abstract class SettingsStoreBase with Store {
457 }
458
459 Future<void> reload({required Box<Node> nodeSource}) async {
433 -
460 final sharedPreferences = await getIt.getAsync<SharedPreferences>();
461
462 fiatCurrency = FiatCurrency.deserialize(
463 raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
464
465 priority[WalletType.monero] = monero?.deserializeMoneroTransactionPriority(
440 - raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
466 + raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
467 priority[WalletType.monero]!;
468 priority[WalletType.bitcoin] = bitcoin?.deserializeBitcoinTransactionPriority(
443 - sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
469 + sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
470 priority[WalletType.bitcoin]!;
471
472 if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
473 priority[WalletType.haven] = monero?.deserializeMoneroTransactionPriority(
448 - raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ??
474 + raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ??
475 priority[WalletType.haven]!;
476 }
477 if (sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority) != null) {
478 priority[WalletType.litecoin] = bitcoin?.deserializeLitecoinTransactionPriority(
453 - sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
479 + sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
480 priority[WalletType.litecoin]!;
481 }
482
483 balanceDisplayMode = BalanceDisplayMode.deserialize(
458 - raw: sharedPreferences
459 - .getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
484 + raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
485 shouldSaveRecipientAddress =
486 + sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
487 + shouldSaveRecipientAddress;
488 + totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
489 + useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
490 + numberOfFailedTokenTrials =
491 + sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
492 sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? shouldSaveRecipientAddress;
493 isAppSecure =
494 sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
@@ -472,19 +503,16 @@ abstract class SettingsStoreBase with Store {
503 sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
504 shouldShowMarketPlaceInDashboard;
505 exchangeStatus = ExchangeApiMode.deserialize(
475 - raw: sharedPreferences
476 - .getInt(PreferencesKey.exchangeStatusKey) ?? ExchangeApiMode.enabled.raw);
477 - final legacyTheme =
478 - (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
479 - ? ThemeType.dark.index
480 - : ThemeType.bright.index;
506 + raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
507 + ExchangeApiMode.enabled.raw);
508 + final legacyTheme = (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
509 + ? ThemeType.dark.index
510 + : ThemeType.bright.index;
511 currentTheme = ThemeList.deserialize(
482 - raw: sharedPreferences.getInt(PreferencesKey.currentTheme) ??
483 - legacyTheme);
512 + raw: sharedPreferences.getInt(PreferencesKey.currentTheme) ?? legacyTheme);
513 actionlistDisplayMode = ObservableList<ActionListDisplayMode>();
514 actionlistDisplayMode.addAll(deserializeActionlistDisplayModes(
486 - sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ??
487 - defaultActionsMode));
515 + sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode));
516 var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength);
517 // If no value
518 if (pinLength == null || pinLength == 0) {
@@ -493,15 +521,15 @@ abstract class SettingsStoreBase with Store {
521 pinCodeLength = pinLength;
522
523 languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
496 - shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
524 + shouldShowYatPopup =
525 + sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
526
527 final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
499 - final bitcoinElectrumServerId = sharedPreferences
500 - .getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
501 - final litecoinElectrumServerId = sharedPreferences
502 - .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
503 - final havenNodeId = sharedPreferences
504 - .getInt(PreferencesKey.currentHavenNodeIdKey);
528 + final bitcoinElectrumServerId =
529 + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
530 + final litecoinElectrumServerId =
531 + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
532 + final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
533 final moneroNode = nodeSource.get(nodeId);
534 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
535 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@@ -535,12 +563,10 @@ abstract class SettingsStoreBase with Store {
563 PreferencesKey.currentLitecoinElectrumSererIdKey, node.key as int);
564 break;
565 case WalletType.monero:
538 - await _sharedPreferences.setInt(
539 - PreferencesKey.currentNodeIdKey, node.key as int);
566 + await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, node.key as int);
567 break;
568 case WalletType.haven:
542 - await _sharedPreferences.setInt(
543 - PreferencesKey.currentHavenNodeIdKey, node.key as int);
569 + await _sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
570 break;
571 default:
572 break;
@@ -548,4 +574,29 @@ abstract class SettingsStoreBase with Store {
574
575 nodes[walletType] = node;
576 }
577 +
578 + static Future<String?> _getDeviceName() async {
579 + String? deviceName = '';
580 + final deviceInfoPlugin = DeviceInfoPlugin();
581 +
582 + if (Platform.isAndroid) {
583 + final androidInfo = await deviceInfoPlugin.androidInfo;
584 + deviceName = '${androidInfo.brand}%20${androidInfo.manufacturer}%20${androidInfo.model}';
585 + print(deviceName);
586 + } else if (Platform.isIOS) {
587 + final iosInfo = await deviceInfoPlugin.iosInfo;
588 + deviceName = iosInfo.model;
589 + } else if (Platform.isLinux) {
590 + final linuxInfo = await deviceInfoPlugin.linuxInfo;
591 + deviceName = linuxInfo.prettyName;
592 + } else if (Platform.isMacOS) {
593 + final macInfo = await deviceInfoPlugin.macOsInfo;
594 + deviceName = macInfo.computerName;
595 + } else if (Platform.isWindows) {
596 + final windowsInfo = await deviceInfoPlugin.windowsInfo;
597 + deviceName = windowsInfo.productName;
598 + }
599 +
600 + return deviceName;
601 + }
602 }
lib/utils/totp_utils.dart new
+83
@@ -0,0 +1,83 @@
1 +import 'dart:math';
2 +import 'package:base32/base32.dart';
3 +import 'package:crypto/crypto.dart';
4 +
5 +import 'package:flutter/foundation.dart';
6 +
7 +//*========================== TOTP 2FA Related Utilities ==========================================
8 +
9 +String generateRandomBase32SecretKey(int byteLength) {
10 + final Random _secureRandom = Random.secure();
11 + // Generate random bytes
12 + final randomBytes = Uint8List.fromList(
13 + List<int>.generate(byteLength, (i) => _secureRandom.nextInt(256)),
14 + );
15 +
16 + // Encode bytes to base32
17 + final base32SecretKey = base32.encode(randomBytes);
18 +
19 + return base32SecretKey;
20 +}
21 +
22 +String generateOTP({required String secretKey, required int input}) {
23 + /// base32 decode the secret
24 + var hmacKey = base32.decode(secretKey);
25 +
26 + /// initial the HMAC-SHA1 object
27 + var hmacSha = Hmac(sha512, hmacKey);
28 +
29 + /// get hmac answer
30 + var hmac = hmacSha.convert(intToBytelist(input: input)).bytes;
31 +
32 + /// calculate the init offset
33 + int offset = hmac[hmac.length - 1] & 0xf;
34 +
35 + /// calculate the code
36 + int code = ((hmac[offset] & 0x7f) << 24 |
37 + (hmac[offset + 1] & 0xff) << 16 |
38 + (hmac[offset + 2] & 0xff) << 8 |
39 + (hmac[offset + 3] & 0xff));
40 +
41 + /// get the initial string code
42 + var strCode = (code % pow(10, 8)).toString();
43 + strCode = strCode.padLeft(8, '0');
44 +
45 + return strCode;
46 +}
47 +
48 +List<int> intToBytelist({required int input, int padding = 8}) {
49 + List<int> _result = [];
50 + var _input = input;
51 + while (_input != 0) {
52 + _result.add(_input & 0xff);
53 + _input >>= padding;
54 + }
55 + _result.addAll(List<int>.generate(padding, (_) => 0));
56 + _result = _result.sublist(0, padding);
57 + _result = _result.reversed.toList();
58 + return _result;
59 +}
60 +
61 +String totpNow(String secretKey) {
62 + int _formatTime = timeFormat(time: DateTime.now());
63 + return generateOTP(input: _formatTime, secretKey: secretKey);
64 +}
65 +
66 +int timeFormat({required DateTime time}) {
67 + final _timeStr = time.millisecondsSinceEpoch.toString();
68 + final _formatTime = _timeStr.substring(0, _timeStr.length - 3);
69 +
70 + return int.parse(_formatTime) ~/ 30;
71 +}
72 +
73 +bool verify({String? otp, DateTime? time, required String secretKey}) {
74 + if (otp == null) {
75 + return false;
76 + }
77 +
78 + var _time = time ?? DateTime.now();
79 + var _input = timeFormat(time: _time);
80 +
81 + String otpTime = generateOTP(input: _input, secretKey: secretKey);
82 + return otp == otpTime;
83 +}
lib/view_model/set_up_2fa_viewmodel.dart new
+159
@@ -0,0 +1,159 @@
1 +// ignore_for_file: prefer_final_fields
2 +
3 +import 'package:cake_wallet/store/settings_store.dart';
4 +import 'package:cake_wallet/utils/totp_utils.dart' as Utils;
5 +import 'package:cake_wallet/view_model/auth_state.dart';
6 +import 'package:flutter/widgets.dart';
7 +import 'package:mobx/mobx.dart';
8 +import 'package:shared_preferences/shared_preferences.dart';
9 +
10 +import '../core/auth_service.dart';
11 +import '../core/execution_state.dart';
12 +import '../generated/i18n.dart';
13 +
14 +part 'set_up_2fa_viewmodel.g.dart';
15 +
16 +class Setup2FAViewModel = Setup2FAViewModelBase with _$Setup2FAViewModel;
17 +
18 +abstract class Setup2FAViewModelBase with Store {
19 + final SettingsStore _settingsStore;
20 + final AuthService _authService;
21 + final SharedPreferences _sharedPreferences;
22 +
23 + Setup2FAViewModelBase(this._settingsStore, this._sharedPreferences, this._authService)
24 + : _failureCounter = 0,
25 + enteredOTPCode = '',
26 + state = InitialExecutionState() {
27 + _getRandomBase32SecretKey();
28 + reaction((_) => state, _saveLastAuthTime);
29 + }
30 +
31 + static const maxFailedTrials = 3;
32 + static const banTimeout = 180; // 3 minutes
33 + final banTimeoutKey = S.current.auth_store_ban_timeout;
34 +
35 + String get secretKey => _settingsStore.totpSecretKey;
36 + String get deviceName => _settingsStore.deviceName;
37 + String get totpVersionOneLink => _settingsStore.totpVersionOneLink;
38 +
39 + @observable
40 + ExecutionState state;
41 +
42 + @observable
43 + int _failureCounter;
44 +
45 + @observable
46 + String enteredOTPCode;
47 +
48 + @computed
49 + bool get useTOTP2FA => _settingsStore.useTOTP2FA;
50 +
51 + void _getRandomBase32SecretKey() {
52 + final randomBase32Key = Utils.generateRandomBase32SecretKey(16);
53 + _setBase32SecretKey(randomBase32Key);
54 + }
55 +
56 + @action
57 + void setUseTOTP2FA(bool value) {
58 + _settingsStore.useTOTP2FA = value;
59 + }
60 +
61 + @action
62 + void _setBase32SecretKey(String value) {
63 + if (_settingsStore.totpSecretKey == '') {
64 + _settingsStore.totpSecretKey = value;
65 + }
66 + }
67 +
68 + @action
69 + void clearBase32SecretKey() {
70 + _settingsStore.totpSecretKey = '';
71 + }
72 +
73 + Duration? banDuration() {
74 + final unbanTimestamp = _sharedPreferences.getInt(banTimeoutKey);
75 +
76 + if (unbanTimestamp == null) {
77 + return null;
78 + }
79 +
80 + final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
81 + final now = DateTime.now();
82 +
83 + if (now.isAfter(unbanTime)) {
84 + return null;
85 + }
86 +
87 + return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
88 + }
89 +
90 + Future<Duration> ban() async {
91 + final multiplier = _failureCounter - maxFailedTrials;
92 + final timeout = (multiplier * banTimeout) * 1000;
93 + final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
94 + await _sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
95 +
96 + return Duration(milliseconds: timeout);
97 + }
98 +
99 + @action
100 + Future<bool> totp2FAAuth(String otpText, bool isForSetup) async {
101 + state = InitialExecutionState();
102 + _failureCounter = _settingsStore.numberOfFailedTokenTrials;
103 + final _banDuration = banDuration();
104 +
105 + if (_banDuration != null) {
106 + state = AuthenticationBanned(
107 + error: S.current.auth_store_banned_for +
108 + '${_banDuration.inMinutes}' +
109 + S.current.auth_store_banned_minutes);
110 + return false;
111 + }
112 +
113 + final result = Utils.verify(
114 + secretKey: secretKey,
115 + otp: otpText,
116 + );
117 +
118 + isForSetup ? setUseTOTP2FA(result) : null;
119 +
120 + if (result) {
121 + return true;
122 + } else {
123 + final value = _settingsStore.numberOfFailedTokenTrials + 1;
124 + adjustTokenTrialNumber(value);
125 + print(value);
126 + if (_failureCounter >= maxFailedTrials) {
127 + final banDuration = await ban();
128 + state = AuthenticationBanned(
129 + error: S.current.auth_store_banned_for +
130 + '${banDuration.inMinutes}' +
131 + S.current.auth_store_banned_minutes);
132 + return false;
133 + }
134 +
135 + state = FailureState('Incorrect code');
136 + return false;
137 + }
138 + }
139 +
140 + @action
141 + void success() {
142 + WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
143 + state = ExecutedSuccessfullyState();
144 + adjustTokenTrialNumber(0);
145 + });
146 + }
147 +
148 + @action
149 + void adjustTokenTrialNumber(int value) {
150 + _failureCounter = value;
151 + _settingsStore.numberOfFailedTokenTrials = value;
152 + }
153 +
154 + void _saveLastAuthTime(ExecutionState state) {
155 + if (state is ExecutedSuccessfullyState) {
156 + _authService.saveLastAuthTime();
157 + }
158 + }
159 +}
lib/view_model/settings/security_settings_view_model.dart
+3
@@ -21,6 +21,9 @@ abstract class SecuritySettingsViewModelBase with Store {
21 @computed
22 bool get allowBiometricalAuthentication => _settingsStore.allowBiometricalAuthentication;
23
24 + @computed
25 + bool get useTotp2FA => _settingsStore.useTOTP2FA;
26 +
27 @computed
28 PinCodeRequiredDuration get pinCodeRequiredDuration => _settingsStore.pinTimeOutDuration;
29
macos/Flutter/GeneratedPluginRegistrant.swift
+1 -1
@@ -25,7 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
25 CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin"))
26 DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
27 DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
28 - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
28 + FlutterSecureStorageMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageMacosPlugin"))
29 InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
30 FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))
31 PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
pubspec_base.yaml
+1
@@ -71,6 +71,7 @@ dependencies:
71 wakelock: ^0.6.2
72 flutter_mailer: ^2.0.2
73 device_info_plus: 8.1.0
74 + base32: 2.1.3
75 in_app_review: ^2.0.6
76 cake_backup:
77 git:
res/values/strings_ar.arb
+17
@@ -707,6 +707,23 @@
707 "error_text_input_above_maximum_limit":"المبلغ أكبر من الحد الأقصى",
708 "show_market_place": "إظهار السوق",
709 "prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة",
710 + "modify_2fa": "تعديل 2 عامل المصادقة",
711 + "disable_cake_2fa": "تعطيل 2 عامل المصادقة",
712 + "question_to_disable_2fa":"هل أنت متأكد أنك تريد تعطيل Cake 2FA؟ لن تكون هناك حاجة إلى رمز 2FA للوصول إلى المحفظة ووظائف معينة.",
713 + "disable": "إبطال",
714 + "setup_2fa": "تعيين 2 عامل المصادقة",
715 + "verify_with_2fa": "تحقق مع Cake 2FA",
716 + "totp_code": "كود TOTP",
717 + "please_fill_totp": "يرجى ملء الرمز المكون من 8 أرقام الموجود على جهازك الآخر",
718 + "totp_2fa_success": "نجاح! تم تمكين Cake 2FA لهذه المحفظة. تذكر حفظ بذرة ذاكري في حالة فقد الوصول إلى المحفظة.",
719 + "totp_verification_success" :"تم التحقق بنجاح!",
720 + "totp_2fa_failure": "شفرة خاطئة. يرجى تجربة رمز مختلف أو إنشاء مفتاح سري جديد. استخدم تطبيق 2FA متوافقًا يدعم الرموز المكونة من 8 أرقام و SHA512.",
721 + "enter_totp_code": "الرجاء إدخال رمز TOTP.",
722 + "add_secret_code":"أضف هذا الرمز السري إلى جهاز آخر",
723 + "totp_secret_code":"كود TOTP السري",
724 + "important_note": "ملاحظة مهمة",
725 + "setup_2fa_text": "كعكة 2FA ليست آمنة مثل التخزين البارد. تحمي 2FA من الأنواع الأساسية للهجمات ، مثل قيام صديقك بتقديم بصمة إصبعك أثناء نومك. لا تحمي Cake 2FA من جهاز مخترق من قِبل مهاجم متطور. إذا فقدت الوصول إلى رموز 2FA الخاصة بك ، ستفقد إمكانية الوصول إلى هذه المحفظة. سوف تحتاج إلى استعادة محفظتك من بذرة ذاكري. يجب عليك بالتالي الاحتفاظ بنسخة احتياطية من بذور الذاكرة الخاصة بك! علاوة على ذلك ، سيتمكن أي شخص لديه حق الوصول إلى بذرة (بذور) ذاكري من سرقة أموالك ، متجاوزًا Cake 2FA. لن يتمكن فريق دعم الكيك من مساعدتك إذا فقدت الوصول إلى بذرتك ، نظرًا لأن Cake هي المحفظة غير الحافظة.",
726 + "setup_totp_recommended": "إعداد TOTP (موصى به)",
727 "disable_buy": "تعطيل إجراء الشراء",
728 "disable_sell": "قم بتعطيل إجراء البيع"
729 }
res/values/strings_bg.arb
+17
@@ -703,6 +703,23 @@
703 "error_text_input_above_maximum_limit" : "Сумата надвишава максималната",
704 "show_market_place":"Покажи пазар",
705 "prevent_screenshots": "Предотвратете екранни снимки и запис на екрана",
706 + "modify_2fa": "Модифициране на тортата 2FA",
707 + "disable_cake_2fa": "Деактивирайте Cake 2FA",
708 + "question_to_disable_2fa":"Сигурни ли сте, че искате да деактивирате Cake 2FA? Вече няма да е необходим 2FA код за достъп до портфейла и определени функции.",
709 + "disable": "Деактивиране",
710 + "setup_2fa": "Настройка на Cake 2FA",
711 + "verify_with_2fa": "Проверете с Cake 2FA",
712 + "totp_code": "TOTP код",
713 + "please_fill_totp": "Моля, попълнете 8-цифрения код на другото ви устройство",
714 + "totp_2fa_success": "Успех! Cake 2FA е активиран за този портфейл. Не забравяйте да запазите мнемоничното начало, в случай че загубите достъп до портфейла.",
715 + "totp_verification_success" :"Проверката е успешна!",
716 + "totp_2fa_failure": "Грешен код. Моля, опитайте с различен код или генерирайте нов таен ключ. Използвайте съвместимо 2FA приложение, което поддържа 8-цифрени кодове и SHA512.",
717 + "enter_totp_code": "Моля, въведете TOTP кода.",
718 + "add_secret_code":"Добавете този таен код към друго устройство",
719 + "totp_secret_code":"TOTP таен код",
720 + "important_note": "Важна забележка",
721 + "setup_2fa_text": "Тортата 2FA НЕ е толкова сигурна, колкото хладилното съхранение. 2FA защитава срещу основни видове атаки, като например вашият приятел да предостави вашия пръстов отпечатък, докато спите.\n\n Cake 2FA НЕ защитава срещу компрометирано устройство от сложен хакер.\n\n Ако загубите достъп до своите 2FA кодове , ЩЕ ЗАГУБИТЕ ДОСТЪП ДО ТОЗИ ПОРТФЕЙЛ. Ще трябва да възстановите портфейла си от мнемонично семе. ЗАТОВА ТРЯБВА ДА НАПРАВИТЕ РЕЗЕРВНО КОПИЕ НА ВАШИТЕ МНЕМОНИЧНИ СЕМЕНА! Освен това, някой с достъп до вашите мнемонични начални точки ще може да открадне вашите средства, заобикаляйки Cake 2FA.\n\n Персоналът по поддръжката на Cake няма да може да ви помогне, ако загубите достъп до вашите мнемонични начални стойности, тъй като Cake е портфейл без попечителство.",
722 + "setup_totp_recommended": "Настройка на TOTP (препоръчително)",
723 "disable_buy": "Деактивирайте действието за покупка",
724 "disable_sell": "Деактивирайте действието за продажба"
725 }
res/values/strings_cs.arb
+17
@@ -703,6 +703,23 @@
703 "error_text_input_above_maximum_limit" : "Částka je větší než maximální hodnota",
704 "show_market_place": "Zobrazit trh",
705 "prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky",
706 + "modify_2fa": "Upravte Cake 2FA",
707 + "disable_cake_2fa": "Zakázat Cake 2FA",
708 + "question_to_disable_2fa":"Opravdu chcete deaktivovat Cake 2FA? Pro přístup k peněžence a některým funkcím již nebude potřeba kód 2FA.",
709 + "disable": "Zakázat",
710 + "setup_2fa": "Nastavení Cake 2FA",
711 + "verify_with_2fa": "Ověřte pomocí Cake 2FA",
712 + "totp_code": "Kód TOTP",
713 + "please_fill_totp": "Vyplňte prosím 8místný kód na vašem druhém zařízení",
714 + "totp_2fa_success": "Úspěch! Pro tuto peněženku povolen Cake 2FA. Nezapomeňte si uložit mnemotechnický klíč pro případ, že ztratíte přístup k peněžence.",
715 + "totp_verification_success" :"Ověření proběhlo úspěšně!",
716 + "totp_2fa_failure": "Nesprávný kód. Zkuste prosím jiný kód nebo vygenerujte nový tajný klíč. Použijte kompatibilní aplikaci 2FA, která podporuje 8místné kódy a SHA512.",
717 + "enter_totp_code": "Zadejte kód TOTP.",
718 + "add_secret_code":"Přidejte tento tajný kód do jiného zařízení",
719 + "totp_secret_code":"Tajný kód TOTP",
720 + "important_note": "Důležitá poznámka",
721 + "setup_2fa_text": "Cake 2FA NENÍ tak bezpečný jako skladování v chladu. 2FA chrání před základními typy útoků, jako je váš přítel, který vám poskytne otisk prstu, když spíte.\n\n Cake 2FA nechrání před napadením zařízení sofistikovaným útočníkem.\n\n Pokud ztratíte přístup ke svým kódům 2FA , ZTRÁTÍTE PŘÍSTUP K TÉTO PENĚŽENCE. Budete muset obnovit svou peněženku z mnemotechnického semínka. MUSÍTE TEDY ZÁLOHOVAT SVÉ MNEMONICKÉ SEMÉNKY! Kromě toho někdo s přístupem k vašemu mnemotechnickému semenu bude moci ukrást vaše finanční prostředky a obejít Cake 2FA.\n\n Pracovníci podpory Cake vám nebudou schopni pomoci, pokud ztratíte přístup k vašemu mnemotechnickému semenu, protože Cake je nevazební peněženka.",
722 + "setup_totp_recommended": "Nastavit TOTP (doporučeno)",
723 "disable_buy": "Zakázat akci nákupu",
724 "disable_sell": "Zakázat akci prodeje"
725 }
res/values/strings_de.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "Menge ist über dem Maximum",
710 "show_market_place": "Marktplatz anzeigen",
711 "prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen",
712 + "modify_2fa": "Kuchen 2FA ändern",
713 + "disable_cake_2fa": "Kuchen 2FA deaktivieren",
714 + "question_to_disable_2fa":"Sind Sie sicher, dass Sie Cake 2FA deaktivieren möchten? Für den Zugriff auf die Brieftasche und bestimmte Funktionen wird kein 2FA-Code mehr benötigt.",
715 + "disable": "Deaktivieren",
716 + "setup_2fa": "Setup-Kuchen 2FA",
717 + "verify_with_2fa": "Verifizieren Sie mit Cake 2FA",
718 + "totp_code": "TOTP-Code",
719 + "please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist",
720 + "totp_2fa_success": "Erfolg! Cake 2FA für dieses Wallet aktiviert. Denken Sie daran, Ihren mnemonischen Seed zu speichern, falls Sie den Zugriff auf die Brieftasche verlieren.",
721 + "totp_verification_success" :"Verifizierung erfolgreich!",
722 + "totp_2fa_failure": "Falscher Code. Bitte versuchen Sie es mit einem anderen Code oder generieren Sie einen neuen geheimen Schlüssel. Verwenden Sie eine kompatible 2FA-App, die 8-stellige Codes und SHA512 unterstützt.",
723 + "enter_totp_code": "Bitte geben Sie den TOTP-Code ein.",
724 + "add_secret_code":"Fügen Sie diesen Geheimcode einem anderen Gerät hinzu",
725 + "totp_secret_code":"TOTP-Geheimcode",
726 + "important_note": "Wichtiger Hinweis",
727 + "setup_2fa_text": "Cake 2FA ist NICHT so sicher wie eine Kühllagerung. 2FA schützt vor grundlegenden Arten von Angriffen, z. B. wenn Ihr Freund Ihren Fingerabdruck bereitstellt, während Sie schlafen.\n\n Cake 2FA schützt NICHT vor einem kompromittierten Gerät durch einen raffinierten Angreifer.\n\n Wenn Sie den Zugriff auf Ihre 2FA-Codes verlieren , VERLIEREN SIE DEN ZUGANG ZU DIESEM WALLET. Sie müssen Ihre Brieftasche aus mnemonic Seed wiederherstellen. SIE MÜSSEN DESHALB IHRE MNEMONISCHEN SEEDS SICHERN! Außerdem kann jemand mit Zugriff auf Ihre mnemonischen Seed(s) Ihr Geld stehlen und Cake 2FA umgehen.\n\n Cake-Supportmitarbeiter können Ihnen nicht helfen, wenn Sie den Zugriff auf Ihre mnemonischen Seed(s) verlieren, da Cake ein Brieftasche ohne Verwahrung.",
728 + "setup_totp_recommended": "TOTP einrichten (empfohlen)",
729 "disable_buy": "Kaufaktion deaktivieren",
730 "disable_sell": "Verkaufsaktion deaktivieren"
731 }
res/values/strings_en.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "Amount is more than the maximum",
710 "show_market_place" :"Show Marketplace",
711 "prevent_screenshots": "Prevent screenshots and screen recording",
712 + "modify_2fa": "Modify Cake 2FA",
713 + "disable_cake_2fa": "Disable Cake 2FA",
714 + "question_to_disable_2fa":"Are you sure that you want to disable Cake 2FA? A 2FA code will no longer be needed to access the wallet and certain functions.",
715 + "disable": "Disable",
716 + "setup_2fa": "Setup Cake 2FA",
717 + "verify_with_2fa": "Verify with Cake 2FA",
718 + "totp_code": "TOTP Code",
719 + "please_fill_totp": "Please fill in the 8-digit code present on your other device",
720 + "totp_2fa_success": "Success! Cake 2FA enabled for this wallet. Remember to save your mnemonic seed in case you lose wallet access.",
721 + "totp_verification_success" :"Verification Successful!",
722 + "totp_2fa_failure": "Incorrect code. Please try a different code or generate a new secret key. Use a compatible 2FA app that supports 8-digit codes and SHA512.",
723 + "enter_totp_code": "Please enter the TOTP Code.",
724 + "add_secret_code":"Add this secret code to another device",
725 + "totp_secret_code":"TOTP Secret Code",
726 + "important_note": "Important note",
727 + "setup_2fa_text": "Cake 2FA is NOT as secure as cold storage. 2FA protects against basic types of attacks, such as your friend providing your fingerprint while you are sleeping.\n\n Cake 2FA does NOT protect against a compromised device by a sophisticated attacker.\n\n If you lose access to your 2FA codes, YOU WILL LOSE ACCESS TO THIS WALLET. You will need to restore your wallet from mnemonic seed. YOU MUST THEREFORE BACK UP YOUR MNEMONIC SEEDS! Further, someone with access to your mnemonic seed(s) will be able to steal your funds, bypassing Cake 2FA.\n\n Cake support staff will be unable to assist you if you lose access to your mnemonic seed, since Cake is a noncustodial wallet.",
728 + "setup_totp_recommended": "Set up TOTP (Recommended)",
729 "disable_buy": "Disable buy action",
730 "disable_sell": "Disable sell action"
731 }
res/values/strings_es.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "La cantidad es más que el máximo",
710 "show_market_place": "Mostrar mercado",
711 "prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla",
712 + "modify_2fa": "Modificar torta 2FA",
713 + "disable_cake_2fa": "Desactivar pastel 2FA",
714 + "question_to_disable_2fa":"¿Está seguro de que desea deshabilitar Cake 2FA? Ya no se necesitará un código 2FA para acceder a la billetera y a ciertas funciones.",
715 + "disable": "Desactivar",
716 + "setup_2fa": "Configurar pastel 2FA",
717 + "verify_with_2fa": "Verificar con Cake 2FA",
718 + "totp_code": "Código TOTP",
719 + "please_fill_totp": "Complete el código de 8 dígitos presente en su otro dispositivo",
720 + "totp_2fa_success": "¡Éxito! Cake 2FA habilitado para esta billetera. Recuerde guardar su semilla mnemotécnica en caso de que pierda el acceso a la billetera.",
721 + "totp_verification_success" :"¡Verificación exitosa!",
722 + "totp_2fa_failure": "Código incorrecto. Intente con un código diferente o genere una nueva clave secreta. Use una aplicación 2FA compatible que admita códigos de 8 dígitos y SHA512.",
723 + "enter_totp_code": "Ingrese el código TOTP.",
724 + "add_secret_code":"Agregue este código secreto a otro dispositivo",
725 + "totp_secret_code":"Código secreto TOTP",
726 + "important_note": "Nota IMPORTANTE",
727 + "setup_2fa_text": "Cake 2FA NO es tan seguro como el almacenamiento en frío. 2FA protege contra tipos básicos de ataques, como cuando un amigo proporciona su huella digital mientras usted duerme.\n\n Cake 2FA NO protege contra un dispositivo comprometido por un atacante sofisticado.\n\n Si pierde el acceso a sus códigos 2FA , PERDERÁS EL ACCESO A ESTA BILLETERA. Deberá restaurar su billetera desde la semilla mnemotécnica. ¡POR LO TANTO, DEBE HACER UNA COPIA DE SEGURIDAD DE SUS SEMILLAS MNEMÓNICAS! Además, alguien con acceso a sus semillas mnemotécnicas podrá robar sus fondos, sin pasar por Cake 2FA.\n\n El personal de soporte de Cake no podrá ayudarlo si pierde el acceso a su semilla mnemotécnica, ya que Cake es un billetera sin custodia.",
728 + "setup_totp_recommended": "Configurar TOTP (Recomendado)",
729 "disable_buy": "Desactivar acción de compra",
730 "disable_sell": "Desactivar acción de venta"
731 }
res/values/strings_fr.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "Le montant est supérieur au maximum",
710 "show_market_place" :"Afficher la place de marché",
711 "prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran",
712 + "modify_2fa": "Modifier le gâteau 2FA",
713 + "disable_cake_2fa": "Désactiver le gâteau 2FA",
714 + "question_to_disable_2fa":"Êtes-vous sûr de vouloir désactiver Cake 2FA ? Un code 2FA ne sera plus nécessaire pour accéder au portefeuille et à certaines fonctions.",
715 + "disable": "Désactiver",
716 + "setup_2fa": "Gâteau d'installation 2FA",
717 + "verify_with_2fa": "Vérifier avec Cake 2FA",
718 + "totp_code": "Code TOTP",
719 + "please_fill_totp": "Veuillez renseigner le code à 8 chiffres présent sur votre autre appareil",
720 + "totp_2fa_success": "Succès! Cake 2FA activé pour ce portefeuille. N'oubliez pas de sauvegarder votre graine mnémonique au cas où vous perdriez l'accès au portefeuille.",
721 + "totp_verification_success" :"Vérification réussie !",
722 + "totp_2fa_failure": "Code incorrect. Veuillez essayer un code différent ou générer une nouvelle clé secrète. Utilisez une application 2FA compatible qui prend en charge les codes à 8 chiffres et SHA512.",
723 + "enter_totp_code": "Veuillez entrer le code TOTP.",
724 + "add_secret_code":"Ajouter ce code secret à un autre appareil",
725 + "totp_secret_code":"Code secret TOTP",
726 + "important_note": "Note importante",
727 + "setup_2fa_text": "Cake 2FA n'est PAS aussi sûr que le stockage à froid. 2FA protège contre les types d'attaques de base, comme votre ami fournissant votre empreinte digitale pendant que vous dormez.\n\n Cake 2FA ne protège PAS contre un appareil compromis par un attaquant sophistiqué.\n\n Si vous perdez l'accès à vos codes 2FA , VOUS PERDREZ L'ACCÈS À CE PORTEFEUILLE. Vous devrez restaurer votre portefeuille à partir de graines mnémotechniques. VOUS DEVEZ DONC SAUVEGARDER VOS SEMENCES MNEMONIQUES ! De plus, quelqu'un ayant accès à vos graines mnémoniques pourra voler vos fonds, en contournant Cake 2FA.\n\n Le personnel d'assistance de Cake ne pourra pas vous aider si vous perdez l'accès à vos graines mnémoniques, puisque Cake est un portefeuille non dépositaire.",
728 + "setup_totp_recommended": "Configurer TOTP (recommandé)",
729 "disable_buy": "Désactiver l'action d'achat",
730 "disable_sell": "Désactiver l'action de vente"
731 }
res/values/strings_hi.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "राशि अधिकतम से अधिक है",
710 "show_market_place":"बाज़ार दिखाएँ",
711 "prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें",
712 + "modify_2fa": "केक 2FA संशोधित करें",
713 + "disable_cake_2fa": "केक 2FA अक्षम करें",
714 + "question_to_disable_2fa":"क्या आप सुनिश्चित हैं कि आप Cake 2FA को अक्षम करना चाहते हैं? वॉलेट और कुछ कार्यों तक पहुँचने के लिए अब 2FA कोड की आवश्यकता नहीं होगी।",
715 + "disable": "अक्षम करना",
716 + "setup_2fa": "सेटअप केक 2FA",
717 + "verify_with_2fa": "केक 2FA के साथ सत्यापित करें",
718 + "totp_code": "टीओटीपी कोड",
719 + "please_fill_totp": "कृपया अपने दूसरे डिवाइस पर मौजूद 8 अंकों का कोड भरें",
720 + "totp_2fa_success": "सफलता! इस वॉलेट के लिए Cake 2FA सक्षम है। यदि आप वॉलेट एक्सेस खो देते हैं तो अपने स्मरक बीज को सहेजना याद रखें।",
721 + "totp_verification_success" :"सत्यापन सफल!",
722 + "totp_2fa_failure": "गलत कोड़। कृपया एक अलग कोड का प्रयास करें या एक नई गुप्त कुंजी उत्पन्न करें। 8-अंकीय कोड और SHA512 का समर्थन करने वाले संगत 2FA ऐप का उपयोग करें।",
723 + "enter_totp_code": "कृपया TOTP कोड दर्ज करें।",
724 + "add_secret_code":"इस गुप्त कोड को किसी अन्य डिवाइस में जोड़ें",
725 + "totp_secret_code":"टीओटीपी गुप्त कोड",
726 + "important_note": "महत्वपूर्ण लेख",
727 + "setup_2fa_text": "केक 2FA कोल्ड स्टोरेज जितना सुरक्षित नहीं है। 2FA बुनियादी प्रकार के हमलों से बचाता है, जैसे कि आपका मित्र सोते समय आपको अपना फ़िंगरप्रिंट प्रदान करता है।\n\n Cake 2FA परिष्कृत हमलावर द्वारा किसी डिवाइस से छेड़छाड़ से रक्षा नहीं करता है।\n\n यदि आप अपने 2FA कोड तक पहुंच खो देते हैं , आप इस वॉलेट तक पहुंच खो देंगे। आपको अपने बटुए को स्मरणीय बीज से पुनर्स्थापित करने की आवश्यकता होगी। इसलिए आपको अपने स्मरणीय बीजों का बैकअप लेना चाहिए! इसके अलावा, आपके स्मरक बीज (बीजों) तक पहुंच रखने वाला कोई व्यक्ति केक 2FA को दरकिनार कर आपके धन की चोरी करने में सक्षम होगा। अप्रबंधित बटुआ।",
728 + "setup_totp_recommended": "टीओटीपी सेट अप करें (अनुशंसित)",
729 "disable_buy": "खरीद कार्रवाई अक्षम करें",
730 "disable_sell": "बेचने की कार्रवाई अक्षम करें"
731 }
res/values/strings_hr.arb
+22
@@ -709,6 +709,28 @@
709 "error_text_input_above_maximum_limit" : "Iznos je veći od maskimalnog",
710 "show_market_place" : "Prikaži tržište",
711 "prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona",
712 + "modify_2fa": "Izmijenite tortu 2FA",
713 + "disable_cake_2fa": "Onemogući Cake 2FA",
714 + "question_to_disable_2fa":"Jeste li sigurni da želite onemogućiti Cake 2FA? 2FA kod više neće biti potreban za pristup novčaniku i određenim funkcijama.",
715 + "disable": "Onemogući",
716 + "setup_2fa": "Postavljanje torte 2FA",
717 + "verify_with_2fa": "Provjerite s Cake 2FA",
718 + "totp_code": "TOTP kod",
719 + "please_fill_totp": "Unesite 8-znamenkasti kod koji se nalazi na vašem drugom uređaju",
720 + "totp_2fa_success": "Uspjeh! Cake 2FA omogućen za ovaj novčanik. Ne zaboravite spremiti svoje mnemoničko sjeme u slučaju da izgubite pristup novčaniku.",
721 + "totp_verification_success" :"Provjera uspješna!",
722 + "totp_2fa_failure": "Neispravan kod. Pokušajte s drugim kodom ili generirajte novi tajni ključ. Koristite kompatibilnu 2FA aplikaciju koja podržava 8-znamenkasti kod i SHA512.",
723 + "enter_totp_code": "Unesite TOTP kod.",
724 + "add_secret_code":"Dodajte ovaj tajni kod na drugi uređaj",
725 + "totp_secret_code":"TOTP tajni kod",
726 + "important_note": "Važna nota",
727 + "setup_2fa_text": "Torta 2FA NIJE sigurna kao hladno skladište. 2FA štiti od osnovnih vrsta napada, kao što je vaš prijatelj koji vam daje otisak prsta dok spavate.\n\n Cake 2FA NE štiti od kompromitiranog uređaja od strane sofisticiranog napadača.\n\n Ako izgubite pristup svojim 2FA kodovima , IZGUBIT ĆETE PRISTUP OVOM NOVČANIKU. Morat ćete obnoviti svoj novčanik iz mnemoničkog sjemena. STOGA MORATE NAPRAVITI SIGURNOSNE KOPIJE SVOJIH MNEMONIČKIH SJEMENA! Nadalje, netko tko ima pristup vašem mnemoničkom seedu(ima) moći će ukrasti vaša sredstva, zaobilazeći Cake 2FA.\n\n Cake osoblje za podršku neće vam moći pomoći ako izgubite pristup svom mnemoničkom seedu, budući da je Cake neskrbnički novčanik.",
728 + "setup_totp_recommended": "Postavite TOTP (preporučeno)",
729 "disable_buy": "Onemogući kupnju",
730 "disable_sell": "Onemogući akciju prodaje"
731 }
732 +
733 +
734 +
735 +
736 +
res/values/strings_id.arb
+28
@@ -685,6 +685,34 @@
685 "error_text_input_above_maximum_limit" : "Jumlah lebih dari maksimal",
686 "show_market_place": "Tampilkan Pasar",
687 "prevent_screenshots": "Cegah tangkapan layar dan perekaman layar",
688 + "modify_2fa": "Ubah Kue 2FA",
689 + "disable_cake_2fa": "Nonaktifkan Kue 2FA",
690 + "question_to_disable_2fa":"Apakah Anda yakin ingin menonaktifkan Cake 2FA? Kode 2FA tidak lagi diperlukan untuk mengakses dompet dan fungsi tertentu.",
691 + "disable": "Cacat",
692 + "setup_2fa": "Siapkan Kue 2FA",
693 + "verify_with_2fa": "Verifikasi dengan Cake 2FA",
694 + "totp_code": "Kode TOTP",
695 + "please_fill_totp": "Harap isi kode 8 digit yang ada di perangkat Anda yang lain",
696 + "totp_2fa_success": "Kesuksesan! Cake 2FA diaktifkan untuk dompet ini. Ingatlah untuk menyimpan benih mnemonik Anda jika Anda kehilangan akses dompet.",
697 + "totp_verification_success" :"Verifikasi Berhasil!",
698 + "totp_2fa_failure": "Kode salah. Silakan coba kode lain atau buat kunci rahasia baru. Gunakan aplikasi 2FA yang kompatibel yang mendukung kode 8 digit dan SHA512.",
699 + "enter_totp_code": "Masukkan Kode TOTP.",
700 + "add_secret_code":"Tambahkan kode rahasia ini ke perangkat lain",
701 + "totp_secret_code":"Kode Rahasia TOTP",
702 + "important_note": "Catatan penting",
703 + "setup_2fa_text": "Cake 2FA TIDAK seaman cold storage. 2FA melindungi dari jenis serangan dasar, seperti teman Anda memberikan sidik jari saat Anda sedang tidur.\n\n Cake 2FA TIDAK melindungi dari perangkat yang disusupi oleh penyerang canggih.\n\n Jika Anda kehilangan akses ke kode 2FA , ANDA AKAN KEHILANGAN AKSES KE DOMPET INI. Anda perlu memulihkan dompet Anda dari benih mnemonik. OLEH KARENA ITU, ANDA HARUS MENYIMPAN BIJI MNEMONIK ANDA! Selanjutnya, seseorang yang memiliki akses ke benih mnemonik Anda akan dapat mencuri dana Anda, melewati Cake 2FA.\n\n Staf pendukung Cake tidak akan dapat membantu Anda jika Anda kehilangan akses ke benih mnemonik Anda, karena Cake adalah dompet tanpa hak asuh.",
704 + "setup_totp_recommended": "Siapkan TOTP (Disarankan)",
705 "disable_buy": "Nonaktifkan tindakan beli",
706 "disable_sell": "Nonaktifkan aksi jual"
707 }
708 +
709 +
710 +
711 +
712 +
713 +
714 +
715 +
716 +
717 +
718 +
res/values/strings_it.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "L'ammontare è superiore al massimo",
710 "show_market_place":"Mostra mercato",
711 "prevent_screenshots": "Impedisci screenshot e registrazione dello schermo",
712 + "modify_2fa": "Modifica Torta 2FA",
713 + "disable_cake_2fa": "Disabilita Cake 2FA",
714 + "question_to_disable_2fa":"Sei sicuro di voler disabilitare Cake 2FA? Non sarà più necessario un codice 2FA per accedere al portafoglio e ad alcune funzioni.",
715 + "disable": "disattivare",
716 + "setup_2fa": "Imposta la torta 2FA",
717 + "verify_with_2fa": "Verifica con Cake 2FA",
718 + "totp_code": "Codice TOTP",
719 + "please_fill_totp": "Inserisci il codice di 8 cifre presente sull'altro tuo dispositivo",
720 + "totp_2fa_success": "Successo! Cake 2FA abilitato per questo portafoglio. Ricordati di salvare il tuo seme mnemonico nel caso in cui perdi l'accesso al portafoglio.",
721 + "totp_verification_success" :"Verifica riuscita!",
722 + "totp_2fa_failure": "Codice non corretto. Prova un codice diverso o genera una nuova chiave segreta. Utilizza un'app 2FA compatibile che supporti codici a 8 cifre e SHA512.",
723 + "enter_totp_code": "Inserisci il codice TOTP.",
724 + "add_secret_code":"Aggiungi questo codice segreto a un altro dispositivo",
725 + "totp_secret_code":"TOTP codice segreto",
726 + "important_note": "Nota importante",
727 + "setup_2fa_text": "Cake 2FA NON è sicuro come la cella frigorifera. 2FA protegge da tipi di attacchi di base, come il tuo amico che fornisce la tua impronta digitale mentre dormi.\n\n Cake 2FA NON protegge da un dispositivo compromesso da un aggressore sofisticato.\n\n Se perdi l'accesso ai tuoi codici 2FA , PERDERAI L'ACCESSO A QUESTO PORTAFOGLIO. Dovrai ripristinare il tuo portafoglio dal seme mnemonico. DOVETE QUINDI SOSTITUIRE I VOSTRI SEMI MNEMONICI! Inoltre, qualcuno con accesso ai tuoi seed mnemonici sarà in grado di rubare i tuoi fondi, aggirando Cake 2FA.\n\n Il personale di supporto di Cake non sarà in grado di aiutarti se perdi l'accesso al tuo seed mnemonico, poiché Cake è un portafoglio non detentivo.",
728 + "setup_totp_recommended": "Imposta TOTP (consigliato)",
729 "disable_buy": "Disabilita l'azione di acquisto",
730 "disable_sell": "Disabilita l'azione di vendita"
731 }
res/values/strings_ja.arb
+18 -1
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "金額は最大値を超えています",
710 "show_market_place":"マーケットプレイスを表示",
711 "prevent_screenshots": "スクリーンショットと画面録画を防止する",
712 + "modify_2fa": "ケーキの 2FA を変更する",
713 + "disable_cake_2fa": "Cake 2FA を無効にする",
714 + "question_to_disable_2fa":"Cake 2FA を無効にしてもよろしいですか?ウォレットと特定の機能にアクセスするために 2FA コードは必要なくなります。",
715 + "disable": "無効にする",
716 + "setup_2fa": "セットアップ ケーキ 2FA",
717 + "verify_with_2fa": "Cake 2FA で検証する",
718 + "totp_code": "TOTP コード",
719 + "please_fill_totp": "他のデバイスにある 8 桁のコードを入力してください",
720 + "totp_2fa_success": "成功!このウォレットでは Cake 2FA が有効になっています。ウォレットへのアクセスを失った場合に備えて、ニーモニック シードを忘れずに保存してください。",
721 + "totp_verification_success" :"検証成功!",
722 + "totp_2fa_failure": "コードが正しくありません。 別のコードを試すか、新しい秘密鍵を生成してください。 8 桁のコードと SHA512 をサポートする互換性のある 2FA アプリを使用してください。",
723 + "enter_totp_code": "TOTPコードを入力してください。",
724 + "add_secret_code":"このシークレット コードを別のデバイスに追加する",
725 + "totp_secret_code":"TOTPシークレットコード",
726 + "important_note": "重要な注意点",
727 + "setup_2fa_text": "Cake 2FA は、コールド ストレージほど安全ではありません。 2FA は、あなたが寝ているときに友人が指紋を提供するなどの基本的なタイプの攻撃から保護します。\n\n Cake 2FA は、巧妙な攻撃者による侵害されたデバイスから保護しません。\n\n 2FA コードにアクセスできなくなった場合、このウォレットにアクセスできなくなります。ニーモニック シードからウォレットを復元する必要があります。したがって、ニーモニック シードをバックアップする必要があります。さらに、あなたのニーモニック シードにアクセスできる誰かが、Cake 2FA をバイパスして、あなたの資金を盗むことができます。\n\n Cake は無印の財布。",
728 + "setup_totp_recommended": "TOTP を設定する (推奨)",
729 "disable_buy": "購入アクションを無効にする",
730 "disable_sell": "販売アクションを無効にする"
714 -}
731 +}
\ No newline at end of file
res/values/strings_ko.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "금액이 최대 값보다 많습니다.",
710 "show_market_place":"마켓플레이스 표시",
711 "prevent_screenshots": "스크린샷 및 화면 녹화 방지",
712 + "modify_2fa": "수정 케이크 2FA",
713 + "disable_cake_2fa": "케이크 2FA 비활성화",
714 + "question_to_disable_2fa":"Cake 2FA를 비활성화하시겠습니까? 지갑 및 특정 기능에 액세스하는 데 더 이상 2FA 코드가 필요하지 않습니다.",
715 + "disable": "장애를 입히다",
716 + "setup_2fa": "케이크 2FA 설정",
717 + "verify_with_2fa": "케이크 2FA로 확인",
718 + "totp_code": "TOTP 코드",
719 + "please_fill_totp": "다른 기기에 있는 8자리 코드를 입력하세요.",
720 + "totp_2fa_success": "성공! 이 지갑에 케이크 2FA가 활성화되었습니다. 지갑 액세스 권한을 잃을 경우를 대비하여 니모닉 시드를 저장하는 것을 잊지 마십시오.",
721 + "totp_verification_success" :"확인 성공!",
722 + "totp_2fa_failure": "잘못된 코드입니다. 다른 코드를 시도하거나 새 비밀 키를 생성하십시오. 8자리 코드와 SHA512를 지원하는 호환되는 2FA 앱을 사용하세요.",
723 + "enter_totp_code": "TOTP 코드를 입력하세요.",
724 + "add_secret_code":"이 비밀 코드를 다른 장치에 추가",
725 + "totp_secret_code":"TOTP 비밀 코드",
726 + "important_note": "중요 사항",
727 + "setup_2fa_text": "케이크 2FA는 냉장 보관만큼 안전하지 않습니다. 2FA는 당신이 잠자는 동안 친구가 지문을 제공하는 것과 같은 기본적인 유형의 공격으로부터 보호합니다.\n\n Cake 2FA는 정교한 공격자에 의해 손상된 장치로부터 보호하지 않습니다.\n\n 2FA 코드에 대한 액세스 권한을 잃으면 , 이 지갑에 대한 액세스 권한을 잃게 됩니다. 니모닉 시드에서 지갑을 복원해야 합니다. 따라서 니모닉 시드를 백업해야 합니다! 또한 니모닉 시드에 액세스할 수 있는 사람이 Cake 2FA를 우회하여 자금을 훔칠 수 있습니다.\n\n 니모닉 시드에 대한 액세스 권한을 잃으면 Cake 지원 직원이 도와줄 수 없습니다. 비수탁 지갑.",
728 + "setup_totp_recommended": "TOTP 설정(권장)",
729 "disable_buy": "구매 행동 비활성화",
730 "disable_sell": "판매 조치 비활성화"
731 }
res/values/strings_my.arb
+29
@@ -709,6 +709,35 @@
709 "error_text_input_above_maximum_limit" : "ပမာဏသည် အများဆုံးထက် ပိုများသည်။",
710 "show_market_place":"စျေးကွက်ကိုပြသပါ။",
711 "prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။",
712 + "modify_2fa": "ကိတ်မုန့် 2FA ကို ပြင်ဆင်ပါ။",
713 + "disable_cake_2fa": "ကိတ်မုန့် 2FA ကို ပိတ်ပါ။",
714 + "question_to_disable_2fa":"Cake 2FA ကို ပိတ်လိုသည်မှာ သေချာပါသလား။ ပိုက်ဆံအိတ်နှင့် အချို့သောလုပ်ဆောင်ချက်များကို အသုံးပြုရန်အတွက် 2FA ကုဒ်တစ်ခု မလိုအပ်တော့ပါ။",
715 + "disable": "ပိတ်ပါ။",
716 + "setup_2fa": "ကိတ်မုန့် 2FA စနစ်ထည့်သွင်းပါ။",
717 + "verify_with_2fa": "Cake 2FA ဖြင့် စစ်ဆေးပါ။",
718 + "totp_code": "TOTP ကုဒ်",
719 + "please_fill_totp": "သင့်အခြားစက်တွင်ရှိသော ဂဏန်း ၈ လုံးကုဒ်ကို ကျေးဇူးပြု၍ ဖြည့်ပါ။",
720 + "totp_2fa_success": "အောင်မြင် ဤပိုက်ဆံအိတ်အတွက် ကိတ်မုန့် 2FA ကို ဖွင့်ထားသည်။ ပိုက်ဆံအိတ်ဝင်ရောက်ခွင့်ဆုံးရှုံးသွားသောအခါတွင် သင်၏ mnemonic မျိုးစေ့များကို သိမ်းဆည်းရန် မမေ့ပါနှင့်။",
721 + "totp_verification_success" :"အတည်ပြုခြင်း အောင်မြင်ပါသည်။",
722 + "totp_2fa_failure": "ကုဒ်မမှန်ပါ။ ကျေးဇူးပြု၍ အခြားကုဒ်တစ်ခုကို စမ်းကြည့်ပါ သို့မဟုတ် လျှို့ဝှက်သော့အသစ်တစ်ခု ဖန်တီးပါ။ ဂဏန်း ၈ လုံးကုဒ်များနှင့် SHA512 ကို ပံ့ပိုးပေးသည့် တွဲဖက်အသုံးပြုနိုင်သော 2FA အက်ပ်ကို အသုံးပြုပါ။",
723 + "enter_totp_code": "ကျေးဇူးပြု၍ TOTP ကုဒ်ကို ထည့်ပါ။",
724 + "add_secret_code":"ဤလျှို့ဝှက်ကုဒ်ကို အခြားစက်ပစ္စည်းသို့ ထည့်ပါ။",
725 + "totp_secret_code":"TOTP လျှို့ဝှက်ကုဒ်",
726 + "important_note": "အရေးကြီးသောမှတ်ချက်",
727 + "setup_2fa_text": "ကိတ်မုန့် 2FA သည် အအေးခန်းကဲ့သို့ မလုံခြုံပါ။ 2FA သည် သင်အိပ်နေစဉ်တွင် သင့်သူငယ်ချင်းသည် သင့်လက်ဗွေရာကို ပေးဆောင်ခြင်းကဲ့သို့သော အခြေခံတိုက်ခိုက်မှုအမျိုးအစားများကို ကာကွယ်ပေးပါသည်။\n\n Cake 2FA သည် ခေတ်မီဆန်းပြားသော တိုက်ခိုက်သူ၏ အန္တရာယ်ပြုသည့်စက်ပစ္စည်းကို မကာကွယ်ပါ။\n\n သင်၏ 2FA ကုဒ်များကို အသုံးပြုခွင့်ဆုံးရှုံးသွားပါက၊ ဤပိုက်ဆံအိတ်ကို သင်ဝင်ရောက်ခွင့်ဆုံးရှုံးလိမ့်မည်။ သင့်ပိုက်ဆံအိတ်ကို mnemonic မျိုးစေ့မှ ပြန်လည်ရယူရန် လိုအပ်မည်ဖြစ်သည်။ ထို့ကြောင့် သင်၏ MNEMONIC မျိုးစေ့များကို အရန်သိမ်းထားရပါမည်။ ထို့အပြင်၊ သင်၏ mnemonic မျိုးစေ့(များ) ကို အသုံးပြုခွင့်ရှိသူတစ်ဦးက Cake 2FA ကိုကျော်ဖြတ်ကာ သင့်ရန်ပုံငွေများကို ခိုးယူနိုင်ပါမည်။\n\n ကိတ်မုန့်သည် သင့် mnemonic မျိုးစေ့သို့ ဝင်ရောက်ခွင့်ဆုံးရှုံးသွားပါက သင့်အား ကူညီပေးနိုင်မည်မဟုတ်ပါ၊ အထိန်းအချုပ်မရှိသော ပိုက်ဆံအိတ်။",
728 + "setup_totp_recommended": "TOTP ကို ​​စနစ်ထည့်သွင်းပါ (အကြံပြုထားသည်)",
729 "disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
730 "disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။"
731 }
732 +
733 +
734 +
735 +
736 +
737 +
738 +
739 +
740 +
741 +
742 +
743 +
res/values/strings_nl.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "Bedrag is meer dan maximaal",
710 "show_market_place":"Toon Marktplaats",
711 "prevent_screenshots": "Voorkom screenshots en schermopname",
712 + "modify_2fa": "Wijzig Cake 2FA",
713 + "disable_cake_2fa": "Taart 2FA uitschakelen",
714 + "question_to_disable_2fa":"Weet je zeker dat je Cake 2FA wilt uitschakelen? Er is geen 2FA-code meer nodig om toegang te krijgen tot de portemonnee en bepaalde functies.",
715 + "disable": "Uitzetten",
716 + "setup_2fa": "Opstelling Taart 2FA",
717 + "verify_with_2fa": "Controleer met Cake 2FA",
718 + "totp_code": "TOTP-code",
719 + "please_fill_totp": "Vul de 8-cijferige code in die op uw andere apparaat aanwezig is",
720 + "totp_2fa_success": "Succes! Cake 2FA ingeschakeld voor deze portemonnee. Vergeet niet om uw geheugensteuntje op te slaan voor het geval u de toegang tot de portemonnee kwijtraakt.",
721 + "totp_verification_success" :"Verificatie geslaagd!",
722 + "totp_2fa_failure": "Foute code. Probeer een andere code of genereer een nieuwe geheime sleutel. Gebruik een compatibele 2FA-app die 8-cijferige codes en SHA512 ondersteunt.",
723 + "enter_totp_code": "Voer de TOTP-code in.",
724 + "add_secret_code":"Voeg deze geheime code toe aan een ander apparaat",
725 + "totp_secret_code":"TOTP-geheime code",
726 + "important_note": "Belangrijke notitie",
727 + "setup_2fa_text": "Cake 2FA is NIET zo veilig als koude opslag. 2FA beschermt tegen basistypen aanvallen, zoals uw vriend die uw vingerafdruk geeft terwijl u slaapt.\n\n Cake 2FA biedt GEEN bescherming tegen een gecompromitteerd apparaat door een geavanceerde aanvaller.\n\n Als u de toegang tot uw 2FA-codes kwijtraakt , VERLIEST U DE TOEGANG TOT DEZE PORTEFEUILLE. U moet uw portemonnee herstellen van mnemonic seed. JE MOET DAAROM EEN BACK-UP MAKEN VAN JE MNEMONISCHE ZADEN! Verder kan iemand met toegang tot je geheugensteuntje(s) je geld stelen, waarbij Cake 2FA wordt omzeild.\n\n Het ondersteunend personeel van Cake kan je niet helpen als je de toegang tot je geheugensteuntje kwijtraakt, aangezien Cake een niet-bewaarbare portemonnee.",
728 + "setup_totp_recommended": "TOTP instellen (aanbevolen)",
729 "disable_buy": "Koopactie uitschakelen",
730 "disable_sell": "Verkoopactie uitschakelen"
731 }
res/values/strings_pl.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "Kwota jest większa niż maksymalna",
710 "show_market_place" : "Pokaż rynek",
711 "prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu",
712 + "modify_2fa": "Zmodyfikuj ciasto 2FA",
713 + "disable_cake_2fa": "Wyłącz Cake 2FA",
714 + "question_to_disable_2fa":"Czy na pewno chcesz wyłączyć Cake 2FA? Kod 2FA nie będzie już potrzebny do uzyskania dostępu do portfela i niektórych funkcji.",
715 + "disable": "Wyłączyć",
716 + "setup_2fa": "Skonfiguruj ciasto 2FA",
717 + "verify_with_2fa": "Sprawdź za pomocą Cake 2FA",
718 + "totp_code": "Kod TOTP",
719 + "please_fill_totp": "Wpisz 8-cyfrowy kod znajdujący się na drugim urządzeniu",
720 + "totp_2fa_success": "Powodzenie! Cake 2FA włączony dla tego portfela. Pamiętaj, aby zapisać swoje mnemoniczne ziarno na wypadek utraty dostępu do portfela.",
721 + "totp_verification_success" :"Weryfikacja powiodła się!",
722 + "totp_2fa_failure": "Błędny kod. Spróbuj użyć innego kodu lub wygeneruj nowy tajny klucz. Użyj kompatybilnej aplikacji 2FA, która obsługuje 8-cyfrowe kody i SHA512.",
723 + "enter_totp_code": "Wprowadź kod TOTP.",
724 + "add_secret_code":"Dodaj ten tajny kod do innego urządzenia",
725 + "totp_secret_code":"Tajny kod TOTP",
726 + "important_note": "Ważna uwaga",
727 + "setup_2fa_text": "Cake 2FA NIE jest tak bezpieczny jak przechowywanie w chłodni. 2FA chroni przed podstawowymi typami ataków, takimi jak udostępnienie odcisku palca przez znajomego podczas snu.\n\n Cake 2FA NIE chroni przed zhakowanym urządzeniem przez wyrafinowanego atakującego.\n\n Jeśli utracisz dostęp do swoich kodów 2FA , UTRACISZ DOSTĘP DO TEGO PORTFELA. Będziesz musiał przywrócić swój portfel z mnemonicznego materiału siewnego. DLATEGO MUSISZ ZROBIĆ KOPIĘ SWOICH NASION MNEMONICZNYCH! Co więcej, ktoś z dostępem do twoich mnemonicznych nasion będzie mógł ukraść twoje fundusze, omijając Cake 2FA.\n\n Personel pomocniczy Cake nie będzie mógł ci pomóc, jeśli stracisz dostęp do swojego mnemonicznego seeda, ponieważ Cake jest portfel niezabezpieczony.",
728 + "setup_totp_recommended": "Skonfiguruj TOTP (zalecane)",
729 "disable_buy": "Wyłącz akcję kupna",
730 "disable_sell": "Wyłącz akcję sprzedaży"
731 }
res/values/strings_pt.arb
+17
@@ -708,6 +708,23 @@
708 "error_text_input_above_maximum_limit" : "O valor é superior ao máximo",
709 "show_market_place":"Mostrar mercado",
710 "prevent_screenshots": "Evite capturas de tela e gravação de tela",
711 + "modify_2fa": "Modificar Bolo 2FA",
712 + "disable_cake_2fa": "Desabilitar Bolo 2FA",
713 + "question_to_disable_2fa":"Tem certeza de que deseja desativar o Cake 2FA? Um código 2FA não será mais necessário para acessar a carteira e certas funções.",
714 + "disable": "Desativar",
715 + "setup_2fa": "Bolo de Configuração 2FA",
716 + "verify_with_2fa": "Verificar com Cake 2FA",
717 + "totp_code": "Código TOTP",
718 + "please_fill_totp": "Por favor, preencha o código de 8 dígitos presente em seu outro dispositivo",
719 + "totp_2fa_success": "Sucesso! Cake 2FA ativado para esta carteira. Lembre-se de salvar sua semente mnemônica caso perca o acesso à carteira.",
720 + "totp_verification_success" :"Verificação bem-sucedida!",
721 + "totp_2fa_failure": "Código incorreto. Tente um código diferente ou gere uma nova chave secreta. Use um aplicativo 2FA compatível com códigos de 8 dígitos e SHA512.",
722 + "enter_totp_code": "Digite o código TOTP.",
723 + "add_secret_code":"Adicione este código secreto a outro dispositivo",
724 + "totp_secret_code":"Código Secreto TOTP",
725 + "important_note": "Nota importante",
726 + "setup_2fa_text": "O Cake 2FA NÃO é tão seguro quanto o armazenamento a frio. O 2FA protege contra tipos básicos de ataques, como seu amigo fornecer sua impressão digital enquanto você está dormindo.\n\n O Cake 2FA NÃO protege contra um dispositivo comprometido por um invasor sofisticado.\n\n Se você perder o acesso aos seus códigos 2FA , VOCÊ PERDERÁ O ACESSO A ESTA CARTEIRA. Você precisará restaurar sua carteira da semente mnemônica. VOCÊ DEVE, PORTANTO, FAZER BACKUP DE SUAS SEMENTES MNEMÔNICAS! Além disso, alguém com acesso às suas sementes mnemônicas poderá roubar seus fundos, ignorando o Cake 2FA.\n\n A equipe de suporte do Cake não poderá ajudá-lo se você perder o acesso à sua semente mnemônica, pois o Cake é um carteira não custodial.",
727 + "setup_totp_recommended": "Configurar TOTP (recomendado)",
728 "disable_buy": "Desativar ação de compra",
729 "disable_sell": "Desativar ação de venda"
730 }
res/values/strings_ru.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "Сумма больше максимальной",
710 "show_market_place":"Показать торговую площадку",
711 "prevent_screenshots": "Предотвратить скриншоты и запись экрана",
712 + "modify_2fa": "Изменить торт 2FA",
713 + "disable_cake_2fa": "Отключить торт 2FA",
714 + "question_to_disable_2fa":"Вы уверены, что хотите отключить Cake 2FA? Код 2FA больше не потребуется для доступа к кошельку и некоторым функциям.",
715 + "disable": "Запрещать",
716 + "setup_2fa": "Настройка торта 2FA",
717 + "verify_with_2fa": "Подтвердить с помощью Cake 2FA",
718 + "totp_code": "TOTP-код",
719 + "please_fill_totp": "Пожалуйста, введите 8-значный код на другом устройстве",
720 + "totp_2fa_success": "Успех! Для этого кошелька включена двухфакторная аутентификация Cake. Не забудьте сохранить мнемоническое семя на случай, если вы потеряете доступ к кошельку.",
721 + "totp_verification_success" :"Проверка прошла успешно!",
722 + "totp_2fa_failure": "Неверный код. Пожалуйста, попробуйте другой код или создайте новый секретный ключ. Используйте совместимое приложение 2FA, которое поддерживает 8-значные коды и SHA512.",
723 + "enter_totp_code": "Пожалуйста, введите TOTP-код.",
724 + "add_secret_code":"Добавьте этот секретный код на другое устройство",
725 + "totp_secret_code":"Секретный код ТОТП",
726 + "important_note": "Важная заметка",
727 + "setup_2fa_text": "Cake 2FA НЕ так безопасен, как холодное хранилище. Двухфакторная аутентификация защищает от основных типов атак, таких как отпечаток вашего друга, когда вы спите.\n\n Двухфакторная аутентификация Cake НЕ защищает от взлома устройства опытным злоумышленником.\n\n Если вы потеряете доступ к своим кодам двухфакторной аутентификации. , ВЫ ПОТЕРЯЕТЕ ДОСТУП К ЭТОМУ КОШЕЛЬКУ. Вам нужно будет восстановить свой кошелек из мнемонической семени. ПОЭТОМУ ВЫ ДОЛЖНЫ СОЗДАТЬ РЕЗЕРВНУЮ ВЕРСИЮ СВОИХ МНЕМОНИКОВ! Кроме того, кто-то, имеющий доступ к вашему мнемоническому семени, сможет украсть ваши средства, минуя Cake 2FA.\n\n Персонал службы поддержки Cake не сможет помочь вам, если вы потеряете доступ к своему мнемоническому семени, поскольку Cake — это некастодиальный кошелек.",
728 + "setup_totp_recommended": "Настроить TOTP (рекомендуется)",
729 "disable_buy": "Отключить действие покупки",
730 "disable_sell": "Отключить действие продажи"
731 }
res/values/strings_th.arb
+17
@@ -707,6 +707,23 @@
707 "error_text_input_above_maximum_limit" : "จำนวนเงินสูงกว่าค่าสูงสุด",
708 "show_market_place":"แสดงตลาดกลาง",
709 "prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ",
710 + "modify_2fa": "แก้ไขเค้ก 2FA",
711 + "disable_cake_2fa": "ปิดการใช้งานเค้ก 2FA",
712 + "question_to_disable_2fa":"คุณแน่ใจหรือไม่ว่าต้องการปิดการใช้งาน Cake 2FA ไม่จำเป็นต้องใช้รหัส 2FA ในการเข้าถึงกระเป๋าเงินและฟังก์ชั่นบางอย่างอีกต่อไป",
713 + "disable": "ปิดการใช้งาน",
714 + "setup_2fa": "ตั้งค่าเค้ก 2FA",
715 + "verify_with_2fa": "ตรวจสอบกับ Cake 2FA",
716 + "totp_code": "รหัสทีโอพี",
717 + "please_fill_totp": "กรุณากรอกรหัส 8 หลักที่อยู่ในอุปกรณ์อื่นของคุณ",
718 + "totp_2fa_success": "ความสำเร็จ! Cake 2FA เปิดใช้งานสำหรับกระเป๋าเงินนี้ อย่าลืมบันทึกเมล็ดช่วยจำของคุณในกรณีที่คุณสูญเสียการเข้าถึงกระเป๋าเงิน",
719 + "totp_verification_success" :"การยืนยันสำเร็จ!",
720 + "totp_2fa_failure": "รหัสไม่ถูกต้อง. โปรดลองใช้รหัสอื่นหรือสร้างรหัสลับใหม่ ใช้แอพ 2FA ที่เข้ากันได้ซึ่งรองรับรหัส 8 หลักและ SHA512",
721 + "enter_totp_code": "กรุณาใส่รหัสทีโอที",
722 + "add_secret_code":"เพิ่มรหัสลับนี้ไปยังอุปกรณ์อื่น",
723 + "totp_secret_code":"รหัสลับ TOTP",
724 + "important_note": "โน๊ตสำคัญ",
725 + "setup_2fa_text": "Cake 2FA ไม่ปลอดภัยเท่าห้องเย็น 2FA ป้องกันการโจมตีประเภทพื้นฐาน เช่น เพื่อนของคุณให้ลายนิ้วมือขณะที่คุณนอนหลับ\n\n Cake 2FA ไม่ป้องกันอุปกรณ์ที่ถูกบุกรุกโดยผู้โจมตีที่เชี่ยวชาญ\n\n หากคุณสูญเสียการเข้าถึงรหัส 2FA ของคุณ คุณจะสูญเสียการเข้าถึงกระเป๋าเงินนี้ คุณจะต้องกู้คืนกระเป๋าเงินของคุณจากเมล็ดช่วยจำ คุณต้องสำรองเมล็ดความจำของคุณ! นอกจากนี้ ผู้ที่สามารถเข้าถึงเมล็ดช่วยจำของคุณจะสามารถขโมยเงินของคุณ โดยผ่าน Cake 2FA\n\n เจ้าหน้าที่ช่วยเหลือของ Cake จะไม่สามารถช่วยเหลือคุณได้ หากคุณสูญเสียการเข้าถึงเมล็ดช่วยจำ เนื่องจาก Cake เป็น กระเป๋าสตางค์ที่ไม่เป็นผู้ดูแล",
726 + "setup_totp_recommended": "ตั้งค่า TOTP (แนะนำ)",
727 "disable_buy": "ปิดการใช้งานการซื้อ",
728 "disable_sell": "ปิดการใช้งานการขาย"
729 }
res/values/strings_tr.arb
+17
@@ -709,6 +709,23 @@
709 "error_text_input_above_maximum_limit" : "Miktar maksimumdan daha fazla",
710 "show_market_place":"Pazar Yerini Göster",
711 "prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin",
712 + "modify_2fa": "Cake 2FA'yı Değiştirin",
713 + "disable_cake_2fa": "Cake 2FA'yı Devre Dışı Bırak",
714 + "question_to_disable_2fa":"Cake 2FA'yı devre dışı bırakmak istediğinizden emin misiniz? M-cüzdana ve belirli işlevlere erişmek için artık 2FA koduna gerek kalmayacak.",
715 + "disable": "Devre dışı bırakmak",
716 + "setup_2fa": "Kurulum Pastası 2FA",
717 + "verify_with_2fa": "Cake 2FA ile Doğrulayın",
718 + "totp_code": "TOTP Kodu",
719 + "please_fill_totp": "Lütfen diğer cihazınızda bulunan 8 haneli kodu girin",
720 + "totp_2fa_success": "Başarı! Bu cüzdan için Cake 2FA etkinleştirildi. Mnemonic seed'inizi cüzdan erişiminizi kaybetme ihtimaline karşı kaydetmeyi unutmayın.",
721 + "totp_verification_success" :"Doğrulama Başarılı!",
722 + "totp_2fa_failure": "Yanlış kod. Lütfen farklı bir kod deneyin veya yeni bir gizli anahtar oluşturun. 8 basamaklı kodları ve SHA512'yi destekleyen uyumlu bir 2FA uygulaması kullanın.",
723 + "enter_totp_code": "Lütfen TOTP Kodunu giriniz.",
724 + "add_secret_code":"Bu gizli kodu başka bir cihaza ekleyin",
725 + "totp_secret_code":"TOTP Gizli Kodu",
726 + "important_note": "Önemli Not",
727 + "setup_2fa_text": "Cake 2FA, soğuk hava deposu kadar güvenli DEĞİLDİR. 2FA, siz uyurken arkadaşınızın parmak izinizi sağlaması gibi temel saldırı türlerine karşı koruma sağlar.\n\n Cake 2FA, gelişmiş bir saldırgan tarafından güvenliği ihlal edilmiş bir cihaza karşı koruma SAĞLAMAZ.\n\n 2FA kodlarınıza erişimi kaybederseniz , BU CÜZDANA ERİŞİMİNİZİ KAYBEDECEKSİNİZ. Mnemonic seed'den cüzdanınızı geri yüklemeniz gerekecek. BU NEDENLE HATIRLAYICI TOHUMLARINIZI YEDEKLEMELİSİNİZ! Ayrıca anımsatıcı tohumlarınıza erişimi olan biri, Cake 2FA'yı atlayarak paranızı çalabilir.\n\n Cake, anımsatıcı tohumlarınıza erişimi kaybederseniz size yardımcı olamaz, çünkü Cake bir saklama dışı cüzdan.",
728 + "setup_totp_recommended": "TOTP'yi kurun (Önerilir)",
729 "disable_buy": "Satın alma işlemini devre dışı bırak",
730 "disable_sell": "Satış işlemini devre dışı bırak"
731 }
res/values/strings_uk.arb
+17
@@ -708,6 +708,23 @@
708 "error_text_input_above_maximum_limit" : "Сума більше максимальної",
709 "show_market_place":"Відображати маркетплейс",
710 "prevent_screenshots": "Запобігати знімкам екрана та запису екрана",
711 + "modify_2fa": "Змінити торт 2FA",
712 + "disable_cake_2fa": "Вимкнути Cake 2FA",
713 + "question_to_disable_2fa":"Ви впевнені, що хочете вимкнути Cake 2FA? Код 2FA більше не потрібен для доступу до гаманця та певних функцій.",
714 + "disable": "Вимкнути",
715 + "setup_2fa": "Налаштування Cake 2FA",
716 + "verify_with_2fa": "Перевірте за допомогою Cake 2FA",
717 + "totp_code": "Код TOTP",
718 + "please_fill_totp": "Будь ласка, введіть 8-значний код, наявний на вашому іншому пристрої",
719 + "totp_2fa_success": "Успіх! Cake 2FA увімкнено для цього гаманця. Пам’ятайте про збереження мнемоніки на випадок, якщо ви втратите доступ до гаманця.",
720 + "totp_verification_success" :"Перевірка успішна!",
721 + "totp_2fa_failure": "Невірний код. Спробуйте інший код або створіть новий секретний ключ. Використовуйте сумісний додаток 2FA, який підтримує 8-значні коди та SHA512.",
722 + "enter_totp_code": "Будь ласка, введіть код TOTP.",
723 + "add_secret_code":"Додайте цей секретний код на інший пристрій",
724 + "totp_secret_code":"Секретний код TOTP",
725 + "important_note": "Важливе зауваження",
726 + "setup_2fa_text": "Торт 2FA НЕ такий безпечний, як холодне зберігання. 2FA захищає від основних типів атак, наприклад ваш друг надає ваш відбиток пальця, поки ви спите.\n\n Cake 2FA НЕ захищає від скомпрометованого пристрою досвідченим зловмисником.\n\n Якщо ви втратите доступ до своїх кодів 2FA , ВИ ВТРАТИТЕ ДОСТУП ДО ЦЬОГО ГАМАНЦЯ. Вам потрібно буде відновити свій гаманець з мнемонічного коду. ТОМУ ВИ ПОВИННІ СТВОРИТИ РЕЗЕРВНУ КОПІЮ СВОЇХ МНЕМОНІЧНИХ НАСІН! Крім того, хтось із доступом до ваших мнемонічних початкових значень зможе викрасти ваші кошти, оминаючи Cake 2FA.\n\n Співробітники служби підтримки Cake не зможуть вам допомогти, якщо ви втратите доступ до своїх мнемонічних вихідних даних, оскільки Cake є гаманець без опіки.",
727 + "setup_totp_recommended": "Налаштувати TOTP (рекомендовано)",
728 "disable_buy": "Вимкнути дію покупки",
729 "disable_sell": "Вимкнути дію продажу"
730 }
res/values/strings_ur.arb
+17
@@ -704,6 +704,23 @@
704 "error_text_input_above_maximum_limit" : "رقم زیادہ سے زیادہ سے زیادہ ہے۔",
705 "show_market_place":"بازار دکھائیں۔",
706 "prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔",
707 + "modify_2fa": "کیک 2FA میں ترمیم کریں۔",
708 + "disable_cake_2fa": "کیک 2FA کو غیر فعال کریں۔",
709 + "question_to_disable_2fa":"کیا آپ واقعی کیک 2FA کو غیر فعال کرنا چاہتے ہیں؟ بٹوے اور بعض افعال تک رسائی کے لیے اب 2FA کوڈ کی ضرورت نہیں ہوگی۔",
710 + "disable": "غیر فعال کریں۔",
711 + "setup_2fa": "سیٹ اپ کیک 2FA",
712 + "verify_with_2fa": "کیک 2FA سے تصدیق کریں۔",
713 + "totp_code": "TOTP کوڈ",
714 + "please_fill_totp": "براہ کرم اپنے دوسرے آلے پر موجود 8 ہندسوں کا کوڈ پُر کریں۔",
715 + "totp_2fa_success": "کامیابی! کیک 2FA اس بٹوے کے لیے فعال ہے۔ بٹوے تک رسائی سے محروم ہونے کی صورت میں اپنے یادداشت کے بیج کو محفوظ کرنا یاد رکھیں۔",
716 + "totp_verification_success" :"توثیق کامیاب!",
717 + "totp_2fa_failure": "غلط کوڈ. براہ کرم ایک مختلف کوڈ آزمائیں یا ایک نئی خفیہ کلید بنائیں۔ ایک ہم آہنگ 2FA ایپ استعمال کریں جو 8 ہندسوں کے کوڈز اور SHA512 کو سپورٹ کرتی ہو۔",
718 + "enter_totp_code": "براہ کرم TOTP کوڈ درج کریں۔",
719 + "add_secret_code":"اس خفیہ کوڈ کو کسی اور ڈیوائس میں شامل کریں۔",
720 + "totp_secret_code":"TOTP خفیہ کوڈ",
721 + "important_note": "اہم نوٹ",
722 + "setup_2fa_text": "کیک 2FA کولڈ اسٹوریج کی طرح محفوظ نہیں ہے۔ 2FA بنیادی قسم کے حملوں سے حفاظت کرتا ہے، جیسے کہ آپ کا دوست آپ کے سوتے وقت آپ کے فنگر پرنٹ فراہم کرتا ہے۔\n\n کیک 2FA کسی جدید حملہ آور کے ذریعے سمجھوتہ کرنے والے آلہ سے حفاظت نہیں کرتا ہے۔\n\n اگر آپ اپنے 2FA کوڈز تک رسائی کھو دیتے ہیں ، آپ اس بٹوے تک رسائی سے محروم ہو جائیں گے۔ آپ کو یادداشت کے بیج سے اپنے بٹوے کو بحال کرنے کی ضرورت ہوگی۔ اس لیے آپ کو اپنے یادداشت کے بیجوں کا بیک اپ لینا چاہیے! اس کے علاوہ، آپ کے یادداشت کے بیج تک رسائی رکھنے والا کوئی شخص کیک 2FA کو نظرانداز کرتے ہوئے آپ کے فنڈز چرا سکے گا۔\n\n اگر آپ اپنے یادداشت کے بیج تک رسائی کھو دیتے ہیں تو کیک کا معاون عملہ آپ کی مدد نہیں کر سکے گا، کیونکہ کیک ایک ہے غیر نگہداشت پرس.",
723 + "setup_totp_recommended": "TOTP ترتیب دیں (تجویز کردہ)",
724 "disable_buy": "خرید ایکشن کو غیر فعال کریں۔",
725 "disable_sell": "فروخت کی کارروائی کو غیر فعال کریں۔"
726 }
res/values/strings_zh.arb
+17
@@ -708,6 +708,23 @@
708 "error_text_input_above_maximum_limit" : "金额大于最大值",
709 "show_market_place" :"显示市场",
710 "prevent_screenshots": "防止截屏和录屏",
711 + "modify_2fa": "修改蛋糕2FA",
712 + "disable_cake_2fa": "禁用蛋糕 2FA",
713 + "question_to_disable_2fa":"您确定要禁用 Cake 2FA 吗?访问钱包和某些功能将不再需要 2FA 代码。",
714 + "disable": "停用",
715 + "setup_2fa": "设置蛋糕 2FA",
716 + "verify_with_2fa": "用 Cake 2FA 验证",
717 + "totp_code": "TOTP代码",
718 + "please_fill_totp": "请填写您其他设备上的 8 位代码",
719 + "totp_2fa_success": "成功!为此钱包启用了 Cake 2FA。请记住保存您的助记词种子,以防您无法访问钱包。",
720 + "totp_verification_success" :"验证成功!",
721 + "totp_2fa_failure": "不正确的代码。 请尝试不同的代码或生成新的密钥。 使用支持 8 位代码和 SHA512 的兼容 2FA 应用程序。",
722 + "enter_totp_code": "请输入 TOTP 代码。",
723 + "add_secret_code":"将此密码添加到另一台设备",
724 + "totp_secret_code":"TOTP密码",
725 + "important_note": "重要的提示",
726 + "setup_2fa_text": "Cake 2FA 不如冷藏安全。 2FA 可防止基本类型的攻击,例如您的朋友在您睡觉时提供您的指纹。\n\n Cake 2FA 无法防止老练的攻击者破坏设备。\n\n 如果您无法访问您的 2FA 代码, 您将无法访问此钱包。您将需要从助记词种子恢复您的钱包。因此,您必须备份您的助记词种子!此外,有权访问您的助记种子的人将能够绕过 Cake 2FA 窃取您的资金。\n\n 如果您无法访问您的助记种子,Cake 支持人员将无法帮助您,因为 Cake 是一个非托管钱包。",
727 + "setup_totp_recommended": "设置 TOTP(推荐)",
728 "disable_buy": "禁用购买操作",
729 "disable_sell": "禁用卖出操作"
730 }