Files
DriverVault/Phone App/lib/screens/lock_screen.dart
T
2026-07-06 08:50:52 +02:00

111 lines
3.7 KiB
Dart

import "package:flutter/material.dart";
import "../biometric.dart";
import "../main.dart";
import "../theme.dart";
/// Shown when the app is authenticated but locked (biometric login is enabled
/// and the app was just launched or resumed). The session token is still valid;
/// a successful biometric check simply reveals the app. "Use password" drops the
/// session and returns to the login screen.
class LockScreen extends StatefulWidget {
const LockScreen({super.key});
@override
State<LockScreen> createState() => _LockScreenState();
}
class _LockScreenState extends State<LockScreen> {
bool _busy = false;
String? _error;
@override
void initState() {
super.initState();
// Prompt once as soon as the lock screen appears.
WidgetsBinding.instance.addPostFrameCallback((_) => _unlock());
}
Future<void> _unlock() async {
if (_busy) return;
setState(() {
_busy = true;
_error = null;
});
try {
final creds = await biometricAuth.unlock(reason: "Unlock DriverVault");
if (creds == null) {
// Cancelled — leave locked; the buttons let them retry or use password.
if (mounted) setState(() => _busy = false);
return;
}
// Token from a previous session is still held by ApiClient; just reveal.
authService.unlock();
} on BiometricException catch (e) {
if (mounted) setState(() => _error = e.message);
} catch (e) {
if (mounted) setState(() => _error = e.toString());
} finally {
if (mounted) setState(() => _busy = false);
}
}
@override
Widget build(BuildContext context) {
final u = authService.user;
final name = u == null ? null : (u.name.isNotEmpty ? u.name : u.email);
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: DriverVault.brand700,
borderRadius: BorderRadius.circular(16),
),
child: const Icon(Icons.lock_outline, color: Colors.white, size: 32),
),
const SizedBox(height: 16),
const Text("DriverVault is locked",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700, letterSpacing: -0.4)),
if (name != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(name, style: TextStyle(color: DriverVault.muted(context))),
),
const SizedBox(height: 24),
if (_error != null)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Text(_error!,
textAlign: TextAlign.center,
style: const TextStyle(color: DriverVault.danger)),
),
SizedBox(
width: 260,
child: FilledButton.icon(
onPressed: _busy ? null : _unlock,
icon: const Icon(Icons.fingerprint),
label: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(_busy ? "Unlocking…" : "Unlock"),
),
),
),
const SizedBox(height: 8),
TextButton(
onPressed: _busy ? null : () => authService.logout(),
child: const Text("Use password instead"),
),
],
),
),
),
);
}
}