Files
votianlt/app/lib/services/location_service.dart

185 lines
4.9 KiB
Dart

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<bool> _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<void> 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<void> _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<void> _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;
}