From c4d4032b6b30f00cbb14625b993493660d153b3d Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Thu, 12 Feb 2026 09:00:10 +0900 Subject: [PATCH] feat(04-01): add timestamp to ProcessedGpsData and precision/lifecycle constants - Add timestamp field to ProcessedGpsData with copyWith support - Add precision switching thresholds: 3.33 m/s (12 km/h) up, 2.22 m/s (8 km/h) down - Add PRECISION_SWITCH_FIRST_FIX_DELAY (5s) to prevent early switching - Add BACKGROUND_GRACE_PERIOD (7s) for lifecycle-aware GPS management - Mark GPS_GRACE_PERIOD as deprecated in favor of BACKGROUND_GRACE_PERIOD - Change GpsService.positionStream to createPositionStream() with dynamic accuracy - Keep deprecated positionStream getter for backward compatibility Co-Authored-By: Claude Opus 4.6 --- lib/config/gps_constants.dart | 9 +++++++++ lib/config/timing_constants.dart | 4 ++++ lib/gps_service.dart | 15 ++++++++++++--- lib/models/processed_gps_data.dart | 4 ++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/config/gps_constants.dart b/lib/config/gps_constants.dart index 343dec4..cb164d6 100644 --- a/lib/config/gps_constants.dart +++ b/lib/config/gps_constants.dart @@ -19,4 +19,13 @@ abstract class GpsConfig { /// Speed threshold below which heading is considered unreliable (m/s) static const double LOW_SPEED_THRESHOLD = 1.0; + + /// Speed threshold for switching to HIGH precision (m/s) - 12 km/h + static const double PRECISION_UP_THRESHOLD_MPS = 3.33; + + /// Speed threshold for switching to BALANCED precision (m/s) - 8 km/h + static const double PRECISION_DOWN_THRESHOLD_MPS = 2.22; + + /// Minimum time after first fix before precision switching starts + static const Duration PRECISION_SWITCH_FIRST_FIX_DELAY = Duration(seconds: 5); } diff --git a/lib/config/timing_constants.dart b/lib/config/timing_constants.dart index 43e71d9..4be685d 100644 --- a/lib/config/timing_constants.dart +++ b/lib/config/timing_constants.dart @@ -15,5 +15,9 @@ abstract class TimingConfig { static const Duration LONG_PRESS_SIGNAL_DELAY = Duration(milliseconds: 50); /// GPS grace period - continues for 30s after overlay close when backgrounded + /// DEPRECATED: Replaced by BACKGROUND_GRACE_PERIOD (7s) for lifecycle management static const Duration GPS_GRACE_PERIOD = Duration(seconds: 30); + + /// Background grace period - time before GPS stops when app backgrounded without overlay + static const Duration BACKGROUND_GRACE_PERIOD = Duration(seconds: 7); } diff --git a/lib/gps_service.dart b/lib/gps_service.dart index 9b481c4..452d5e6 100644 --- a/lib/gps_service.dart +++ b/lib/gps_service.dart @@ -6,15 +6,24 @@ import 'config/gps_constants.dart'; class GpsService { static double? _lastValidHeading; - static Stream get positionStream { + /// Create a position stream with dynamic accuracy setting + static Stream createPositionStream({ + LocationAccuracy accuracy = LocationAccuracy.high, + }) { return Geolocator.getPositionStream( - locationSettings: const LocationSettings( - accuracy: LocationAccuracy.bestForNavigation, + locationSettings: LocationSettings( + accuracy: accuracy, distanceFilter: 0, ), ); } + /// Deprecated: Use createPositionStream() instead + @Deprecated('Use createPositionStream(accuracy: LocationAccuracy.bestForNavigation) instead') + static Stream get positionStream { + return createPositionStream(accuracy: LocationAccuracy.bestForNavigation); + } + static Future requestPermissions() async { final permission = await Permission.locationWhenInUse.request(); return permission.isGranted; diff --git a/lib/models/processed_gps_data.dart b/lib/models/processed_gps_data.dart index 642815a..404ef82 100644 --- a/lib/models/processed_gps_data.dart +++ b/lib/models/processed_gps_data.dart @@ -5,6 +5,7 @@ class ProcessedGpsData { final String displayHeading; // Formatted heading text final bool isSpeedValid; // True if from recent GPS final bool isHeadingValid; // True if from recent GPS + final DateTime timestamp; // Timestamp for staleness detection const ProcessedGpsData({ required this.speed, @@ -13,6 +14,7 @@ class ProcessedGpsData { required this.displayHeading, required this.isSpeedValid, required this.isHeadingValid, + required this.timestamp, }); ProcessedGpsData copyWith({ @@ -22,6 +24,7 @@ class ProcessedGpsData { String? displayHeading, bool? isSpeedValid, bool? isHeadingValid, + DateTime? timestamp, }) { return ProcessedGpsData( speed: speed ?? this.speed, @@ -30,6 +33,7 @@ class ProcessedGpsData { displayHeading: displayHeading ?? this.displayHeading, isSpeedValid: isSpeedValid ?? this.isSpeedValid, isHeadingValid: isHeadingValid ?? this.isHeadingValid, + timestamp: timestamp ?? this.timestamp, ); } }