diff --git a/arduino/hello.ino b/arduino/hello.ino deleted file mode 100644 index 47c69e6..0000000 --- a/arduino/hello.ino +++ /dev/null @@ -1,10 +0,0 @@ -void setup() { - pinMode(LED_BUILTIN, OUTPUT); -} - -void loop() { - digitalWrite(LED_BUILTIN, HIGH); - delay(10); - digitalWrite(LED_BUILTIN, LOW); - delay(1000); -} diff --git a/pi/ui/lib/app_root.dart b/pi/ui/lib/app_root.dart new file mode 100644 index 0000000..4015afd --- /dev/null +++ b/pi/ui/lib/app_root.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import 'screens/splash_screen.dart'; +import 'screens/dashboard_screen.dart'; + +/// Root widget that manages app state transitions +class AppRoot extends StatefulWidget { + const AppRoot({super.key}); + + @override + State createState() => _AppRootState(); +} + +class _AppRootState extends State { + bool _initialized = false; + String _initStatus = 'Starting...'; + + @override + void initState() { + super.initState(); + _runInitSequence(); + } + + Future _runInitSequence() async { + // Simulate init checks - replace with real checks later + // (UART, GPS, sensors, etc.) + + setState(() => _initStatus = 'Checking systems...'); + await Future.delayed(const Duration(milliseconds: 800)); + + setState(() => _initStatus = 'UART: standby'); + await Future.delayed(const Duration(milliseconds: 400)); + + setState(() => _initStatus = 'GPS: standby'); + await Future.delayed(const Duration(milliseconds: 400)); + + setState(() => _initStatus = 'Ready'); + await Future.delayed(const Duration(milliseconds: 300)); + + setState(() => _initialized = true); + } + + @override + Widget build(BuildContext context) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: _initialized + ? const DashboardScreen(key: ValueKey('dashboard')) + : SplashScreen(key: const ValueKey('splash'), status: _initStatus), + ); + } +} diff --git a/pi/ui/lib/main.dart b/pi/ui/lib/main.dart index 98504d2..d89e8aa 100644 --- a/pi/ui/lib/main.dart +++ b/pi/ui/lib/main.dart @@ -1,7 +1,7 @@ -import 'dart:async'; -import 'dart:math'; import 'package:flutter/material.dart'; +import 'app_root.dart'; + void main() { runApp(const SmartSerowApp()); } @@ -25,230 +25,3 @@ class SmartSerowApp extends StatelessWidget { ); } } - -/// Root widget that manages app state transitions -class AppRoot extends StatefulWidget { - const AppRoot({super.key}); - - @override - State createState() => _AppRootState(); -} - -class _AppRootState extends State { - bool _initialized = false; - String _initStatus = 'Starting...'; - - @override - void initState() { - super.initState(); - _runInitSequence(); - } - - Future _runInitSequence() async { - // Simulate init checks - replace with real checks later - // (UART, GPS, sensors, etc.) - - setState(() => _initStatus = 'Checking systems...'); - await Future.delayed(const Duration(milliseconds: 800)); - - setState(() => _initStatus = 'UART: standby'); - await Future.delayed(const Duration(milliseconds: 400)); - - setState(() => _initStatus = 'GPS: standby'); - await Future.delayed(const Duration(milliseconds: 400)); - - setState(() => _initStatus = 'Ready'); - await Future.delayed(const Duration(milliseconds: 300)); - - setState(() => _initialized = true); - } - - @override - Widget build(BuildContext context) { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - child: _initialized - ? const DashboardScreen(key: ValueKey('dashboard')) - : SplashScreen(key: const ValueKey('splash'), status: _initStatus), - ); - } -} - -/// Splash screen - shown during initialization -class SplashScreen extends StatelessWidget { - final String status; - - const SplashScreen({super.key, required this.status}); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.terrain, - size: 120, - color: Theme.of(context).colorScheme.primary, - ), - const SizedBox(height: 24), - Text( - 'Smart Serow', - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - Text( - status, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Colors.grey, - ), - ), - ], - ), - ), - ); - } -} - -/// Main dashboard - placeholder with random updating values -class DashboardScreen extends StatefulWidget { - const DashboardScreen({super.key}); - - @override - State createState() => _DashboardScreenState(); -} - -class _DashboardScreenState extends State { - final _random = Random(); - Timer? _timer; - - int _speed = 0; - int _rpm = 0; - double _voltage = 12.6; - int _temp = 25; - - @override - void initState() { - super.initState(); - // Update random values every 500ms - simulates live data - _timer = Timer.periodic(const Duration(milliseconds: 500), (_) { - setState(() { - _speed = _random.nextInt(120); - _rpm = 1000 + _random.nextInt(8000); - _voltage = 11.5 + _random.nextDouble() * 2; - _temp = 20 + _random.nextInt(60); - }); - }); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: Padding( - padding: const EdgeInsets.all(32), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Header - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'SMART SEROW', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Colors.teal, - letterSpacing: 2, - ), - ), - Text( - '${_voltage.toStringAsFixed(1)}V', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: _voltage < 12.0 ? Colors.red : Colors.green, - ), - ), - ], - ), - - const SizedBox(height: 48), - - // Main speed display - Expanded( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '$_speed', - style: const TextStyle( - fontSize: 180, - fontWeight: FontWeight.w200, - color: Colors.white, - height: 1, - ), - ), - Text( - 'km/h', - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - color: Colors.grey, - ), - ), - ], - ), - ), - ), - - // Bottom stats row - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _StatBox(label: 'RPM', value: _rpm.toString()), - _StatBox(label: 'TEMP', value: '$_temp°C'), - _StatBox(label: 'GEAR', value: '—'), - ], - ), - ], - ), - ), - ); - } -} - -class _StatBox extends StatelessWidget { - final String label; - final String value; - - const _StatBox({required this.label, required this.value}); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Text( - value, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.white, - ), - ), - Text( - label, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.grey, - letterSpacing: 1, - ), - ), - ], - ); - } -} diff --git a/pi/ui/lib/screens/dashboard_screen.dart b/pi/ui/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..79a69e3 --- /dev/null +++ b/pi/ui/lib/screens/dashboard_screen.dart @@ -0,0 +1,115 @@ +import 'dart:async'; +import 'dart:math'; +import 'package:flutter/material.dart'; + +import '../widgets/stat_box.dart'; + +/// Main dashboard - placeholder with random updating values +class DashboardScreen extends StatefulWidget { + const DashboardScreen({super.key}); + + @override + State createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + final _random = Random(); + Timer? _timer; + + int _speed = 0; + int _rpm = 0; + double _voltage = 12.6; + int _temp = 25; + + @override + void initState() { + super.initState(); + // Update random values every 500ms - simulates live data + _timer = Timer.periodic(const Duration(milliseconds: 500), (_) { + setState(() { + _speed = _random.nextInt(120); + _rpm = 1000 + _random.nextInt(8000); + _voltage = 11.5 + _random.nextDouble() * 2; + _temp = 20 + _random.nextInt(60); + }); + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: Padding( + padding: const EdgeInsets.all(32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'SMART SEROW', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Colors.teal, + letterSpacing: 2, + ), + ), + Text( + '${_voltage.toStringAsFixed(1)}V', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: _voltage < 12.0 ? Colors.red : Colors.green, + ), + ), + ], + ), + + const SizedBox(height: 48), + + // Main speed display + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '$_speed', + style: const TextStyle( + fontSize: 180, + fontWeight: FontWeight.w200, + color: Colors.white, + height: 1, + ), + ), + Text( + 'km/h', + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: Colors.grey, + ), + ), + ], + ), + ), + ), + + // Bottom stats row + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + StatBox(label: 'RPM', value: _rpm.toString()), + StatBox(label: 'TEMP', value: '$_temp°C'), + StatBox(label: 'GEAR', value: '—'), + ], + ), + ], + ), + ), + ); + } +} diff --git a/pi/ui/lib/screens/splash_screen.dart b/pi/ui/lib/screens/splash_screen.dart new file mode 100644 index 0000000..d09dd78 --- /dev/null +++ b/pi/ui/lib/screens/splash_screen.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +/// Splash screen - shown during initialization +class SplashScreen extends StatelessWidget { + final String status; + + const SplashScreen({super.key, required this.status}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.terrain, + size: 120, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(height: 24), + Text( + 'Smart Serow', + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Text( + status, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.grey, + ), + ), + ], + ), + ), + ); + } +} diff --git a/pi/ui/lib/widgets/stat_box.dart b/pi/ui/lib/widgets/stat_box.dart new file mode 100644 index 0000000..2ef0644 --- /dev/null +++ b/pi/ui/lib/widgets/stat_box.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +/// A labeled stat display box for the dashboard +class StatBox extends StatelessWidget { + final String label; + final String value; + + const StatBox({super.key, required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text( + value, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.white, + ), + ), + Text( + label, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey, + letterSpacing: 1, + ), + ), + ], + ); + } +} diff --git a/scripts/__pycache__/build.cpython-39.pyc b/scripts/__pycache__/build.cpython-39.pyc new file mode 100644 index 0000000..94c3e1a Binary files /dev/null and b/scripts/__pycache__/build.cpython-39.pyc differ diff --git a/scripts/__pycache__/deploy.cpython-39.pyc b/scripts/__pycache__/deploy.cpython-39.pyc new file mode 100644 index 0000000..8b82490 Binary files /dev/null and b/scripts/__pycache__/deploy.cpython-39.pyc differ