Skip to content

Commit 47b4f39

Browse files
committed
feat: dynamic tracking mode depending on distance to the target
1 parent a7a36f5 commit 47b4f39

1 file changed

Lines changed: 62 additions & 45 deletions

File tree

lib/controller/location_provider.dart

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class LocationProvider with ChangeNotifier {
3131
VoidCallback? onAlarmTriggered;
3232
StreamSubscription<Activity>? _activitySubscription;
3333
bool _isGpsActive = false;
34+
Timer? _adaptiveTimer;
35+
static const double kAdaptiveThreshold = 10000;
3436

3537
final FlutterLocalNotificationsPlugin _notificationsPlugin =
3638
FlutterLocalNotificationsPlugin();
@@ -60,6 +62,44 @@ class LocationProvider with ChangeNotifier {
6062
);
6163
}
6264

65+
Future<void> _decideTrackingMode() async {
66+
if (_selectedLocationIndex == null || !_isTracking) return;
67+
68+
final position = await _locationService.getCurrentPosition(
69+
desiredAccuracy: LocationAccuracy.medium,
70+
);
71+
72+
final target = _locations[_selectedLocationIndex!];
73+
final distance = _locationService.calculateDistance(
74+
position.latitude,
75+
position.longitude,
76+
target.latitude,
77+
target.longitude,
78+
);
79+
80+
logHere("📍 Check: ${distance.toStringAsFixed(0)}m away.");
81+
82+
if (distance > kAdaptiveThreshold) {
83+
if (_isGpsActive) _stopGpsStream();
84+
85+
int intervalMinutes = (distance / 2000).floor().clamp(2, 30);
86+
87+
logHere("🌍 Far away (>10km). Sleeping for $intervalMinutes mins.");
88+
89+
_adaptiveTimer?.cancel();
90+
_adaptiveTimer = Timer(Duration(minutes: intervalMinutes), () {
91+
_decideTrackingMode();
92+
});
93+
} else {
94+
logHere("🎯 Close (<10km)! Switching to High Precision Stream.");
95+
_adaptiveTimer?.cancel();
96+
97+
if (!_isGpsActive) {
98+
_startGpsStream();
99+
}
100+
}
101+
}
102+
63103
Future<void> _initActivityRecognition() async {
64104
// 1. Check or Request Permission
65105
bool isGranted = await Permission.activityRecognition.isGranted;
@@ -81,28 +121,30 @@ class LocationProvider with ChangeNotifier {
81121
logHere("Detected Activity: ${activity.type}");
82122

83123
if (activity.type == ActivityType.STILL) {
84-
// Device is STILL -> Pause GPS to save battery
85-
if (_isGpsActive) {
86-
logHere("💤 Device is STILL. Pausing GPS.");
87-
_stopGpsStream();
88-
}
124+
// STOP EVERYTHING when still
125+
logHere("💤 Still. Pausing all location checks.");
126+
_stopGpsStream();
127+
_adaptiveTimer?.cancel();
89128
} else {
90-
// Device is MOVING (Walking, Vehicle, etc.) -> Turn ON GPS
91-
if (!_isGpsActive) {
92-
logHere("🚗 Device is MOVING. Activating GPS.");
93-
_startGpsStream();
129+
// MOVING? Let the brain decide strategy (Stream vs Timer)
130+
// Only trigger if we aren't already streaming or waiting on a timer
131+
if (!_isGpsActive &&
132+
(_adaptiveTimer == null || !_adaptiveTimer!.isActive)) {
133+
logHere("🚗 Moving. Calculating best tracking mode...");
134+
_decideTrackingMode();
94135
}
95136
}
96137
}
97138

98139
void _startGpsStream() {
99140
if (_selectedLocationIndex == null || _isGpsActive) return;
100-
141+
101142
final target = _locations[_selectedLocationIndex!];
102143
_isGpsActive = true;
103144

104145
_locationService.startListening(
105-
accuracy: locationAccuracyOptions[_settingsProvider.locationTrackingAccuracy],
146+
accuracy:
147+
locationAccuracyOptions[_settingsProvider.locationTrackingAccuracy],
106148
distanceFilter: 20,
107149
onUpdate: (position) {
108150
_sendRealtimeUpdate(position, target);
@@ -195,20 +237,19 @@ class LocationProvider with ChangeNotifier {
195237
}
196238

197239
void stopTracking() {
198-
// 1. Stop Activity Listener
199240
_activitySubscription?.cancel();
200241
_activitySubscription = null;
201242

202-
// 2. Stop GPS
203243
_stopGpsStream();
244+
_adaptiveTimer?.cancel(); // NEW: Kill the timer
245+
_adaptiveTimer = null;
204246

205-
// 3. Cleanup Notifications & Background Service
206247
_notificationsPlugin.cancel(1);
207248
_notificationsPlugin.cancel(0);
208249
if (FlutterBackground.isBackgroundExecutionEnabled) {
209250
FlutterBackground.disableBackgroundExecution();
210251
}
211-
252+
212253
_alarmTriggered = false;
213254
_isTracking = false;
214255
_isInitializingTracking = false;
@@ -217,42 +258,18 @@ class LocationProvider with ChangeNotifier {
217258
}
218259

219260
Future<bool> _startTracking() async {
220-
if (_selectedLocationIndex == null || !_isTracking) return false;
221-
final target = _locations[_selectedLocationIndex!];
261+
if (_selectedLocationIndex == null) return false;
222262

223263
try {
224-
// 1. Initial Position Check (Force one-time fetch to ensure we aren't already there)
225-
final position = await _locationService.getCurrentPosition(
226-
desiredAccuracy: locationAccuracyOptions[_settingsProvider.locationTrackingAccuracy],
227-
);
228-
229-
final distance = _locationService.calculateDistance(
230-
position.latitude, position.longitude,
231-
target.latitude, target.longitude,
232-
);
233-
234-
if (distance <= target.radius) {
235-
Fluttertoast.showToast(msg: msgAlreadyWithinRadius, toastLength: Toast.LENGTH_SHORT);
236-
_isInitializingTracking = false;
237-
_isTracking = false;
238-
notifyListeners();
239-
return false;
240-
}
241-
242-
// 2. Start Smart Tracking (Activity Recognition)
243-
// We start GPS immediately as a safety measure, the Activity Listener
244-
// will turn it off in a few seconds if it detects we are 'STILL'.
245-
_startGpsStream();
264+
// 1. Start Activity Recognition
246265
await _initActivityRecognition();
247-
248-
return true;
249266

267+
// 2. Trigger immediate check to decide strategy
268+
await _decideTrackingMode();
269+
270+
return true;
250271
} catch (e) {
251272
logHere('$kLogInitialPositionFailed $e');
252-
Fluttertoast.showToast(msg: msgUnableToFetch);
253-
_isInitializingTracking = false;
254-
_isTracking = false;
255-
notifyListeners();
256273
return false;
257274
}
258275
}

0 commit comments

Comments
 (0)