fix: Tame rapid GPS updates #41

This commit is contained in:
Janez T
2026-03-23 20:51:48 +01:00
parent 3e3c9a34d5
commit 28a9235168
5 changed files with 54 additions and 16 deletions

View File

@@ -341,7 +341,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
keyboardType: const TextInputType.numberWithOptions(decimal: true), keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Meters', labelText: 'Meters',
helperText: 'Valid range: 1 to 1000 meters', helperText: 'Valid range: 10 to 1000 meters',
), ),
), ),
actions: [ actions: [
@@ -353,7 +353,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
onPressed: () { onPressed: () {
final parsed = double.tryParse(controller.text.trim()); final parsed = double.tryParse(controller.text.trim());
if (parsed == null) return; if (parsed == null) return;
Navigator.pop(context, parsed.clamp(1.0, 1000.0)); Navigator.pop(context, parsed.clamp(10.0, 1000.0));
}, },
child: const Text('Save'), child: const Text('Save'),
), ),
@@ -382,7 +382,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Seconds', labelText: 'Seconds',
helperText: 'Valid range: 5 to 60 seconds', helperText: 'Valid range: 10 to 31 seconds',
), ),
), ),
actions: [ actions: [
@@ -394,7 +394,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
onPressed: () { onPressed: () {
final parsed = int.tryParse(controller.text.trim()); final parsed = int.tryParse(controller.text.trim());
if (parsed == null) return; if (parsed == null) return;
Navigator.pop(context, parsed.clamp(5, 60)); Navigator.pop(context, parsed.clamp(10, 31));
}, },
child: const Text('Save'), child: const Text('Save'),
), ),

View File

@@ -144,11 +144,11 @@ class AppConfigSnapshotService {
} }
if (section.fastLocationMovementThresholdMeters != null) { if (section.fastLocationMovementThresholdMeters != null) {
locationTracking.fastLocationMovementThresholdMeters = locationTracking.fastLocationMovementThresholdMeters =
section.fastLocationMovementThresholdMeters!; section.fastLocationMovementThresholdMeters!.clamp(10.0, 1000.0);
} }
if (section.fastLocationActiveCadenceSeconds != null) { if (section.fastLocationActiveCadenceSeconds != null) {
locationTracking.fastLocationActiveCadenceSeconds = locationTracking.fastLocationActiveCadenceSeconds =
section.fastLocationActiveCadenceSeconds!; section.fastLocationActiveCadenceSeconds!.clamp(10, 31);
} }
await locationTracking.saveSettings(); await locationTracking.saveSettings();
await appProvider.reloadProfileScopedSettings(); await appProvider.reloadProfileScopedSettings();

View File

