Merge branch 'lockscreen_options' of https://github.com/ente-io/ente into lockscreen_options

This commit is contained in:
Aman Raj Singh Mourya
2024-07-03 13:13:34 +05:30
13 changed files with 323 additions and 533 deletions

View File

@@ -34,7 +34,7 @@ import 'package:photos/services/search_service.dart';
import 'package:photos/services/sync_service.dart'; import 'package:photos/services/sync_service.dart';
import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/file_uploader.dart'; import 'package:photos/utils/file_uploader.dart';
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lock_screen_settings.dart";
import 'package:photos/utils/validator_util.dart'; import 'package:photos/utils/validator_util.dart';
import "package:photos/utils/wakelock_util.dart"; import "package:photos/utils/wakelock_util.dart";
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@@ -73,9 +73,6 @@ class Configuration {
"has_selected_all_folders_for_backup"; "has_selected_all_folders_for_backup";
static const anonymousUserIDKey = "anonymous_user_id"; static const anonymousUserIDKey = "anonymous_user_id";
static const endPointKey = "endpoint"; static const endPointKey = "endpoint";
static const password = "user_pass";
static const pin = "user_pin";
static const saltKey = "user_salt";
static final _logger = Logger("Configuration"); static final _logger = Logger("Configuration");
String? _cachedToken; String? _cachedToken;
@@ -86,7 +83,6 @@ class Configuration {
late FlutterSecureStorage _secureStorage; late FlutterSecureStorage _secureStorage;
late String _tempDocumentsDirPath; late String _tempDocumentsDirPath;
late String _thumbnailCacheDirectory; late String _thumbnailCacheDirectory;
final LockscreenSetting _lockscreenSetting = LockscreenSetting.instance;
// 6th July 22: Remove this after 3 months. Hopefully, active users // 6th July 22: Remove this after 3 months. Hopefully, active users
// will migrate to newer version of the app, where shared media is stored // will migrate to newer version of the app, where shared media is stored
// on appSupport directory which OS won't clean up automatically // on appSupport directory which OS won't clean up automatically
@@ -624,8 +620,8 @@ class Configuration {
} }
Future<bool> shouldShowLockScreen() async { Future<bool> shouldShowLockScreen() async {
final bool isPin = await _lockscreenSetting.isPinSet(); final bool isPin = await LockScreenSettings.instance.isPinSet();
final bool isPass = await _lockscreenSetting.isPasswordSet(); final bool isPass = await LockScreenSettings.instance.isPasswordSet();
return isPin || isPass || shouldShowSystemLockScreen(); return isPin || isPass || shouldShowSystemLockScreen();
} }

View File

@@ -9,7 +9,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:flutter/rendering.dart"; import "package:flutter/rendering.dart";
import "package:flutter_displaymode/flutter_displaymode.dart"; import "package:flutter_displaymode/flutter_displaymode.dart";
import "package:flutter_secure_storage/flutter_secure_storage.dart";
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import "package:media_kit/media_kit.dart"; import "package:media_kit/media_kit.dart";
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@@ -54,7 +53,7 @@ import 'package:photos/utils/crypto_util.dart';
import "package:photos/utils/email_util.dart"; import "package:photos/utils/email_util.dart";
import 'package:photos/utils/file_uploader.dart'; import 'package:photos/utils/file_uploader.dart';
import 'package:photos/utils/local_settings.dart'; import 'package:photos/utils/local_settings.dart';
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lock_screen_settings.dart";
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
final _logger = Logger("main"); final _logger = Logger("main");
@@ -197,7 +196,6 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
_isProcessRunning = true; _isProcessRunning = true;
_logger.info("Initializing... inBG =$isBackground via: $via"); _logger.info("Initializing... inBG =$isBackground via: $via");
final SharedPreferences preferences = await SharedPreferences.getInstance(); final SharedPreferences preferences = await SharedPreferences.getInstance();
const secureStorage = FlutterSecureStorage();
await _logFGHeartBeatInfo(); await _logFGHeartBeatInfo();
_logger.info("_logFGHeartBeatInfo done"); _logger.info("_logFGHeartBeatInfo done");
unawaited(_scheduleHeartBeat(preferences, isBackground)); unawaited(_scheduleHeartBeat(preferences, isBackground));
@@ -212,7 +210,7 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
CryptoUtil.init(); CryptoUtil.init();
_logger.info("Lockscreen init"); _logger.info("Lockscreen init");
LockscreenSetting.instance.init(secureStorage, preferences); LockScreenSettings.instance.init(preferences);
_logger.info("Configuration init"); _logger.info("Configuration init");
await Configuration.instance.init(); await Configuration.instance.init();

View File

@@ -3,8 +3,8 @@ import "dart:async";
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import "package:photos/ui/settings/lockscreen/lockscreen_password.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_password.dart";
import "package:photos/ui/settings/lockscreen/lockscreen_pin.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_pin.dart";
import 'package:photos/ui/tools/app_lock.dart'; import 'package:photos/ui/tools/app_lock.dart';
import 'package:photos/utils/auth_util.dart'; import 'package:photos/utils/auth_util.dart';
import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/dialog_util.dart';
@@ -39,7 +39,7 @@ class LocalAuthenticationService {
BuildContext context, BuildContext context,
String? savedPin, String? savedPin,
String? savedPassword, { String? savedPassword, {
bool isLockscreenAuth = false, bool isOnOpeningApp = false,
}) async { }) async {
if (savedPassword != null) { if (savedPassword != null) {
final result = await Navigator.of(context).push( final result = await Navigator.of(context).push(
@@ -47,7 +47,7 @@ class LocalAuthenticationService {
builder: (BuildContext context) { builder: (BuildContext context) {
return LockScreenPassword( return LockScreenPassword(
isAuthenticating: true, isAuthenticating: true,
isLockscreenAuth: isLockscreenAuth, isOnOpeningApp: isOnOpeningApp,
authPass: savedPassword, authPass: savedPassword,
); );
}, },
@@ -63,7 +63,7 @@ class LocalAuthenticationService {
builder: (BuildContext context) { builder: (BuildContext context) {
return LockScreenPin( return LockScreenPin(
isAuthenticating: true, isAuthenticating: true,
isLockscreenAuth: isLockscreenAuth, isOnOpeningApp: isOnOpeningApp,
authPin: savedPin, authPin: savedPin,
); );
}, },

View File

@@ -0,0 +1,196 @@
import "package:flutter/material.dart";
import "package:photos/theme/ente_theme.dart";
class CustomPinKeypad extends StatelessWidget {
final TextEditingController controller;
const CustomPinKeypad({required this.controller, super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
padding: const EdgeInsets.all(2),
color: getEnteColorScheme(context).strokeFainter,
child: Column(
children: [
Row(
children: [
_Button(
text: '',
number: '1',
onTap: () {
_onKeyTap('1');
},
),
_Button(
text: "ABC",
number: '2',
onTap: () {
_onKeyTap('2');
},
),
_Button(
text: "DEF",
number: '3',
onTap: () {
_onKeyTap('3');
},
),
],
),
Row(
children: [
_Button(
number: '4',
text: "GHI",
onTap: () {
_onKeyTap('4');
},
),
_Button(
number: '5',
text: 'JKL',
onTap: () {
_onKeyTap('5');
},
),
_Button(
number: '6',
text: 'MNO',
onTap: () {
_onKeyTap('6');
},
),
],
),
Row(
children: [
_Button(
number: '7',
text: 'PQRS',
onTap: () {
_onKeyTap('7');
},
),
_Button(
number: '8',
text: 'TUV',
onTap: () {
_onKeyTap('8');
},
),
_Button(
number: '9',
text: 'WXYZ',
onTap: () {
_onKeyTap('9');
},
),
],
),
Row(
children: [
const _Button(
number: '',
text: '',
muteButton: true,
onTap: null,
),
_Button(
number: '0',
text: '',
onTap: () {
_onKeyTap('0');
},
),
_Button(
number: '',
text: '',
icon: const Icon(Icons.backspace_outlined),
onTap: () {
_onBackspace();
},
),
],
),
],
),
),
);
}
void _onKeyTap(String number) {
controller.text += number;
return;
}
void _onBackspace() {
if (controller.text.isNotEmpty) {
controller.text =
controller.text.substring(0, controller.text.length - 1);
}
return;
}
}
class _Button extends StatelessWidget {
final String number;
final String text;
final VoidCallback? onTap;
final bool muteButton;
final Widget? icon;
const _Button({
required this.number,
required this.text,
this.muteButton = false,
required this.onTap,
this.icon,
});
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textTheme = getEnteTextTheme(context);
return Expanded(
child: GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6),
color: muteButton
? colorScheme.fillFaintPressed
: icon == null
? colorScheme.backgroundElevated2
: null,
),
child: Center(
child: muteButton
? const SizedBox.shrink()
: icon != null
? Container(
child: icon,
)
: Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
number,
style: textTheme.h3,
),
Text(
text,
style: textTheme.tinyBold,
),
],
),
),
),
),
),
);
}
}

