- Updated docs and inventory files - Added new architecture docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
196 lines
5.1 KiB
TypeScript
196 lines
5.1 KiB
TypeScript
/**
|
|
* Location Service
|
|
* ERP Transportistas
|
|
* Sprint S8 - TASK-007
|
|
*
|
|
* Background GPS tracking with expo-location and expo-task-manager.
|
|
*/
|
|
|
|
import * as Location from 'expo-location';
|
|
import * as TaskManager from 'expo-task-manager';
|
|
import { syncService } from './SyncService';
|
|
|
|
const LOCATION_TASK_NAME = 'erp-transportistas-location-tracking';
|
|
const LOCATION_UPDATE_INTERVAL = 30000; // 30 seconds
|
|
const LOCATION_DISTANCE_FILTER = 50; // 50 meters
|
|
|
|
// Define the background task
|
|
TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
|
|
if (error) {
|
|
console.error('Location task error:', error);
|
|
return;
|
|
}
|
|
|
|
if (data) {
|
|
const { locations } = data as { locations: Location.LocationObject[] };
|
|
|
|
for (const location of locations) {
|
|
try {
|
|
await syncService.guardarPosicionGPS(
|
|
location.coords.latitude,
|
|
location.coords.longitude,
|
|
location.coords.speed || 0,
|
|
location.coords.heading || 0,
|
|
location.coords.accuracy || 0
|
|
);
|
|
} catch (err) {
|
|
console.error('Error saving GPS position:', err);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
class LocationService {
|
|
private isTracking = false;
|
|
private foregroundSubscription: Location.LocationSubscription | null = null;
|
|
|
|
async requestPermissions(): Promise<boolean> {
|
|
const { status: foregroundStatus } =
|
|
await Location.requestForegroundPermissionsAsync();
|
|
|
|
if (foregroundStatus !== 'granted') {
|
|
return false;
|
|
}
|
|
|
|
const { status: backgroundStatus } =
|
|
await Location.requestBackgroundPermissionsAsync();
|
|
|
|
return backgroundStatus === 'granted';
|
|
}
|
|
|
|
async checkPermissions(): Promise<{
|
|
foreground: boolean;
|
|
background: boolean;
|
|
}> {
|
|
const foreground = await Location.getForegroundPermissionsAsync();
|
|
const background = await Location.getBackgroundPermissionsAsync();
|
|
|
|
return {
|
|
foreground: foreground.status === 'granted',
|
|
background: background.status === 'granted',
|
|
};
|
|
}
|
|
|
|
async getCurrentPosition(): Promise<Location.LocationObject | null> {
|
|
try {
|
|
const permissions = await this.checkPermissions();
|
|
if (!permissions.foreground) {
|
|
return null;
|
|
}
|
|
|
|
return await Location.getCurrentPositionAsync({
|
|
accuracy: Location.Accuracy.High,
|
|
});
|
|
} catch (error) {
|
|
console.error('Error getting current position:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async startTracking(): Promise<boolean> {
|
|
if (this.isTracking) {
|
|
return true;
|
|
}
|
|
|
|
const permissions = await this.checkPermissions();
|
|
|
|
if (!permissions.foreground) {
|
|
console.warn('Foreground location permission not granted');
|
|
return false;
|
|
}
|
|
|
|
// Start foreground tracking
|
|
this.foregroundSubscription = await Location.watchPositionAsync(
|
|
{
|
|
accuracy: Location.Accuracy.High,
|
|
timeInterval: LOCATION_UPDATE_INTERVAL,
|
|
distanceInterval: LOCATION_DISTANCE_FILTER,
|
|
},
|
|
async (location) => {
|
|
try {
|
|
await syncService.guardarPosicionGPS(
|
|
location.coords.latitude,
|
|
location.coords.longitude,
|
|
location.coords.speed || 0,
|
|
location.coords.heading || 0,
|
|
location.coords.accuracy || 0
|
|
);
|
|
} catch (err) {
|
|
console.error('Error saving foreground GPS position:', err);
|
|
}
|
|
}
|
|
);
|
|
|
|
// Start background tracking if permitted
|
|
if (permissions.background) {
|
|
await this.startBackgroundTracking();
|
|
}
|
|
|
|
this.isTracking = true;
|
|
return true;
|
|
}
|
|
|
|
private async startBackgroundTracking(): Promise<void> {
|
|
const isTaskRegistered = await TaskManager.isTaskRegisteredAsync(
|
|
LOCATION_TASK_NAME
|
|
);
|
|
|
|
if (isTaskRegistered) {
|
|
return;
|
|
}
|
|
|
|
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
|
|
accuracy: Location.Accuracy.High,
|
|
timeInterval: LOCATION_UPDATE_INTERVAL,
|
|
distanceInterval: LOCATION_DISTANCE_FILTER,
|
|
foregroundService: {
|
|
notificationTitle: 'ERP Transportistas',
|
|
notificationBody: 'Rastreo de ubicación activo',
|
|
notificationColor: '#2563eb',
|
|
},
|
|
pausesUpdatesAutomatically: false,
|
|
showsBackgroundLocationIndicator: true,
|
|
});
|
|
}
|
|
|
|
async stopTracking(): Promise<void> {
|
|
if (this.foregroundSubscription) {
|
|
this.foregroundSubscription.remove();
|
|
this.foregroundSubscription = null;
|
|
}
|
|
|
|
const isTaskRegistered = await TaskManager.isTaskRegisteredAsync(
|
|
LOCATION_TASK_NAME
|
|
);
|
|
|
|
if (isTaskRegistered) {
|
|
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
|
|
}
|
|
|
|
this.isTracking = false;
|
|
}
|
|
|
|
isCurrentlyTracking(): boolean {
|
|
return this.isTracking;
|
|
}
|
|
|
|
async getTrackingStatus(): Promise<{
|
|
isTracking: boolean;
|
|
hasPermissions: boolean;
|
|
backgroundEnabled: boolean;
|
|
}> {
|
|
const permissions = await this.checkPermissions();
|
|
const isTaskRegistered = await TaskManager.isTaskRegisteredAsync(
|
|
LOCATION_TASK_NAME
|
|
);
|
|
|
|
return {
|
|
isTracking: this.isTracking,
|
|
hasPermissions: permissions.foreground,
|
|
backgroundEnabled: isTaskRegistered,
|
|
};
|
|
}
|
|
}
|
|
|
|
export const locationService = new LocationService();
|