import 'package:local_auth/local_auth.dart'; /// Thin wrapper over [LocalAuthentication] for the login screen. Exposes what /// the device can do (fingerprint vs. face) and a single [authenticate] call. class BiometricAuth { final LocalAuthentication _auth = LocalAuthentication(); bool _supported = false; bool _canCheck = false; List _types = const []; /// Whether any biometric login can be offered (device supports it and the /// user has at least one biometric enrolled). bool get available => _supported && _canCheck; /// The device advertises a face enrolment. On some Androids the platform only /// reports weak/strong instead of the specific modality — see [showFace]. bool get hasFace => _types.contains(BiometricType.face); bool get _hasFingerprint => _types.contains(BiometricType.fingerprint) || _types.contains(BiometricType.strong) || _types.contains(BiometricType.weak); /// Show the face button when face is reported, or when the device supports /// biometrics but reports no specific modality (BiometricPrompt still lets the /// user use whatever strong biometric — often face — is enrolled). bool get showFace => available && (hasFace || _types.isEmpty); /// Show the fingerprint button when fingerprint is reported, or as the generic /// fallback when no specific modality is advertised. bool get showFingerprint => available && (_hasFingerprint || _types.isEmpty); /// Refreshes the capability flags. Safe to call repeatedly. Future refresh() async { try { _supported = await _auth.isDeviceSupported(); _canCheck = await _auth.canCheckBiometrics; _types = _supported ? await _auth.getAvailableBiometrics() : const []; } catch (_) { _supported = false; _canCheck = false; _types = const []; } } /// Prompts the OS biometric sheet. Returns true only on a verified match. Future authenticate({required String reason}) { return _auth.authenticate( localizedReason: reason, options: const AuthenticationOptions( biometricOnly: true, stickyAuth: true, useErrorDialogs: true, ), ); } }