Skip to content

Commit

Permalink
Support biometric storage
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert-Stackflow committed Sep 7, 2024
1 parent fd645c5 commit 5e2e44d
Show file tree
Hide file tree
Showing 64 changed files with 3,921 additions and 501 deletions.
3 changes: 3 additions & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
5 changes: 5 additions & 0 deletions lib/Database/database_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class DatabaseManager {
return _database!;
}

static resetDatabase() async {
await _database?.close();
_database = null;
}

static Future<void> initDataBase(String password) async {
if (_database == null) {
appProvider.currentDatabasePassword = password;
Expand Down
128 changes: 84 additions & 44 deletions lib/Screens/Lock/database_decrypt_screen.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import 'dart:math';

import 'package:biometric_storage/biometric_storage.dart';
import 'package:cloudotp/Resources/theme.dart';
import 'package:cloudotp/Utils/itoast.dart';
import 'package:cloudotp/Utils/route_util.dart';
import 'package:cloudotp/Widgets/Dialog/custom_dialog.dart';
import 'package:cloudotp/Widgets/Item/item_builder.dart';
import 'package:cloudotp/Widgets/Scaffold/my_scaffold.dart';
import 'package:flutter/material.dart';
import 'package:local_auth/local_auth.dart';
import 'package:window_manager/window_manager.dart';

import '../../Database/database_manager.dart';
import '../../Utils/app_provider.dart';
import '../../Utils/biometric_util.dart';
import '../../Utils/constant.dart';
import '../../Utils/hive_util.dart';
Expand All @@ -34,27 +36,69 @@ class DatabaseDecryptScreenState extends State<DatabaseDecryptScreen>
GlobalKey<FormState> formKey = GlobalKey<FormState>();
bool _isMaximized = false;
bool _isStayOnTop = false;
bool _biometricAvailable = false;
final bool _enableDatabaseBiometric = HiveUtil.getBool(
HiveUtil.enableDatabaseBiometricKey,
defaultValue: false);
bool _isValidated = true;
final bool _allowDatabaseBiometric =
HiveUtil.getBool(HiveUtil.allowDatabaseBiometricKey, defaultValue: false);
String? canAuthenticateResponseString;
CanAuthenticateResponse? canAuthenticateResponse;

bool get _biometricAvailable => canAuthenticateResponse?.isSuccess ?? false;

auth() async {
String? password = await BiometricUtil.getDatabasePassword();
if (password != null && password.isNotEmpty) {
validateAsyncController.controller.text = password;
onSubmit();
try {
canAuthenticateResponse = await BiometricUtil.canAuthenticate();
canAuthenticateResponseString =
await BiometricUtil.getCanAuthenticateResponseString();
if (canAuthenticateResponse == CanAuthenticateResponse.success) {
String? password = await BiometricUtil.getDatabasePassword();
if (password == null) {
setState(() {
_isValidated = false;
HiveUtil.put(HiveUtil.allowDatabaseBiometricKey, false);
});
IToast.showTop(S.current.biometricChanged);
FocusScope.of(context).requestFocus(_focusNode);
}
if (password != null && password.isNotEmpty) {
validateAsyncController.controller.text = password;
onSubmit();
}
} else {
IToast.showTop(canAuthenticateResponseString ?? "");
}
} catch (e, t) {
ILogger.error("Failed to authenticate with biometric", e, t);
if (e is AuthException) {
switch (e.code) {
case AuthExceptionCode.userCanceled:
IToast.showTop(S.current.biometricUserCanceled);
break;
case AuthExceptionCode.timeout:
IToast.showTop(S.current.biometricTimeout);
break;
case AuthExceptionCode.unknown:
IToast.showTop(S.current.biometricLockout);
break;
case AuthExceptionCode.canceled:
default:
IToast.showTop(S.current.biometricError);
break;
}
} else {
IToast.showTop(S.current.biometricError);
}
}
}

initBiometricAuthentication() async {
LocalAuthentication localAuth = LocalAuthentication();
bool available = await localAuth.canCheckBiometrics;
setState(() {
_biometricAvailable = available;
});
if (_biometricAvailable && _enableDatabaseBiometric) {
canAuthenticateResponse = await BiometricUtil.canAuthenticate();
canAuthenticateResponseString =
await BiometricUtil.getCanAuthenticateResponseString();
setState(() {});
if (_biometricAvailable && _allowDatabaseBiometric) {
auth();
} else {
FocusScope.of(context).requestFocus(_focusNode);
}
}

Expand Down Expand Up @@ -95,9 +139,6 @@ class DatabaseDecryptScreenState extends State<DatabaseDecryptScreen>
super.initState();
initBiometricAuthentication();
windowManager.addListener(this);
Future.delayed(const Duration(milliseconds: 200), () {
FocusScope.of(context).requestFocus(_focusNode);
});
validateAsyncController = InputValidateAsyncController(
listen: false,
validator: (text) async {
Expand Down Expand Up @@ -164,7 +205,8 @@ class DatabaseDecryptScreenState extends State<DatabaseDecryptScreen>
if (isValidAsync) {
if (DatabaseManager.initialized) {
Navigator.of(context).pushReplacement(RouteUtil.getFadeRoute(
ItemBuilder.buildContextMenuOverlay(const MainScreen())));
ItemBuilder.buildContextMenuOverlay(
MainScreen(key: mainScreenKey))));
}
} else {
_focusNode.requestFocus();
Expand Down Expand Up @@ -215,33 +257,31 @@ class DatabaseDecryptScreenState extends State<DatabaseDecryptScreen>
),
),
const SizedBox(height: 30),
ItemBuilder.buildRoundButton(
context,
text: S.current.confirm,
fontSizeDelta: 2,
background: Theme.of(context).primaryColor,
padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 12),
onTap: onSubmit,
),
const Spacer(),
ItemBuilder.buildClickItem(
clickable: _biometricAvailable && _enableDatabaseBiometric,
GestureDetector(
onTap: _biometricAvailable && _enableDatabaseBiometric
? () {
auth();
}
: null,
child: Text(
_biometricAvailable && _enableDatabaseBiometric
? S.current.biometric
: "",
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_biometricAvailable)
ItemBuilder.buildRoundButton(
context,
text: S.current.biometric,
fontSizeDelta: 2,
disabled: !(_allowDatabaseBiometric && _isValidated),
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
onTap: () => auth(),
),
if (_biometricAvailable) const SizedBox(width: 10),
ItemBuilder.buildRoundButton(
context,
text: S.current.confirm,
fontSizeDelta: 2,
background: Theme.of(context).primaryColor,
padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 12),
onTap: onSubmit,
),
),
],
),
const Spacer(),
],
);
}
Expand Down
44 changes: 26 additions & 18 deletions lib/Screens/Lock/pin_change_screen.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:math';

