diff --git a/lib/geolocation_service.dart b/lib/geolocation_service.dart new file mode 100644 index 0000000..d4d5948 --- /dev/null +++ b/lib/geolocation_service.dart @@ -0,0 +1,56 @@ + +import 'dart:io'; + +import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; +import 'package:traccar_client/preferences.dart'; +import 'package:wakelock_partial_android/wakelock_partial_android.dart'; + +class GeolocationService { + static Future init() async { + await bg.BackgroundGeolocation.ready(Preferences.geolocationConfig()); + if (Platform.isAndroid) { + await bg.BackgroundGeolocation.registerHeadlessTask(headlessTask); + } + bg.BackgroundGeolocation.onEnabledChange(onEnabledChange); + bg.BackgroundGeolocation.onMotionChange(onMotionChange); + bg.BackgroundGeolocation.onHeartbeat(onHeartbeat); + } + + static Future onEnabledChange(bool enabled) async { + if (Preferences.instance.getBool(Preferences.wakelock) ?? false) { + if (!enabled) { + await WakelockPartialAndroid.release(); + } + } + } + + static Future onMotionChange(bg.Location location) async { + if (Preferences.instance.getBool(Preferences.wakelock) ?? false) { + if (location.isMoving) { + await WakelockPartialAndroid.acquire(); + } else { + await WakelockPartialAndroid.release(); + } + } + } + + static Future onHeartbeat(bg.HeartbeatEvent event) async { + await bg.BackgroundGeolocation.getCurrentPosition(samples: 1, persist: true); + } +} + +@pragma('vm:entry-point') +void headlessTask(bg.HeadlessEvent headlessEvent) async { + await Preferences.init(); + switch (headlessEvent.name) { + case bg.Event.ENABLEDCHANGE: + await GeolocationService.onEnabledChange(headlessEvent.event); + break; + case bg.Event.MOTIONCHANGE: + await GeolocationService.onMotionChange(headlessEvent.event); + break; + case bg.Event.HEARTBEAT: + await GeolocationService.onHeartbeat(headlessEvent.event); + break; + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3317fbf..818e554 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -4,6 +4,7 @@ "statusTitle": "Logs", "saveButton": "Save", "cancelButton": "Cancel", + "okButton": "OK", "locationButton": "Send location", "statusButton": "Show status", "settingsButton": "Change settings", @@ -25,6 +26,7 @@ "trackingLabel": "Continuous tracking", "motionLabel": "Active movement", "advancedLabel": "Advanced settings", + "optimizationMessage": "To ensure reliable tracking, please disable battery optimization for this app.", "startAction": "Start service", "stopAction": "Stop service", "sosAction": "Send SOS" diff --git a/lib/main.dart b/lib/main.dart index 0dd76d1..0e9e9df 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,8 @@ -import 'dart:io'; - import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; import 'package:rate_my_app/rate_my_app.dart'; +import 'package:traccar_client/geolocation_service.dart'; import 'package:traccar_client/quick_actions.dart'; import 'l10n/app_localizations.dart'; @@ -16,23 +14,11 @@ void main() async { await Firebase.initializeApp(); FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; await Preferences.init(); - await bg.BackgroundGeolocation.ready(Preferences.geolocationConfig()); - if (Platform.isAndroid) { - await bg.BackgroundGeolocation.registerHeadlessTask(headlessTask); - } - bg.BackgroundGeolocation.onHeartbeat((bg.HeartbeatEvent event) async { - await bg.BackgroundGeolocation.getCurrentPosition(samples: 1, persist: true); - }); + await Preferences.migrate(); + await GeolocationService.init(); runApp(const MainApp()); } -@pragma('vm:entry-point') -void headlessTask(bg.HeadlessEvent headlessEvent) async { - if (headlessEvent.name == bg.Event.HEARTBEAT) { - await bg.BackgroundGeolocation.getCurrentPosition(samples: 1, persist: true); - } -} - class MainApp extends StatefulWidget { const MainApp({super.key}); diff --git a/lib/main_screen.dart b/lib/main_screen.dart index a553604..f39afcf 100644 --- a/lib/main_screen.dart +++ b/lib/main_screen.dart @@ -45,6 +45,30 @@ class _MainScreenState extends State { }); } + Future _checkBatteryOptimizations(BuildContext context) async { + try { + if (!await bg.DeviceSettings.isIgnoringBatteryOptimizations) { + final request = await bg.DeviceSettings.showIgnoreBatteryOptimizations(); + if (!request.seen && context.mounted) { + showDialog( + context: context, + builder: (_) => AlertDialog( + content: Text(AppLocalizations.of(context)!.optimizationMessage), + actions: [ + TextButton( + onPressed: () => bg.DeviceSettings.show(request), + child: Text(AppLocalizations.of(context)!.okButton), + ), + ], + ), + ); + } + } + } catch (error) { + debugPrint(error.toString()); + } + } + Widget _buildTrackingCard() { return Card( child: Padding( @@ -69,6 +93,7 @@ class _MainScreenState extends State { onChanged: (bool value) { if (value) { bg.BackgroundGeolocation.start(); + _checkBatteryOptimizations(context); } else { bg.BackgroundGeolocation.stop(); } diff --git a/lib/preferences.dart b/lib/preferences.dart index eff5550..f86740e 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -32,6 +32,9 @@ class Preferences { }, ), ); + } + + static Future migrate() async { if (Platform.isAndroid) { if (instance.get(interval) is String) { final stringValue = instance.getString(interval); diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index e1c83e4..70deef9 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; +import 'package:wakelock_partial_android/wakelock_partial_android.dart'; import 'l10n/app_localizations.dart'; import 'preferences.dart'; @@ -179,6 +180,14 @@ class _SettingsScreenState extends State { value: wakelock, onChanged: (value) async { await Preferences.instance.setBool(Preferences.wakelock, value); + if (value) { + final state = await bg.BackgroundGeolocation.state; + if (state.isMoving == true) { + WakelockPartialAndroid.acquire(); + } + } else { + WakelockPartialAndroid.release(); + } setState(() => wakelock = value); }, ), diff --git a/pubspec.lock b/pubspec.lock index 780fbc5..1ef57d6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -490,6 +490,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + wakelock_partial_android: + dependency: "direct main" + description: + name: wakelock_partial_android + sha256: b99f917728ee4d62fd637e021cdd78e2f271eaf3855ad8ee584a4a7df318e69e + url: "https://pub.dev" + source: hosted + version: "1.0.0" web: dependency: transitive description: @@ -507,5 +515,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.8.1 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index cd39d29..912c9d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: quick_actions: ^1.1.0 rate_my_app: ^2.3.1 shared_preferences_android: ^2.4.10 + wakelock_partial_android: ^1.0.0 dev_dependencies: flutter_test: