diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index 2ae37e2b0..54f89437a 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -4,12 +4,19 @@ 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}); + AuthService({ + required this.secureStorage, + required this.sharedPreferences, + required this.settingsStore, + }); final FlutterSecureStorage secureStorage; final SharedPreferences sharedPreferences; + final SettingsStore settingsStore; Future setPassword(String password) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); @@ -19,8 +26,7 @@ class AuthService with Store { Future canAuthenticate() async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); - final walletName = - sharedPreferences.getString(PreferencesKey.currentWalletName) ?? ''; + final walletName = sharedPreferences.getString(PreferencesKey.currentWalletName) ?? ''; var password = ''; try { @@ -39,4 +45,25 @@ 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 = settingsStore.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 a60cf3148..1acdad2dd 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -309,7 +309,10 @@ Future setup( getIt.registerFactory(() => AuthService( secureStorage: getIt.get(), - sharedPreferences: getIt.get())); + sharedPreferences: getIt.get(), + settingsStore: getIt.get(), + ), + ); getIt.registerFactory(() => AuthViewModel( getIt.get(), @@ -397,7 +400,10 @@ Future setup( getIt.registerFactory(() => WalletListViewModel( _walletInfoSource, getIt.get(), - getIt.get())); + getIt.get(), + getIt.get(), + ), + ); getIt.registerFactory(() => WalletListPage(walletListViewModel: getIt.get())); @@ -457,7 +463,7 @@ Future setup( }); getIt.registerFactory(() { - return SecuritySettingsViewModel(getIt.get()); + return SecuritySettingsViewModel(getIt.get(), getIt.get()); }); getIt @@ -828,4 +834,4 @@ Future setup( AdvancedPrivacySettingsViewModel(type, getIt.get())); _isSetupFinished = true; -} +} \ No newline at end of file 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 6d9e93de6..90d4dcad7 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -27,6 +27,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/main.dart b/lib/main.dart index ebdad1500..eaabb8339 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/ionia/ionia_category.dart'; @@ -251,6 +252,7 @@ class AppState extends State with SingleTickerProviderStateMixin { Widget build(BuildContext context) { return Observer(builder: (BuildContext context) { final appStore = getIt.get(); + final authService = getIt.get(); final settingsStore = appStore.settingsStore; final statusBarColor = Colors.transparent; final authenticationStore = getIt.get(); @@ -275,6 +277,7 @@ class AppState extends State with SingleTickerProviderStateMixin { appStore: appStore, authenticationStore: authenticationStore, navigatorKey: navigatorKey, + authService: authService, child: MaterialApp( navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index 014dd0451..0ab1c23c1 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -9,17 +10,19 @@ import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:uni_links/uni_links.dart'; class Root extends StatefulWidget { - Root( - {required Key key, - required this.authenticationStore, - required this.appStore, - required this.child, - required this.navigatorKey}) - : super(key: key); + Root({ + required Key key, + required this.authenticationStore, + required this.appStore, + required this.child, + required this.navigatorKey, + required this.authService, + }) : super(key: key); final AuthenticationStore authenticationStore; final AppStore appStore; final GlobalKey navigatorKey; + final AuthService authService; final Widget child; @override @@ -28,20 +31,23 @@ class Root extends StatefulWidget { class RootState extends State with WidgetsBindingObserver { RootState() - : _isInactiveController = StreamController.broadcast(), - _isInactive = false, - _postFrameCallback = false; + : _isInactiveController = StreamController.broadcast(), + _isInactive = false, + _requestAuth = true, + _postFrameCallback = false; Stream get isInactive => _isInactiveController.stream; StreamController _isInactiveController; bool _isInactive; bool _postFrameCallback; + bool _requestAuth; StreamSubscription? stream; Uri? launchUri; @override void initState() { + _requestAuth = widget.authService.requireAuth(); _isInactiveController = StreamController.broadcast(); _isInactive = false; _postFrameCallback = false; @@ -85,8 +91,11 @@ class RootState extends State with WidgetsBindingObserver { return; } - if (!_isInactive && - widget.authenticationStore.state == AuthenticationState.allowed) { + setState(() { + _requestAuth = widget.authService.requireAuth(); + }); + + if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) { setState(() => _setInactive(true)); } @@ -98,7 +107,7 @@ class RootState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - if (_isInactive && !_postFrameCallback) { + if (_isInactive && !_postFrameCallback && _requestAuth) { _postFrameCallback = true; WidgetsBinding.instance.addPostFrameCallback((_) { widget.navigatorKey.currentState?.pushNamed(Routes.unlock, diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index 224c941ee..102a94b36 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/security_settings_view_model.dart'; @@ -25,22 +27,26 @@ class SecurityBackupPage extends BasePage { child: Column(mainAxisSize: MainAxisSize.min, children: [ SettingsCellWithArrow( title: S.current.show_keys, - handler: (_) => Navigator.of(context).pushNamed(Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - if (isAuthenticatedSuccessfully) { - auth.close(route: Routes.showKeys); - } - }), + handler: (_) => _securitySettingsViewModel.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, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - if (isAuthenticatedSuccessfully) { - auth.close(route: Routes.backup); - } - }), + handler: (_) => _securitySettingsViewModel.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( @@ -65,10 +71,12 @@ class SecurityBackupPage extends BasePage { arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { if (isAuthenticatedSuccessfully) { if (await _securitySettingsViewModel.biometricAuthenticated()) { - _securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + _securitySettingsViewModel + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } } else { - _securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + _securitySettingsViewModel + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } auth.close(); @@ -78,6 +86,16 @@ class SecurityBackupPage extends BasePage { } }); }), + Observer(builder: (_) { + return SettingsPickerCell( + title: S.current.require_pin_after, + items: PinCodeRequiredDuration.values, + selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration, + onItemSelected: (PinCodeRequiredDuration code) { + _securitySettingsViewModel.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..d76631b3f 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -220,68 +220,88 @@ class WalletListBodyState extends State { } Future _loadWallet(WalletListItem wallet) async { - await Navigator.of(context).pushNamed(Routes.auth, arguments: - (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (!isAuthenticatedSuccessfully) { - return; - } + if (await widget.walletListViewModel.checkIfAuthRequired()) { + await Navigator.of(context).pushNamed(Routes.auth, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { + if (!isAuthenticatedSuccessfully) { + return; + } + try { + auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); + await widget.walletListViewModel.loadWallet(wallet); + auth.hideProgressText(); + auth.close(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pop(); + }); + } catch (e) { + auth.changeProcessText( + S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); + } + }); + } else { try { - auth.changeProcessText( - S.of(context).wallet_list_loading_wallet(wallet.name)); + changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); await widget.walletListViewModel.loadWallet(wallet); - auth.hideProgressText(); - auth.close(); - WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(context).pop(); - }); + hideProgressText(); + Navigator.of(context).pop(); } catch (e) { - auth.changeProcessText(S - .of(context) - .wallet_list_failed_to_load(wallet.name, e.toString())); + 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: - (bool isAuthenticatedSuccessfully, AuthPageState auth) async { - if (!isAuthenticatedSuccessfully) { - return; - } - - bool confirmed = false; - - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).delete_wallet, - alertContent: S.of(context).delete_wallet_confirm_message(wallet.name), - leftButtonText: S.of(context).cancel, - rightButtonText: S.of(context).delete, - actionLeftButton: () => Navigator.of(context).pop(), - actionRightButton: () { - confirmed = true; - Navigator.of(context).pop(); - }, - ); - }); - - if (confirmed) { - try { - auth.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())); + 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); + } + } - auth.close(); - }); + void _onSuccessfulAuth(WalletListItem wallet, AuthPageState? auth) async { + bool confirmed = false; + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).delete_wallet, + alertContent: S.of(context).delete_wallet_confirm_message(wallet.name), + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).delete, + actionLeftButton: () => Navigator.of(context).pop(), + actionRightButton: () { + confirmed = true; + Navigator.of(context).pop(); + }, + ); + }); + + if (confirmed) { + try { + 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 != 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(); } void changeProcessText(String text) { @@ -294,16 +314,16 @@ class WalletListBodyState extends State { } ActionPane _actionPane(WalletListItem wallet) => ActionPane( - motion: const ScrollMotion(), - extentRatio: 0.3, - children: [ - SlidableAction( - onPressed: (_) => _removeWallet(wallet), - backgroundColor: Colors.red, - foregroundColor: Colors.white, - icon: CupertinoIcons.delete, - label: S.of(context).delete, - ), - ], - ); + motion: const ScrollMotion(), + extentRatio: 0.3, + children: [ + SlidableAction( + onPressed: (_) => _removeWallet(wallet), + backgroundColor: Colors.red, + foregroundColor: Colors.white, + icon: CupertinoIcons.delete, + label: S.of(context).delete, + ), + ], + ); } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 96d74a23a..b10e0d08d 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1,4 +1,5 @@ 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'; @@ -17,7 +18,6 @@ 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/entities/fiat_api_mode.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; part 'settings_store.g.dart'; @@ -41,6 +41,7 @@ abstract class SettingsStoreBase with Store { required this.shouldShowYatPopup, required this.isBitcoinBuyEnabled, required this.actionlistDisplayMode, + required this.pinTimeOutDuration, TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialHavenTransactionPriority, @@ -141,6 +142,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( @@ -162,6 +168,7 @@ abstract class SettingsStoreBase with Store { static const defaultPinLength = 4; static const defaultActionsMode = 11; + static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenminutes; @observable FiatCurrency fiatCurrency; @@ -193,6 +200,9 @@ abstract class SettingsStoreBase with Store { @observable int pinCodeLength; + @observable + PinCodeRequiredDuration pinTimeOutDuration; + @computed ThemeData get theme => currentTheme.themeData; @@ -288,6 +298,11 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode)); var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength); + final timeOutDuration = sharedPreferences.getInt(PreferencesKey.pinTimeOutDuration); + final pinCodeTimeOutDuration = timeOutDuration != null + ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) + : defaultPinCodeTimeOutDuration; + // If no value if (pinLength == null || pinLength == 0) { pinLength = defaultPinLength; @@ -343,6 +358,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..e50f4db0c 100644 --- a/lib/view_model/auth_view_model.dart +++ b/lib/view_model/auth_view_model.dart @@ -14,10 +14,12 @@ part 'auth_view_model.g.dart'; class AuthViewModel = AuthViewModelBase with _$AuthViewModel; abstract class AuthViewModelBase with Store { - AuthViewModelBase(this._authService, this._sharedPreferences, - this._settingsStore, this._biometricAuth) + 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 @@ -28,8 +30,7 @@ abstract class AuthViewModelBase with Store { int get pinLength => _settingsStore.pinCodeLength; - bool get isBiometricalAuthenticationAllowed => - _settingsStore.allowBiometricalAuthentication; + bool get isBiometricalAuthenticationAllowed => _settingsStore.allowBiometricalAuthentication; @observable int _failureCounter; @@ -114,8 +115,14 @@ abstract class AuthViewModelBase with Store { state = ExecutedSuccessfullyState(); } } - } catch(e) { + } catch (e) { state = FailureState(e.toString()); } } + + void _saveLastAuthTime(ExecutionState state) { + if (state is ExecutedSuccessfullyState) { + _authService.saveLastAuthTime(); + } + } } diff --git a/lib/view_model/settings/security_settings_view_model.dart b/lib/view_model/settings/security_settings_view_model.dart index 4a88b633f..c48223af6 100644 --- a/lib/view_model/settings/security_settings_view_model.dart +++ b/lib/view_model/settings/security_settings_view_model.dart @@ -1,4 +1,6 @@ +import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/entities/biometric_auth.dart'; +import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:mobx/mobx.dart'; @@ -7,19 +9,33 @@ part 'security_settings_view_model.g.dart'; class SecuritySettingsViewModel = SecuritySettingsViewModelBase with _$SecuritySettingsViewModel; abstract class SecuritySettingsViewModelBase with Store { - SecuritySettingsViewModelBase(this._settingsStore) : _biometricAuth = BiometricAuth(); + SecuritySettingsViewModelBase( + this._settingsStore, + this._authService, + ) : _biometricAuth = BiometricAuth(); final BiometricAuth _biometricAuth; final SettingsStore _settingsStore; + final AuthService _authService; @computed bool get allowBiometricalAuthentication => _settingsStore.allowBiometricalAuthentication; + @computed + PinCodeRequiredDuration get pinCodeRequiredDuration => _settingsStore.pinTimeOutDuration; + @action Future biometricAuthenticated() async { return await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated(); } @action - void setAllowBiometricalAuthentication(bool value) => _settingsStore.allowBiometricalAuthentication = value; + void setAllowBiometricalAuthentication(bool value) => + _settingsStore.allowBiometricalAuthentication = value; + + @action + setPinCodeRequiredDuration(PinCodeRequiredDuration duration) => + _settingsStore.pinTimeOutDuration = duration; + + 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..6d63675ba 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'; @@ -15,9 +15,12 @@ part 'wallet_list_view_model.g.dart'; class WalletListViewModel = WalletListViewModelBase with _$WalletListViewModel; abstract class WalletListViewModelBase with Store { - WalletListViewModelBase(this._walletInfoSource, this._appStore, - this._walletLoadingService) - : wallets = ObservableList() { + WalletListViewModelBase( + this._walletInfoSource, + this._appStore, + this._walletLoadingService, + this._authService, + ) : wallets = ObservableList() { _updateList(); } @@ -27,6 +30,7 @@ abstract class WalletListViewModelBase with Store { final AppStore _appStore; final Box _walletInfoSource; final WalletLoadingService _walletLoadingService; + final AuthService _authService; WalletType get currentWalletType => _appStore.wallet!.type; @@ -47,12 +51,20 @@ abstract class WalletListViewModelBase with Store { void _updateList() { wallets.clear(); - wallets.addAll(_walletInfoSource.values.map((info) => WalletListItem( - name: info.name, - type: info.type, - key: info.key, - isCurrent: info.name == _appStore.wallet!.name && - info.type == _appStore.wallet!.type, - isEnabled: availableWalletTypes.contains(info.type)))); + wallets.addAll( + _walletInfoSource.values.map( + (info) => WalletListItem( + name: info.name, + type: info.type, + key: info.key, + isCurrent: info.name == _appStore.wallet!.name && info.type == _appStore.wallet!.type, + isEnabled: availableWalletTypes.contains(info.type), + ), + ), + ); + } + + bool checkIfAuthRequired() { + return _authService.requireAuth(); } } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 571bae86d..2b66a1b28 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -661,6 +661,9 @@ "privacy": "Datenschutz", "display_settings": "Anzeigeeinstellungen", "other_settings": "Andere Einstellungen", + "require_pin_after": "PIN anfordern nach", + "always": "immer", + "minutes_to_pin_code": "${minute} Minuten", "disable_exchange": "Exchange deaktivieren", "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", "settings_can_be_changed_later": "Diese Einstellungen können später in den App-Einstellungen geändert werden", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 8e39c1e8e..92d763cc2 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -661,6 +661,9 @@ "privacy": "Privacy", "display_settings": "Display settings", "other_settings": "Other settings", + "require_pin_after": "Require PIN after", + "always": "Always", + "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Disable exchange", "advanced_privacy_settings": "Advanced Privacy Settings", "settings_can_be_changed_later": "These settings can be changed later in the app settings", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index b9e115273..df23d994e 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -661,6 +661,9 @@ "privacy": "Privacidad", "display_settings": "Configuración de pantalla", "other_settings": "Otras configuraciones", + "require_pin_after": "Requerir PIN después de", + "always": "siempre", + "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Deshabilitar intercambio", "advanced_privacy_settings": "Configuración avanzada de privacidad", "settings_can_be_changed_later": "Estas configuraciones se pueden cambiar más tarde en la configuración de la aplicación", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f17e4e7b6..349bb9dfc 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -659,6 +659,9 @@ "privacy": "Confidentialité", "display_settings": "Paramètres d'affichage", "other_settings": "Autres paramètres", + "require_pin_after": "NIP requis après", + "always": "toujours", + "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Désactiver l'échange", "advanced_privacy_settings": "Paramètres de confidentialité avancés", "settings_can_be_changed_later": "Ces paramètres peuvent être modifiés ultérieurement dans les paramètres de l'application", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 91ceff77d..66d40ddc2 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -661,6 +661,9 @@ "privacy": "गोपनीयता", "display_settings": "प्रदर्शन सेटिंग्स", "other_settings": "अन्य सेटिंग्स", + "require_pin_after": "इसके बाद पिन आवश्यक है", + "always": "हमेशा", + "minutes_to_pin_code": "${minute} मिनट", "disable_exchange": "एक्सचेंज अक्षम करें", "advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स", "settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index a7ee21582..5eb394b01 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -661,6 +661,9 @@ "privacy": "Privatnost", "display_settings": "Postavke zaslona", "other_settings": "Ostale postavke", + "require_pin_after": "Zahtijevaj PIN nakon", + "always": "Uvijek", + "minutes_to_pin_code": "${minute} minuta", "disable_exchange": "Onemogući exchange", "advanced_privacy_settings": "Napredne postavke privatnosti", "settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 0dd72425b..7ff01753f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -661,6 +661,9 @@ "privacy": "Privacy", "display_settings": "Impostazioni di visualizzazione", "other_settings": "Altre impostazioni", + "require_pin_after": "Richiedi PIN dopo", + "always": "sempre", + "minutes_to_pin_code": "${minute} minuti", "disable_exchange": "Disabilita scambio", "advanced_privacy_settings": "Impostazioni avanzate sulla privacy", "settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 483b2a173..97ccb7160 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -661,6 +661,9 @@ "privacy": "プライバシー", "display_settings": "表示設定", "other_settings": "その他の設定", + "require_pin_after": "後に PIN が必要", + "always": "いつも", + "minutes_to_pin_code": "${minute} 分", "disable_exchange": "交換を無効にする", "advanced_privacy_settings": "高度なプライバシー設定", "settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index ff2985c1f..470a1e405 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -661,6 +661,9 @@ "privacy": "프라이버시", "display_settings": "디스플레이 설정", "other_settings": "기타 설정", + "require_pin_after": "다음 이후에 PIN 필요", + "always": "언제나", + "minutes_to_pin_code": "${minute}분", "disable_exchange": "교환 비활성화", "advanced_privacy_settings": "고급 개인 정보 설정", "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index dfe888c03..a198f8672 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -661,6 +661,9 @@ "privacy": "Privacy", "display_settings": "Weergave-instellingen", "other_settings": "Andere instellingen", + "require_pin_after": "Pincode vereist na", + "always": "altijd", + "minutes_to_pin_code": "${minute} minuten", "disable_exchange": "Uitwisseling uitschakelen", "advanced_privacy_settings": "Geavanceerde privacy-instellingen", "settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 94fc16b77..dc1ffbd84 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -661,6 +661,9 @@ "privacy": "Prywatność", "display_settings": "Ustawienia wyświetlania", "other_settings": "Inne ustawienia", + "require_pin_after": "Wymagaj kodu PIN po", + "always": "zawsze", + "minutes_to_pin_code": "${minute} minut", "disable_exchange": "Wyłącz wymianę", "advanced_privacy_settings": "Zaawansowane ustawienia prywatności", "settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 40383df38..6a128d109 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -660,6 +660,9 @@ "privacy": "Privacidade", "display_settings": "Configurações de exibição", "other_settings": "Outras configurações", + "require_pin_after": "Exigir PIN após", + "always": "sempre", + "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Desativar troca", "advanced_privacy_settings": "Configurações de privacidade avançadas", "settings_can_be_changed_later": "Essas configurações podem ser alteradas posteriormente nas configurações do aplicativo", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 722b76ac4..04a7f0665 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -661,6 +661,9 @@ "privacy": "Конфиденциальность", "display_settings": "Настройки отображения", "other_settings": "Другие настройки", + "require_pin_after": "Требовать ПИН после", + "always": "всегда", + "minutes_to_pin_code": "${minute} минут", "disable_exchange": "Отключить обмен", "advanced_privacy_settings": "Расширенные настройки конфиденциальности", "settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения.", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 90c2fb5be..58d19e636 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -660,6 +660,9 @@ "privacy": "Конфіденційність", "display_settings": "Налаштування дисплея", "other_settings": "Інші налаштування", + "require_pin_after": "Вимагати PIN після", + "always": "Завжди", + "minutes_to_pin_code": "${minute} хвилин", "disable_exchange": "Вимкнути exchange", "advanced_privacy_settings": "Розширені налаштування конфіденційності", "settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 2c2321078..eb4571943 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -659,6 +659,9 @@ "privacy":"隐私", "display_settings": "显示设置", "other_settings": "其他设置", + "require_pin_after": "之后需要 PIN", + "always": "总是", + "minutes_to_pin_code": "${minute} 分钟", "disable_exchange": "禁用交换", "advanced_privacy_settings": "高级隐私设置", "settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置",