diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 9085051..8966d00 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -17,5 +17,5 @@
],
"defaultMode": "acceptEdits"
},
- "outputStyle": "Explanatory"
+ "outputStyle": "koivisto"
}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 3820a95..5680cd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,14 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
+
+# User notes - contains oversights and current matters
+# DO NOT REMOVE FROM GITIGNORE
+NOTES.txt
+
+# Local TODO for next Claude Code sessions
+# DO NOT REMOVE FROM GITIGNORE
+TODO.md
+
+# Development tools - generic utilities, not project-specific
+.tools/
\ No newline at end of file
diff --git a/README.md b/README.md
index bc44a16..65dddaf 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,28 @@
# GPS Speedometer
-A customizable Flutter speedometer app using GPS location services to display real-time speed.
+A GPS Speedometer aimed at Android on Motorcycle / Bicycle applications.
**Created with the help of Claude Code**
## Features
### Core Functionality
- Real-time speed display using GPS
-- High-precision location tracking
-- Works offline (no internet connection required)
-- Automatic portrait/landscape orientation support
+- Maybe also direction display using GPS
+- Good floating window / split screen support(hopefully)
### Customizable Settings
- **Speed Units**: mph, km/h, knots (with convenient unit selector buttons)
-- **Display Style**: Digital speedometer, analog gauge (tap gauge to switch)
- **Theme**: Light/Dark mode (expandable for custom themes)
-- **Metrics Display**: Toggle additional metrics on/off
- - Current location coordinates
- - Distance traveled
- - Trip duration
- - Current speed accuracy
### Technical Specifications
- Target Platform: Android (primary)
-- Screen Orientation: Auto-rotate (portrait/landscape)
- Target Device: Mobile phones and tablets
- GPS Requirements: Location permissions required
- 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
-The app is designed with customization and extensibility in mind:
-- Settings system supports multiple options per category
-- Modular design allows easy addition of new speedometer styles
-- Theme system supports unlimited themes
-- Metrics system allows toggle individual data points
+*To be updated*
## Permissions Required
- `ACCESS_FINE_LOCATION` - For high-precision GPS tracking
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 0d29021..2d1006a 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,28 +1,5 @@
-# This file configures the analyzer, which statically analyzes Dart code to
-# check for errors, warnings, and lints.
-#
-# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
-# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
-# invoked from the command line by running `flutter analyze`.
-
-# The following line activates a set of recommended lints for Flutter apps,
-# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
-linter:
- # The lint rules applied to this project can be customized in the
- # section below to disable rules from the `package:flutter_lints/flutter.yaml`
- # included above or to enable additional rules. A list of all available lints
- # and their documentation is published at https://dart.dev/lints.
- #
- # Instead of disabling a lint rule for the entire project in the
- # section below, it can also be suppressed for a single line of code
- # or a specific dart file by using the `// ignore: name_of_lint` and
- # `// ignore_for_file: name_of_lint` syntax on the line or in the file
- # producing the lint.
- rules:
- # avoid_print: false # Uncomment to disable the `avoid_print` rule
- # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
+analyzer:
+ exclude:
+ - backup_v1.1/**
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 3643690..2a51eea 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,6 +5,9 @@
+
+
+
function configurationCacheProblems() { return (
// 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."}]]}],"problemsReport":{"totalProblemCount":1,"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."}]]},{"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":[]}}
// end-report-data
);}
diff --git a/backup_v1.1/lib/main.dart b/backup_v1.1/lib/main.dart
new file mode 100644
index 0000000..a7aa7d1
--- /dev/null
+++ b/backup_v1.1/lib/main.dart
@@ -0,0 +1,52 @@
+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';
+
+void main() {
+ runApp(const SpeedometerApp());
+}
+
+class SpeedometerApp extends StatelessWidget {
+ const SpeedometerApp({super.key});
+
+ @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(
+ 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,
+ ),
+ );
+ }
+}
diff --git a/lib/providers/location_provider.dart b/backup_v1.1/lib/providers/location_provider.dart
similarity index 100%
rename from lib/providers/location_provider.dart
rename to backup_v1.1/lib/providers/location_provider.dart
diff --git a/lib/providers/settings_provider.dart b/backup_v1.1/lib/providers/settings_provider.dart
similarity index 100%
rename from lib/providers/settings_provider.dart
rename to backup_v1.1/lib/providers/settings_provider.dart
diff --git a/lib/screens/settings_screen.dart b/backup_v1.1/lib/screens/settings_screen.dart
similarity index 100%
rename from lib/screens/settings_screen.dart
rename to backup_v1.1/lib/screens/settings_screen.dart
diff --git a/lib/screens/speedometer_screen.dart b/backup_v1.1/lib/screens/speedometer_screen.dart
similarity index 100%
rename from lib/screens/speedometer_screen.dart
rename to backup_v1.1/lib/screens/speedometer_screen.dart
diff --git a/lib/widgets/analog_speedometer.dart b/backup_v1.1/lib/widgets/analog_speedometer.dart
similarity index 100%
rename from lib/widgets/analog_speedometer.dart
rename to backup_v1.1/lib/widgets/analog_speedometer.dart
diff --git a/lib/widgets/digital_speedometer.dart b/backup_v1.1/lib/widgets/digital_speedometer.dart
similarity index 100%
rename from lib/widgets/digital_speedometer.dart
rename to backup_v1.1/lib/widgets/digital_speedometer.dart
diff --git a/lib/widgets/info_panel.dart b/backup_v1.1/lib/widgets/info_panel.dart
similarity index 100%
rename from lib/widgets/info_panel.dart
rename to backup_v1.1/lib/widgets/info_panel.dart
diff --git a/lib/widgets/speed_unit_selector.dart b/backup_v1.1/lib/widgets/speed_unit_selector.dart
similarity index 100%
rename from lib/widgets/speed_unit_selector.dart
rename to backup_v1.1/lib/widgets/speed_unit_selector.dart
diff --git a/backup_v1.1/src/debug/AndroidManifest.xml b/backup_v1.1/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/backup_v1.1/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/backup_v1.1/src/main/AndroidManifest.xml b/backup_v1.1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3643690
--- /dev/null
+++ b/backup_v1.1/src/main/AndroidManifest.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/backup_v1.1/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/backup_v1.1/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
new file mode 100644
index 0000000..6ca0ba5
--- /dev/null
+++ b/backup_v1.1/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
@@ -0,0 +1,34 @@
+package io.flutter.plugins;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import io.flutter.Log;
+
+import io.flutter.embedding.engine.FlutterEngine;
+
+/**
+ * Generated file. Do not edit.
+ * This file is generated by the Flutter tool based on the
+ * plugins that support the Android platform.
+ */
+@Keep
+public final class GeneratedPluginRegistrant {
+ private static final String TAG = "GeneratedPluginRegistrant";
+ public static void registerWith(@NonNull FlutterEngine flutterEngine) {
+ try {
+ flutterEngine.getPlugins().add(new com.baseflow.geolocator.GeolocatorPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin geolocator_android, com.baseflow.geolocator.GeolocatorPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new com.baseflow.permissionhandler.PermissionHandlerPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin permission_handler_android, com.baseflow.permissionhandler.PermissionHandlerPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e);
+ }
+ }
+}
diff --git a/backup_v1.1/src/main/kotlin/com/example/gps_speedo/MainActivity.kt b/backup_v1.1/src/main/kotlin/com/example/gps_speedo/MainActivity.kt
new file mode 100644
index 0000000..d5079cc
--- /dev/null
+++ b/backup_v1.1/src/main/kotlin/com/example/gps_speedo/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.gps_speedo
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/backup_v1.1/src/main/res/drawable-v21/launch_background.xml b/backup_v1.1/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/backup_v1.1/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/backup_v1.1/src/main/res/drawable/launch_background.xml b/backup_v1.1/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/backup_v1.1/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/backup_v1.1/src/main/res/mipmap-hdpi/ic_launcher.png b/backup_v1.1/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/backup_v1.1/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/backup_v1.1/src/main/res/mipmap-mdpi/ic_launcher.png b/backup_v1.1/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/backup_v1.1/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/backup_v1.1/src/main/res/mipmap-xhdpi/ic_launcher.png b/backup_v1.1/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/backup_v1.1/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/backup_v1.1/src/main/res/mipmap-xxhdpi/ic_launcher.png b/backup_v1.1/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/backup_v1.1/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/backup_v1.1/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/backup_v1.1/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/backup_v1.1/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/backup_v1.1/src/main/res/values-night/styles.xml b/backup_v1.1/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/backup_v1.1/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/backup_v1.1/src/main/res/values/styles.xml b/backup_v1.1/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/backup_v1.1/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/backup_v1.1/src/profile/AndroidManifest.xml b/backup_v1.1/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/backup_v1.1/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/lib/color_themes.dart b/lib/color_themes.dart
new file mode 100644
index 0000000..acbb24d
--- /dev/null
+++ b/lib/color_themes.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+
+class ColorTheme {
+ final Color background;
+ final Color speedText;
+ final Color headingText;
+ final Color unitText;
+ final String name;
+
+ const ColorTheme({
+ required this.background,
+ required this.speedText,
+ required this.headingText,
+ required this.unitText,
+ required this.name,
+ });
+}
+
+class ColorThemes {
+ static const List themes = [
+ ColorTheme(
+ name: 'Classic Dark',
+ background: Color(0xFF121212),
+ speedText: Color(0xFF4CAF50),
+ headingText: Color(0xFFFF9800),
+ unitText: Color(0xFF9E9E9E),
+ ),
+ ColorTheme(
+ name: 'Electric Blue',
+ background: Color(0xFF0D1421),
+ speedText: Color(0xFF00E5FF),
+ headingText: Color(0xFFFFEB3B),
+ unitText: Color(0xFF78909C),
+ ),
+ ColorTheme(
+ name: 'Racing Red',
+ background: Color(0xFF1A0A0A),
+ speedText: Color(0xFFFF1744),
+ headingText: Color(0xFFFFFFFF),
+ unitText: Color(0xFFB0BEC5),
+ ),
+ ];
+
+ static ColorTheme getTheme(int index) {
+ return themes[index % themes.length];
+ }
+
+ static int getNextThemeIndex(int currentIndex) {
+ return (currentIndex + 1) % themes.length;
+ }
+}
\ No newline at end of file
diff --git a/lib/gps_service.dart b/lib/gps_service.dart
new file mode 100644
index 0000000..fa133e2
--- /dev/null
+++ b/lib/gps_service.dart
@@ -0,0 +1,36 @@
+import 'dart:async';
+import 'package:geolocator/geolocator.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+class GpsService {
+ static Stream get positionStream {
+ return Geolocator.getPositionStream(
+ locationSettings: const LocationSettings(
+ accuracy: LocationAccuracy.bestForNavigation,
+ distanceFilter: 1,
+ ),
+ );
+ }
+
+ static Future requestPermissions() async {
+ final permission = await Permission.locationWhenInUse.request();
+ return permission.isGranted;
+ }
+
+ static Future isLocationServiceEnabled() async {
+ return await Geolocator.isLocationServiceEnabled();
+ }
+
+ static String getCompassDirection(double heading) {
+ if (heading < 0) return 'N/A';
+
+ const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
+ final index = ((heading + 22.5) / 45).floor() % 8;
+ return directions[index];
+ }
+
+ static String formatHeading(double heading) {
+ if (heading < 0) return 'N/A';
+ return '${getCompassDirection(heading)} ${heading.round()}°';
+ }
+}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index a7aa7d1..146fb08 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -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(
- 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 createState() => _SpeedometerScreenState();
+}
+
+class _SpeedometerScreenState extends State {
+ double _speed = 0.0;
+ double _heading = -1.0;
+ SpeedUnit _currentUnit = SpeedUnit.kmh;
+ int _currentThemeIndex = 0;
+ StreamSubscription? _positionSubscription;
+ String _errorMessage = '';
+
+ @override
+ void initState() {
+ super.initState();
+ _initializeGps();
+ }
+
+ @override
+ void dispose() {
+ _positionSubscription?.cancel();
+ super.dispose();
+ }
+
+ Future _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,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/speed_units.dart b/lib/speed_units.dart
new file mode 100644
index 0000000..914b731
--- /dev/null
+++ b/lib/speed_units.dart
@@ -0,0 +1,20 @@
+enum SpeedUnit {
+ kmh('km/h', 1.0),
+ mph('mph', 0.621371),
+ knots('kts', 0.539957);
+
+ const SpeedUnit(this.label, this.multiplier);
+
+ final String label;
+ final double multiplier;
+
+ double convert(double speedMps) {
+ return speedMps * 3.6 * multiplier;
+ }
+
+ SpeedUnit get next {
+ final values = SpeedUnit.values;
+ final currentIndex = values.indexOf(this);
+ return values[(currentIndex + 1) % values.length];
+ }
+}
\ No newline at end of file
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index de906b1..977f380 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,9 +6,7 @@ import FlutterMacOS
import Foundation
import geolocator_apple
-import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
- SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index 9de250e..b191756 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -65,22 +65,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
- ffi:
- dependency: transitive
- description:
- name: ffi
- sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
- url: "https://pub.dev"
- source: hosted
- version: "2.1.4"
- file:
- dependency: transitive
- description:
- name: file
- sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
- url: "https://pub.dev"
- source: hosted
- version: "7.0.1"
fixnum:
dependency: transitive
description:
@@ -98,18 +82,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
- sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
+ sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev"
source: hosted
- version: "5.0.0"
- flutter_material_color_picker:
- dependency: "direct main"
- description:
- name: flutter_material_color_picker
- sha256: ca1e7749d228c9155ea24bce98e647cdbffa350e6f334f6c001f841cd3d9c987
- url: "https://pub.dev"
- source: hosted
- version: "1.2.0"
+ version: "6.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -196,10 +172,10 @@ packages:
dependency: transitive
description:
name: lints
- sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
+ sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
url: "https://pub.dev"
source: hosted
- version: "5.1.1"
+ version: "6.0.0"
matcher:
dependency: transitive
description:
@@ -224,14 +200,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.16.0"
- nested:
- dependency: transitive
- description:
- name: nested
- sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
- url: "https://pub.dev"
- source: hosted
- version: "1.0.0"
path:
dependency: transitive
description:
@@ -240,30 +208,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
- path_provider_linux:
- dependency: transitive
- description:
- name: path_provider_linux
- sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
- url: "https://pub.dev"
- source: hosted
- version: "2.2.1"
- path_provider_platform_interface:
- dependency: transitive
- description:
- name: path_provider_platform_interface
- sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
- url: "https://pub.dev"
- source: hosted
- version: "2.1.2"
- path_provider_windows:
- dependency: transitive
- description:
- name: path_provider_windows
- sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
- url: "https://pub.dev"
- source: hosted
- version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
@@ -312,14 +256,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.1"
- platform:
- dependency: transitive
- description:
- name: platform
- sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
- url: "https://pub.dev"
- source: hosted
- version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -328,70 +264,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
- provider:
- dependency: "direct main"
- description:
- name: provider
- sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
- url: "https://pub.dev"
- source: hosted
- version: "6.1.5+1"
- shared_preferences:
- dependency: "direct main"
- description:
- name: shared_preferences
- sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
- url: "https://pub.dev"
- source: hosted
- version: "2.5.3"
- shared_preferences_android:
- dependency: transitive
- description:
- name: shared_preferences_android
- sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
- url: "https://pub.dev"
- source: hosted
- version: "2.4.11"
- shared_preferences_foundation:
- dependency: transitive
- description:
- name: shared_preferences_foundation
- sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
- url: "https://pub.dev"
- source: hosted
- version: "2.5.4"
- shared_preferences_linux:
- dependency: transitive
- description:
- name: shared_preferences_linux
- sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
- url: "https://pub.dev"
- source: hosted
- version: "2.4.1"
- shared_preferences_platform_interface:
- dependency: transitive
- description:
- name: shared_preferences_platform_interface
- sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
- url: "https://pub.dev"
- source: hosted
- version: "2.4.1"
- shared_preferences_web:
- dependency: transitive
- description:
- name: shared_preferences_web
- sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
- url: "https://pub.dev"
- source: hosted
- version: "2.4.3"
- shared_preferences_windows:
- dependency: transitive
- description:
- name: shared_preferences_windows
- sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
- url: "https://pub.dev"
- source: hosted
- version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
@@ -493,14 +365,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
- xdg_directories:
- dependency: transitive
- description:
- name: xdg_directories
- sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
- url: "https://pub.dev"
- source: hosted
- version: "1.1.0"
sdks:
dart: ">=3.9.0 <4.0.0"
- flutter: ">=3.27.0"
+ flutter: ">=3.24.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 6f7c509..068f5e4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,99 +1,26 @@
name: gps_speedo
-description: "A new Flutter project."
-# The following line prevents the package from being accidentally published to
-# pub.dev using `flutter pub publish`. This is preferred for private packages.
-publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+description: "Ultra-lightweight GPS speedometer for Android split-screen usage."
+publish_to: 'none'
-# The following defines the version and build number for your application.
-# A version number is three numbers separated by dots, like 1.2.43
-# followed by an optional build number separated by a +.
-# Both the version and the builder number may be overridden in flutter
-# build by specifying --build-name and --build-number, respectively.
-# In Android, build-name is used as versionName while build-number used as versionCode.
-# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
-# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
-# Read more about iOS versioning at
-# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-# In Windows, build-name is used as the major, minor, and patch parts
-# of the product and file versions while build-number is used as the build suffix.
-version: 1.0.0+1
+version: 2.0.0+1
environment:
sdk: ^3.9.0
-# Dependencies specify other packages that your package needs in order to work.
-# To automatically upgrade your package dependencies to the latest versions
-# consider running `flutter pub upgrade --major-versions`. Alternatively,
-# dependencies can be manually updated by changing the version numbers below to
-# the latest version available on pub.dev. To see which dependencies have newer
-# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
-
- # UI Icons
+
cupertino_icons: ^1.0.8
- # GPS and Location Services
+ # GPS and Location Services - Essential only
geolocator: ^13.0.2
permission_handler: ^11.3.1
-
- # State Management and Settings
- provider: ^6.1.2
- shared_preferences: ^2.3.3
-
- # UI and Theming
- flutter_material_color_picker: ^1.2.0
dev_dependencies:
flutter_test:
sdk: flutter
+ flutter_lints: ^6.0.0
- # The "flutter_lints" package below contains a set of recommended lints to
- # encourage good coding practices. The lint set provided by the package is
- # activated in the `analysis_options.yaml` file located at the root of your
- # package. See that file for information about deactivating specific lint
- # rules and activating additional ones.
- flutter_lints: ^5.0.0
-
-# For information on the generic Dart part of this file, see the
-# following page: https://dart.dev/tools/pub/pubspec
-
-# The following section is specific to Flutter packages.
flutter:
-
- # The following line ensures that the Material Icons font is
- # included with your application, so that you can use the icons in
- # the material Icons class.
- uses-material-design: true
-
- # To add assets to your application, add an assets section, like this:
- # assets:
- # - images/a_dot_burr.jpeg
- # - images/a_dot_ham.jpeg
-
- # An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.dev/to/resolution-aware-images
-
- # For details regarding adding assets from package dependencies, see
- # https://flutter.dev/to/asset-from-package
-
- # To add custom fonts to your application, add a fonts section here,
- # in this "flutter" section. Each entry in this list should have a
- # "family" key with the font family name, and a "fonts" key with a
- # list giving the asset and other descriptors for the font. For
- # example:
- # fonts:
- # - family: Schyler
- # fonts:
- # - asset: fonts/Schyler-Regular.ttf
- # - asset: fonts/Schyler-Italic.ttf
- # style: italic
- # - family: Trajan Pro
- # fonts:
- # - asset: fonts/TrajanPro.ttf
- # - asset: fonts/TrajanPro_Bold.ttf
- # weight: 700
- #
- # For details regarding fonts from package dependencies,
- # see https://flutter.dev/to/font-from-package
+ uses-material-design: true
\ No newline at end of file
diff --git a/test/widget_test.dart b/test/widget_test.dart
index 61d81ea..834d5db 100644
--- a/test/widget_test.dart
+++ b/test/widget_test.dart
@@ -13,9 +13,9 @@ import 'package:gps_speedo/main.dart';
void main() {
testWidgets('GPS Speedometer app loads', (WidgetTester tester) async {
// Build our app and trigger a frame.
- await tester.pumpWidget(const SpeedometerApp());
+ await tester.pumpWidget(const GpsSpeedoApp());
// Verify that the app loads with the title
- expect(find.text('GPS Speedometer'), findsOneWidget);
+ expect(find.text('0'), findsOneWidget);
});
}