5 Commits

Author SHA1 Message Date
Mikkeli Matlock e6ffb00b2a Updated speed displays 2025-08-25 07:41:49 +09:00
Mikkeli Matlock b3e85983e7 Changed GPS force update timeout to 5s 2025-08-25 07:30:35 +09:00
Mikkeli Matlock 89ce53bb1b Improve GPS performance and heading display
- Reduce GPS distanceFilter to 0 for faster updates
- Add timeLimit to ensure regular position updates
- Implement cached heading to avoid showing N/A when GPS heading unavailable
- Add speed display logic: show "--" when stationary with no heading data
- Add rotating navigation icon that points in heading direction
- Fix degree symbol in heading format display

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-25 07:26:20 +09:00
Mikkeli Matlock 56327c00ec Added Yuuparo light palette 2025-08-25 07:09:08 +09:00
Mikkeli Matlock 20a7691ac2 Cleaning up Kotlin messes 2025-08-24 23:52:49 +09:00
5 changed files with 50 additions and 15 deletions
+9 -2
View File
@@ -26,9 +26,9 @@ class ColorThemes {
unitText: Color.fromARGB(255, 244, 201, 255), unitText: Color.fromARGB(255, 244, 201, 255),
), ),
ColorTheme( ColorTheme(
name: 'Yuubari', name: 'Yuuparo',
background: Color.fromARGB(255, 85, 99, 112), background: Color.fromARGB(255, 85, 99, 112),
speedText: Color.fromARGB(255, 255, 166, 0), speedText: Color.fromARGB(255, 255, 171, 16),
headingText: Color.fromARGB(255, 228, 235, 238), headingText: Color.fromARGB(255, 228, 235, 238),
unitText: Color.fromARGB(255, 141, 196, 184), unitText: Color.fromARGB(255, 141, 196, 184),
), ),
@@ -39,6 +39,13 @@ class ColorThemes {
headingText: Color.fromARGB(255, 140, 94, 151), headingText: Color.fromARGB(255, 140, 94, 151),
unitText: Color.fromARGB(255, 114, 85, 109), unitText: Color.fromARGB(255, 114, 85, 109),
), ),
ColorTheme(
name: 'Yuuparo Light',
background: Color.fromARGB(255, 228, 235, 238),
speedText: Color.fromARGB(255, 85, 99, 112),
headingText: Color.fromARGB(255, 255, 157, 65),
unitText: Color.fromARGB(255, 98, 124, 118),
),
]; ];
static ColorTheme getTheme(int index) { static ColorTheme getTheme(int index) {
+15 -5
View File
@@ -3,11 +3,13 @@ import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class GpsService { class GpsService {
static double? _lastValidHeading;
static Stream<Position> get positionStream { static Stream<Position> get positionStream {
return Geolocator.getPositionStream( return Geolocator.getPositionStream(
locationSettings: const LocationSettings( locationSettings: const LocationSettings(
accuracy: LocationAccuracy.bestForNavigation, accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 1, distanceFilter: 0,
), ),
); );
} }
@@ -21,16 +23,24 @@ class GpsService {
return await Geolocator.isLocationServiceEnabled(); return await Geolocator.isLocationServiceEnabled();
} }
static double getCachedHeading(double heading) {
if (heading >= 0 && heading <= 360) {
_lastValidHeading = heading;
return heading;
}
return _lastValidHeading ?? 0.0;
}
static String getCompassDirection(double heading) { static String getCompassDirection(double heading) {
if (heading < 0) return 'N/A'; final cachedHeading = getCachedHeading(heading);
const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']; const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
final index = ((heading + 22.5) / 45).floor() % 8; final index = ((cachedHeading + 22.5) / 45).floor() % 8;
return directions[index]; return directions[index];
} }
static String formatHeading(double heading) { static String formatHeading(double heading) {
if (heading < 0) return 'N/A'; final cachedHeading = getCachedHeading(heading);
return '${heading.round()} ${getCompassDirection(heading)}'; return '${cachedHeading.round()}° ${getCompassDirection(heading)}';
} }
} }
+25 -8
View File
@@ -5,6 +5,7 @@ import 'package:wakelock_plus/wakelock_plus.dart';
import 'speed_units.dart'; import 'speed_units.dart';
import 'gps_service.dart'; import 'gps_service.dart';
import 'color_themes.dart'; import 'color_themes.dart';
import 'dart:math' as math; // Import for math.pi
void main() { void main() {
runApp(const SpeedoApp()); runApp(const SpeedoApp());
@@ -91,7 +92,13 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
_positionSubscription = GpsService.positionStream.listen( _positionSubscription = GpsService.positionStream.listen(
_onPositionUpdate, _onPositionUpdate,
onError: (error) => setState(() => _errorMessage = 'GPS error: $error'), onError: (error) {
// Only show persistent GPS errors, not temporary signal issues
final errorString = error.toString().toLowerCase();
if (!errorString.contains('timeout') && !errorString.contains('temporarily')) {
setState(() => _errorMessage = 'GPS error: $error');
}
},
); );
} catch (e) { } catch (e) {
setState(() => _errorMessage = 'Failed to initialize GPS'); setState(() => _errorMessage = 'Failed to initialize GPS');
@@ -117,11 +124,18 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
_currentThemeIndex = ColorThemes.getNextThemeIndex(_currentThemeIndex); _currentThemeIndex = ColorThemes.getNextThemeIndex(_currentThemeIndex);
}); });
} }
String _getSpeedDisplayText(double displaySpeed) {
if (_speed < 1.0 && (_heading < 0.0 || _heading >= 360.0)) {
return '--';
}
return displaySpeed.toStringAsFixed(1);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final displaySpeed = _currentUnit.convert(_speed); final displaySpeed = _currentUnit.convert(_speed);
final speedText = displaySpeed.toStringAsFixed(0); final speedText = _getSpeedDisplayText(displaySpeed);
final currentTheme = ColorThemes.getTheme(_currentThemeIndex); final currentTheme = ColorThemes.getTheme(_currentThemeIndex);
return Scaffold( return Scaffold(
@@ -151,7 +165,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
color: Colors.transparent, color: Colors.transparent,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(12),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@@ -177,7 +191,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
child: Text( child: Text(
speedText, speedText,
style: TextStyle( style: TextStyle(
fontSize: 200, fontSize: 160,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
color: currentTheme.speedText, color: currentTheme.speedText,
fontFamily: 'DIN1451Alt', fontFamily: 'DIN1451Alt',
@@ -222,10 +236,13 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
children: [ children: [
Flexible( Flexible(
flex: 2, flex: 2,
child: Icon( child: Transform.rotate(
Icons.navigation, angle: (_heading >= 0 && _heading < 360) ? (_heading * math.pi / 180.0) : 0,
size: 100, child: Icon(
color: currentTheme.headingText, Icons.navigation,
size: 100,
color: currentTheme.headingText,
),
), ),
), ),
Flexible( Flexible(
+1
View File
@@ -21,6 +21,7 @@ dependencies:
# Keep screen awake during navigation # Keep screen awake during navigation
wakelock_plus: ^1.2.8 wakelock_plus: ^1.2.8
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter