6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
148 lines
3.5 KiB
JavaScript
148 lines
3.5 KiB
JavaScript
/**
|
|
* useLocation Hook
|
|
* React hook for GPS location tracking
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
import Geolocation from '@react-native-community/geolocation';
|
|
|
|
export function useLocation(options = {}) {
|
|
const [location, setLocation] = useState(null);
|
|
const [hasPermission, setHasPermission] = useState(false);
|
|
const [isTracking, setIsTracking] = useState(false);
|
|
const [error, setError] = useState(null);
|
|
|
|
const watchIdRef = useRef(null);
|
|
|
|
const defaultOptions = {
|
|
enableHighAccuracy: true,
|
|
timeout: 15000,
|
|
maximumAge: 10000,
|
|
distanceFilter: 10, // meters
|
|
...options,
|
|
};
|
|
|
|
/**
|
|
* Request location permission
|
|
*/
|
|
const requestPermission = useCallback(async () => {
|
|
try {
|
|
const result = await Geolocation.requestAuthorization();
|
|
setHasPermission(result === 'granted');
|
|
return result === 'granted';
|
|
} catch (err) {
|
|
setError(err.message);
|
|
return false;
|
|
}
|
|
}, []);
|
|
|
|
/**
|
|
* Get current location (one-time)
|
|
*/
|
|
const getCurrentLocation = useCallback(() => {
|
|
return new Promise((resolve, reject) => {
|
|
Geolocation.getCurrentPosition(
|
|
(position) => {
|
|
const loc = {
|
|
latitude: position.coords.latitude,
|
|
longitude: position.coords.longitude,
|
|
altitude: position.coords.altitude,
|
|
accuracy: position.coords.accuracy,
|
|
heading: position.coords.heading,
|
|
speed: position.coords.speed,
|
|
timestamp: new Date(position.timestamp).toISOString(),
|
|
};
|
|
setLocation(loc);
|
|
setError(null);
|
|
resolve(loc);
|
|
},
|
|
(err) => {
|
|
setError(err.message);
|
|
reject(err);
|
|
},
|
|
defaultOptions
|
|
);
|
|
});
|
|
}, [defaultOptions]);
|
|
|
|
/**
|
|
* Start continuous location tracking
|
|
*/
|
|
const startTracking = useCallback(() => {
|
|
if (watchIdRef.current) {
|
|
return; // Already tracking
|
|
}
|
|
|
|
setIsTracking(true);
|
|
|
|
watchIdRef.current = Geolocation.watchPosition(
|
|
(position) => {
|
|
const loc = {
|
|
latitude: position.coords.latitude,
|
|
longitude: position.coords.longitude,
|
|
altitude: position.coords.altitude,
|
|
accuracy: position.coords.accuracy,
|
|
heading: position.coords.heading,
|
|
speed: position.coords.speed,
|
|
timestamp: new Date(position.timestamp).toISOString(),
|
|
};
|
|
setLocation(loc);
|
|
setError(null);
|
|
},
|
|
(err) => {
|
|
setError(err.message);
|
|
},
|
|
{
|
|
...defaultOptions,
|
|
distanceFilter: defaultOptions.distanceFilter,
|
|
}
|
|
);
|
|
}, [defaultOptions]);
|
|
|
|
/**
|
|
* Stop location tracking
|
|
*/
|
|
const stopTracking = useCallback(() => {
|
|
if (watchIdRef.current) {
|
|
Geolocation.clearWatch(watchIdRef.current);
|
|
watchIdRef.current = null;
|
|
}
|
|
setIsTracking(false);
|
|
}, []);
|
|
|
|
/**
|
|
* Toggle tracking
|
|
*/
|
|
const toggleTracking = useCallback(() => {
|
|
if (isTracking) {
|
|
stopTracking();
|
|
} else {
|
|
startTracking();
|
|
}
|
|
}, [isTracking, startTracking, stopTracking]);
|
|
|
|
// Cleanup on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (watchIdRef.current) {
|
|
Geolocation.clearWatch(watchIdRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
// State
|
|
location,
|
|
hasPermission,
|
|
isTracking,
|
|
error,
|
|
|
|
// Actions
|
|
requestPermission,
|
|
getCurrentLocation,
|
|
startTracking,
|
|
stopTracking,
|
|
toggleTracking,
|
|
};
|
|
}
|