Files
kooltab/lib/viewmodels/pin_lock_view_model.dart
T

134 lines
3.2 KiB
Dart

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<void> ensureLoaded() async {
if (_hasLoaded || _isLoading) return;
await load();
}
/// Checks whether a PIN has already been configured on this device.
Future<void> 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<bool> 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<bool> 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<bool> 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<bool> 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();
}
}