Files
meshcore-sar_android/lib/services/background_location_service.dart
Janez T 4fae6e4c55 feat: Implement background location tracking service
- Removed existing app icons from the asset catalog.
- Added new `Contents.json` for asset catalog.
- Updated `Info.plist` to include background modes and task scheduler identifiers.
- Exposed BLE service in `ConnectionProvider` for background tracking.
- Enhanced `MapTab` to support background location tracking with new service.
- Added `BackgroundLocationService` to manage location updates in the background.
- Updated `SettingsScreen` to allow users to configure GPS update distance and toggle background tracking.
- Implemented functionality to send location updates via BLE in `MeshCoreBleService`.
- Updated dependencies in `pubspec.yaml` for background service support.
- Adjusted `TileCacheService` to ensure ObjectBox is initialized only once.
2025-10-14 09:53:21 +02:00

183 lines
5.5 KiB
Dart

import 'dart:async';
import 'dart:ui';
import 'package:flutter/widgets.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:geolocator/geolocator.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'meshcore_ble_service.dart';
/// Background location tracking service for SAR operations
/// Tracks user location and sends periodic updates via MeshCore BLE
class BackgroundLocationService {
static const String _prefKeyEnabled = 'background_tracking_enabled';
static const String _prefKeyDistance = 'background_tracking_distance';
MeshCoreBleService? _bleService;
bool _isInitialized = false;
/// Initialize the service with BLE service reference
void initialize(MeshCoreBleService bleService) {
_bleService = bleService;
_isInitialized = true;
}
/// Start background location tracking
/// Returns true if successful, false otherwise
Future<bool> startTracking({double distanceThreshold = 10.0}) async {
if (!_isInitialized || _bleService == null) {
return false;
}
// Check location permissions
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return false;
}
}
if (permission == LocationPermission.deniedForever) {
return false;
}
// Save settings
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefKeyEnabled, true);
await prefs.setDouble(_prefKeyDistance, distanceThreshold);
// Initialize background service if not already running
final service = FlutterBackgroundService();
final isRunning = await service.isRunning();
if (!isRunning) {
await _initializeBackgroundService();
}
// Start the service
await service.startService();
return true;
}
/// Stop background location tracking
Future<void> stopTracking() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefKeyEnabled, false);
final service = FlutterBackgroundService();
service.invoke('stop');
}
/// Update the distance threshold for location updates
void updateDistanceThreshold(double distance) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble(_prefKeyDistance, distance);
final service = FlutterBackgroundService();
service.invoke('updateDistance', {'distance': distance});
}
/// Initialize the background service
Future<void> _initializeBackgroundService() async {
final service = FlutterBackgroundService();
await service.configure(
iosConfiguration: IosConfiguration(
autoStart: false,
onForeground: _onStart,
onBackground: _onIosBackground,
),
androidConfiguration: AndroidConfiguration(
autoStart: false,
onStart: _onStart,
isForegroundMode: true,
autoStartOnBoot: false,
),
);
}
/// iOS background entry point
@pragma('vm:entry-point')
static bool _onIosBackground(ServiceInstance service) {
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();
return true;
}
/// Background service entry point
@pragma('vm:entry-point')
static void _onStart(ServiceInstance service) async {
// Ensure Flutter binding is initialized
DartPluginRegistrant.ensureInitialized();
Position? lastPosition;
StreamSubscription<Position>? positionSubscription;
double distanceThreshold = 10.0;
// Load settings
final prefs = await SharedPreferences.getInstance();
final enabled = prefs.getBool(_prefKeyEnabled) ?? false;
distanceThreshold = prefs.getDouble(_prefKeyDistance) ?? 10.0;
if (!enabled) {
service.stopSelf();
return;
}
// Start location tracking
try {
positionSubscription = Geolocator.getPositionStream(
locationSettings: LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: distanceThreshold.toInt(),
),
).listen((Position position) async {
// Calculate distance from last position
if (lastPosition != null) {
final distance = Geolocator.distanceBetween(
lastPosition!.latitude,
lastPosition!.longitude,
position.latitude,
position.longitude,
);
// Only update if moved enough distance
if (distance < distanceThreshold) {
return;
}
}
// Store last position
lastPosition = position;
// Note: In a real implementation, we would need to communicate with
// the BLE service via isolate communication or shared storage.
// For now, this is a placeholder for the background tracking logic.
// Send location update via notification or data channel
service.invoke('location', {
'latitude': position.latitude,
'longitude': position.longitude,
'timestamp': position.timestamp.millisecondsSinceEpoch,
});
});
} catch (e) {
service.stopSelf();
return;
}
// Listen for service commands
service.on('stop').listen((event) async {
await positionSubscription?.cancel();
service.stopSelf();
});
service.on('updateDistance').listen((event) {
if (event != null && event['distance'] != null) {
distanceThreshold = event['distance'] as double;
}
});
}
}