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

110 lines
3.6 KiB
Dart

import "package:flutter/material.dart";
import "api.dart";
import "app_settings.dart";
import "auth.dart";
import "biometric.dart";
import "theme.dart";
import "screens/lock_screen.dart";
import "screens/login_screen.dart";
import "screens/root_shell.dart";
// Simple app-wide singletons (small app — no DI framework needed).
final apiClient = ApiClient();
final authService = AuthService(apiClient);
final appSettings = AppSettings();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await apiClient.loadServerUrl();
await appSettings.loadFromStorage();
await authService.loadFromStorage();
// Prime the biometric-enabled cache; if a session survived from a previous
// run and biometric login is on, start locked so the app requires an unlock
// instead of jumping straight to the dashboard.
final bioEnabled = await biometricAuth.isEnabled();
if (authService.isAuthenticated && bioEnabled) {
authService.lock();
}
// If a token survived from a previous run, refresh the profile so the saved
// appearance prefs (theme/font/date format) apply on boot.
if (authService.isAuthenticated) {
apiClient.getMe().then((p) => appSettings.applyFromProfile(p)).catchError((_) {});
}
runApp(const CarControlApp());
}
class CarControlApp extends StatefulWidget {
const CarControlApp({super.key});
@override
State<CarControlApp> createState() => _CarControlAppState();
}
class _CarControlAppState extends State<CarControlApp> with WidgetsBindingObserver {
// Grace period: a quick app-switch (returning within this window) does NOT
// re-lock; staying in the background longer does.
static const _lockGrace = Duration(seconds: 30);
DateTime? _backgroundedAt;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// Re-lock when the app returns from the background, but only if it was away
// longer than the grace period — so quick app-switches don't re-lock.
// Handle only 'paused'/'resumed' (not 'inactive', which also fires
// transiently while the OS biometric prompt is showing).
if (state == AppLifecycleState.paused) {
_backgroundedAt = DateTime.now();
} else if (state == AppLifecycleState.resumed) {
final since = _backgroundedAt;
_backgroundedAt = null;
if (since != null &&
DateTime.now().difference(since) > _lockGrace &&
authService.isAuthenticated &&
biometricAuth.enabledCached) {
authService.lock();
}
}
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: appSettings,
builder: (context, _) => MaterialApp(
title: "DriverVault",
debugShowCheckedModeBanner: false,
theme: DriverVault.theme(Brightness.light),
darkTheme: DriverVault.theme(Brightness.dark),
themeMode: appSettings.themeMode,
// Apply the user's font-size preference app-wide.
builder: (context, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(appSettings.textScale),
),
child: child!,
),
home: ListenableBuilder(
listenable: authService,
builder: (context, _) {
if (!authService.isAuthenticated) return const LoginScreen();
if (authService.locked) return const LockScreen();
return const RootShell();
},
),
),
);
}
}