import 'package:flutter/foundation.dart'; import '../services/pin_lock_service.dart'; class PinLockViewModel extends ChangeNotifier { final PinLockService pinLockService; PinLockViewModel({required this.pinLockService}); bool _isPinSet = false; bool _isUnlocked = false; bool _isLoading = false; bool _hasLoaded = false; String? _errorMessage; bool get isPinSet => _isPinSet; bool get isUnlocked => _isUnlocked; bool get isLoading => _isLoading; bool get hasLoaded => _hasLoaded; String? get errorMessage => _errorMessage; Future ensureLoaded() async { if (_hasLoaded || _isLoading) return; await load(); } /// Checks whether a PIN has already been configured on this device. Future load() async { if (_isLoading) return; _isLoading = true; _errorMessage = null; notifyListeners(); try { _isPinSet = await pinLockService.hasPin(); } catch (_) { _errorMessage = 'Could not check PIN status.'; } finally { _hasLoaded = true; _isLoading = false; notifyListeners(); } } /// Creates a new PIN (first-time setup, or after disabling an old one). /// Unlocks the app immediately on success, since the person just proved /// they know it by typing it. Future setPin(String pin) async { _errorMessage = null; try { await pinLockService.setPin(pin); _isPinSet = true; _isUnlocked = true; notifyListeners(); return true; } catch (_) { _errorMessage = 'Could not save PIN.'; notifyListeners(); return false; } } /// Checks an entered PIN against the stored one, unlocking on match. Future verify(String pin) async { _errorMessage = null; try { final matches = await pinLockService.verifyPin(pin); if (matches) { _isUnlocked = true; notifyListeners(); return true; } _errorMessage = 'Incorrect PIN.'; notifyListeners(); return false; } catch (_) { _errorMessage = 'Could not verify PIN.'; notifyListeners(); return false; } } /// Replaces the current PIN. Requires the current PIN to match first. Future changePin({ required String currentPin, required String newPin, }) async { final currentMatches = await pinLockService.verifyPin(currentPin); if (!currentMatches) { _errorMessage = 'Current PIN is incorrect.'; notifyListeners(); return false; } return setPin(newPin); } /// Removes the PIN entirely. Requires the current PIN to confirm. Future disablePin(String currentPin) async { final matches = await pinLockService.verifyPin(currentPin); if (!matches) { _errorMessage = 'Current PIN is incorrect.'; notifyListeners(); return false; } await pinLockService.clearPin(); _isPinSet = false; notifyListeners(); return true; } /// Re-locks the app. Call this on app backgrounding (e.g. from a /// WidgetsBindingObserver on AppLifecycleState.paused) if you want the /// PIN required again after the app is put away, not just on cold start. void lock() { if (!_isUnlocked) return; _isUnlocked = false; notifyListeners(); } }