@@ -20,6 +20,14 @@ import 'profiles_feature_service.dart';
/// - MeshCore mesh network integration /// - MeshCore mesh network integration
/// - Real-time position updates via callbacks /// - Real-time position updates via callbacks
class LocationTrackingService { class LocationTrackingService {
static const double _defaultFastLocationMovementThresholdMeters = 10.0;
static const double _minFastLocationMovementThresholdMeters = 10.0;
static const int _defaultFastLocationActiveCadenceSeconds = 10;
static const int _minFastLocationActiveCadenceSeconds = 10;
static const int _maxFastLocationActiveCadenceSeconds = 31;
static const Duration _fastLocationMinimumUpdateInterval = Duration(
seconds: 31,
);
// ============================================================================ // ============================================================================
// Singleton Pattern // Singleton Pattern
// ============================================================================ // ============================================================================
@@ -75,10 +83,12 @@ class LocationTrackingService {
bool fastLocationUpdatesEnabled = false; bool fastLocationUpdatesEnabled = false;
/// Distance threshold for fast GPS updates /// Distance threshold for fast GPS updates
double fastLocationMovementThresholdMeters = 10.0; double fastLocationMovementThresholdMeters =
_defaultFastLocationMovementThresholdMeters;
/// Cadence for active-use fast GPS updates /// Cadence for active-use fast GPS updates
int fastLocationActiveCadenceSeconds = 10; int fastLocationActiveCadenceSeconds =
_defaultFastLocationActiveCadenceSeconds;
/// Target channel index for fast GPS updates; null means disabled/unset. /// Target channel index for fast GPS updates; null means disabled/unset.
int? fastLocationChannelIdx; int? fastLocationChannelIdx;
@@ -522,12 +532,18 @@ class LocationTrackingService {
} }
Future<void> updateFastLocationMovementThreshold(double meters) async { Future<void> updateFastLocationMovementThreshold(double meters) async {
fastLocationMovementThresholdMeters = meters.clamp(1.0, 1000.0); fastLocationMovementThresholdMeters = meters.clamp(
_minFastLocationMovementThresholdMeters,
1000.0,
);
await saveSettings(); await saveSettings();
} }
Future<void> updateFastLocationActiveCadenceSeconds(int seconds) async { Future<void> updateFastLocationActiveCadenceSeconds(int seconds) async {
fastLocationActiveCadenceSeconds = seconds.clamp(5, 60); fastLocationActiveCadenceSeconds = seconds.clamp(
_minFastLocationActiveCadenceSeconds,
_maxFastLocationActiveCadenceSeconds,
);
await saveSettings(); await saveSettings();
_refreshFastLocationTimer(); _refreshFastLocationTimer();
} }
@@ -583,6 +599,11 @@ class LocationTrackingService {
final now = DateTime.now(); final now = DateTime.now();
final previous = _lastFastLocationSentPosition; final previous = _lastFastLocationSentPosition;
final previousTime = _lastFastLocationSentAt; final previousTime = _lastFastLocationSentAt;
if (previousTime != null &&
now.difference(previousTime) < _fastLocationMinimumUpdateInterval) {
return;
}
if (previous != null && previousTime != null) { if (previous != null && previousTime != null) {
final distance = Geolocator.distanceBetween( final distance = Geolocator.distanceBetween(
previous.latitude, previous.latitude,
@@ -673,12 +694,14 @@ class LocationTrackingService {
prefs.getBool(_scopedKey(_prefKeyFastLocationEnabled)) ?? false; prefs.getBool(_scopedKey(_prefKeyFastLocationEnabled)) ?? false;
fastLocationMovementThresholdMeters = fastLocationMovementThresholdMeters =
(prefs.getDouble(_scopedKey(_prefKeyFastMovementThreshold)) ?? (prefs.getDouble(_scopedKey(_prefKeyFastMovementThreshold)) ??
gpsUpdateDistance) _defaultFastLocationMovementThresholdMeters)
.clamp(1.0, 1000.0); .clamp(_minFastLocationMovementThresholdMeters, 1000.0);
fastLocationActiveCadenceSeconds = fastLocationActiveCadenceSeconds =
(prefs.getInt(_scopedKey(_prefKeyFastActiveCadence)) ?? 10).clamp( (prefs.getInt(_scopedKey(_prefKeyFastActiveCadence)) ??
5, _defaultFastLocationActiveCadenceSeconds)
60, .clamp(
_minFastLocationActiveCadenceSeconds,
_maxFastLocationActiveCadenceSeconds,
); );
fastLocationChannelIdx = prefs.getInt(_scopedKey(_prefKeyFastChannelIdx)); fastLocationChannelIdx = prefs.getInt(_scopedKey(_prefKeyFastChannelIdx));

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 2026.0323.1+40 version: 2026.0323.2+41
environment: environment:
sdk: ^3.9.2 sdk: ^3.9.2

View File

@@ -19,6 +19,13 @@ void main() {
service.fastLocationChannelIdx = null; service.fastLocationChannelIdx = null;
}); });
test('loads conservative fast location defaults', () async {
await service.loadSettings();
expect(service.fastLocationMovementThresholdMeters, 10.0);
expect(service.fastLocationActiveCadenceSeconds, 10);
});
test('persists and restores fast location channel idx', () async { test('persists and restores fast location channel idx', () async {
await service.updateFastLocationChannelIdx(3); await service.updateFastLocationChannelIdx(3);
@@ -37,4 +44,12 @@ void main() {
expect(service.fastLocationChannelIdx, isNull); expect(service.fastLocationChannelIdx, isNull);
}); });
test('clamps fast location settings to conservative limits', () async {
await service.updateFastLocationMovementThreshold(3);
await service.updateFastLocationActiveCadenceSeconds(45);
expect(service.fastLocationMovementThresholdMeters, 10.0);
expect(service.fastLocationActiveCadenceSeconds, 31);
});
} }