Implement v1.1 enhancements: auto-rotate, interactive gauge, and extended units

Features added:
- Auto-rotate support for portrait/landscape orientations
- Interactive gauge: tap to switch between digital and analog modes
- Extended speed units: added knots with quick selector buttons
- New SpeedUnitSelector widget for convenient unit switching

Known issues identified:
- 10 package dependencies need updates to latest compatible versions
- Orientation switching needs to override system auto-rotate toggle
- Background color customization moved back to TODO (build issues)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2025-08-21 01:58:46 +09:00
parent b0125dff6b
commit 71b7611a48
9 changed files with 155 additions and 32 deletions
+5 -1
View File
@@ -8,7 +8,11 @@
"Bash(flutter pub:*)", "Bash(flutter pub:*)",
"Bash(mkdir:*)", "Bash(mkdir:*)",
"Bash(flutter analyze:*)", "Bash(flutter analyze:*)",
"Bash(flutter build:*)" "Bash(flutter build:*)",
"Bash(git push:*)",
"Bash(cp:*)",
"Bash(flutter:*)",
"Bash(powershell:*)"
], ],
"defaultMode": "acceptEdits" "defaultMode": "acceptEdits"
} }
+16 -6
View File
@@ -3,8 +3,8 @@
## Project Overview ## Project Overview
A customizable Flutter GPS speedometer app targeting Android devices, emphasizing modularity and user customization. A customizable Flutter GPS speedometer app targeting Android devices, emphasizing modularity and user customization.
## Current Status: v1.0 Complete  ## Current Status: v1.1 Complete 
**Fully functional GPS speedometer with all core requirements implemented.** **Enhanced GPS speedometer with improved user experience and customization options.**
### Architecture Implemented ### Architecture Implemented
- **State Management:** Provider pattern - **State Management:** Provider pattern
@@ -15,7 +15,7 @@ A customizable Flutter GPS speedometer app targeting Android devices, emphasizin
### Core Features Completed ### Core Features Completed
-  Real-time GPS speed tracking with accuracy monitoring -  Real-time GPS speed tracking with accuracy monitoring
-  Customizable speed units (km/h ” mph) with persistent storage -  Customizable speed units (km/h � mph) with persistent storage
-  Dual display styles: Digital speedometer + Analog gauge -  Dual display styles: Digital speedometer + Analog gauge
-  Dark/Light theme support with system integration -  Dark/Light theme support with system integration
-  Optional metrics panel: coordinates, distance, trip time, accuracy -  Optional metrics panel: coordinates, distance, trip time, accuracy
@@ -31,12 +31,22 @@ Architecture: Provider + Consumer pattern for reactive state management
Permissions: Location services with graceful degradation Permissions: Location services with graceful degradation
``` ```
## TODO - Future Improvements = ## Recent Updates (v1.1)
### ✅ Completed Features
- [x] **Portrait/Landscape Support**: Automatic orientation detection implemented
- [x] **Interactive Gauge**: Click gauge face to switch digital/analog modes
- [x] **Extended Speed Units**: Added knots support with quick selector buttons
## TODO - Future Improvements
### Critical Issues
- [ ] **Dependency Updates**: Update 10 packages to latest compatible versions
- [ ] **Orientation Override**: Fix auto-rotate to work regardless of system toggle status
### UI/UX Enhancements ### UI/UX Enhancements
- [ ] **Portrait/Landscape Support**: Automatic orientation detection (ignore system auto-rotate toggle) - [ ] **Background Customization**: Custom background color selection
- [ ] **Interface Rearrangement**: User-defined layout customization - [ ] **Interface Rearrangement**: User-defined layout customization
- [ ] **Interactive Gauge**: Click gauge face to switch digital/analog modes
- [ ] **Settings Menu Refurbishment**: More intuitive interactions and better UX - [ ] **Settings Menu Refurbishment**: More intuitive interactions and better UX
### Technical Improvements ### Technical Improvements
+11 -5
View File
@@ -9,11 +9,11 @@ A customizable Flutter speedometer app using GPS location services to display re
- Real-time speed display using GPS - Real-time speed display using GPS
- High-precision location tracking - High-precision location tracking
- Works offline (no internet connection required) - Works offline (no internet connection required)
- Portrait orientation for mobile phones - Automatic portrait/landscape orientation support
### Customizable Settings ### Customizable Settings
- **Speed Units**: mph, km/h (expandable for more units) - **Speed Units**: mph, km/h, knots (with convenient unit selector buttons)
- **Display Style**: Digital speedometer, analog gauge (expandable for more styles) - **Display Style**: Digital speedometer, analog gauge (tap gauge to switch)
- **Theme**: Light/Dark mode (expandable for custom themes) - **Theme**: Light/Dark mode (expandable for custom themes)
- **Metrics Display**: Toggle additional metrics on/off - **Metrics Display**: Toggle additional metrics on/off
- Current location coordinates - Current location coordinates
@@ -23,11 +23,17 @@ A customizable Flutter speedometer app using GPS location services to display re
### Technical Specifications ### Technical Specifications
- Target Platform: Android (primary) - Target Platform: Android (primary)
- Screen Orientation: Portrait (locked) - Screen Orientation: Auto-rotate (portrait/landscape)
- Target Device: Mobile phones - Target Device: Mobile phones and tablets
- GPS Requirements: Location permissions required - GPS Requirements: Location permissions required
- Offline Capability: Full functionality without internet - Offline Capability: Full functionality without internet
### New Features in v1.1
- **Auto-rotate Support**: App now supports both portrait and landscape orientations
- **Interactive Gauge**: Tap the speedometer to switch between digital and analog modes
- **Extended Units**: Added knots as a speed unit option
- **Unit Selector**: Quick access unit selection buttons next to the speedometer
## Architecture Design ## Architecture Design
The app is designed with customization and extensibility in mind: The app is designed with customization and extensibility in mind:
@@ -650,7 +650,7 @@ code + .copy-button {
<script type="text/javascript"> <script type="text/javascript">
function configurationCacheProblems() { return ( function configurationCacheProblems() { return (
// begin-report-data // begin-report-data
{"diagnostics":[{"problem":[{"text":"Executing Gradle on JVM versions 16 and lower has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This will fail with an error in Gradle 9.0."}],"contextualLabel":"Executing Gradle on JVM versions 16 and lower has been deprecated.","documentationLink":"https://docs.gradle.org/8.12/userguide/upgrading_version_8.html#minimum_daemon_jvm_version","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"executing-gradle-on-jvm-versions-and-lower","displayName":"Executing Gradle on JVM versions 16 and lower has been deprecated."}],"solutions":[[{"text":"Use JVM 17 or greater to execute Gradle. Projects can continue to use older JVM versions via toolchains."}]]},{"problem":[{"text":"No variants exist that would match the request"}],"severity":"ERROR","contextualLabel":"Dependency requires at least JVM runtime version 11. This build uses a Java 8 JVM.","documentationLink":"https://docs.gradle.org/8.12/userguide/variant_model.html#sec:variant-select-errors","problemId":[{"name":"dependency-variant-resolution","displayName":"Variant resolution"},{"name":"no-compatible-variants","displayName":"No variants exist that would match the request"}]}],"problemsReport":{"totalProblemCount":2,"requestedTasks":"","documentationLink":"https://docs.gradle.org/8.12/userguide/problems-report.html","documentationLinkCaption":"Problem report","summaries":[]}} {"diagnostics":[{"problem":[{"text":"Executing Gradle on JVM versions 16 and lower has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This will fail with an error in Gradle 9.0."}],"contextualLabel":"Executing Gradle on JVM versions 16 and lower has been deprecated.","documentationLink":"https://docs.gradle.org/8.12/userguide/upgrading_version_8.html#minimum_daemon_jvm_version","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"executing-gradle-on-jvm-versions-and-lower","displayName":"Executing Gradle on JVM versions 16 and lower has been deprecated."}],"solutions":[[{"text":"Use JVM 17 or greater to execute Gradle. Projects can continue to use older JVM versions via toolchains."}]]}],"problemsReport":{"totalProblemCount":1,"requestedTasks":"","documentationLink":"https://docs.gradle.org/8.12/userguide/problems-report.html","documentationLinkCaption":"Problem report","summaries":[]}}
// end-report-data // end-report-data
);} );}
</script> </script>
+6 -1
View File
@@ -14,7 +14,12 @@ class SpeedometerApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
return MultiProvider( return MultiProvider(
providers: [ providers: [
+18 -6
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
enum SpeedUnit { kmh, mph } enum SpeedUnit { kmh, mph, knots }
enum SpeedometerStyle { digital, analog } enum SpeedometerStyle { digital, analog }
class SettingsProvider extends ChangeNotifier { class SettingsProvider extends ChangeNotifier {
@@ -21,7 +21,16 @@ class SettingsProvider extends ChangeNotifier {
bool get showTripTime => _showTripTime; bool get showTripTime => _showTripTime;
bool get showAccuracy => _showAccuracy; bool get showAccuracy => _showAccuracy;
String get speedUnitString => _speedUnit == SpeedUnit.kmh ? 'km/h' : 'mph'; String get speedUnitString {
switch (_speedUnit) {
case SpeedUnit.kmh:
return 'km/h';
case SpeedUnit.mph:
return 'mph';
case SpeedUnit.knots:
return 'knots';
}
}
SettingsProvider() { SettingsProvider() {
_loadSettings(); _loadSettings();
@@ -89,10 +98,13 @@ class SettingsProvider extends ChangeNotifier {
} }
double convertSpeed(double speedMps) { double convertSpeed(double speedMps) {
if (_speedUnit == SpeedUnit.kmh) { switch (_speedUnit) {
return speedMps * 3.6; case SpeedUnit.kmh:
} else { return speedMps * 3.6;
return speedMps * 2.237; case SpeedUnit.mph:
return speedMps * 2.237;
case SpeedUnit.knots:
return speedMps * 1.944;
} }
} }
} }
+12
View File
@@ -128,6 +128,17 @@ class SettingsScreen extends StatelessWidget {
} }
}, },
), ),
RadioListTile<SpeedUnit>(
title: const Text('Knots'),
value: SpeedUnit.knots,
groupValue: settings.speedUnit,
onChanged: (value) {
if (value != null) {
settings.setSpeedUnit(value);
Navigator.pop(context);
}
},
),
], ],
), ),
actions: [ actions: [
@@ -183,4 +194,5 @@ class SettingsScreen extends StatelessWidget {
), ),
); );
} }
} }
+29 -12
View File
@@ -5,6 +5,7 @@ import '../providers/settings_provider.dart';
import '../widgets/digital_speedometer.dart'; import '../widgets/digital_speedometer.dart';
import '../widgets/analog_speedometer.dart'; import '../widgets/analog_speedometer.dart';
import '../widgets/info_panel.dart'; import '../widgets/info_panel.dart';
import '../widgets/speed_unit_selector.dart';
import 'settings_screen.dart'; import 'settings_screen.dart';
class SpeedometerScreen extends StatefulWidget { class SpeedometerScreen extends StatefulWidget {
@@ -62,7 +63,17 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
children: [ children: [
Expanded( Expanded(
flex: 3, flex: 3,
child: _buildSpeedometer(locationProvider, settingsProvider), child: Column(
children: [
Expanded(
child: _buildSpeedometer(locationProvider, settingsProvider),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0),
child: const SpeedUnitSelector(),
),
],
),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
@@ -81,17 +92,23 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
Widget _buildSpeedometer(LocationProvider locationProvider, SettingsProvider settingsProvider) { Widget _buildSpeedometer(LocationProvider locationProvider, SettingsProvider settingsProvider) {
final speed = settingsProvider.convertSpeed(locationProvider.currentSpeed); final speed = settingsProvider.convertSpeed(locationProvider.currentSpeed);
if (settingsProvider.speedometerStyle == SpeedometerStyle.digital) { return GestureDetector(
return DigitalSpeedometer( onTap: () {
speed: speed, final newStyle = settingsProvider.speedometerStyle == SpeedometerStyle.digital
unit: settingsProvider.speedUnitString, ? SpeedometerStyle.analog
); : SpeedometerStyle.digital;
} else { settingsProvider.setSpeedometerStyle(newStyle);
return AnalogSpeedometer( },
speed: speed, child: settingsProvider.speedometerStyle == SpeedometerStyle.digital
unit: settingsProvider.speedUnitString, ? DigitalSpeedometer(
); speed: speed,
} unit: settingsProvider.speedUnitString,
)
: AnalogSpeedometer(
speed: speed,
unit: settingsProvider.speedUnitString,
),
);
} }
Widget _buildPermissionError(String message) { Widget _buildPermissionError(String message) {
+57
View File
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/settings_provider.dart';
class SpeedUnitSelector extends StatelessWidget {
const SpeedUnitSelector({super.key});
@override
Widget build(BuildContext context) {
return Consumer<SettingsProvider>(
builder: (context, settings, child) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: SpeedUnit.values.map((unit) {
final isSelected = settings.speedUnit == unit;
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ElevatedButton(
onPressed: () => settings.setSpeedUnit(unit),
style: ElevatedButton.styleFrom(
backgroundColor: isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surface,
foregroundColor: isSelected
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface,
elevation: isSelected ? 4 : 1,
padding: const EdgeInsets.symmetric(vertical: 8),
),
child: Text(
_getUnitDisplayText(unit),
style: TextStyle(
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 12,
),
),
),
),
);
}).toList(),
);
},
);
}
String _getUnitDisplayText(SpeedUnit unit) {
switch (unit) {
case SpeedUnit.kmh:
return 'km/h';
case SpeedUnit.mph:
return 'mph';
case SpeedUnit.knots:
return 'knots';
}
}
}