new startup screen logic
This commit is contained in:
@@ -19,7 +19,7 @@ class AppRoot extends StatefulWidget {
|
|||||||
class _AppRootState extends State<AppRoot> {
|
class _AppRootState extends State<AppRoot> {
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
bool _overheatTriggered = false;
|
bool _overheatTriggered = false;
|
||||||
String _initStatus = 'Starting...';
|
final Map<String, String> _initStatuses = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -33,27 +33,32 @@ class _AppRootState extends State<AppRoot> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateStatus(String key, String value) {
|
||||||
|
setState(() => _initStatuses[key] = value);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _runInitSequence() async {
|
Future<void> _runInitSequence() async {
|
||||||
// Load config first
|
// Show all items from the start so the row doesn't jump around
|
||||||
setState(() => _initStatus = 'Loading config...');
|
_updateStatus('Config', '...');
|
||||||
|
_updateStatus('UART', '...');
|
||||||
|
_updateStatus('Navigator', '...');
|
||||||
|
|
||||||
|
// Config must load first (everything else depends on it)
|
||||||
|
_updateStatus('Config', 'Loading');
|
||||||
await ConfigService.instance.load();
|
await ConfigService.instance.load();
|
||||||
|
_updateStatus('Config', 'Ready');
|
||||||
|
|
||||||
setState(() => _initStatus = 'Checking systems...');
|
// UART health check and navigator image preload run truly in parallel
|
||||||
|
_updateStatus('UART', 'Connecting');
|
||||||
|
_updateStatus('Navigator', 'Loading');
|
||||||
|
await Future.wait([
|
||||||
|
_waitForUart(),
|
||||||
|
_preloadNavigatorImages(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Let the user see the all-ready state for a moment
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
// Check UART connection via backend health endpoint
|
|
||||||
// Also preload navigator images in parallel (usually UART is the bottleneck)
|
|
||||||
setState(() => _initStatus = 'UART: connecting...');
|
|
||||||
final imagePreloadFuture = _preloadNavigatorImages();
|
|
||||||
await _waitForUart();
|
|
||||||
await imagePreloadFuture;
|
|
||||||
|
|
||||||
setState(() => _initStatus = 'GPS: standby');
|
|
||||||
await Future.delayed(const Duration(milliseconds: 400));
|
|
||||||
|
|
||||||
setState(() => _initStatus = 'Ready');
|
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
|
||||||
|
|
||||||
// Start overheat monitoring
|
// Start overheat monitoring
|
||||||
OverheatMonitor.instance.start(
|
OverheatMonitor.instance.start(
|
||||||
onOverheat: () {
|
onOverheat: () {
|
||||||
@@ -78,11 +83,8 @@ class _AppRootState extends State<AppRoot> {
|
|||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||||
final arduinoOk = data['arduino_connected'] == true;
|
if (data['arduino_connected'] == true) {
|
||||||
|
_updateStatus('UART', 'Ready');
|
||||||
if (arduinoOk) {
|
|
||||||
setState(() => _initStatus = 'UART: OK');
|
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,28 +92,24 @@ class _AppRootState extends State<AppRoot> {
|
|||||||
// Backend not reachable yet - keep trying
|
// Backend not reachable yet - keep trying
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not connected yet
|
_updateStatus('UART', 'Waiting');
|
||||||
setState(() => _initStatus = 'UART: waiting...');
|
|
||||||
await Future.delayed(retryDelay);
|
await Future.delayed(retryDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout - proceed anyway (UI will show stale data indicators)
|
// Timeout - proceed anyway (UI will show stale data indicators)
|
||||||
setState(() => _initStatus = 'UART: timeout');
|
_updateStatus('UART', 'Timeout');
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preload navigator images into Flutter's image cache
|
/// Preload navigator images into Flutter's image cache
|
||||||
///
|
///
|
||||||
/// Scans for all PNGs in the navigator folder and precaches them.
|
/// Scans for all PNGs in the navigator folder and precaches them.
|
||||||
/// Runs silently - no status updates (meant to run parallel with UART).
|
|
||||||
Future<void> _preloadNavigatorImages() async {
|
Future<void> _preloadNavigatorImages() async {
|
||||||
final images = await ConfigService.instance.getNavigatorImages();
|
final images = await ConfigService.instance.getNavigatorImages();
|
||||||
for (final file in images) {
|
for (final file in images) {
|
||||||
// precacheImage needs a context, but we're in initState territory
|
|
||||||
// Use the root context via a post-frame callback workaround
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await precacheImage(FileImage(file), context);
|
await precacheImage(FileImage(file), context);
|
||||||
}
|
}
|
||||||
|
_updateStatus('Navigator', 'Ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -121,7 +119,7 @@ class _AppRootState extends State<AppRoot> {
|
|||||||
if (_overheatTriggered) {
|
if (_overheatTriggered) {
|
||||||
child = const OverheatScreen(key: ValueKey('overheat'));
|
child = const OverheatScreen(key: ValueKey('overheat'));
|
||||||
} else if (!_initialized) {
|
} else if (!_initialized) {
|
||||||
child = SplashScreen(key: const ValueKey('splash'), status: _initStatus);
|
child = SplashScreen(key: const ValueKey('splash'), statuses: _initStatuses);
|
||||||
} else {
|
} else {
|
||||||
child = const DashboardScreen(key: ValueKey('dashboard'));
|
child = const DashboardScreen(key: ValueKey('dashboard'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import 'package:flutter/material.dart';
|
|||||||
import '../theme/app_theme.dart';
|
import '../theme/app_theme.dart';
|
||||||
|
|
||||||
/// Splash screen - shown during initialization
|
/// Splash screen - shown during initialization
|
||||||
|
///
|
||||||
|
/// Displays parallel status items that independently flip to "Ready".
|
||||||
class SplashScreen extends StatelessWidget {
|
class SplashScreen extends StatelessWidget {
|
||||||
final String status;
|
final Map<String, String> statuses;
|
||||||
|
|
||||||
const SplashScreen({super.key, required this.status});
|
const SplashScreen({super.key, required this.statuses});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -33,13 +35,19 @@ class SplashScreen extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 32),
|
||||||
Text(
|
Row(
|
||||||
status,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: statuses.entries.map((entry) {
|
||||||
|
final isReady = entry.value == 'Ready';
|
||||||
|
return Text(
|
||||||
|
'${entry.key}: ${entry.value}',
|
||||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
fontSize: 80,
|
fontSize: 48,
|
||||||
color: theme.subdued,
|
color: isReady ? theme.foreground : theme.subdued,
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user