Initial commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user