From fb7926d38df09a59135835ed3d0a36b38af8b6bc Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Sun, 24 Aug 2025 23:14:19 +0900 Subject: [PATCH] Colour patterns and speed unit selection working --- .gitignore | 6 +- android/app/build.gradle.kts | 4 +- android/app/src/main/AndroidManifest.xml | 2 +- .../speedo}/MainActivity.kt | 2 +- ios/Runner/Info.plist | 2 +- lib/color_themes.dart | 30 +- lib/gps_service.dart | 2 +- lib/main.dart | 323 +++++++++++++----- macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 146 +++++++- pubspec.yaml | 19 +- test/widget_test.dart | 3 +- 12 files changed, 417 insertions(+), 126 deletions(-) rename android/app/src/main/kotlin/com/{example/gps_speedo => novoyuuparosk/speedo}/MainActivity.kt (73%) diff --git a/.gitignore b/.gitignore index 5680cd6..b042877 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,8 @@ NOTES.txt TODO.md # Development tools - generic utilities, not project-specific -.tools/ \ No newline at end of file +.tools/ + +# Font(s) provided for embedding into the app +# DO NOT REMOVE FROM GITIGNORE +fonts/ \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 9d2e7c6..87ed120 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.gps_speedo" + namespace = "com.novoyuuparosk.speedo" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -21,7 +21,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.gps_speedo" + applicationId = "com.novoyuuparosk.speedo" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2a51eea..2c835a7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Gps Speedo + Speedo CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/lib/color_themes.dart b/lib/color_themes.dart index acbb24d..32c2c0f 100644 --- a/lib/color_themes.dart +++ b/lib/color_themes.dart @@ -19,25 +19,25 @@ class ColorTheme { class ColorThemes { static const List 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), ), ]; diff --git a/lib/gps_service.dart b/lib/gps_service.dart index fa133e2..3ae26a8 100644 --- a/lib/gps_service.dart +++ b/lib/gps_service.dart @@ -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)}'; } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 146fb08..787f874 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 { void initState() { super.initState(); _initializeGps(); + _enableWakelock(); + } + + Future _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 { 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', + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } } \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 977f380..a03a80b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,11 @@ import FlutterMacOS import Foundation import geolocator_apple +import package_info_plus +import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index b191756..d67bfed 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -49,14 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - cupertino_icons: - dependency: "direct main" + dbus: + dependency: transitive description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "0.7.11" fake_async: dependency: transitive description: @@ -65,6 +73,14 @@ 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" fixnum: dependency: transitive description: @@ -96,22 +112,30 @@ packages: description: flutter source: sdk version: "0.0.0" + geoclue: + dependency: transitive + description: + name: geoclue + sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f + url: "https://pub.dev" + source: hosted + version: "0.1.1" geolocator: dependency: "direct main" description: name: geolocator - sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2 + sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516" url: "https://pub.dev" source: hosted - version: "13.0.4" + version: "14.0.2" geolocator_android: dependency: transitive description: name: geolocator_android - sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d + sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a" url: "https://pub.dev" source: hosted - version: "4.6.2" + version: "5.0.2" geolocator_apple: dependency: transitive description: @@ -120,6 +144,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.13" + geolocator_linux: + dependency: transitive + description: + name: geolocator_linux + sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3 + url: "https://pub.dev" + source: hosted + version: "0.2.3" geolocator_platform_interface: dependency: transitive description: @@ -144,6 +176,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.5" + gsettings: + dependency: transitive + description: + name: gsettings + sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c" + url: "https://pub.dev" + source: hosted + version: "0.2.8" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -200,6 +256,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + url: "https://pub.dev" + source: hosted + version: "8.3.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: transitive description: @@ -212,18 +284,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 url: "https://pub.dev" source: hosted - version: "11.4.0" + version: "12.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "13.0.1" permission_handler_apple: dependency: transitive description: @@ -256,6 +328,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" plugin_platform_interface: dependency: transitive description: @@ -357,6 +437,22 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + wakelock_plus: + dependency: "direct main" + description: + name: wakelock_plus + sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 + url: "https://pub.dev" + source: hosted + version: "1.3.2" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 + url: "https://pub.dev" + source: hosted + version: "1.2.3" web: dependency: transitive description: @@ -365,6 +461,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + url: "https://pub.dev" + source: hosted + version: "5.14.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" sdks: dart: ">=3.9.0 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 068f5e4..32857a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: gps_speedo -description: "Ultra-lightweight GPS speedometer for Android split-screen usage." +description: "Speedo - A Flutter application." publish_to: 'none' version: 2.0.0+1 @@ -11,11 +11,15 @@ dependencies: flutter: sdk: flutter - cupertino_icons: ^1.0.8 + # cupertino_icons: ^1.0.8 + # Probably no icons needed for now # GPS and Location Services - Essential only - geolocator: ^13.0.2 - permission_handler: ^11.3.1 + geolocator: ^14.0.2 + permission_handler: ^12.0.1 + + # Keep screen awake during navigation + wakelock_plus: ^1.2.8 dev_dependencies: flutter_test: @@ -23,4 +27,9 @@ dev_dependencies: flutter_lints: ^6.0.0 flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true + + fonts: + - family: DIN1451Alt + fonts: + - asset: fonts/din1451alt.ttf \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart index 834d5db..eb3847d 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,7 +5,6 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:gps_speedo/main.dart'; @@ -13,7 +12,7 @@ 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 GpsSpeedoApp()); + await tester.pumpWidget(const SpeedoApp()); // Verify that the app loads with the title expect(find.text('0'), findsOneWidget);