Initial upload: fin65-mobile

This commit is contained in:
Abderrahim-Mouhcine
2026-08-16 19:59:55 +01:00
commit 46558a5ec8
242 changed files with 16334 additions and 0 deletions
+311
View File
@@ -0,0 +1,311 @@
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<SecurityScreen> createState() => _SecurityScreenState();
}
class _SecurityScreenState extends State<SecurityScreen> {
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<BiometricType> _availableBiometrics = const [];
String? _message;
@override
void initState() {
super.initState();
_load();
}
@override
void dispose() {
_currentPinController.dispose();
_newPinController.dispose();
_confirmPinController.dispose();
super.dispose();
}
Future<void> _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<bool> _verifyCurrentPinFromField() async {
final current = _currentPinController.text.trim();
final ok = await widget.securityService.verifyPin(current);
if (!ok) {
setState(() => _message = 'PIN actuel incorrect.');
return false;
}
return true;
}
Future<void> _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 {
if (_enabled) {
final currentOk = await _verifyCurrentPinFromField();
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<void> _disablePin() async {
setState(() {
_saving = true;
_message = null;
});
try {
final currentOk = await _verifyCurrentPinFromField();
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<void> _toggleBiometrics(bool value) async {
if (_saving) return;
setState(() {
_saving = true;
_message = null;
});
try {
if (!_enabled) {
setState(() => _message = 'Active d’abord un PIN avant la biométrie.');
return;
}
if (value && !_biometricAvailable) {
setState(() => _message = 'Aucune empreinte/biométrie disponible sur ce téléphone.');
return;
}
final currentOk = await _verifyCurrentPinFromField();
if (!currentOk) return;
await widget.securityService.setBiometricEnabled(value);
_currentPinController.clear();
await _load();
setState(() {
_message = value
? 'Déverrouillage par empreinte activé. Verrouille l’app puis teste avec le bouton empreinte.'
: '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()} Entre le PIN actuel ci-dessous puis active l’interrupteur.'
: '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: 'Nécessaire pour changer le PIN, désactiver le PIN ou activer/désactiver l’empreinte.',
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.',
),
),
),
],
),
);
}
}