- Drawer custom (overlay Stack) avec mode installateur PIN SHA-256 - GoRouter + ShellRoute : navigation préservée entre onglets - 6 providers : NavigationProvider, InstallerModeProvider, AppSettingsProvider, EnergySetupProvider, SchedulerProvider, TariffProvider - Écrans Energy Manager : RoleConfigFlow (3 étapes), Scheduler, Tarifs, Timeline - Écrans Paramètres : Apparence, Écrans actifs, AppSettingsScreen - DrawerMenuButton présent dans les 5 AppBars principaux - Simulation : _thingClasses générées avec interfaces EMS pour filtrage des rôles - Compteur solaire : ajout smartmeter aux interfaces compatibles - Thème ETM (etm_theme.dart), ProLockBadge, widgets PowerBar/RoleCard/TimelineSlotCard - Dépendances : go_router, shared_preferences, crypto, url_launcher Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
114 lines
4.3 KiB
Dart
114 lines
4.3 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'package:crypto/crypto.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
/// Gère le mode installateur : déverrouillage par PIN, auto-lock, tentatives.
|
|
///
|
|
/// Le PIN n'est jamais stocké en clair — SHA-256 uniquement.
|
|
/// PIN par défaut au premier lancement : "1234".
|
|
class InstallerModeProvider extends ChangeNotifier {
|
|
static const _pinHashKey = 'installer_pin_hash';
|
|
static const _defaultPin = '1234';
|
|
static const _maxAttempts = 3;
|
|
static const _lockDuration = Duration(seconds: 30);
|
|
static const _autoLockDuration = Duration(minutes: 10);
|
|
|
|
bool _isUnlocked = false;
|
|
int _failedAttempts = 0;
|
|
DateTime? _lockedUntil;
|
|
Timer? _autoLockTimer;
|
|
|
|
bool get isUnlocked => _isUnlocked;
|
|
bool get isLocked =>
|
|
_lockedUntil != null && DateTime.now().isBefore(_lockedUntil!);
|
|
int get failedAttempts => _failedAttempts;
|
|
|
|
/// Durée restante avant déverrouillage (0 si non verrouillé).
|
|
Duration get lockRemaining {
|
|
if (!isLocked) return Duration.zero;
|
|
return _lockedUntil!.difference(DateTime.now());
|
|
}
|
|
|
|
// ── SHA-256 helper ──────────────────────────────────────────────────────────
|
|
static String _hash(String pin) {
|
|
final bytes = utf8.encode(pin);
|
|
return sha256.convert(bytes).toString();
|
|
}
|
|
|
|
// ── Initialisation ──────────────────────────────────────────────────────────
|
|
Future<void> init() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
// Crée le hash du PIN par défaut si aucun n'est encore défini
|
|
if (!prefs.containsKey(_pinHashKey)) {
|
|
await prefs.setString(_pinHashKey, _hash(_defaultPin));
|
|
}
|
|
}
|
|
|
|
// ── Déverrouillage ──────────────────────────────────────────────────────────
|
|
Future<UnlockResult> unlock(String pin) async {
|
|
if (isLocked) return UnlockResult.locked;
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final storedHash = prefs.getString(_pinHashKey) ?? _hash(_defaultPin);
|
|
|
|
if (_hash(pin) == storedHash) {
|
|
_isUnlocked = true;
|
|
_failedAttempts = 0;
|
|
_lockedUntil = null;
|
|
_resetAutoLockTimer();
|
|
notifyListeners();
|
|
return UnlockResult.success;
|
|
} else {
|
|
_failedAttempts++;
|
|
if (_failedAttempts >= _maxAttempts) {
|
|
_lockedUntil = DateTime.now().add(_lockDuration);
|
|
_failedAttempts = 0;
|
|
// Déclenche rebuild après le délai pour effacer le verrou
|
|
Timer(_lockDuration, () {
|
|
_lockedUntil = null;
|
|
notifyListeners();
|
|
});
|
|
}
|
|
notifyListeners();
|
|
return UnlockResult.wrongPin;
|
|
}
|
|
}
|
|
|
|
// ── Verrouillage ────────────────────────────────────────────────────────────
|
|
void lock() {
|
|
_isUnlocked = false;
|
|
_autoLockTimer?.cancel();
|
|
notifyListeners();
|
|
}
|
|
|
|
// ── Changement de PIN ───────────────────────────────────────────────────────
|
|
Future<void> changePin(String newPin) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_pinHashKey, _hash(newPin));
|
|
}
|
|
|
|
// ── Auto-lock après inactivité ───────────────────────────────────────────────
|
|
void _resetAutoLockTimer() {
|
|
_autoLockTimer?.cancel();
|
|
_autoLockTimer = Timer(_autoLockDuration, () {
|
|
if (_isUnlocked) lock();
|
|
});
|
|
}
|
|
|
|
/// Appeler lors d'une interaction en mode installateur pour réinitialiser
|
|
/// le timer d'auto-lock.
|
|
void onInstallerActivity() {
|
|
if (_isUnlocked) _resetAutoLockTimer();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_autoLockTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
enum UnlockResult { success, wrongPin, locked }
|