From 9173c3b93a41280aab89583271883ed51de53c31 Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Sun, 8 Feb 2026 02:56:32 +0900 Subject: [PATCH] new startup screen logic --- pi/ui/lib/app_root.dart | 58 ++++++++++++++-------------- pi/ui/lib/screens/splash_screen.dart | 26 ++++++++----- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/pi/ui/lib/app_root.dart b/pi/ui/lib/app_root.dart index a5b7441..bdb3ea5 100644 --- a/pi/ui/lib/app_root.dart +++ b/pi/ui/lib/app_root.dart @@ -19,7 +19,7 @@ class AppRoot extends StatefulWidget { class _AppRootState extends State { bool _initialized = false; bool _overheatTriggered = false; - String _initStatus = 'Starting...'; + final Map _initStatuses = {}; @override void initState() { @@ -33,27 +33,32 @@ class _AppRootState extends State { super.dispose(); } + void _updateStatus(String key, String value) { + setState(() => _initStatuses[key] = value); + } + Future _runInitSequence() async { - // Load config first - setState(() => _initStatus = 'Loading config...'); + // Show all items from the start so the row doesn't jump around + _updateStatus('Config', '...'); + _updateStatus('UART', '...'); + _updateStatus('Navigator', '...'); + + // Config must load first (everything else depends on it) + _updateStatus('Config', 'Loading'); 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)); - // 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 OverheatMonitor.instance.start( onOverheat: () { @@ -78,11 +83,8 @@ class _AppRootState extends State { if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; - final arduinoOk = data['arduino_connected'] == true; - - if (arduinoOk) { - setState(() => _initStatus = 'UART: OK'); - await Future.delayed(const Duration(milliseconds: 300)); + if (data['arduino_connected'] == true) { + _updateStatus('UART', 'Ready'); return; } } @@ -90,28 +92,24 @@ class _AppRootState extends State { // Backend not reachable yet - keep trying } - // Not connected yet - setState(() => _initStatus = 'UART: waiting...'); + _updateStatus('UART', 'Waiting'); await Future.delayed(retryDelay); } // Timeout - proceed anyway (UI will show stale data indicators) - setState(() => _initStatus = 'UART: timeout'); - await Future.delayed(const Duration(milliseconds: 500)); + _updateStatus('UART', 'Timeout'); } /// Preload navigator images into Flutter's image cache /// /// Scans for all PNGs in the navigator folder and precaches them. - /// Runs silently - no status updates (meant to run parallel with UART). Future _preloadNavigatorImages() async { final images = await ConfigService.instance.getNavigatorImages(); 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; await precacheImage(FileImage(file), context); } + _updateStatus('Navigator', 'Ready'); } @override @@ -121,7 +119,7 @@ class _AppRootState extends State { if (_overheatTriggered) { child = const OverheatScreen(key: ValueKey('overheat')); } else if (!_initialized) { - child = SplashScreen(key: const ValueKey('splash'), status: _initStatus); + child = SplashScreen(key: const ValueKey('splash'), statuses: _initStatuses); } else { child = const DashboardScreen(key: ValueKey('dashboard')); } diff --git a/pi/ui/lib/screens/splash_screen.dart b/pi/ui/lib/screens/splash_screen.dart index 4cf541c..b8ca7a3 100644 --- a/pi/ui/lib/screens/splash_screen.dart +++ b/pi/ui/lib/screens/splash_screen.dart @@ -3,10 +3,12 @@ import 'package:flutter/material.dart'; import '../theme/app_theme.dart'; /// Splash screen - shown during initialization +/// +/// Displays parallel status items that independently flip to "Ready". class SplashScreen extends StatelessWidget { - final String status; + final Map statuses; - const SplashScreen({super.key, required this.status}); + const SplashScreen({super.key, required this.statuses}); @override Widget build(BuildContext context) { @@ -33,13 +35,19 @@ class SplashScreen extends StatelessWidget { fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 16), - Text( - status, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - fontSize: 80, - color: theme.subdued, - ), + const SizedBox(height: 32), + Row( + 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( + fontSize: 48, + color: isReady ? theme.foreground : theme.subdued, + ), + ); + }).toList(), ), ], ),