import 'dart:convert'; import 'dart:math'; import 'package:cryptography/cryptography.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class AppLockService extends ChangeNotifier { AppLockService._(); static final AppLockService instance = AppLockService._(); static const _enabledKey = 'app_lock_enabled_v1'; static const _saltKey = 'app_lock_salt_v1'; static const _hashKey = 'app_lock_hash_v1'; static const _pbkdf2Iterations = 120000; final FlutterSecureStorage _storage = const FlutterSecureStorage(); final Pbkdf2 _kdf = Pbkdf2( macAlgorithm: Hmac.sha256(), iterations: _pbkdf2Iterations, bits: 256, ); bool _initialized = false; bool _enabled = false; bool _locked = false; bool get initialized => _initialized; bool get isEnabled => _enabled; bool get isLocked => _enabled && _locked; Future init() async { if (_initialized) return; await _reloadFromStorage(lockIfEnabled: true); } Future refresh() async { await _reloadFromStorage(lockIfEnabled: false); } Future setPasscode(String passcode) async { final normalized = _normalizePasscode(passcode); final salt = _generateSalt(); final hash = await _derivePasscodeHash(normalized, salt); await _storage.write(key: _saltKey, value: salt); await _storage.write(key: _hashKey, value: hash); await _storage.write(key: _enabledKey, value: '1'); _enabled = true; _locked = false; _initialized = true; notifyListeners(); } Future verifyPasscode(String passcode) async { final isValid = await _matchesStoredPasscode(passcode); if (!isValid) return false; _enabled = true; _locked = false; _initialized = true; notifyListeners(); return true; } Future changePasscode({ required String currentPasscode, required String newPasscode, }) async { final isCurrentValid = await _matchesStoredPasscode(currentPasscode); if (!isCurrentValid) return false; await setPasscode(newPasscode); return true; } Future disablePasscode(String currentPasscode) async { final isCurrentValid = await _matchesStoredPasscode(currentPasscode); if (!isCurrentValid) return false; await clear(); return true; } Future clear() async { await _storage.delete(key: _enabledKey); await _storage.delete(key: _saltKey); await _storage.delete(key: _hashKey); _enabled = false; _locked = false; _initialized = true; notifyListeners(); } void lock() { if (!_enabled || _locked) return; _locked = true; notifyListeners(); } Future _reloadFromStorage({required bool lockIfEnabled}) async { try { final enabledValue = await _storage.read(key: _enabledKey); _enabled = enabledValue == '1'; _locked = _enabled && lockIfEnabled ? true : (_enabled && _locked); } catch (e) { debugPrint('[APP_LOCK] Failed to load state: $e'); _enabled = false; _locked = false; } if (!_enabled) { _locked = false; } _initialized = true; notifyListeners(); } Future _matchesStoredPasscode(String passcode) async { try { final normalized = _normalizePasscode(passcode); final salt = await _storage.read(key: _saltKey); final storedHash = await _storage.read(key: _hashKey); final enabledValue = await _storage.read(key: _enabledKey); if (enabledValue != '1' || salt == null || storedHash == null) { return false; } final computedHash = await _derivePasscodeHash(normalized, salt); return computedHash == storedHash; } catch (e) { debugPrint('[APP_LOCK] Failed to verify passcode: $e'); return false; } } Future _derivePasscodeHash(String passcode, String salt) async { final secretKey = await _kdf.deriveKey( secretKey: SecretKey(utf8.encode(passcode)), nonce: utf8.encode(salt), ); final bytes = await secretKey.extractBytes(); return base64UrlEncode(bytes); } String _generateSalt() { final random = Random.secure(); final bytes = List.generate(16, (_) => random.nextInt(256)); return base64UrlEncode(bytes); } String _normalizePasscode(String input) => input.trim(); }