View File

@@ -5,7 +5,7 @@ import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/common/dynamic_fab.dart"; import "package:photos/ui/common/dynamic_fab.dart";
import "package:photos/ui/components/buttons/icon_button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart";
import "package:photos/ui/components/text_input_widget.dart"; import "package:photos/ui/components/text_input_widget.dart";
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lock_screen_settings.dart";
class LockScreenConfirmPassword extends StatefulWidget { class LockScreenConfirmPassword extends StatefulWidget {
const LockScreenConfirmPassword({ const LockScreenConfirmPassword({
@@ -23,7 +23,7 @@ class _LockScreenConfirmPasswordState extends State<LockScreenConfirmPassword> {
/// _confirmPasswordController is disposed by the [TextInputWidget] /// _confirmPasswordController is disposed by the [TextInputWidget]
final _confirmPasswordController = TextEditingController(text: null); final _confirmPasswordController = TextEditingController(text: null);
final LockscreenSetting _lockscreenSetting = LockscreenSetting.instance; final LockScreenSettings _lockscreenSetting = LockScreenSettings.instance;
final _focusNode = FocusNode(); final _focusNode = FocusNode();
final _isFormValid = ValueNotifier<bool>(false); final _isFormValid = ValueNotifier<bool>(false);
final _submitNotifier = ValueNotifier(false); final _submitNotifier = ValueNotifier(false);

View File

@@ -1,10 +1,9 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter/services.dart"; import "package:flutter/services.dart";
import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart"; import "package:photos/theme/ente_theme.dart";
import "package:photos/theme/text_style.dart";
import "package:photos/ui/components/buttons/icon_button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart";
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/ui/settings/lock_screen/custom_pin_keypad.dart";
import "package:photos/utils/lock_screen_settings.dart";
import "package:pinput/pinput.dart"; import "package:pinput/pinput.dart";
class LockScreenConfirmPin extends StatefulWidget { class LockScreenConfirmPin extends StatefulWidget {
@@ -17,7 +16,8 @@ class LockScreenConfirmPin extends StatefulWidget {
class _LockScreenConfirmPinState extends State<LockScreenConfirmPin> { class _LockScreenConfirmPinState extends State<LockScreenConfirmPin> {
final _confirmPinController = TextEditingController(text: null); final _confirmPinController = TextEditingController(text: null);
bool isConfirmPinValid = false; bool isConfirmPinValid = false;
final LockscreenSetting _lockscreenSetting = LockscreenSetting.instance;
final LockScreenSettings _lockscreenSetting = LockScreenSettings.instance;
final _pinPutDecoration = PinTheme( final _pinPutDecoration = PinTheme(
height: 48, height: 48,
width: 48, width: 48,
@@ -34,19 +34,6 @@ class _LockScreenConfirmPinState extends State<LockScreenConfirmPin> {
_confirmPinController.dispose(); _confirmPinController.dispose();
} }
void _onKeyTap(String number) {
_confirmPinController.text += number;
return;
}
void _onBackspace() {
if (_confirmPinController.text.isNotEmpty) {
_confirmPinController.text = _confirmPinController.text
.substring(0, _confirmPinController.text.length - 1);
}
return;
}
Future<void> _confirmPinMatch() async { Future<void> _confirmPinMatch() async {
if (widget.pin == _confirmPinController.text) { if (widget.pin == _confirmPinController.text) {
await _lockscreenSetting.setPin(_confirmPinController.text); await _lockscreenSetting.setPin(_confirmPinController.text);
@@ -214,197 +201,9 @@ class _LockScreenConfirmPinState extends State<LockScreenConfirmPin> {
isPortrait isPortrait
? const Spacer() ? const Spacer()
: const Padding(padding: EdgeInsets.all(12)), : const Padding(padding: EdgeInsets.all(12)),
customKeyPad(colorTheme, textTheme), CustomPinKeypad(controller: _confirmPinController),
], ],
), ),
); );
} }
Widget customKeyPad(EnteColorScheme colorTheme, EnteTextTheme textTheme) {
return SafeArea(
child: Container(
padding: const EdgeInsets.all(2),
color: colorTheme.strokeFainter,
child: Column(
children: [
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
text: '',
number: '1',
onTap: () {
_onKeyTap('1');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
text: "ABC",
number: '2',
onTap: () {
_onKeyTap('2');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
text: "DEF",
number: '3',
onTap: () {
_onKeyTap('3');
},
),
],
),
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '4',
text: "GHI",
onTap: () {
_onKeyTap('4');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '5',
text: 'JKL',
onTap: () {
_onKeyTap('5');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '6',
text: 'MNO',
onTap: () {
_onKeyTap('6');
},
),
],
),
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '7',
text: 'PQRS',
onTap: () {
_onKeyTap('7');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '8',
text: 'TUV',
onTap: () {
_onKeyTap('8');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '9',
text: 'WXYZ',
onTap: () {
_onKeyTap('9');
},
),
],
),
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '',
text: '',
muteButton: true,
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '0',
text: '',
onTap: () {
_onKeyTap('0');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '',
text: '',
icons: const Icon(Icons.backspace_outlined),
onTap: () {
_onBackspace();
},
),
],
),
],
),
),
);
}
Widget buttonWidget({
colorTheme,
textTheme,
text,
number,
muteButton = false,
icons,
onTap,
}) {
return Expanded(
child: GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6),
color: muteButton
? colorTheme.fillFaintPressed
: icons == null
? colorTheme.backgroundElevated2
: null,
),
child: Center(
child: muteButton
? Container()
: icons != null
? Container(
child: icons,
)
: Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
number,
style: textTheme.h3,
),
Text(
text,
style: textTheme.small,
),
],
),
),
),
),
),
);
}
} }

