import 'dart:async'; import 'dart:convert'; import 'package:geolocator/geolocator.dart'; import 'package:votianlt_app/services/developer.dart' as developer; import 'websocket_service.dart'; /// Service for tracking and sending GPS location. /// Sends position every 30 seconds when online. /// Does not buffer location data when offline. class LocationService { static final LocationService _instance = LocationService._internal(); factory LocationService() => _instance; LocationService._internal(); Timer? _locationTimer; bool _isTracking = false; Position? _lastPosition; static const String _topic = '/server/location'; static const int _sendIntervalSeconds = 30; /// Check if location services are enabled and permission is granted Future _checkPermissions() async { // Check if location services are enabled bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { developer.log( 'Location services are disabled', name: 'LocationService', ); return false; } // Check location permission LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { developer.log( 'Location permission denied', name: 'LocationService', ); return false; } } if (permission == LocationPermission.deniedForever) { developer.log( 'Location permission permanently denied', name: 'LocationService', ); return false; } return true; } /// Start location tracking and periodic sending Future startTracking() async { if (_isTracking) { developer.log( 'Location tracking already active', name: 'LocationService', ); return; } final hasPermission = await _checkPermissions(); if (!hasPermission) { developer.log( 'Cannot start location tracking - permission not granted', name: 'LocationService', ); return; } _isTracking = true; developer.log( 'Starting location tracking (sending every $_sendIntervalSeconds seconds)', name: 'LocationService', ); // Get initial position await _updateAndSendPosition(); // Start periodic timer _locationTimer = Timer.periodic( const Duration(seconds: _sendIntervalSeconds), (_) => _updateAndSendPosition(), ); } /// Stop location tracking void stopTracking() { if (!_isTracking) return; developer.log( 'Stopping location tracking', name: 'LocationService', ); _locationTimer?.cancel(); _locationTimer = null; _isTracking = false; } /// Get current position and send to server if online Future _updateAndSendPosition() async { try { final position = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.best, ), ); _lastPosition = position; developer.log( 'Position updated: ${position.latitude}, ${position.longitude}', name: 'LocationService', ); await _sendPosition(position); } catch (e, st) { developer.log( 'Error getting position: $e', name: 'LocationService', ); developer.log('Stack: $st', name: 'LocationService'); } } /// Send position to server if online /// Does NOT buffer when offline - location data is time-sensitive Future _sendPosition(Position position) async { final wsService = WebSocketService(); // Only send if connected and authenticated if (!wsService.isConnected || !wsService.isAuthenticated) { developer.log( 'Not sending position - not connected/authenticated', name: 'LocationService', ); return; } final payload = { 'latitude': position.latitude, 'longitude': position.longitude, 'accuracy': position.accuracy, 'altitude': position.altitude, 'speed': position.speed, 'heading': position.heading, 'timestamp': position.timestamp.toIso8601String(), }; try { const topic = _topic; final jsonPayload = jsonEncode(payload); // Use direct WebSocket send to avoid buffering wsService.sendMessage(topic, jsonPayload); developer.log( 'Position sent to server: ${position.latitude}, ${position.longitude}', name: 'LocationService', ); } catch (e, st) { developer.log( 'Error sending position: $e', name: 'LocationService', ); developer.log('Stack: $st', name: 'LocationService'); } } /// Get the last known position Position? get lastPosition => _lastPosition; /// Check if tracking is active bool get isTracking => _isTracking; }