import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../services/security_service.dart'; class AppLockGate extends StatefulWidget { const AppLockGate({ super.key, required this.securityService, required this.child, }); final SecurityService securityService; final Widget child; @override State createState() => _AppLockGateState(); } class _AppLockGateState extends State with WidgetsBindingObserver { bool _checking = true; bool _locked = false; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _loadLockState(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) { _lockIfNeeded(); } } Future _loadLockState() async { final enabled = await widget.securityService.isPinEnabled(); if (!mounted) return; setState(() { _locked = enabled; _checking = false; }); } Future _lockIfNeeded() async { final enabled = await widget.securityService.isPinEnabled(); if (!mounted || !enabled) return; setState(() => _locked = true); } void _unlock() { setState(() => _locked = false); } @override Widget build(BuildContext context) { if (_checking) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } if (_locked) { return PinUnlockScreen( securityService: widget.securityService, onUnlocked: _unlock, ); } return widget.child; } } class PinUnlockScreen extends StatefulWidget { const PinUnlockScreen({ super.key, required this.securityService, required this.onUnlocked, }); final SecurityService securityService; final VoidCallback onUnlocked; @override State createState() => _PinUnlockScreenState(); } class _PinUnlockScreenState extends State { final _pinController = TextEditingController(); bool _checking = false; bool _checkingBiometric = false; bool _biometricEnabled = false; bool _biometricAvailable = false; bool _biometricAttempted = false; String? _error; @override void initState() { super.initState(); _loadBiometricState(); } @override void dispose() { _pinController.dispose(); super.dispose(); } Future _loadBiometricState() async { final enabled = await widget.securityService.isBiometricEnabled(); final available = await widget.securityService.isBiometricAvailable(); if (!mounted) return; setState(() { _biometricEnabled = enabled; _biometricAvailable = available; }); if (enabled && available && !_biometricAttempted) { _biometricAttempted = true; await Future.delayed(const Duration(milliseconds: 250)); if (mounted) await _unlockWithBiometrics(); } } Future _unlockWithBiometrics() async { setState(() { _checkingBiometric = true; _error = null; }); final ok = await widget.securityService.authenticateWithBiometrics(); if (!mounted) return; if (ok) { widget.onUnlocked(); } else { setState(() { _checkingBiometric = false; _error = 'Empreinte refusée ou annulée. Utilise le PIN.'; }); } } Future _unlock() async { setState(() { _checking = true; _error = null; }); final ok = await widget.securityService.verifyPin(_pinController.text.trim()); if (!mounted) return; if (ok) { _pinController.clear(); widget.onUnlocked(); } else { HapticFeedback.vibrate(); setState(() { _checking = false; _error = 'PIN incorrect.'; }); } } @override Widget build(BuildContext context) { final showBiometric = _biometricEnabled && _biometricAvailable; return Scaffold( body: SafeArea( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: ListView( shrinkWrap: true, padding: const EdgeInsets.all(24), children: [ Icon(showBiometric ? Icons.fingerprint : Icons.lock, size: 64), const SizedBox(height: 16), Text( 'FIN65 verrouillé', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Text( showBiometric ? 'Déverrouille avec ton empreinte ou ton PIN.' : 'Entre ton PIN pour accéder aux données locales.', textAlign: TextAlign.center, ), if (showBiometric) ...[ const SizedBox(height: 24), FilledButton.icon( onPressed: _checkingBiometric ? null : _unlockWithBiometrics, icon: _checkingBiometric ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.fingerprint), label: Text(_checkingBiometric ? 'Vérification...' : 'Utiliser l’empreinte'), ), ], const SizedBox(height: 24), TextField( controller: _pinController, autofocus: !showBiometric, obscureText: true, keyboardType: TextInputType.number, maxLength: 8, decoration: InputDecoration( labelText: 'PIN', border: const OutlineInputBorder(), errorText: _error, counterText: '', ), onSubmitted: (_) => _checking ? null : _unlock(), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: _checking ? null : _unlock, icon: _checking ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.lock_open), label: Text(_checking ? 'Vérification...' : 'Déverrouiller avec le PIN'), ), ], ), ), ), ), ); } }