Files
sure/mobile/lib/services/biometric_service.dart
Tristan Katana 9c199a6dcd feat(mobile): Add biometric lock for app resume (#1474)
* Feature: Biometric lock for app resume

User enables "Biometric Lock" in Settings → prompted to verify fingerprint/face first.
User backgrounds the app → _isLocked = true.
User returns → lock screen appears, auto-triggers biometric prompt.
Success → app unlocks, state preserved underneath.
Can retry or log out as fallback.
Add USE_BIOMETRIC permission to AndroidManifest.

* Fix: Remove duplicate local_auth entry in pubspec.lock and add NSFaceIDUsageDescription to iOS Info.plist

* fix(mobile) : Remove duplicate local auth files first

* fix(mobile): keep MainNavigationScreen in the Stack, let the lock screen float above, no unmounting

* Updtae: Swap out Flutter Activity for FlutterFragmentActivity that extends the lock scan feature

* fix(mobile): address biometric lock PR review feedback

* fix(mobile): only require biometric auth when enabling lock, not disabling

Prevents users from getting locked out if biometrics start failing —
they can now disable the lock without needing to pass biometric auth.

* fix(mobile): add missing closing brace in setBiometricEnabled

---------

Signed-off-by: Tristan Katana <50181095+felixmuinde@users.noreply.github.com>
2026-04-15 19:48:13 +02:00

48 lines
1.4 KiB
Dart

import 'package:local_auth/local_auth.dart';
import 'log_service.dart';
class BiometricService {
static final BiometricService instance = BiometricService._();
BiometricService._();
final LocalAuthentication _auth = LocalAuthentication();
/// Returns true when the platform supports biometrics AND at least one
/// biometric is enrolled on the device.
Future<bool> isDeviceSupported() async {
try {
final isSupported = await _auth.isDeviceSupported();
if (!isSupported) return false;
final enrolled = await _auth.getAvailableBiometrics();
return enrolled.isNotEmpty;
} catch (_) {
return false;
}
}
/// Returns the list of enrolled biometric types (fingerprint, face, etc.).
Future<List<BiometricType>> getAvailableBiometrics() async {
try {
return await _auth.getAvailableBiometrics();
} catch (_) {
return [];
}
}
/// Triggers the OS biometric prompt. Returns true if authentication succeeds.
Future<bool> authenticate({String reason = 'Unlock Sure to continue'}) async {
try {
return await _auth.authenticate(
localizedReason: reason,
options: const AuthenticationOptions(
stickyAuth: true,
biometricOnly: false,
),
);
} catch (e, stack) {
LogService.instance.error('BiometricService', 'authenticate() failed: $e\n$stack');
return false;
}
}
}