185 lines
4.9 KiB
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;
|
|
}
|