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 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-02-12 09:00:10 +09:00
parent 40c74677be
commit c4d4032b6b
4 changed files with 29 additions and 3 deletions
+9
View File
@@ -19,4 +19,13 @@ abstract class GpsConfig {
/// Speed threshold below which heading is considered unreliable (m/s) /// Speed threshold below which heading is considered unreliable (m/s)
static const double LOW_SPEED_THRESHOLD = 1.0; 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);
} }
+4
View File
@@ -15,5 +15,9 @@ abstract class TimingConfig {
static const Duration LONG_PRESS_SIGNAL_DELAY = Duration(milliseconds: 50); static const Duration LONG_PRESS_SIGNAL_DELAY = Duration(milliseconds: 50);
/// GPS grace period - continues for 30s after overlay close when backgrounded /// 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); 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);
} }
+12 -3
View File
@@ -6,15 +6,24 @@ import 'config/gps_constants.dart';
class GpsService { class GpsService {
static double? _lastValidHeading; static double? _lastValidHeading;
static Stream<Position> get positionStream { /// Create a position stream with dynamic accuracy setting
static Stream<Position> createPositionStream({
LocationAccuracy accuracy = LocationAccuracy.high,
}) {
return Geolocator.getPositionStream( return Geolocator.getPositionStream(
locationSettings: const LocationSettings( locationSettings: LocationSettings(
accuracy: LocationAccuracy.bestForNavigation, accuracy: accuracy,
distanceFilter: 0, distanceFilter: 0,
), ),
); );
} }
/// Deprecated: Use createPositionStream() instead
@Deprecated('Use createPositionStream(accuracy: LocationAccuracy.bestForNavigation) instead')
static Stream<Position> get positionStream {
return createPositionStream(accuracy: LocationAccuracy.bestForNavigation);
}
static Future<bool> requestPermissions() async { static Future<bool> requestPermissions() async {
final permission = await Permission.locationWhenInUse.request(); final permission = await Permission.locationWhenInUse.request();
return permission.isGranted; return permission.isGranted;
+4
View File
@@ -5,6 +5,7 @@ class ProcessedGpsData {
final String displayHeading; // Formatted heading text final String displayHeading; // Formatted heading text
final bool isSpeedValid; // True if from recent GPS final bool isSpeedValid; // True if from recent GPS
final bool isHeadingValid; // True if from recent GPS final bool isHeadingValid; // True if from recent GPS
final DateTime timestamp; // Timestamp for staleness detection
const ProcessedGpsData({ const ProcessedGpsData({
required this.speed, required this.speed,
@@ -13,6 +14,7 @@ class ProcessedGpsData {
required this.displayHeading, required this.displayHeading,
required this.isSpeedValid, required this.isSpeedValid,
required this.isHeadingValid, required this.isHeadingValid,
required this.timestamp,
}); });
ProcessedGpsData copyWith({ ProcessedGpsData copyWith({
@@ -22,6 +24,7 @@ class ProcessedGpsData {
String? displayHeading, String? displayHeading,
bool? isSpeedValid, bool? isSpeedValid,
bool? isHeadingValid, bool? isHeadingValid,
DateTime? timestamp,
}) { }) {
return ProcessedGpsData( return ProcessedGpsData(
speed: speed ?? this.speed, speed: speed ?? this.speed,
@@ -30,6 +33,7 @@ class ProcessedGpsData {
displayHeading: displayHeading ?? this.displayHeading, displayHeading: displayHeading ?? this.displayHeading,
isSpeedValid: isSpeedValid ?? this.isSpeedValid, isSpeedValid: isSpeedValid ?? this.isSpeedValid,
isHeadingValid: isHeadingValid ?? this.isHeadingValid, isHeadingValid: isHeadingValid ?? this.isHeadingValid,
timestamp: timestamp ?? this.timestamp,
); );
} }
} }