diff --git a/client/app/_layout.tsx b/client/app/_layout.tsx
index e79865c..87d2877 100644
--- a/client/app/_layout.tsx
+++ b/client/app/_layout.tsx
@@ -12,8 +12,8 @@ import { useReactQueryDevTools } from '@dev-plugins/react-query'
import { useColorScheme } from '@/hooks/useColorScheme'
import store from '@/utils/store'
import { useStartBackgroundFetchServiceEffect } from '@/utils/background'
-import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
import LoginProvider from '@/components/LoginProvider'
+import GeolocationProvider from '@/components/GeolocationProvider'
const queryClient = new QueryClient({
defaultOptions: {
@@ -26,7 +26,6 @@ const queryClient = new QueryClient({
})
export default function RootLayout() {
- useStartGeolocationServiceEffect()
useStartBackgroundFetchServiceEffect()
const colorScheme = useColorScheme()
@@ -45,16 +44,18 @@ export default function RootLayout() {
persistOptions={{ persister: asyncStoragePersister }}
onSuccess={() => queryClient.resumePausedMutations().then(() => queryClient.invalidateQueries())}>
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/components/GeolocationProvider.tsx b/client/components/GeolocationProvider.tsx
new file mode 100644
index 0000000..a5a89ca
--- /dev/null
+++ b/client/components/GeolocationProvider.tsx
@@ -0,0 +1,32 @@
+import { ReactNode, useEffect } from 'react'
+import { useAuth } from '@/hooks/useAuth'
+import { useQueuedLocations, useUnqueueLocation } from '@/hooks/useLocation'
+import { useGeolocationMutation } from '@/hooks/mutations/useGeolocationMutation'
+import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
+
+export default function GeolocationProvider({ children }: { children: ReactNode }) {
+ useStartGeolocationServiceEffect()
+
+ const auth = useAuth()
+ const geolocationsQueue = useQueuedLocations()
+ const unqueueLocation = useUnqueueLocation()
+ const geolocationMutation = useGeolocationMutation({
+ auth,
+ onPostSuccess: ({ data, variables: location }) => {
+ unqueueLocation(location)
+ geolocationMutation.reset()
+ },
+ onError: ({ response, error }) => { console.error(response, error) }
+ })
+
+ useEffect(() => {
+ if (geolocationsQueue.length === 0 || geolocationMutation.isPending)
+ return
+ const locToSend = geolocationsQueue[0]
+ geolocationMutation.mutate(locToSend)
+ }, [auth, geolocationsQueue])
+
+ return <>
+ {children}
+ >
+}
\ No newline at end of file
diff --git a/client/components/Map.tsx b/client/components/Map.tsx
index a598b91..7278efb 100644
--- a/client/components/Map.tsx
+++ b/client/components/Map.tsx
@@ -2,10 +2,10 @@ import { StyleSheet } from 'react-native'
import MapLibreGL, { Camera, FillLayer, LineLayer, MapView, PointAnnotation, RasterLayer, RasterSource, ShapeSource, UserLocation } from '@maplibre/maplibre-react-native'
import { FontAwesome5 } from '@expo/vector-icons'
import { circle } from '@turf/circle'
-import { useLocation } from '@/hooks/useLocation'
+import { useLastOwnLocation } from '@/hooks/useLocation'
export default function Map() {
- const userLocation = useLocation()
+ const userLocation = useLastOwnLocation()
MapLibreGL.setAccessToken(null)
const accuracyCircle = circle([userLocation?.coords.longitude ?? 0, userLocation?.coords.latitude ?? 0], userLocation?.coords.accuracy ?? 0, {steps: 64, units: 'meters'})
return (
diff --git a/client/components/Map.web.tsx b/client/components/Map.web.tsx
index 1be6dd6..eb4ea1b 100644
--- a/client/components/Map.web.tsx
+++ b/client/components/Map.web.tsx
@@ -1,4 +1,4 @@
-import { useLocation } from "@/hooks/useLocation"
+import { useLastOwnLocation } from "@/hooks/useLocation"
import { circle } from "@turf/circle"
import { type Map as MaplibreGLMap } from "maplibre-gl"
import { RLayer, RMap, RMarker, RNavigationControl, RSource, useMap } from "maplibre-react-components"
@@ -21,7 +21,7 @@ export default function Map() {
}
function UserLocation() {
- const userLocation = useLocation()
+ const userLocation = useLastOwnLocation()
const [firstUserPositionFetched, setFirstUserPositionFetched] = useState(false)
const map: MaplibreGLMap = useMap()
if (userLocation != null && !firstUserPositionFetched) {
diff --git a/client/constants/Constants.ts b/client/constants/Constants.ts
new file mode 100644
index 0000000..5e2e0de
--- /dev/null
+++ b/client/constants/Constants.ts
@@ -0,0 +1,3 @@
+export const Constants = {
+ MIN_DELAY_LOCATION_SENT: 20
+}
diff --git a/client/hooks/mutations/useGeolocationMutation.ts b/client/hooks/mutations/useGeolocationMutation.ts
new file mode 100644
index 0000000..761f3a0
--- /dev/null
+++ b/client/hooks/mutations/useGeolocationMutation.ts
@@ -0,0 +1,56 @@
+import { AuthState } from "@/utils/features/location/authSlice"
+import { useMutation } from "@tanstack/react-query"
+import { LocationObject } from "expo-location"
+
+type ErrorResponse = {
+ error: string
+ message: string
+ statusCode: number
+}
+
+type LoginForm = {
+ name: string
+ password: string
+}
+
+type onPostSuccessFunc = (data: any, variables: LocationObject, context: unknown) => void
+type ErrorFuncProps = { response?: ErrorResponse, error?: Error }
+type onErrorFunc = (props: ErrorFuncProps) => void
+
+type PostProps = {
+ auth: AuthState
+ onPostSuccess?: onPostSuccessFunc
+ onError?: onErrorFunc
+}
+
+export const useGeolocationMutation = ({ auth, onPostSuccess, onError }: PostProps) => {
+ return useMutation({
+ mutationFn: async (location: LocationObject) => {
+ return fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/geolocations/`, {
+ method: "POST",
+ headers: {
+ "Authorization": `Bearer ${auth.token}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ longitude: location.coords.longitude,
+ latitude: location.coords.latitude,
+ speed: location.coords.speed,
+ accuracy: location.coords.accuracy,
+ altitude: location.coords.altitude,
+ altitudeAccuracy: location.coords.altitudeAccuracy,
+ timestamp: location.timestamp,
+ })
+ }).then(resp => resp.json())
+ },
+ networkMode: 'offlineFirst',
+ onSuccess: async (data, location: LocationObject, context: unknown) => {
+ if (onPostSuccess)
+ onPostSuccess(data, location, context)
+ },
+ onError: async (error: Error) => {
+ if (onError)
+ onError({ error: error })
+ }
+ })
+}
diff --git a/client/hooks/useLocation.ts b/client/hooks/useLocation.ts
index 856b708..eb4efb7 100644
--- a/client/hooks/useLocation.ts
+++ b/client/hooks/useLocation.ts
@@ -1,9 +1,15 @@
import { LocationObject } from "expo-location"
import { useAppDispatch, useAppSelector } from "./useStore"
-import { setLocation } from "@/utils/features/location/locationSlice"
+import { setLastLocation, unqueueLocation } from "@/utils/features/location/locationSlice"
-export const useLocation = () => useAppSelector((state) => state.location.location)
-export const useSetLocation = () => {
+export const useLastOwnLocation = () => useAppSelector((state) => state.location.lastOwnLocation)
+export const useQueuedLocations = () => useAppSelector((state) => state.location.queuedLocations)
+
+export const useSetLastLocation = () => {
const dispatch = useAppDispatch()
- return (location: LocationObject) => dispatch(setLocation(location))
+ return (location: LocationObject) => dispatch(setLastLocation(location))
+}
+export const useUnqueueLocation = () => {
+ const dispatch = useAppDispatch()
+ return (location: LocationObject) => dispatch(unqueueLocation(location))
}
diff --git a/client/utils/features/location/authSlice.ts b/client/utils/features/location/authSlice.ts
index 88c9c5e..7192b21 100644
--- a/client/utils/features/location/authSlice.ts
+++ b/client/utils/features/location/authSlice.ts
@@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import * as SecureStore from '@/utils/SecureStore'
import { Platform } from 'react-native'
-interface AuthState {
+export interface AuthState {
loggedIn: boolean,
name: string | null,
token: string | null,
diff --git a/client/utils/features/location/locationSlice.ts b/client/utils/features/location/locationSlice.ts
index ed606a4..c7c808d 100644
--- a/client/utils/features/location/locationSlice.ts
+++ b/client/utils/features/location/locationSlice.ts
@@ -1,24 +1,37 @@
+import { Constants } from '@/constants/Constants'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { LocationObject } from 'expo-location'
interface LocationState {
- location: LocationObject | null
+ lastOwnLocation: LocationObject | null
+ lastSentLocation: LocationObject | null
+ queuedLocations: LocationObject[]
}
const initialState: LocationState = {
- location: null
+ lastOwnLocation: null,
+ lastSentLocation: null,
+ queuedLocations: []
}
export const locationSlice = createSlice({
name: 'location',
initialState: initialState,
reducers: {
- setLocation: (state, action: PayloadAction) => {
- state.location = action.payload
+ setLastLocation: (state, action: PayloadAction) => {
+ const location: LocationObject = action.payload
+ state.lastOwnLocation = location
+ if (state.lastSentLocation === null || (location.timestamp - state.lastSentLocation.timestamp) >= Constants.MIN_DELAY_LOCATION_SENT * 1000) {
+ state.lastSentLocation = location
+ state.queuedLocations.push(location)
+ }
+ },
+ unqueueLocation: (state, action: PayloadAction) => {
+ state.queuedLocations.pop()
},
},
})
-export const { setLocation } = locationSlice.actions
+export const { setLastLocation, unqueueLocation } = locationSlice.actions
export default locationSlice.reducer
diff --git a/client/utils/geolocation.ts b/client/utils/geolocation.ts
index f7c6a62..bf95cd4 100644
--- a/client/utils/geolocation.ts
+++ b/client/utils/geolocation.ts
@@ -1,7 +1,7 @@
import * as Location from 'expo-location'
import * as TaskManager from 'expo-task-manager'
import { Platform } from 'react-native'
-import { setLocation } from './features/location/locationSlice'
+import { setLastLocation } from './features/location/locationSlice'
import store from './store'
import { useEffect } from 'react'
@@ -13,10 +13,7 @@ TaskManager.defineTask(LOCATION_TASK, async ({ data, error }: any) => {
return
}
const { locations } = data
- store.dispatch(setLocation(locations.at(-1)))
- for (let location of locations) {
- // TODO Envoyer les positions au serveur
- }
+ store.dispatch(setLastLocation(locations.at(-1)))
})
export async function startGeolocationService(): Promise void)> {
@@ -48,7 +45,7 @@ export async function startGeolocationService(): Promise void)> {
return async () => await Location.stopLocationUpdatesAsync(LOCATION_TASK)
}
else {
- const locationSubscription = await Location.watchPositionAsync({accuracy: Location.Accuracy.BestForNavigation}, location_nouveau => store.dispatch(setLocation(location_nouveau)))
+ const locationSubscription = await Location.watchPositionAsync({accuracy: Location.Accuracy.BestForNavigation}, location_nouveau => store.dispatch(setLastLocation(location_nouveau)))
return locationSubscription.remove
}
}