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:
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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: [
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
case SpeedUnit.kmh:
|
||||||
return speedMps * 3.6;
|
return speedMps * 3.6;
|
||||||
} else {
|
case SpeedUnit.mph:
|
||||||
return speedMps * 2.237;
|
return speedMps * 2.237;
|
||||||
|
case SpeedUnit.knots:
|
||||||
|
return speedMps * 1.944;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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,8 +63,18 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
child: _buildSpeedometer(locationProvider, settingsProvider),
|
child: _buildSpeedometer(locationProvider, settingsProvider),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0),
|
||||||
|
child: const SpeedUnitSelector(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: InfoPanel(
|
child: InfoPanel(
|
||||||
@@ -81,18 +92,24 @@ 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: () {
|
||||||
|
final newStyle = settingsProvider.speedometerStyle == SpeedometerStyle.digital
|
||||||
|
? SpeedometerStyle.analog
|
||||||
|
: SpeedometerStyle.digital;
|
||||||
|
settingsProvider.setSpeedometerStyle(newStyle);
|
||||||
|
},
|
||||||
|
child: settingsProvider.speedometerStyle == SpeedometerStyle.digital
|
||||||
|
? DigitalSpeedometer(
|
||||||
speed: speed,
|
speed: speed,
|
||||||
unit: settingsProvider.speedUnitString,
|
unit: settingsProvider.speedUnitString,
|
||||||
);
|
)
|
||||||
} else {
|
: AnalogSpeedometer(
|
||||||
return AnalogSpeedometer(
|
|
||||||
speed: speed,
|
speed: speed,
|
||||||
unit: settingsProvider.speedUnitString,
|
unit: settingsProvider.speedUnitString,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPermissionError(String message) {
|
Widget _buildPermissionError(String message) {
|
||||||
return Center(
|
return Center(
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user