import 'package:biometric_storage/biometric_storage.dart';
import 'package:cloudotp/Utils/itoast.dart';
import 'package:cloudotp/Utils/responsive_util.dart';
import 'package:cloudotp/Widgets/General/Unlock/gesture_notifier.dart';
Expand All @@ -8,6 +9,7 @@ import 'package:cloudotp/Widgets/General/Unlock/gesture_unlock_view.dart';
import 'package:cloudotp/Widgets/Item/item_builder.dart';
import 'package:flutter/material.dart';

import '../../Utils/biometric_util.dart';
import '../../Utils/hive_util.dart';
import '../../Utils/utils.dart';
import '../../generated/l10n.dart';
Expand All @@ -26,8 +28,8 @@ class PinChangeScreenState extends State<PinChangeScreen> {
final String? _oldPassword = HiveUtil.getString(HiveUtil.guesturePasswdKey);
bool _isEditMode = HiveUtil.getString(HiveUtil.guesturePasswdKey) != null &&
HiveUtil.getString(HiveUtil.guesturePasswdKey)!.isNotEmpty;
late final bool _isUseBiometric =
_isEditMode && HiveUtil.getBool(HiveUtil.enableBiometricKey);
late final bool _enableBiometric =
HiveUtil.getBool(HiveUtil.enableBiometricKey);
late final GestureNotifier _notifier = _isEditMode
? GestureNotifier(
status: GestureStatus.verify,
Expand All @@ -38,10 +40,22 @@ class PinChangeScreenState extends State<PinChangeScreen> {
final GlobalKey<GestureState> _gestureUnlockView = GlobalKey();
final GlobalKey<GestureUnlockIndicatorState> _indicator = GlobalKey();

String? canAuthenticateResponseString;
CanAuthenticateResponse? canAuthenticateResponse;

bool get _biometricAvailable => canAuthenticateResponse?.isSuccess ?? false;

@override
void initState() {
super.initState();
if (_isUseBiometric) {
initBiometricAuthentication();
}

initBiometricAuthentication() async {
canAuthenticateResponse = await BiometricUtil.canAuthenticate();
canAuthenticateResponseString =
await BiometricUtil.getCanAuthenticateResponseString();
if (_biometricAvailable && _enableBiometric && _isEditMode) {
auth();
}
}
Expand Down Expand Up @@ -100,22 +114,16 @@ class PinChangeScreenState extends State<PinChangeScreen> {
onCompleted: _gestureComplete,
),
),
GestureDetector(
onTap: () {
if (_isEditMode && _isUseBiometric) {
Visibility(
visible: _isEditMode && _biometricAvailable && _enableBiometric,
child: ItemBuilder.buildRoundButton(
context,
text: ResponsiveUtil.isWindows()
? S.current.biometricVerifyPin
: S.current.biometric,
onTap: () {
auth();
}
},
child: ItemBuilder.buildClickItem(
clickable: _isEditMode && _isUseBiometric,
Text(
_isEditMode && _isUseBiometric
? (ResponsiveUtil.isWindows()
? S.current.biometricVerifyPin
: S.current.biometricVerifyFingerprint)
: "",
style: Theme.of(context).textTheme.titleSmall,
),
},
),
),
const SizedBox(height: 50),
Expand Down
Loading

0 comments on commit 5e2e44d

Please sign in to comment.