View File

@@ -7,21 +7,21 @@ import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
import "package:photos/ui/components/title_bar_title_widget.dart"; import "package:photos/ui/components/title_bar_title_widget.dart";
import "package:photos/ui/components/title_bar_widget.dart"; import "package:photos/ui/components/title_bar_widget.dart";
import "package:photos/ui/components/toggle_switch_widget.dart"; import "package:photos/ui/components/toggle_switch_widget.dart";
import "package:photos/ui/settings/lockscreen/lockscreen_password.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_password.dart";
import "package:photos/ui/settings/lockscreen/lockscreen_pin.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_pin.dart";
import "package:photos/ui/tools/app_lock.dart"; import "package:photos/ui/tools/app_lock.dart";
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lock_screen_settings.dart";
class LockScreenOption extends StatefulWidget { class LockScreenOptions extends StatefulWidget {
const LockScreenOption({super.key}); const LockScreenOptions({super.key});
@override @override
State<LockScreenOption> createState() => _LockScreenOptionState(); State<LockScreenOptions> createState() => _LockScreenOptionsState();
} }
class _LockScreenOptionState extends State<LockScreenOption> { class _LockScreenOptionsState extends State<LockScreenOptions> {
final Configuration _configuration = Configuration.instance; final Configuration _configuration = Configuration.instance;
final LockscreenSetting _lockscreenSetting = LockscreenSetting.instance; final LockScreenSettings _lockscreenSetting = LockScreenSettings.instance;
late bool appLock; late bool appLock;
bool isPinEnabled = false; bool isPinEnabled = false;
bool isPasswordEnabled = false; bool isPasswordEnabled = false;

View File

@@ -8,23 +8,22 @@ import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/common/dynamic_fab.dart"; import "package:photos/ui/common/dynamic_fab.dart";
import "package:photos/ui/components/buttons/icon_button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart";
import "package:photos/ui/components/text_input_widget.dart"; import "package:photos/ui/components/text_input_widget.dart";
import "package:photos/ui/settings/lockscreen/lock_screen_option.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_confirm_password.dart";
import "package:photos/ui/settings/lockscreen/lockscreen_confirm_password.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_options.dart";
import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/crypto_util.dart";
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lock_screen_settings.dart";
class LockScreenPassword extends StatefulWidget { class LockScreenPassword extends StatefulWidget {
const LockScreenPassword({ const LockScreenPassword({
super.key, super.key,
this.isAuthenticating = false, this.isAuthenticating = false,
this.isLockscreenAuth = false, this.isOnOpeningApp = false,
this.authPass, this.authPass,
}); });
/// If [isLockscreenAuth] is true then we are authenticating the user at Lock screen //Is false when setting a new password
/// If [isAuthenticating] is true then we are authenticating the user at Setting screen
final bool isAuthenticating; final bool isAuthenticating;
final bool isLockscreenAuth; final bool isOnOpeningApp;
final String? authPass; final String? authPass;
@override @override
State<LockScreenPassword> createState() => _LockScreenPasswordState(); State<LockScreenPassword> createState() => _LockScreenPasswordState();
@@ -38,8 +37,7 @@ class _LockScreenPasswordState extends State<LockScreenPassword> {
final _submitNotifier = ValueNotifier(false); final _submitNotifier = ValueNotifier(false);
int invalidAttemptsCount = 0; int invalidAttemptsCount = 0;
final LockscreenSetting _lockscreenSetting = LockscreenSetting.instance; final _lockscreenSetting = LockScreenSettings.instance;
late String enteredHashedPassword;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -57,57 +55,6 @@ class _LockScreenPasswordState extends State<LockScreenPassword> {
_isFormValid.dispose(); _isFormValid.dispose();
} }
Future<bool> confirmPasswordAuth(String code) async {
final Uint8List? salt = await _lockscreenSetting.getSalt();
final hash = cryptoPwHash({
"password": utf8.encode(code),
"salt": salt,
"opsLimit": Sodium.cryptoPwhashOpslimitInteractive,
"memLimit": Sodium.cryptoPwhashMemlimitInteractive,
});
enteredHashedPassword = base64Encode(hash);
if (widget.authPass == enteredHashedPassword) {
await _lockscreenSetting.setInvalidAttemptCount(0);
widget.isLockscreenAuth
? Navigator.of(context).pop(true)
: Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const LockScreenOption(),
),
);
return true;
} else {
if (widget.isLockscreenAuth) {
invalidAttemptsCount++;
if (invalidAttemptsCount > 4) {
await _lockscreenSetting.setInvalidAttemptCount(invalidAttemptsCount);
Navigator.of(context).pop(false);
}
}
await HapticFeedback.vibrate();
throw Exception("Incorrect password");
}
}
Future<void> _confirmPassword() async {
if (widget.isAuthenticating) {
await confirmPasswordAuth(_passwordController.text);
return;
} else {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => LockScreenConfirmPassword(
password: _passwordController.text,
),
),
);
_passwordController.clear();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorTheme = getEnteColorScheme(context); final colorTheme = getEnteColorScheme(context);
@@ -239,4 +186,54 @@ class _LockScreenPasswordState extends State<LockScreenPassword> {
), ),
); );
} }
Future<bool> _confirmPasswordAuth(String inputtedPassword) async {
final Uint8List? salt = await _lockscreenSetting.getSalt();
final hash = cryptoPwHash({
"password": utf8.encode(inputtedPassword),
"salt": salt,
"opsLimit": Sodium.cryptoPwhashOpslimitInteractive,
"memLimit": Sodium.cryptoPwhashMemlimitInteractive,
});
if (widget.authPass == base64Encode(hash)) {
await _lockscreenSetting.setInvalidAttemptCount(0);
widget.isOnOpeningApp
? Navigator.of(context).pop(true)
: Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const LockScreenOptions(),
),
);
return true;
} else {
if (widget.isOnOpeningApp) {
invalidAttemptsCount++;
if (invalidAttemptsCount > 4) {
await _lockscreenSetting.setInvalidAttemptCount(invalidAttemptsCount);
Navigator.of(context).pop(false);
}
}
await HapticFeedback.vibrate();
throw Exception("Incorrect password");
}
}
Future<void> _confirmPassword() async {
if (widget.isAuthenticating) {
await _confirmPasswordAuth(_passwordController.text);
return;
} else {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => LockScreenConfirmPassword(
password: _passwordController.text,
),
),
);
_passwordController.clear();
}
}
} }

