diff --git a/mobile/lib/core/configuration.dart b/mobile/lib/core/configuration.dart index 92bb31e763..67c7299f0b 100644 --- a/mobile/lib/core/configuration.dart +++ b/mobile/lib/core/configuration.dart @@ -72,7 +72,8 @@ class Configuration { "has_selected_all_folders_for_backup"; static const anonymousUserIDKey = "anonymous_user_id"; static const endPointKey = "endpoint"; - + static const password = "user_pass"; + static const pin = "user_pin"; static final _logger = Logger("Configuration"); String? _cachedToken; @@ -151,6 +152,29 @@ class Configuration { } } + Future savePin(String userPin) async { + await _preferences.setString(pin, userPin); + await _preferences.remove(password); + } + + Future loadSavedPin() async { + return _preferences.getString(pin); + } + + Future savePassword(String pass) async { + await _preferences.setString(password, pass); + await _preferences.remove(pin); + } + + Future loadSavedPassword() async { + return _preferences.getString(password); + } + + Future removePinAndPassword() async { + await _preferences.remove(pin); + await _preferences.remove(password); + } + // _cleanUpStaleFiles deletes all files in the temp directory that are older // than kTempFolderDeletionTimeBuffer except the the temp encrypted files for upload. // Those file are deleted by file uploader after the upload is complete or those diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 933d8dcf08..00a00e4c2a 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -60,86 +60,6 @@ class S { ); } - /// `Device Lock` - String get deviceLock { - return Intl.message( - 'Device lock', - name: 'deviceLock', - desc: '', - args: [], - ); - } - - /// `PIN Lock` - String get pinLock { - return Intl.message( - 'PIN lock', - name: 'pinLock', - desc: '', - args: [], - ); - } - - /// `Enter the pin to lock the app` - String get enterThePinToLockTheApp { - return Intl.message( - 'Enter the pin to lock the app', - name: 'enterThePinToLockTheApp', - desc: '', - args: [], - ); - } - - /// `Re-enter to confirm the pin` - String get reEnterToConfirmPin { - return Intl.message( - 'Re-enter to confirm the pin', - name: 'reEnterToConfirmPin', - desc: '', - args: [], - ); - } - - /// `Enter the password to lock the app` - String get enterPasswordToLockApp { - return Intl.message( - 'Enter the password to lock the app', - name: 'enterPasswordToLockApp', - desc: '', - args: [], - ); - } - - /// `Next` - String get next { - return Intl.message( - 'Next', - name: 'next', - desc: '', - args: [], - ); - } - - /// `When a pin is set , you need to enter password\n whenever you open the app .` - String get whenPinIsSetYouNeedToEnterPassword { - return Intl.message( - 'When a pin is set , you need to enter password\n whenever you open the app .', - name: 'whenPinIsSetYouNeedToEnterPassword', - desc: '', - args: [], - ); - } - - /// `Enable PIN` - String get enablePin { - return Intl.message( - 'Enable PIN', - name: 'enablePin', - desc: '', - args: [], - ); - } - /// `Welcome back!` String get accountWelcomeBack { return Intl.message( diff --git a/mobile/lib/services/local_authentication_service.dart b/mobile/lib/services/local_authentication_service.dart index 7531088d92..9655d2da1a 100644 --- a/mobile/lib/services/local_authentication_service.dart +++ b/mobile/lib/services/local_authentication_service.dart @@ -3,7 +3,13 @@ import "dart:async"; import 'package:flutter/material.dart'; import 'package:local_auth/local_auth.dart'; import 'package:photos/core/configuration.dart'; +import "package:photos/generated/l10n.dart"; +import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/components/dialog_widget.dart"; +import "package:photos/ui/components/models/button_type.dart"; import "package:photos/ui/settings/TEMP/lock_screen_option.dart"; +import "package:photos/ui/settings/TEMP/lock_screen_option_password.dart"; +import "package:photos/ui/settings/TEMP/lock_screen_option_pin.dart"; import 'package:photos/ui/tools/app_lock.dart'; import 'package:photos/utils/auth_util.dart'; import 'package:photos/utils/dialog_util.dart'; @@ -14,6 +20,8 @@ class LocalAuthenticationService { static final LocalAuthenticationService instance = LocalAuthenticationService._privateConstructor(); + final Configuration _configuration = Configuration.instance; + Future requestLocalAuthentication( BuildContext context, String infoMessage, @@ -41,6 +49,87 @@ class LocalAuthenticationService { String errorDialogContent, [ String errorDialogTitle = "", ]) async { + final String? savedPin = await _configuration.loadSavedPin(); + final String? savedPassword = await _configuration.loadSavedPassword(); + + if (savedPassword != null) { + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return LockScreenOptionPassword( + isAuthenticating: true, + authPass: savedPassword, + ); + }, + ), + ); + if (result) { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return const LockScreenOption(); + }, + ), + ); + return true; + } else { + await showDialogWidget( + context: context, + title: 'Password does not match', + icon: Icons.lock, + body: 'Please re-enter the password.', + isDismissible: true, + buttons: [ + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: S.of(context).ok, + isInAlert: true, + buttonAction: ButtonAction.first, + ), + ], + ); + } + return false; + } else if (savedPin != null) { + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return LockScreenOptionPin( + isAuthenticating: true, + authPin: savedPin, + ); + }, + ), + ); + if (result) { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return const LockScreenOption(); + }, + ), + ); + return true; + } else { + await showDialogWidget( + context: context, + title: 'Pin does not match', + icon: Icons.lock, + body: 'Please re-enter the pin.', + isDismissible: true, + buttons: [ + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: S.of(context).ok, + isInAlert: true, + buttonAction: ButtonAction.first, + ), + ], + ); + } + return false; + } + if (await _isLocalAuthSupportedOnDevice()) { AppLock.of(context)!.disable(); final result = await requestAuthentication( diff --git a/mobile/lib/ui/settings/TEMP/lock_screen_option.dart b/mobile/lib/ui/settings/TEMP/lock_screen_option.dart index 7690954a00..be7d5749cf 100644 --- a/mobile/lib/ui/settings/TEMP/lock_screen_option.dart +++ b/mobile/lib/ui/settings/TEMP/lock_screen_option.dart @@ -52,8 +52,8 @@ class LockScreenOption extends StatelessWidget { bgColor: colorScheme.fillFaint, ), MenuItemWidget( - captionedTextWidget: CaptionedTextWidget( - title: S.of(context).deviceLock, + captionedTextWidget: const CaptionedTextWidget( + title: 'Device Lock', ), alignCaptionedTextToLeft: true, isTopBorderRadiusRemoved: true, @@ -67,8 +67,8 @@ class LockScreenOption extends StatelessWidget { bgColor: colorScheme.fillFaint, ), MenuItemWidget( - captionedTextWidget: CaptionedTextWidget( - title: S.of(context).pinLock, + captionedTextWidget: const CaptionedTextWidget( + title: 'PIN lock', ), alignCaptionedTextToLeft: true, isTopBorderRadiusRemoved: true, @@ -89,8 +89,8 @@ class LockScreenOption extends StatelessWidget { bgColor: colorScheme.fillFaint, ), MenuItemWidget( - captionedTextWidget: CaptionedTextWidget( - title: S.of(context).passwordLock, + captionedTextWidget: const CaptionedTextWidget( + title: 'Password lock', ), alignCaptionedTextToLeft: true, isTopBorderRadiusRemoved: true, diff --git a/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_password.dart b/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_password.dart index b45ef06c19..55370af64d 100644 --- a/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_password.dart +++ b/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_password.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:photos/core/configuration.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/components/buttons/button_widget.dart"; @@ -6,7 +7,6 @@ import "package:photos/ui/components/buttons/icon_button_widget.dart"; import "package:photos/ui/components/dialog_widget.dart"; import "package:photos/ui/components/models/button_type.dart"; import "package:photos/ui/components/text_input_widget.dart"; -import "package:photos/ui/settings/TEMP/lock_screen_option.dart"; class LockScreenOptionConfirmPassword extends StatefulWidget { const LockScreenOptionConfirmPassword({super.key, required this.password}); @@ -18,22 +18,30 @@ class LockScreenOptionConfirmPassword extends StatefulWidget { class _LockScreenOptionConfirmPasswordState extends State { - String confirmPassword = ""; + String _confirmPassword = ""; final _confirmPasswordController = TextEditingController(text: null); - - @override - void dispose() { - super.dispose(); - _confirmPasswordController.dispose(); - } + final Configuration _configuration = Configuration.instance; + final _focusNode = FocusNode(); @override void initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Future.delayed(const Duration(seconds: 1)); + _focusNode.requestFocus(); + }); } - Future _confirmPassword() async { - if (widget.password == confirmPassword) { + @override + void dispose() { + super.dispose(); + _focusNode.dispose(); + _confirmPasswordController.dispose(); + } + + Future _confirmPasswordMatch() async { + if (widget.password == _confirmPassword) { + await _configuration.savePassword(_confirmPassword); await showDialogWidget( context: context, title: 'Password has been set', @@ -49,11 +57,8 @@ class _LockScreenOptionConfirmPasswordState ), ], ); - await Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) => const LockScreenOption(), - ), - ); + Navigator.of(context).pop(); + Navigator.of(context).pop(); } else { await showDialogWidget( context: context, @@ -79,7 +84,6 @@ class _LockScreenOptionConfirmPasswordState final colorTheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); return Scaffold( - // resizeToAvoidBottomInset: false, body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -117,7 +121,7 @@ class _LockScreenOptionConfirmPasswordState ), ), Text( - S.of(context).enterPasswordToLockApp, + 'Enter the password to lock the app', style: textTheme.bodyBold, ), const Padding(padding: EdgeInsets.all(24)), @@ -126,6 +130,7 @@ class _LockScreenOptionConfirmPasswordState child: TextInputWidget( hintText: S.of(context).confirmPassword, borderRadius: 2, + focusNode: _focusNode, isClearable: true, textCapitalization: TextCapitalization.words, textEditingController: _confirmPasswordController, @@ -133,7 +138,7 @@ class _LockScreenOptionConfirmPasswordState isPasswordInput: true, onChange: (String p0) { setState(() { - confirmPassword = p0; + _confirmPassword = p0; }); }, ), @@ -142,12 +147,12 @@ class _LockScreenOptionConfirmPasswordState Padding( padding: const EdgeInsets.all(18.0), child: ButtonWidget( - labelText: S.of(context).next, - buttonType: confirmPassword.length > 8 + labelText: 'Next', + buttonType: _confirmPassword.length > 3 ? ButtonType.primary : ButtonType.secondary, buttonSize: ButtonSize.large, - onTap: () => _confirmPassword(), + onTap: () => _confirmPasswordMatch(), ), ), const Padding(padding: EdgeInsets.only(bottom: 24)), diff --git a/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_pin.dart b/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_pin.dart index bcc01bcc8d..865cfb459a 100644 --- a/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_pin.dart +++ b/mobile/lib/ui/settings/TEMP/lock_screen_option_confirm_pin.dart @@ -1,11 +1,11 @@ import "package:flutter/material.dart"; +import "package:photos/core/configuration.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/components/buttons/button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart"; import "package:photos/ui/components/dialog_widget.dart"; import "package:photos/ui/components/models/button_type.dart"; -import "package:photos/ui/settings/TEMP/lock_screen_option.dart"; import "package:pinput/pin_put/pin_put.dart"; class LockScreenOptionConfirmPin extends StatefulWidget { @@ -19,19 +19,34 @@ class LockScreenOptionConfirmPin extends StatefulWidget { class _LockScreenOptionConfirmPinState extends State { final _confirmPinController = TextEditingController(text: null); + String _confirmPin = ""; + final Configuration _configuration = Configuration.instance; + final _focusNode = FocusNode(); final _pinPutDecoration = BoxDecoration( border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)), borderRadius: BorderRadius.circular(15.0), ); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Future.delayed(const Duration(seconds: 1)); + _focusNode.requestFocus(); + }); + } + @override void dispose() { super.dispose(); + _focusNode.dispose(); _confirmPinController.dispose(); } - Future confirmPin() async { - if (widget.pin == _confirmCode) { + Future _confirmPinMatch() async { + if (widget.pin == _confirmPin) { + await _configuration.savePin(_confirmPin); await showDialogWidget( context: context, title: 'Pin has been set', @@ -47,11 +62,8 @@ class _LockScreenOptionConfirmPinState ), ], ); - await Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) => const LockScreenOption(), - ), - ); + Navigator.of(context).pop(); + Navigator.of(context).pop(); } else { await showDialogWidget( context: context, @@ -72,8 +84,6 @@ class _LockScreenOptionConfirmPinState _confirmPinController.clear(); } - String _confirmCode = ""; - @override Widget build(BuildContext context) { final colorTheme = getEnteColorScheme(context); @@ -123,7 +133,7 @@ class _LockScreenOptionConfirmPinState ), ), Text( - S.of(context).reEnterToConfirmPin, + 'Re-enter to confirm the pin', style: textTheme.bodyBold, ), const Padding(padding: EdgeInsets.all(12)), @@ -132,6 +142,7 @@ class _LockScreenOptionConfirmPinState child: PinPut( fieldsCount: 4, controller: _confirmPinController, + focusNode: _focusNode, submittedFieldDecoration: _pinPutDecoration.copyWith( borderRadius: BorderRadius.circular(20.0), ), @@ -151,7 +162,7 @@ class _LockScreenOptionConfirmPinState obscureText: '*', onChanged: (String pin) { setState(() { - _confirmCode = pin; + _confirmPin = pin; }); }, onSubmit: (value) { @@ -164,11 +175,11 @@ class _LockScreenOptionConfirmPinState padding: const EdgeInsets.all(18.0), child: ButtonWidget( labelText: S.of(context).confirm, - buttonType: _confirmCode.length == 4 + buttonType: _confirmPin.length == 4 ? ButtonType.primary : ButtonType.secondary, buttonSize: ButtonSize.large, - onTap: () => confirmPin(), + onTap: () => _confirmPinMatch(), ), ), const Padding(padding: EdgeInsets.only(bottom: 24)), diff --git a/mobile/lib/ui/settings/TEMP/lock_screen_option_password.dart b/mobile/lib/ui/settings/TEMP/lock_screen_option_password.dart index 88e440f9f5..ce443a9355 100644 --- a/mobile/lib/ui/settings/TEMP/lock_screen_option_password.dart +++ b/mobile/lib/ui/settings/TEMP/lock_screen_option_password.dart @@ -3,14 +3,18 @@ import "package:photos/generated/l10n.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/components/buttons/button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart"; -import "package:photos/ui/components/dialog_widget.dart"; import "package:photos/ui/components/models/button_type.dart"; import "package:photos/ui/components/text_input_widget.dart"; import "package:photos/ui/settings/TEMP/lock_screen_option_confirm_password.dart"; class LockScreenOptionPassword extends StatefulWidget { - const LockScreenOptionPassword({super.key}); - + const LockScreenOptionPassword({ + super.key, + this.isAuthenticating = false, + this.authPass, + }); + final bool isAuthenticating; + final String? authPass; @override State createState() => _LockScreenOptionPasswordState(); @@ -19,19 +23,38 @@ class LockScreenOptionPassword extends StatefulWidget { class _LockScreenOptionPasswordState extends State { final _passwordController = TextEditingController(text: null); String password = ""; - @override - void dispose() { - super.dispose(); - _passwordController.dispose(); - } + final _focusNode = FocusNode(); @override void initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Future.delayed(const Duration(seconds: 1)); + _focusNode.requestFocus(); + }); + } + + @override + void dispose() { + super.dispose(); + _passwordController.dispose(); + _focusNode.dispose(); + } + + Future confirmPasswordAuth(String code) async { + if (widget.authPass == code) { + Navigator.of(context).pop(true); + return true; + } + Navigator.of(context).pop(false); + return false; } Future _confirmPassword() async { - if (password.length > 8) { + if (widget.isAuthenticating) { + await confirmPasswordAuth(password); + return; + } else { await Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) => LockScreenOptionConfirmPassword( @@ -39,22 +62,6 @@ class _LockScreenOptionPasswordState extends State { ), ), ); - } else { - await showDialogWidget( - context: context, - title: 'Password must have at least 8 characters', - icon: Icons.lock, - body: 'Please re-enter the password.', - isDismissible: true, - buttons: [ - ButtonWidget( - buttonType: ButtonType.secondary, - labelText: S.of(context).ok, - isInAlert: true, - buttonAction: ButtonAction.first, - ), - ], - ); } } @@ -63,7 +70,6 @@ class _LockScreenOptionPasswordState extends State { final colorTheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); return Scaffold( - // resizeToAvoidBottomInset: false, body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -101,7 +107,10 @@ class _LockScreenOptionPasswordState extends State { ), ), Text( - S.of(context).enterPasswordToLockApp, + widget.isAuthenticating + ? 'Enter the password to change \nLockscreen settings.' + : 'Enter the password to lock the app', + textAlign: TextAlign.center, style: textTheme.bodyBold, ), const Padding(padding: EdgeInsets.all(24)), @@ -111,6 +120,7 @@ class _LockScreenOptionPasswordState extends State { hintText: S.of(context).password, borderRadius: 2, isClearable: true, + focusNode: _focusNode, textCapitalization: TextCapitalization.words, textEditingController: _passwordController, prefixIcon: Icons.lock_outline, @@ -126,7 +136,7 @@ class _LockScreenOptionPasswordState extends State { Padding( padding: const EdgeInsets.all(18.0), child: ButtonWidget( - labelText: S.of(context).next, + labelText: 'Next', buttonType: password.length > 8 ? ButtonType.primary : ButtonType.secondary, diff --git a/mobile/lib/ui/settings/TEMP/lock_screen_option_pin.dart b/mobile/lib/ui/settings/TEMP/lock_screen_option_pin.dart index 980aad80bf..f04579d23a 100644 --- a/mobile/lib/ui/settings/TEMP/lock_screen_option_pin.dart +++ b/mobile/lib/ui/settings/TEMP/lock_screen_option_pin.dart @@ -1,5 +1,4 @@ import "package:flutter/material.dart"; -import "package:photos/generated/l10n.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/components/buttons/button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart"; @@ -8,32 +7,65 @@ import "package:photos/ui/settings/TEMP/lock_screen_option_confirm_pin.dart"; import "package:pinput/pin_put/pin_put.dart"; class LockScreenOptionPin extends StatefulWidget { - const LockScreenOptionPin({super.key}); + const LockScreenOptionPin({ + super.key, + this.isAuthenticating = false, + this.authPin, + }); + final bool isAuthenticating; + final String? authPin; @override State createState() => _LockScreenOptionPinState(); } class _LockScreenOptionPinState extends State { final _pinController = TextEditingController(text: null); + String _code = ""; + final _focusNode = FocusNode(); + final _pinPutDecoration = BoxDecoration( border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)), borderRadius: BorderRadius.circular(15.0), ); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Future.delayed(const Duration(seconds: 1)); + _focusNode.requestFocus(); + }); + } + @override void dispose() { super.dispose(); _pinController.dispose(); + _focusNode.dispose(); } - String _code = ""; - Future confirmPin(String code) async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) => - LockScreenOptionConfirmPin(pin: code), - ), - ); + Future confirmPinAuth(String code) async { + if (widget.authPin == code) { + Navigator.of(context).pop(true); + return true; + } + Navigator.of(context).pop(false); + return false; + } + + Future _confirmPin(String code) async { + if (widget.isAuthenticating) { + await confirmPinAuth(code); + return; + } else { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => + LockScreenOptionConfirmPin(pin: code), + ), + ); + } } @override @@ -85,13 +117,16 @@ class _LockScreenOptionPinState extends State { ), ), Text( - S.of(context).enterThePinToLockTheApp, + widget.isAuthenticating + ? 'Enter the pin to change Lockscreen settings.' + : 'Enter the pin to lock the app', style: textTheme.bodyBold, ), const Padding(padding: EdgeInsets.all(12)), Padding( padding: const EdgeInsets.fromLTRB(80, 0, 80, 0), child: PinPut( + focusNode: _focusNode, fieldsCount: 4, controller: _pinController, submittedFieldDecoration: _pinPutDecoration.copyWith( @@ -130,7 +165,7 @@ class _LockScreenOptionPinState extends State { ? ButtonType.primary : ButtonType.secondary, buttonSize: ButtonSize.large, - onTap: () => confirmPin(_code), + onTap: () => _confirmPin(_code), ), ), const Padding(padding: EdgeInsets.only(bottom: 24)), diff --git a/mobile/lib/ui/settings/TEMP/lock_screen_option_pin_setting.dart b/mobile/lib/ui/settings/TEMP/lock_screen_option_pin_setting.dart new file mode 100644 index 0000000000..e5d940d36d --- /dev/null +++ b/mobile/lib/ui/settings/TEMP/lock_screen_option_pin_setting.dart @@ -0,0 +1,17 @@ +import "package:flutter/widgets.dart"; + +class LockScreenOptionPinSetting extends StatefulWidget { + const LockScreenOptionPinSetting({super.key}); + + @override + State createState() => + _LockScreenOptionPinSettingState(); +} + +class _LockScreenOptionPinSettingState + extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +}