From 818a8afe208418136cfa49cdf801be928d0a1d74 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo Date: Tue, 22 Nov 2022 22:52:28 +0200 Subject: [PATCH] [CW-225] Add pin timeout setting --- lib/core/auth_service.dart | 24 +++++++ lib/di.dart | 3 +- lib/entities/pin_code_required_duration.dart | 32 ++++++++++ lib/entities/preferences_key.dart | 3 + .../settings/security_backup_page.dart | 64 ++++++++++++------- .../screens/wallet_list/wallet_list_page.dart | 47 ++++++++++---- lib/store/settings_store.dart | 20 ++++-- lib/view_model/auth_view_model.dart | 12 +++- .../settings/settings_view_model.dart | 30 ++++++--- .../wallet_list/wallet_list_view_model.dart | 6 +- res/values/strings_de.arb | 5 +- res/values/strings_en.arb | 5 +- res/values/strings_es.arb | 5 +- res/values/strings_fr.arb | 5 +- res/values/strings_hi.arb | 5 +- res/values/strings_hr.arb | 5 +- res/values/strings_it.arb | 5 +- res/values/strings_ja.arb | 5 +- res/values/strings_ko.arb | 5 +- res/values/strings_nl.arb | 5 +- res/values/strings_pl.arb | 5 +- res/values/strings_pt.arb | 5 +- res/values/strings_ru.arb | 5 +- res/values/strings_uk.arb | 5 +- res/values/strings_zh.arb | 5 +- 25 files changed, 250 insertions(+), 66 deletions(-) create mode 100644 lib/entities/pin_code_required_duration.dart diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index 2ae37e2b0..6b167fa40 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -4,6 +4,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/encrypt.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/store/settings_store.dart'; class AuthService with Store { AuthService({required this.secureStorage, required this.sharedPreferences}); @@ -39,4 +41,26 @@ class AuthService with Store { return decodedPin == pin; } + + void saveLastAuthTime(){ + int timestamp = DateTime.now().millisecondsSinceEpoch; + sharedPreferences.setInt(PreferencesKey.lastAuthTimeMilliseconds, timestamp); + } + + bool requireAuth(){ + final timestamp = sharedPreferences.getInt(PreferencesKey.lastAuthTimeMilliseconds); + final duration = _durationToRequireAuth(timestamp ?? 0); + final requiredPinInterval = getIt.get().pinTimeOutDuration; + + return duration >= requiredPinInterval.value; + } + + int _durationToRequireAuth(int timestamp){ + + DateTime before = DateTime.fromMillisecondsSinceEpoch(timestamp); + DateTime now = DateTime.now(); + Duration timeDifference = now.difference(before); + + return timeDifference.inMinutes; + } } diff --git a/lib/di.dart b/lib/di.dart index 815a3740e..fe8032513 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -440,7 +440,8 @@ Future setup( getIt.registerFactory(() { final appStore = getIt.get(); final yatStore = getIt.get(); - return SettingsViewModel(appStore.settingsStore, yatStore, appStore.wallet!); + final authService = getIt.get(); + return SettingsViewModel(appStore.settingsStore, yatStore, authService, appStore.wallet!); }); getIt diff --git a/lib/entities/pin_code_required_duration.dart b/lib/entities/pin_code_required_duration.dart new file mode 100644 index 000000000..fef5715b5 --- /dev/null +++ b/lib/entities/pin_code_required_duration.dart @@ -0,0 +1,32 @@ +import 'package:cake_wallet/generated/i18n.dart'; + +enum PinCodeRequiredDuration { + always(0), + tenminutes(10), + onehour(60); + + const PinCodeRequiredDuration(this.value); + final int value; + + static PinCodeRequiredDuration deserialize({required int raw}) => + PinCodeRequiredDuration.values.firstWhere((e) => e.value == raw); + + @override + String toString(){ + String label = ''; + switch (this) { + case PinCodeRequiredDuration.always: + label = S.current.always; + break; + case PinCodeRequiredDuration.tenminutes: + label = S.current.minutes_to_pin_code('10'); + break; + case PinCodeRequiredDuration.onehour: + label = S.current.minutes_to_pin_code('60'); + break; + } + return label; + + } + +} \ No newline at end of file diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 6cf7e5608..36394d936 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -23,6 +23,9 @@ class PreferencesKey { static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; + static const pinTimeOutDuration = 'pin_timeout_duration'; + static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds'; + static String moneroWalletUpdateV1Key(String name) => '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index d950597f0..a3babb0bb 100644 --- a/lib/src/screens/settings/security_backup_page.dart +++ b/lib/src/screens/settings/security_backup_page.dart @@ -1,9 +1,11 @@ +import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; @@ -20,27 +22,28 @@ class SecurityBackupPage extends BasePage { @override Widget body(BuildContext context) { + return Container( padding: EdgeInsets.only(top: 10), child: Column(mainAxisSize: MainAxisSize.min, children: [ SettingsCellWithArrow( title: S.current.show_keys, - handler: (_) => Navigator.of(context).pushNamed(Routes.auth, + handler: (_) => settingsViewModel.checkPinCodeRiquired() ? Navigator.of(context).pushNamed(Routes.auth, arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { if (isAuthenticatedSuccessfully) { auth.close(route: Routes.showKeys); } - }), + }) : Navigator.of(context).pushNamed(Routes.showKeys), ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), SettingsCellWithArrow( title: S.current.create_backup, - handler: (_) => Navigator.of(context).pushNamed(Routes.auth, + handler: (_) => settingsViewModel.checkPinCodeRiquired() ? Navigator.of(context).pushNamed(Routes.auth, arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { if (isAuthenticatedSuccessfully) { auth.close(route: Routes.backup); } - }), + }) : Navigator.of(context).pushNamed(Routes.backup), ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), SettingsCellWithArrow( @@ -56,28 +59,41 @@ class SecurityBackupPage extends BasePage { })), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), Observer(builder: (_) { - return SettingsSwitcherCell( - title: S.current.settings_allow_biometrical_authentication, - value: settingsViewModel.allowBiometricalAuthentication, - onValueChange: (BuildContext context, bool value) { - if (value) { - Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (isAuthenticatedSuccessfully) { - if (await settingsViewModel.biometricAuthenticated()) { - settingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); - } - } else { - settingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); - } + return Column( + children: [ + SettingsSwitcherCell( + title: S.current.settings_allow_biometrical_authentication, + value: settingsViewModel.allowBiometricalAuthentication, + onValueChange: (BuildContext context, bool value) { + if (value) { + Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { + if (isAuthenticatedSuccessfully) { + if (await settingsViewModel.biometricAuthenticated()) { + settingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + } + } else { + settingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + } - auth.close(); - }); - } else { - settingsViewModel.setAllowBiometricalAuthentication(value); - } - }); + auth.close(); + }); + } else { + settingsViewModel.setAllowBiometricalAuthentication(value); + } + }), + SettingsPickerCell( + title: S.current.require_pin_after, + items: PinCodeRequiredDuration.values, + selectedItem: settingsViewModel.pinCodeRequiredDuration, + onItemSelected: (PinCodeRequiredDuration code) { + settingsViewModel.setPinCodeRequiredDuration(code); + }, + ), + ], + ); }), + ]), ); } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index c1a7ea953..5d9650ef2 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -220,7 +220,8 @@ class WalletListBodyState extends State { } Future _loadWallet(WalletListItem wallet) async { - await Navigator.of(context).pushNamed(Routes.auth, arguments: + if(await widget.walletListViewModel.checkIfAuthRequired()){ + await Navigator.of(context).pushNamed(Routes.auth, arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { if (!isAuthenticatedSuccessfully) { return; @@ -241,17 +242,36 @@ class WalletListBodyState extends State { .wallet_list_failed_to_load(wallet.name, e.toString())); } }); + }else{ + try { + changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); + await widget.walletListViewModel.loadWallet(wallet); + hideProgressText(); + Navigator.of(context).pop(); + } catch (e) { + changeProcessText(S + .of(context) + .wallet_list_failed_to_load(wallet.name, e.toString())); + } + } } Future _removeWallet(WalletListItem wallet) async { - await Navigator.of(context).pushNamed(Routes.auth, arguments: + if(widget.walletListViewModel.checkIfAuthRequired()){ + await Navigator.of(context).pushNamed(Routes.auth, arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { if (!isAuthenticatedSuccessfully) { return; } + _onSuccessfulAuth(wallet, auth); + }); + }else{ + _onSuccessfulAuth(wallet, null); + } + } - bool confirmed = false; - + _onSuccessfulAuth(WalletListItem wallet, AuthPageState? auth)async{ + bool confirmed = false; await showPopUp( context: context, builder: (BuildContext context) { @@ -270,18 +290,23 @@ class WalletListBodyState extends State { if (confirmed) { try { - auth.changeProcessText( - S.of(context).wallet_list_removing_wallet(wallet.name)); + auth != null ? + auth.changeProcessText( + S.of(context).wallet_list_removing_wallet(wallet.name)) + : changeProcessText( S.of(context).wallet_list_removing_wallet(wallet.name)); await widget.walletListViewModel.remove(wallet); } catch (e) { - auth.changeProcessText(S - .of(context) - .wallet_list_failed_to_remove(wallet.name, e.toString())); + auth != null ? + auth.changeProcessText( + S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()), + ) + : changeProcessText( + S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()), + ); } } - auth.close(); - }); + auth?.close(); } void changeProcessText(String text) { diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 2b3105a34..c6534bcea 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1,9 +1,9 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_list.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -17,7 +17,6 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cw_core/node.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/entities/action_list_display_mode.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; part 'settings_store.g.dart'; @@ -39,6 +38,7 @@ abstract class SettingsStoreBase with Store { required this.shouldShowYatPopup, required this.isBitcoinBuyEnabled, required this.actionlistDisplayMode, + required this.pinTimeOutDuration, TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialMoneroTransactionPriority}) : nodes = ObservableMap.of(nodes), @@ -108,6 +108,11 @@ abstract class SettingsStoreBase with Store { (String languageCode) => sharedPreferences.setString( PreferencesKey.currentLanguageCode, languageCode)); + reaction( + (_) => pinTimeOutDuration, + (PinCodeRequiredDuration pinCodeInterval) => sharedPreferences.setInt( + PreferencesKey.pinTimeOutDuration, pinCodeInterval.value)); + reaction( (_) => balanceDisplayMode, (BalanceDisplayMode mode) => sharedPreferences.setInt( @@ -124,6 +129,7 @@ abstract class SettingsStoreBase with Store { static const defaultPinLength = 4; static const defaultActionsMode = 11; + static const defaultPinCodeTimeOutDuration = 10; @observable FiatCurrency fiatCurrency; @@ -149,6 +155,9 @@ abstract class SettingsStoreBase with Store { @observable int pinCodeLength; + @observable + PinCodeRequiredDuration pinTimeOutDuration; + @computed ThemeData get theme => currentTheme.themeData; @@ -227,13 +236,15 @@ abstract class SettingsStoreBase with Store { : ThemeType.bright.index; final savedTheme = ThemeList.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentTheme) ?? - legacyTheme ?? - 0); + legacyTheme); final actionListDisplayMode = ObservableList(); actionListDisplayMode.addAll(deserializeActionlistDisplayModes( sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode)); var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength); + final pinCodeTimeOutDuration = PinCodeRequiredDuration.deserialize(raw: sharedPreferences.getInt(PreferencesKey.pinTimeOutDuration) + ?? defaultPinCodeTimeOutDuration); + // If no value if (pinLength == null || pinLength == 0) { pinLength = defaultPinLength; @@ -287,6 +298,7 @@ abstract class SettingsStoreBase with Store { initialTheme: savedTheme, actionlistDisplayMode: actionListDisplayMode, initialPinLength: pinLength, + pinTimeOutDuration: pinCodeTimeOutDuration, initialLanguageCode: savedLanguageCode, initialMoneroTransactionPriority: moneroTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority, diff --git a/lib/view_model/auth_view_model.dart b/lib/view_model/auth_view_model.dart index 29ef46b47..42201ddab 100644 --- a/lib/view_model/auth_view_model.dart +++ b/lib/view_model/auth_view_model.dart @@ -17,7 +17,9 @@ abstract class AuthViewModelBase with Store { AuthViewModelBase(this._authService, this._sharedPreferences, this._settingsStore, this._biometricAuth) : _failureCounter = 0, - state = InitialExecutionState(); + state = InitialExecutionState(){ + reaction((_) => state, _saveLastAuthTime); + } static const maxFailedLogins = 3; static const banTimeout = 180; // 3 minutes @@ -57,7 +59,7 @@ abstract class AuthViewModelBase with Store { if (isSuccessfulAuthenticated) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - state = ExecutedSuccessfullyState(); + state = ExecutedSuccessfullyState(); _failureCounter = 0; }); } else { @@ -118,4 +120,10 @@ abstract class AuthViewModelBase with Store { state = FailureState(e.toString()); } } + + void _saveLastAuthTime(ExecutionState state){ + if(state is ExecutedSuccessfullyState){ + _authService.saveLastAuthTime(); + } + } } diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart index 9caacb211..96f11e82e 100644 --- a/lib/view_model/settings/settings_view_model.dart +++ b/lib/view_model/settings/settings_view_model.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:mobx/mobx.dart'; import 'package:package_info/package_info.dart'; @@ -41,6 +43,7 @@ abstract class SettingsViewModelBase with Store { SettingsViewModelBase( this._settingsStore, this._yatStore, + this._authService, WalletBase, TransactionInfo> wallet) @@ -94,6 +97,10 @@ abstract class SettingsViewModelBase with Store { @computed FiatCurrency get fiatCurrency => _settingsStore.fiatCurrency; + @computed + PinCodeRequiredDuration get pinCodeRequiredDuration => + _settingsStore.pinTimeOutDuration; + @computed String get languageCode => _settingsStore.languageCode; @@ -135,6 +142,7 @@ abstract class SettingsViewModelBase with Store { final Map itemHeaders; final SettingsStore _settingsStore; final YatStore _yatStore; + final AuthService _authService; final WalletType walletType; final BiometricAuth _biometricAuth; final WalletBase, @@ -207,19 +215,25 @@ abstract class SettingsViewModelBase with Store { } } + @action + setPinCodeRequiredDuration(PinCodeRequiredDuration duration) => + _settingsStore.pinTimeOutDuration = duration; + String getDisplayPriority(dynamic priority) { - final _priority = priority as TransactionPriority; + final _priority = priority as TransactionPriority; - if (_wallet.type == WalletType.bitcoin - || _wallet.type == WalletType.litecoin) { - final rate = bitcoin!.getFeeRate(_wallet, _priority); - return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); - } + if (_wallet.type == WalletType.bitcoin + || _wallet.type == WalletType.litecoin) { + final rate = bitcoin!.getFeeRate(_wallet, _priority); + return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); + } - return priority.toString(); + return priority.toString(); } void onDisplayPrioritySelected(TransactionPriority priority) => - _settingsStore.priority[_wallet.type] = priority; + _settingsStore.priority[_wallet.type] = priority; + + bool checkPinCodeRiquired() => _authService.requireAuth(); } diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 0bbc68748..50908f24e 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -1,5 +1,5 @@ +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; -import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/di.dart'; @@ -55,4 +55,8 @@ abstract class WalletListViewModelBase with Store { info.type == _appStore.wallet!.type, isEnabled: availableWalletTypes.contains(info.type)))); } + + bool checkIfAuthRequired(){ + return getIt.get().requireAuth(); + } } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index cea889b15..eacc8e618 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -655,5 +655,8 @@ "privacy_settings": "Datenschutzeinstellungen", "privacy": "Datenschutz", "display_settings": "Anzeigeeinstellungen", - "other_settings": "Andere Einstellungen" + "other_settings": "Andere Einstellungen", + "require_pin_after": "PIN anfordern nach", + "always": "immer", + "minutes_to_pin_code": "${minute} Minuten" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e764a0b90..65b72f5c6 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -658,5 +658,8 @@ "privacy_settings": "Privacy settings", "privacy": "Privacy", "display_settings": "Display settings", - "other_settings": "Other settings" + "other_settings": "Other settings", + "require_pin_after": "Require PIN after", + "always": "Always", + "minutes_to_pin_code": "${minute} minutes" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index bd43abe58..903e6c380 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -655,5 +655,8 @@ "privacy_settings": "Configuración de privacidad", "privacy": "Privacidad", "display_settings": "Configuración de pantalla", - "other_settings": "Otras configuraciones" + "other_settings": "Otras configuraciones", + "require_pin_after": "Requerir PIN después de", + "always": "siempre", + "minutes_to_pin_code": "${minute} minutos" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index df43f41a6..d82f1d2de 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -653,5 +653,8 @@ "privacy_settings": "Paramètres de confidentialité", "privacy": "Confidentialité", "display_settings": "Paramètres d'affichage", - "other_settings": "Autres paramètres" + "other_settings": "Autres paramètres", + "require_pin_after": "NIP requis après", + "always": "toujours", + "minutes_to_pin_code": "${minute} minutes" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 06d95d3b2..9175cb17c 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -655,5 +655,8 @@ "privacy_settings": "गोपनीयता सेटिंग्स", "privacy": "गोपनीयता", "display_settings": "प्रदर्शन सेटिंग्स", - "other_settings": "अन्य सेटिंग्स" + "other_settings": "अन्य सेटिंग्स", + "require_pin_after": "इसके बाद पिन आवश्यक है", + "always": "हमेशा", + "minutes_to_pin_code": "${minute} मिनट" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 7c25928e5..e60bedd74 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -655,5 +655,8 @@ "privacy_settings": "Postavke privatnosti", "privacy": "Privatnost", "display_settings": "Postavke zaslona", - "other_settings": "Ostale postavke" + "other_settings": "Ostale postavke", + "require_pin_after": "Zahtijevaj PIN nakon", + "always": "Uvijek", + "minutes_to_pin_code": "${minute} minuta" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 45d9d8164..548c51b22 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -655,5 +655,8 @@ "privacy_settings": "Impostazioni privacy", "privacy": "Privacy", "display_settings": "Impostazioni di visualizzazione", - "other_settings": "Altre impostazioni" + "other_settings": "Altre impostazioni", + "require_pin_after": "Richiedi PIN dopo", + "always": "sempre", + "minutes_to_pin_code": "${minute} minuti" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ffb2cbb71..7fc1f5105 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -655,5 +655,8 @@ "privacy_settings": "プライバシー設定", "privacy": "プライバシー", "display_settings": "表示設定", - "other_settings": "その他の設定" + "other_settings": "その他の設定", + "require_pin_after": "後に PIN が必要", + "always": "いつも", + "minutes_to_pin_code": "${minute} 分" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 598fe2f56..471d9ed11 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -655,5 +655,8 @@ "privacy_settings": "개인정보 설정", "privacy": "프라이버시", "display_settings": "디스플레이 설정", - "other_settings": "기타 설정" + "other_settings": "기타 설정", + "require_pin_after": "다음 이후에 PIN 필요", + "always": "언제나", + "minutes_to_pin_code": "${minute}분" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 12e2f5569..1eb16e051 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -655,5 +655,8 @@ "privacy_settings": "Privacy-instellingen", "privacy": "Privacy", "display_settings": "Weergave-instellingen", - "other_settings": "Andere instellingen" + "other_settings": "Andere instellingen", + "require_pin_after": "Pincode vereist na", + "always": "altijd", + "minutes_to_pin_code": "${minute} minuten" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index fd092d332..78793c074 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -655,5 +655,8 @@ "privacy_settings": "Ustawienia prywatności", "privacy": "Prywatność", "display_settings": "Ustawienia wyświetlania", - "other_settings": "Inne ustawienia" + "other_settings": "Inne ustawienia", + "require_pin_after": "Wymagaj kodu PIN po", + "always": "zawsze", + "minutes_to_pin_code": "${minute} minut" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index e4d7ef647..e7fc9aa92 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -655,5 +655,8 @@ "privacy_settings": "Configurações de privacidade", "privacy": "Privacidade", "display_settings": "Configurações de exibição", - "other_settings": "Outras configurações" + "other_settings": "Outras configurações", + "require_pin_after": "Exigir PIN após", + "always": "sempre", + "minutes_to_pin_code": "${minute} minutos" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 786f11e36..34fe7a145 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -655,5 +655,8 @@ "privacy_settings": "Настройки конфиденциальности", "privacy": "Конфиденциальность", "display_settings": "Настройки отображения", - "other_settings": "Другие настройки" + "other_settings": "Другие настройки", + "require_pin_after": "Требовать ПИН после", + "always": "всегда", + "minutes_to_pin_code": "${minute} минут" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 48951e806..fab6ed71e 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -654,6 +654,9 @@ "privacy_settings": "Налаштування конфіденційності", "privacy": "Конфіденційність", "display_settings": "Налаштування дисплея", - "other_settings": "Інші налаштування" + "other_settings": "Інші налаштування", + "require_pin_after": "Вимагати PIN після", + "always": "Завжди", + "minutes_to_pin_code": "${minute} хвилин" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 29d7351d5..8f89f7b63 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -653,5 +653,8 @@ "privacy_settings": "隐私设置", "privacy":"隐私", "display_settings": "显示设置", - "other_settings": "其他设置" + "other_settings": "其他设置", + "require_pin_after": "之后需要 PIN", + "always": "总是", + "minutes_to_pin_code": "${minute} 分钟" }