View File

@@ -7,24 +7,24 @@ import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart"; import "package:photos/theme/ente_theme.dart";
import "package:photos/theme/text_style.dart"; import "package:photos/theme/text_style.dart";
import "package:photos/ui/components/buttons/icon_button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart";
import "package:photos/ui/settings/lockscreen/lock_screen_option.dart"; import "package:photos/ui/settings/lock_screen/custom_pin_keypad.dart";
import "package:photos/ui/settings/lockscreen/lockscreen_confirm_pin.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_confirm_pin.dart";
import "package:photos/ui/settings/lock_screen/lock_screen_options.dart";
import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/crypto_util.dart";
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lock_screen_settings.dart";
import 'package:pinput/pinput.dart'; import 'package:pinput/pinput.dart';
class LockScreenPin extends StatefulWidget { class LockScreenPin extends StatefulWidget {
const LockScreenPin({ const LockScreenPin({
super.key, super.key,
this.isAuthenticating = false, this.isAuthenticating = false,
this.isLockscreenAuth = false, this.isOnOpeningApp = false,
this.authPin, this.authPin,
}); });
/// If [isLockscreenAuth] is true then we are authenticating the user at the Lock screen //Is false when setting a new password
/// If [isAuthenticating] is true then we are authenticating the user at the Setting screen
final bool isAuthenticating; final bool isAuthenticating;
final bool isLockscreenAuth; final bool isOnOpeningApp;
final String? authPin; final String? authPin;
@override @override
State<LockScreenPin> createState() => _LockScreenPinState(); State<LockScreenPin> createState() => _LockScreenPinState();
@@ -33,8 +33,7 @@ class LockScreenPin extends StatefulWidget {
class _LockScreenPinState extends State<LockScreenPin> { class _LockScreenPinState extends State<LockScreenPin> {
final _pinController = TextEditingController(text: null); final _pinController = TextEditingController(text: null);
final LockscreenSetting _lockscreenSetting = LockscreenSetting.instance; final LockScreenSettings _lockscreenSetting = LockScreenSettings.instance;
late String enteredHashedPin;
bool isPinValid = false; bool isPinValid = false;
int invalidAttemptsCount = 0; int invalidAttemptsCount = 0;
@@ -50,19 +49,6 @@ class _LockScreenPinState extends State<LockScreenPin> {
_pinController.dispose(); _pinController.dispose();
} }
void _onKeyTap(String number) {
_pinController.text += number;
return;
}
void _onBackspace() {
if (_pinController.text.isNotEmpty) {
_pinController.text =
_pinController.text.substring(0, _pinController.text.length - 1);
}
return;
}
Future<bool> confirmPinAuth(String code) async { Future<bool> confirmPinAuth(String code) async {
final Uint8List? salt = await _lockscreenSetting.getSalt(); final Uint8List? salt = await _lockscreenSetting.getSalt();
final hash = cryptoPwHash({ final hash = cryptoPwHash({
@@ -72,15 +58,14 @@ class _LockScreenPinState extends State<LockScreenPin> {
"memLimit": Sodium.cryptoPwhashMemlimitInteractive, "memLimit": Sodium.cryptoPwhashMemlimitInteractive,
}); });
enteredHashedPin = base64Encode(hash); if (widget.authPin == base64Encode(hash)) {
if (widget.authPin == enteredHashedPin) {
invalidAttemptsCount = 0; invalidAttemptsCount = 0;
await _lockscreenSetting.setInvalidAttemptCount(0); await _lockscreenSetting.setInvalidAttemptCount(0);
widget.isLockscreenAuth widget.isOnOpeningApp
? Navigator.of(context).pop(true) ? Navigator.of(context).pop(true)
: Navigator.of(context).pushReplacement( : Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const LockScreenOption(), builder: (context) => const LockScreenOptions(),
), ),
); );
return true; return true;
@@ -95,7 +80,7 @@ class _LockScreenPinState extends State<LockScreenPin> {
isPinValid = false; isPinValid = false;
}); });
if (widget.isLockscreenAuth) { if (widget.isOnOpeningApp) {
invalidAttemptsCount++; invalidAttemptsCount++;
if (invalidAttemptsCount > 4) { if (invalidAttemptsCount > 4) {
await _lockscreenSetting.setInvalidAttemptCount(invalidAttemptsCount); await _lockscreenSetting.setInvalidAttemptCount(invalidAttemptsCount);
@@ -282,197 +267,9 @@ class _LockScreenPinState extends State<LockScreenPin> {
isPortrait isPortrait
? const Spacer() ? const Spacer()
: const Padding(padding: EdgeInsets.all(12)), : const Padding(padding: EdgeInsets.all(12)),
customKeyPad(colorTheme, textTheme), CustomPinKeypad(controller: _pinController),
], ],
), ),
); );
} }
Widget customKeyPad(EnteColorScheme colorTheme, EnteTextTheme textTheme) {
return SafeArea(
child: Container(
padding: const EdgeInsets.all(2),
color: colorTheme.strokeFainter,
child: Column(
children: [
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
text: '',
number: '1',
onTap: () {
_onKeyTap('1');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
text: "ABC",
number: '2',
onTap: () {
_onKeyTap('2');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
text: "DEF",
number: '3',
onTap: () {
_onKeyTap('3');
},
),
],
),
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '4',
text: "GHI",
onTap: () {
_onKeyTap('4');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '5',
text: 'JKL',
onTap: () {
_onKeyTap('5');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '6',
text: 'MNO',
onTap: () {
_onKeyTap('6');
},
),
],
),
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '7',
text: 'PQRS',
onTap: () {
_onKeyTap('7');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '8',
text: 'TUV',
onTap: () {
_onKeyTap('8');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '9',
text: 'WXYZ',
onTap: () {
_onKeyTap('9');
},
),
],
),
Row(
children: [
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '',
text: '',
muteButton: true,
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '0',
text: '',
onTap: () {
_onKeyTap('0');
},
),
buttonWidget(
colorTheme: colorTheme,
textTheme: textTheme,
number: '',
text: '',
icons: const Icon(Icons.backspace_outlined),
onTap: () {
_onBackspace();
},
),
],
),
],
),
),
);
}
Widget buttonWidget({
colorTheme,
textTheme,
text,
number,
muteButton = false,
icons,
onTap,
}) {
return Expanded(
child: GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6),
color: muteButton
? colorTheme.fillFaintPressed
: icons == null
? colorTheme.backgroundElevated2
: null,
),
child: Center(
child: muteButton
? Container()
: icons != null
? Container(
child: icons,
)
: Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
number,
style: textTheme.h3,
),
Text(
text,
style: textTheme.miniBold,
),
],
),
),
),
),
),
);
}
} }

