Colour patterns and speed unit selection working

This commit is contained in:
Mikkeli Matlock
2025-08-24 23:14:19 +09:00
parent bab90a9830
commit fb7926d38d
12 changed files with 417 additions and 126 deletions
+15 -15
View File
@@ -19,25 +19,25 @@ class ColorTheme {
class ColorThemes {
static const List<ColorTheme> themes = [
ColorTheme(
name: 'Classic Dark',
background: Color(0xFF121212),
speedText: Color(0xFF4CAF50),
headingText: Color(0xFFFF9800),
unitText: Color(0xFF9E9E9E),
name: 'Yuzuki',
background: Color.fromARGB(255, 114, 85, 109),
speedText: Color.fromARGB(255, 246, 224, 255),
headingText: Color.fromARGB(255, 244, 201, 255),
unitText: Color.fromARGB(255, 244, 201, 255),
),
ColorTheme(
name: 'Electric Blue',
background: Color(0xFF0D1421),
speedText: Color(0xFF00E5FF),
headingText: Color(0xFFFFEB3B),
unitText: Color(0xFF78909C),
name: 'Yuubari',
background: Color.fromARGB(255, 85, 99, 112),
speedText: Color.fromARGB(255, 255, 166, 0),
headingText: Color.fromARGB(255, 228, 235, 238),
unitText: Color.fromARGB(255, 141, 196, 184),
),
ColorTheme(
name: 'Racing Red',
background: Color(0xFF1A0A0A),
speedText: Color(0xFFFF1744),
headingText: Color(0xFFFFFFFF),
unitText: Color(0xFFB0BEC5),
name: 'Yuzuki Light',
background: Color.fromARGB(255, 246, 224, 255),
speedText: Color.fromARGB(255, 114, 85, 109),
headingText: Color.fromARGB(255, 140, 94, 151),
unitText: Color.fromARGB(255, 114, 85, 109),
),
];
+1 -1
View File
@@ -31,6 +31,6 @@ class GpsService {
static String formatHeading(double heading) {
if (heading < 0) return 'N/A';
return '${getCompassDirection(heading)} ${heading.round()}°';
return '${heading.round()} ${getCompassDirection(heading)}';
}
}
+239 -84
View File
@@ -1,29 +1,30 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:geolocator/geolocator.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'speed_units.dart';
import 'gps_service.dart';
import 'color_themes.dart';
void main() {
runApp(const GpsSpeedoApp());
runApp(const SpeedoApp());
}
// Entry point for future overlay mode
void mainOverlay() {
// TODO: Initialize overlay-specific setup
runApp(const GpsSpeedoApp(isOverlayMode: true));
runApp(const SpeedoApp(isOverlayMode: true));
}
class GpsSpeedoApp extends StatelessWidget {
class SpeedoApp extends StatelessWidget {
final bool isOverlayMode;
const GpsSpeedoApp({super.key, this.isOverlayMode = false});
const SpeedoApp({super.key, this.isOverlayMode = false});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GPS Speedo',
title: 'Speedo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
@@ -56,11 +57,21 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
void initState() {
super.initState();
_initializeGps();
_enableWakelock();
}
Future<void> _enableWakelock() async {
try {
await WakelockPlus.enable();
} catch (e) {
// Wake lock not supported on this platform, continue normally
}
}
@override
void dispose() {
_positionSubscription?.cancel();
WakelockPlus.disable();
super.dispose();
}
@@ -116,87 +127,231 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
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,
),
),
],
),
),
),
],
),
),
child: LayoutBuilder(
builder: (context, constraints) {
final isLandscape = constraints.maxWidth > constraints.maxHeight;
if (isLandscape) {
return _buildLandscapeLayout(speedText, currentTheme);
} else {
return _buildPortraitLayout(speedText, currentTheme);
}
},
),
),
);
}
Widget _buildPortraitLayout(String speedText, ColorTheme currentTheme) {
return Column(
children: [
// Speed area - takes most space
Expanded(
flex: 7,
child: GestureDetector(
onTap: _cycleUnit,
child: Container(
width: double.infinity,
color: Colors.transparent,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_errorMessage.isNotEmpty)
Flexible(
child: Text(
_errorMessage,
style: TextStyle(
color: currentTheme.speedText,
fontSize: 18,
fontFamily: 'DIN1451Alt',
),
textAlign: TextAlign.center,
),
)
else ...[
Flexible(
flex: 3,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
speedText,
style: TextStyle(
fontSize: 200,
fontWeight: FontWeight.w300,
color: currentTheme.speedText,
fontFamily: 'DIN1451Alt',
),
),
),
),
Flexible(
flex: 1,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
_currentUnit.label,
style: TextStyle(
fontSize: 64,
color: currentTheme.unitText,
fontFamily: 'DIN1451Alt',
),
),
),
),
],
],
),
),
),
),
// Compass area - compact but tappable
Expanded(
flex: 3,
child: GestureDetector(
onTap: _cycleTheme,
child: Container(
width: double.infinity,
color: Colors.transparent,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
flex: 2,
child: Icon(
Icons.navigation,
size: 100,
color: currentTheme.headingText,
),
),
Flexible(
flex: 1,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
GpsService.formatHeading(_heading),
style: TextStyle(
fontSize: 96,
color: currentTheme.headingText,
fontFamily: 'DIN1451Alt',
),
),
),
),
],
),
),
),
),
],
);
}
Widget _buildLandscapeLayout(String speedText, ColorTheme currentTheme) {
return Row(
children: [
// Speed area - takes majority of space
Expanded(
flex: 65,
child: GestureDetector(
onTap: _cycleUnit,
child: Container(
height: double.infinity,
color: Colors.transparent,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_errorMessage.isNotEmpty)
Flexible(
child: Text(
_errorMessage,
style: TextStyle(
color: currentTheme.speedText,
fontSize: 16,
fontFamily: 'DIN1451Alt',
),
textAlign: TextAlign.center,
),
)
else ...[
Flexible(
flex: 3,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
speedText,
style: TextStyle(
fontSize: 120,
fontWeight: FontWeight.w300,
color: currentTheme.speedText,
fontFamily: 'DIN1451Alt',
),
),
),
),
Flexible(
flex: 1,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
_currentUnit.label,
style: TextStyle(
fontSize: 24,
color: currentTheme.unitText,
fontFamily: 'DIN1451Alt',
),
),
),
),
],
],
),
),
),
),
// Compass area - smaller but prominent
Expanded(
flex: 35,
child: GestureDetector(
onTap: _cycleTheme,
child: Container(
height: double.infinity,
color: Colors.transparent,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
flex: 2,
child: Icon(
Icons.navigation,
size: 32,
color: currentTheme.headingText,
),
),
const SizedBox(height: 8),
Flexible(
flex: 1,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
GpsService.formatHeading(_heading),
style: TextStyle(
fontSize: 18,
color: currentTheme.headingText,
fontFamily: 'DIN1451Alt',
),
),
),
),
],
),
),
),
),
],
);
}
}