import 'dart:async'; import 'dart:io'; import 'package:flutter/widgets.dart'; import 'package:geolocator/geolocator.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:meshcore_client/meshcore_client.dart'; import 'profiles_feature_service.dart'; /// Background location tracking service for SAR operations /// Tracks user location and sends periodic updates via MeshCore BLE @pragma('vm:entry-point') class BackgroundLocationService { static const String _prefKeyEnabled = 'background_tracking_enabled'; static const String _prefKeyDistance = 'background_tracking_distance'; static const String _prefKeyLastLat = 'background_last_lat'; static const String _prefKeyLastLon = 'background_last_lon'; MeshCoreBleService? _bleService; String _scopedKey(String baseKey) { return ProfileStorageScope.scopedKey(baseKey); } bool _isInitialized = false; StreamSubscription? _positionSubscription; /// Initialize the service with BLE service reference void initialize(MeshCoreBleService bleService) { _bleService = bleService; _isInitialized = true; } /// Start location tracking and automatic advertisement /// Returns true if successful, false otherwise /// /// Note: This is foreground tracking. For true background operation, /// additional platform-specific configuration is required. Future startTracking({double distanceThreshold = 10.0}) async { if (!_isInitialized || _bleService == null) { debugPrint( '⚠️ [BackgroundLocation] Service not initialized or BLE service null', ); return false; } if (!_bleService!.isConnected) { debugPrint('⚠️ [BackgroundLocation] BLE not connected'); return false; } // Check location permissions LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { debugPrint('⚠️ [BackgroundLocation] Location permission denied'); return false; } } if (permission == LocationPermission.deniedForever) { debugPrint( '⚠️ [BackgroundLocation] Location permission permanently denied', ); return false; } if (Platform.isIOS && permission != LocationPermission.always) { permission = await Geolocator.requestPermission(); if (permission != LocationPermission.always) { debugPrint( '⚠️ [BackgroundLocation] iOS background tracking requires Always permission', ); return false; } } // Save settings final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_scopedKey(_prefKeyEnabled), true); await prefs.setDouble(_scopedKey(_prefKeyDistance), distanceThreshold); // Start listening to position updates Position? lastPosition; try { _positionSubscription = Geolocator.getPositionStream( locationSettings: _buildLocationSettings( distanceFilter: distanceThreshold.toInt(), ), ).listen((Position position) async { debugPrint( '📍 [BackgroundLocation] New position: ${position.latitude}, ${position.longitude}', ); // Calculate distance from last position if (lastPosition != null) { final distance = Geolocator.distanceBetween( lastPosition!.latitude, lastPosition!.longitude, position.latitude, position.longitude, ); debugPrint( ' Distance moved: ${distance.toStringAsFixed(1)}m (threshold: ${distanceThreshold}m)', ); // Skip if haven't moved enough if (distance < distanceThreshold) { return; } } // Update last position lastPosition = position; // Save to preferences await prefs.setDouble( _scopedKey(_prefKeyLastLat), position.latitude, ); await prefs.setDouble( _scopedKey(_prefKeyLastLon), position.longitude, ); // Update device's advertised location if (_bleService != null && _bleService!.isConnected) { try { debugPrint( '📤 [BackgroundLocation] Updating device location...', ); await _bleService!.setAdvertLatLon( latitude: position.latitude, longitude: position.longitude, ); // Send advertisement to mesh network debugPrint( '📡 [BackgroundLocation] Broadcasting self advertisement...', ); await _bleService!.sendSelfAdvert(floodMode: true); debugPrint( '✅ [BackgroundLocation] Location update sent successfully', ); } catch (e) { debugPrint( '❌ [BackgroundLocation] Failed to send location update: $e', ); } } else { debugPrint( '⚠️ [BackgroundLocation] BLE disconnected, cannot send update', ); } }); debugPrint( '✅ [BackgroundLocation] Tracking started with ${distanceThreshold}m threshold', ); return true; } catch (e) { debugPrint('❌ [BackgroundLocation] Failed to start tracking: $e'); return false; } } /// Stop location tracking Future stopTracking() async { debugPrint('🛑 [BackgroundLocation] Stopping tracking'); await _positionSubscription?.cancel(); _positionSubscription = null; final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_scopedKey(_prefKeyEnabled), false); debugPrint('✅ [BackgroundLocation] Tracking stopped'); } /// Update the distance threshold for location updates /// Note: This will restart tracking with the new threshold Future updateDistanceThreshold(double distance) async { final prefs = await SharedPreferences.getInstance(); await prefs.setDouble(_scopedKey(_prefKeyDistance), distance); debugPrint( '📏 [BackgroundLocation] Distance threshold updated to ${distance}m', ); // Restart tracking if currently enabled final isEnabled = prefs.getBool(_scopedKey(_prefKeyEnabled)) ?? false; if (isEnabled && _bleService != null) { await stopTracking(); await startTracking(distanceThreshold: distance); } } LocationSettings _buildLocationSettings({int distanceFilter = 10}) { if (Platform.isIOS) { return AppleSettings( accuracy: LocationAccuracy.best, activityType: ActivityType.fitness, allowBackgroundLocationUpdates: true, distanceFilter: distanceFilter, pauseLocationUpdatesAutomatically: false, showBackgroundLocationIndicator: true, ); } return LocationSettings( accuracy: LocationAccuracy.best, distanceFilter: distanceFilter, ); } }