View File

@@ -21,7 +21,7 @@ import 'package:photos/ui/components/expandable_menu_item_widget.dart';
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
import 'package:photos/ui/components/toggle_switch_widget.dart'; import 'package:photos/ui/components/toggle_switch_widget.dart';
import 'package:photos/ui/settings/common_settings.dart'; import 'package:photos/ui/settings/common_settings.dart';
import "package:photos/ui/settings/lockscreen/lock_screen_option.dart"; import "package:photos/ui/settings/lock_screen/lock_screen_options.dart";
import "package:photos/utils/auth_util.dart"; import "package:photos/utils/auth_util.dart";
import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/crypto_util.dart";
import "package:photos/utils/dialog_util.dart"; import "package:photos/utils/dialog_util.dart";
@@ -154,7 +154,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
await Navigator.of(context).push( await Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
return const LockScreenOption(); return const LockScreenOptions();
}, },
), ),
); );

View File

@@ -13,7 +13,6 @@ import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/buttons/icon_button_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart";
import 'package:photos/ui/tools/app_lock.dart'; import 'package:photos/ui/tools/app_lock.dart';
import 'package:photos/utils/auth_util.dart'; import 'package:photos/utils/auth_util.dart';
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lockscreen_setting.dart";
class LockScreen extends StatefulWidget { class LockScreen extends StatefulWidget {
@@ -34,7 +33,16 @@ class _LockScreenState extends State<LockScreen>
int lockedTime = 0; int lockedTime = 0;
int invalidAttemptCount = 0; int invalidAttemptCount = 0;
int remainingTime = 0; int remainingTime = 0;
bool showErrorMessage = true;
final _lockscreenSetting = LockscreenSetting.instance; final _lockscreenSetting = LockscreenSetting.instance;
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
late final animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
@override @override
void initState() { void initState() {
@@ -169,7 +177,7 @@ class _LockScreenState extends State<LockScreen>
if (Platform.isAndroid) { if (Platform.isAndroid) {
return false; return false;
} }
final shortestSide = MediaQuery.of(context).size.shortestSide; final shortestSide = MediaQuery.sizeOf(context).shortestSide;
return shortestSide > 600 ? true : false; return shortestSide > 600 ? true : false;
} }
@@ -311,7 +319,7 @@ class _LockScreenState extends State<LockScreen>
: await requestAuthentication( : await requestAuthentication(
context, context,
context.l10n.authToViewYourMemories, context.l10n.authToViewYourMemories,
isLockscreenAuth: true, isOpeningApp: true,
); );
_logger.finest("LockScreen Result $result $id"); _logger.finest("LockScreen Result $result $id");
_isShowingLockScreen = false; _isShowingLockScreen = false;

View File

@@ -5,26 +5,25 @@ import 'package:local_auth_ios/local_auth_ios.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import "package:photos/generated/l10n.dart"; import "package:photos/generated/l10n.dart";
import "package:photos/services/local_authentication_service.dart"; import "package:photos/services/local_authentication_service.dart";
import "package:photos/utils/lockscreen_setting.dart"; import "package:photos/utils/lock_screen_settings.dart";
Future<bool> requestAuthentication( Future<bool> requestAuthentication(
BuildContext context, BuildContext context,
String reason, { String reason, {
bool isLockscreenAuth = false, bool isOpeningApp = false,
}) async { }) async {
Logger("AuthUtil").info("Requesting authentication"); Logger("AuthUtil").info("Requesting authentication");
await LocalAuthentication().stopAuthentication(); await LocalAuthentication().stopAuthentication();
final LockscreenSetting lockscreenSetting = LockscreenSetting.instance; final String? savedPin = await LockScreenSettings.instance.getPin();
final String? savedPin = await lockscreenSetting.getPin(); final String? savedPassword = await LockScreenSettings.instance.getPassword();
final String? savedPassword = await lockscreenSetting.getPassword();
if (savedPassword != null || savedPin != null) { if (savedPassword != null || savedPin != null) {
return await LocalAuthenticationService.instance return await LocalAuthenticationService.instance
.requestEnteAuthForLockScreen( .requestEnteAuthForLockScreen(
context, context,
savedPin, savedPin,
savedPassword, savedPassword,
isLockscreenAuth: isLockscreenAuth, isOnOpeningApp: isOpeningApp,
); );
} else { } else {
return await LocalAuthentication().authenticate( return await LocalAuthentication().authenticate(

View File

@@ -6,21 +6,21 @@ import "package:flutter_sodium/flutter_sodium.dart";
import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/crypto_util.dart";
import "package:shared_preferences/shared_preferences.dart"; import "package:shared_preferences/shared_preferences.dart";
class LockscreenSetting { class LockScreenSettings {
LockscreenSetting._privateConstructor(); LockScreenSettings._privateConstructor();
static final LockscreenSetting instance = static final LockScreenSettings instance =
LockscreenSetting._privateConstructor(); LockScreenSettings._privateConstructor();
static const password = "user_pass"; static const password = "ls_password";
static const pin = "user_pin"; static const pin = "ls_pin";
static const saltKey = "user_salt"; static const saltKey = "ls_salt";
static const keyInvalidAttempts = "invalid_attempts"; static const keyInvalidAttempts = "ls_invalid_attempts";
static const lastInvalidAttemptTime = "last_invalid_attempt_time"; static const lastInvalidAttemptTime = "ls_last_invalid_attempt_time";
late FlutterSecureStorage _secureStorage; late FlutterSecureStorage _secureStorage;
late SharedPreferences _preferences; late SharedPreferences _preferences;
void init(FlutterSecureStorage secureStorage, SharedPreferences prefs) async { void init(SharedPreferences prefs) async {
_secureStorage = secureStorage; _secureStorage = const FlutterSecureStorage();
_preferences = prefs; _preferences = prefs;
} }
@@ -40,14 +40,14 @@ class LockscreenSetting {
await _preferences.setInt(keyInvalidAttempts, count); await _preferences.setInt(keyInvalidAttempts, count);
} }
static Uint8List generateSalt() { static Uint8List _generateSalt() {
return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes); return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes);
} }
Future<void> setPin(String userPin) async { Future<void> setPin(String userPin) async {
await _secureStorage.delete(key: saltKey); await _secureStorage.delete(key: saltKey);
final salt = generateSalt(); final salt = _generateSalt();
final hash = cryptoPwHash({ final hash = cryptoPwHash({
"password": utf8.encode(userPin), "password": utf8.encode(userPin),
"salt": salt, "salt": salt,
@@ -78,7 +78,7 @@ class LockscreenSetting {
Future<void> setPassword(String pass) async { Future<void> setPassword(String pass) async {
await _secureStorage.delete(key: saltKey); await _secureStorage.delete(key: saltKey);
final salt = generateSalt(); final salt = _generateSalt();
final hash = cryptoPwHash({ final hash = cryptoPwHash({
"password": utf8.encode(pass), "password": utf8.encode(pass),
"salt": salt, "salt": salt,