import 'package:flutter/material.dart'; import 'package:local_auth/local_auth.dart'; import '../services/security_service.dart'; class SecurityScreen extends StatefulWidget { const SecurityScreen({ super.key, required this.securityService, }); final SecurityService securityService; @override State createState() => _SecurityScreenState(); } class _SecurityScreenState extends State { final _currentPinController = TextEditingController(); final _newPinController = TextEditingController(); final _confirmPinController = TextEditingController(); bool _loading = true; bool _saving = false; bool _enabled = false; bool _biometricAvailable = false; bool _biometricEnabled = false; List _availableBiometrics = const []; String? _message; @override void initState() { super.initState(); _load(); } @override void dispose() { _currentPinController.dispose(); _newPinController.dispose(); _confirmPinController.dispose(); super.dispose(); } Future _load() async { final enabled = await widget.securityService.isPinEnabled(); final biometricAvailable = await widget.securityService.isBiometricAvailable(); final biometricEnabled = await widget.securityService.isBiometricEnabled(); final availableBiometrics = await widget.securityService.getAvailableBiometrics(); if (!mounted) return; setState(() { _enabled = enabled; _biometricAvailable = biometricAvailable; _biometricEnabled = biometricEnabled; _availableBiometrics = availableBiometrics; _loading = false; }); } Future _verifyCurrentPinFromFieldIfNeeded() async { if (!_enabled) return true; final current = _currentPinController.text.trim(); final ok = await widget.securityService.verifyPin(current); if (!ok) { setState(() => _message = 'PIN actuel incorrect.'); return false; } return true; } Future _askCurrentPinDialog({ required String title, required String actionLabel, }) async { final controller = TextEditingController(); final result = await showDialog( context: context, barrierDismissible: true, builder: (context) { return AlertDialog( title: Text(title), content: TextField( controller: controller, autofocus: true, obscureText: true, keyboardType: TextInputType.number, maxLength: 8, decoration: const InputDecoration( labelText: 'PIN actuel', border: OutlineInputBorder(), counterText: '', ), onSubmitted: (_) => Navigator.of(context).pop(controller.text.trim()), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Annuler'), ), FilledButton( onPressed: () => Navigator.of(context).pop(controller.text.trim()), child: Text(actionLabel), ), ], ); }, ); controller.dispose(); return result?.trim(); } Future _askAndVerifyCurrentPin({ required String title, required String actionLabel, }) async { final pin = await _askCurrentPinDialog( title: title, actionLabel: actionLabel, ); if (pin == null) { setState(() => _message = 'Action annulée.'); return false; } final ok = await widget.securityService.verifyPin(pin); if (!ok) { setState(() => _message = 'PIN actuel incorrect.'); return false; } return true; } Future _savePin() async { final newPin = _newPinController.text.trim(); final confirm = _confirmPinController.text.trim(); if (newPin != confirm) { setState(() => _message = 'Le nouveau PIN et la confirmation ne correspondent pas.'); return; } setState(() { _saving = true; _message = null; }); try { final currentOk = await _verifyCurrentPinFromFieldIfNeeded(); if (!currentOk) return; await widget.securityService.setPin(newPin); _currentPinController.clear(); _newPinController.clear(); _confirmPinController.clear(); await _load(); setState(() { _message = 'Verrouillage PIN activé.'; }); } catch (e) { setState(() => _message = 'Erreur : $e'); } finally { if (mounted) setState(() => _saving = false); } } Future _disablePin() async { setState(() { _saving = true; _message = null; }); try { final currentOk = await _askAndVerifyCurrentPin( title: 'Désactiver le verrouillage PIN', actionLabel: 'Désactiver', ); if (!currentOk) return; await widget.securityService.disablePin(); _currentPinController.clear(); _newPinController.clear(); _confirmPinController.clear(); await _load(); setState(() { _message = 'Verrouillage PIN et biométrie désactivés.'; }); } catch (e) { setState(() => _message = 'Erreur : $e'); } finally { if (mounted) setState(() => _saving = false); } } Future _toggleBiometrics(bool value) async { if (_saving) return; setState(() { _saving = true; _message = null; }); try { if (value) { if (!_enabled) { setState(() => _message = 'Active d’abord un PIN avant la biométrie.'); return; } if (!_biometricAvailable) { setState(() => _message = 'Aucune empreinte/biométrie disponible sur ce téléphone.'); return; } final currentOk = await _askAndVerifyCurrentPin( title: 'Activer l’empreinte', actionLabel: 'Continuer', ); if (!currentOk) return; await widget.securityService.setBiometricEnabled(true); final confirmed = await widget.securityService.authenticateWithBiometrics(); if (!confirmed) { await widget.securityService.setBiometricEnabled(false); setState(() => _message = 'Empreinte annulée ou refusée. Biométrie non activée.'); return; } } else { final currentOk = await _askAndVerifyCurrentPin( title: 'Désactiver l’empreinte', actionLabel: 'Désactiver', ); if (!currentOk) return; await widget.securityService.setBiometricEnabled(false); } await _load(); setState(() { _message = value ? 'Déverrouillage par empreinte activé.' : 'Déverrouillage par empreinte désactivé.'; }); } catch (e) { setState(() => _message = 'Erreur : $e'); } finally { if (mounted) setState(() => _saving = false); } } String _biometricLabel() { if (_availableBiometrics.isEmpty) return 'Aucune biométrie détectée.'; final labels = _availableBiometrics.map((type) { return switch (type) { BiometricType.fingerprint => 'empreinte', BiometricType.face => 'visage', BiometricType.iris => 'iris', BiometricType.strong => 'biométrie forte', BiometricType.weak => 'biométrie faible', }; }).toSet().join(', '); return 'Disponible : $labels.'; } @override Widget build(BuildContext context) { if (_loading) { return Scaffold( appBar: AppBar(title: const Text('Sécurité')), body: const Center(child: CircularProgressIndicator()), ); } return Scaffold( appBar: AppBar(title: const Text('Sécurité')), body: ListView( padding: const EdgeInsets.all(16), children: [ Card( child: SwitchListTile( value: _enabled, onChanged: null, secondary: Icon(_enabled ? Icons.lock : Icons.lock_open), title: const Text('Verrouillage PIN'), subtitle: Text( _enabled ? 'Activé. L’app se verrouille à la fermeture ou au changement d’application.' : 'Désactivé. Tes données locales sont accessibles sans PIN.', ), ), ), if (_enabled) ...[ const SizedBox(height: 12), Card( child: SwitchListTile( value: _biometricEnabled, onChanged: _saving || !_biometricAvailable ? null : _toggleBiometrics, secondary: const Icon(Icons.fingerprint), title: const Text('Déverrouillage par empreinte'), subtitle: Text( _biometricAvailable ? '${_biometricLabel()} Appuie sur l’interrupteur, entre le PIN actuel, puis valide l’empreinte.' : 'Aucune empreinte/biométrie disponible ou configurée sur ce téléphone.', ), ), ), ], const SizedBox(height: 16), if (_enabled) ...[ TextField( controller: _currentPinController, obscureText: true, keyboardType: TextInputType.number, maxLength: 8, decoration: const InputDecoration( labelText: 'PIN actuel', border: OutlineInputBorder(), helperText: 'Uniquement nécessaire pour changer le PIN.', counterText: '', ), ), const SizedBox(height: 12), ], TextField( controller: _newPinController, obscureText: true, keyboardType: TextInputType.number, maxLength: 8, decoration: InputDecoration( labelText: _enabled ? 'Nouveau PIN' : 'PIN', border: const OutlineInputBorder(), helperText: '4 à 8 chiffres.', counterText: '', ), ), const SizedBox(height: 12), TextField( controller: _confirmPinController, obscureText: true, keyboardType: TextInputType.number, maxLength: 8, decoration: const InputDecoration( labelText: 'Confirmer le PIN', border: OutlineInputBorder(), counterText: '', ), ), const SizedBox(height: 16), FilledButton.icon( onPressed: _saving ? null : _savePin, icon: const Icon(Icons.save), label: Text(_enabled ? 'Changer le PIN' : 'Activer le PIN'), ), if (_enabled) ...[ const SizedBox(height: 8), OutlinedButton.icon( onPressed: _saving ? null : _disablePin, icon: const Icon(Icons.lock_open), label: const Text('Désactiver le PIN'), ), ], if (_message != null) ...[ const SizedBox(height: 16), Card( child: Padding( padding: const EdgeInsets.all(12), child: Text(_message!), ), ), ], const SizedBox(height: 24), const Card( child: Padding( padding: EdgeInsets.all(12), child: Text( 'Le PIN protège l’accès local à l’application. L’empreinte utilise la sécurité biométrique du téléphone. Le PIN reste le secours si l’empreinte échoue.', ), ), ), ], ), ); } }