etm-powersync-app/lib/providers/installer_mode_provider.dart
pakutz79 c19c9d1a98 feat: navigation drawer, EMS setup, scheduler, tarifs, paramètres app
- 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>
2026-02-24 14:52:32 +01:00

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 }