import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:local_auth/local_auth.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/controller/pref_controller.dart'; import 'package:nc_photos/widget/protected_page_password_auth_dialog.dart'; import 'package:nc_photos/widget/protected_page_pin_auth_dialog.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:np_string/np_string.dart'; part 'protected_page_handler.g.dart'; enum ProtectedPageAuthType { biometric, pin, password, ; } class ProtectedPageAuthException implements Exception { const ProtectedPageAuthException([this.message]); @override String toString() => "ProtectedPageAuthException: $message"; final dynamic message; } extension ProtectedPageBuildContextExtension on NavigatorState { Future pushReplacementNamedProtected( String routeName, { U? result, Object? arguments, }) async { if (await _auth()) { return pushReplacementNamed(routeName, arguments: arguments, result: result); } else { throw const ProtectedPageAuthException(); } } Future pushNamedProtected( String routeName, { Object? arguments, }) async { if (await _auth()) { return pushNamed(routeName, arguments: arguments); } else { throw const ProtectedPageAuthException(); } } Future pushProtected(Route route) async { if (await _auth()) { return push(route); } else { throw const ProtectedPageAuthException(); } } Future _auth() async { final securePrefController = context.read(); switch (securePrefController.protectedPageAuthTypeValue) { case null: // unprotected return true; case ProtectedPageAuthType.biometric: return _authBiometric(securePrefController); case ProtectedPageAuthType.pin: return _authPin(securePrefController); case ProtectedPageAuthType.password: return _authPassword(securePrefController); } } Future _authBiometric(SecurePrefController securePrefController) async { if (await _BiometricAuthHandler().auth()) { return true; } else { if (securePrefController.protectedPageAuthPasswordValue != null) { return _authPassword(securePrefController); } else { return _authPin(securePrefController); } } } Future _authPin(SecurePrefController securePrefController) => _PinAuthHandler(context, securePrefController.protectedPageAuthPinValue!) .auth(); Future _authPassword(SecurePrefController securePrefController) => _PasswordAuthHandler( context, securePrefController.protectedPageAuthPasswordValue!) .auth(); } abstract class _AuthHandler { Future auth(); } @npLog class _BiometricAuthHandler implements _AuthHandler { @override Future auth() async { try { final localAuth = LocalAuthentication(); final available = await localAuth.getAvailableBiometrics(); if (available.isEmpty) { return false; } return await localAuth.authenticate( localizedReason: L10n.global().appLockUnlockHint, options: const AuthenticationOptions( biometricOnly: true, ), ); } catch (e, stackTrace) { _log.severe("[auth] Exception", e, stackTrace); return false; } } } class _PinAuthHandler implements _AuthHandler { const _PinAuthHandler(this.context, this.pin); @override Future auth() async { final result = await showDialog( context: context, builder: (context) => ProtectedPagePinAuthDialog(pin: pin), ); return result == true; } final BuildContext context; final CiString pin; } class _PasswordAuthHandler implements _AuthHandler { const _PasswordAuthHandler(this.context, this.password); @override Future auth() async { final result = await showDialog( context: context, builder: (context) => ProtectedPagePasswordAuthDialog(password: password), ); return result == true; } final BuildContext context; final CiString password; }