Complete v2.0 ultra-lightweight rebuild with premium color themes
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+190
-40
@@ -1,52 +1,202 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'providers/location_provider.dart';
|
||||
import 'providers/settings_provider.dart';
|
||||
import 'screens/speedometer_screen.dart';
|
||||
import 'dart:async';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'speed_units.dart';
|
||||
import 'gps_service.dart';
|
||||
import 'color_themes.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const SpeedometerApp());
|
||||
runApp(const GpsSpeedoApp());
|
||||
}
|
||||
|
||||
class SpeedometerApp extends StatelessWidget {
|
||||
const SpeedometerApp({super.key});
|
||||
// Entry point for future overlay mode
|
||||
void mainOverlay() {
|
||||
// TODO: Initialize overlay-specific setup
|
||||
runApp(const GpsSpeedoApp(isOverlayMode: true));
|
||||
}
|
||||
|
||||
class GpsSpeedoApp extends StatelessWidget {
|
||||
final bool isOverlayMode;
|
||||
|
||||
const GpsSpeedoApp({super.key, this.isOverlayMode = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight,
|
||||
]);
|
||||
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => SettingsProvider()),
|
||||
ChangeNotifierProvider(create: (_) => LocationProvider()),
|
||||
],
|
||||
child: Consumer<SettingsProvider>(
|
||||
builder: (context, settings, child) {
|
||||
return MaterialApp(
|
||||
title: 'GPS Speedometer',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: _buildTheme(settings.isDarkMode),
|
||||
home: const SpeedometerScreen(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData _buildTheme(bool isDarkMode) {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: isDarkMode ? Brightness.dark : Brightness.light,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: isDarkMode ? Brightness.dark : Brightness.light,
|
||||
return MaterialApp(
|
||||
title: 'GPS Speedo',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorSchemeSeed: Colors.green,
|
||||
),
|
||||
home: SpeedometerScreen(isOverlayMode: isOverlayMode),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpeedometerScreen extends StatefulWidget {
|
||||
final bool isOverlayMode;
|
||||
|
||||
const SpeedometerScreen({super.key, this.isOverlayMode = false});
|
||||
|
||||
@override
|
||||
State<SpeedometerScreen> createState() => _SpeedometerScreenState();
|
||||
}
|
||||
|
||||
class _SpeedometerScreenState extends State<SpeedometerScreen> {
|
||||
double _speed = 0.0;
|
||||
double _heading = -1.0;
|
||||
SpeedUnit _currentUnit = SpeedUnit.kmh;
|
||||
int _currentThemeIndex = 0;
|
||||
StreamSubscription<Position>? _positionSubscription;
|
||||
String _errorMessage = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeGps();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_positionSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _initializeGps() async {
|
||||
try {
|
||||
final serviceEnabled = await GpsService.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
setState(() => _errorMessage = 'Location services disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
final permissionGranted = await GpsService.requestPermissions();
|
||||
if (!permissionGranted) {
|
||||
setState(() => _errorMessage = 'Location permission denied');
|
||||
return;
|
||||
}
|
||||
|
||||
_positionSubscription = GpsService.positionStream.listen(
|
||||
_onPositionUpdate,
|
||||
onError: (error) => setState(() => _errorMessage = 'GPS error: $error'),
|
||||
);
|
||||
} catch (e) {
|
||||
setState(() => _errorMessage = 'Failed to initialize GPS');
|
||||
}
|
||||
}
|
||||
|
||||
void _onPositionUpdate(Position position) {
|
||||
setState(() {
|
||||
_speed = position.speed;
|
||||
_heading = position.heading;
|
||||
_errorMessage = '';
|
||||
});
|
||||
}
|
||||
|
||||
void _cycleUnit() {
|
||||
setState(() {
|
||||
_currentUnit = _currentUnit.next;
|
||||
});
|
||||
}
|
||||
|
||||
void _cycleTheme() {
|
||||
setState(() {
|
||||
_currentThemeIndex = ColorThemes.getNextThemeIndex(_currentThemeIndex);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final displaySpeed = _currentUnit.convert(_speed);
|
||||
final speedText = displaySpeed.toStringAsFixed(0);
|
||||
final currentTheme = ColorThemes.getTheme(_currentThemeIndex);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: currentTheme.background,
|
||||
body: SafeArea(
|
||||
child: GestureDetector(
|
||||
onTap: _cycleTheme,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
// Speed display - takes most space, tappable
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: GestureDetector(
|
||||
onTap: _cycleUnit,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (_errorMessage.isNotEmpty)
|
||||
Text(
|
||||
_errorMessage,
|
||||
style: TextStyle(
|
||||
color: currentTheme.speedText,
|
||||
fontSize: 16,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
else ...[
|
||||
Text(
|
||||
speedText,
|
||||
style: TextStyle(
|
||||
fontSize: 120,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: currentTheme.speedText,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_currentUnit.label,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: currentTheme.unitText,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Compass display - compact
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.navigation,
|
||||
size: 32,
|
||||
color: currentTheme.headingText,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
GpsService.formatHeading(_heading),
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: currentTheme.headingText,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user