Prototype envoi géolocalisations
This commit is contained in:
parent
db7a0b970d
commit
bdd53eb8bb
@ -12,8 +12,8 @@ import { useReactQueryDevTools } from '@dev-plugins/react-query'
|
|||||||
import { useColorScheme } from '@/hooks/useColorScheme'
|
import { useColorScheme } from '@/hooks/useColorScheme'
|
||||||
import store from '@/utils/store'
|
import store from '@/utils/store'
|
||||||
import { useStartBackgroundFetchServiceEffect } from '@/utils/background'
|
import { useStartBackgroundFetchServiceEffect } from '@/utils/background'
|
||||||
import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
|
|
||||||
import LoginProvider from '@/components/LoginProvider'
|
import LoginProvider from '@/components/LoginProvider'
|
||||||
|
import GeolocationProvider from '@/components/GeolocationProvider'
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@ -26,7 +26,6 @@ const queryClient = new QueryClient({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
useStartGeolocationServiceEffect()
|
|
||||||
useStartBackgroundFetchServiceEffect()
|
useStartBackgroundFetchServiceEffect()
|
||||||
const colorScheme = useColorScheme()
|
const colorScheme = useColorScheme()
|
||||||
|
|
||||||
@ -45,16 +44,18 @@ export default function RootLayout() {
|
|||||||
persistOptions={{ persister: asyncStoragePersister }}
|
persistOptions={{ persister: asyncStoragePersister }}
|
||||||
onSuccess={() => queryClient.resumePausedMutations().then(() => queryClient.invalidateQueries())}>
|
onSuccess={() => queryClient.resumePausedMutations().then(() => queryClient.invalidateQueries())}>
|
||||||
<LoginProvider loginRedirect={'/login'}>
|
<LoginProvider loginRedirect={'/login'}>
|
||||||
<PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}>
|
<GeolocationProvider>
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}>
|
||||||
<Stack>
|
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack>
|
||||||
<Stack.Screen name="login" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="login" options={{ headerShown: false }} />
|
||||||
</Stack>
|
<Stack.Screen name="+not-found" />
|
||||||
<StatusBar style="auto" />
|
</Stack>
|
||||||
</ThemeProvider>
|
<StatusBar style="auto" />
|
||||||
</PaperProvider>
|
</ThemeProvider>
|
||||||
|
</PaperProvider>
|
||||||
|
</GeolocationProvider>
|
||||||
</LoginProvider>
|
</LoginProvider>
|
||||||
</PersistQueryClientProvider>
|
</PersistQueryClientProvider>
|
||||||
</StoreProvider>
|
</StoreProvider>
|
||||||
|
32
client/components/GeolocationProvider.tsx
Normal file
32
client/components/GeolocationProvider.tsx
Normal file
@ -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}
|
||||||
|
</>
|
||||||
|
}
|
@ -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 MapLibreGL, { Camera, FillLayer, LineLayer, MapView, PointAnnotation, RasterLayer, RasterSource, ShapeSource, UserLocation } from '@maplibre/maplibre-react-native'
|
||||||
import { FontAwesome5 } from '@expo/vector-icons'
|
import { FontAwesome5 } from '@expo/vector-icons'
|
||||||
import { circle } from '@turf/circle'
|
import { circle } from '@turf/circle'
|
||||||
import { useLocation } from '@/hooks/useLocation'
|
import { useLastOwnLocation } from '@/hooks/useLocation'
|
||||||
|
|
||||||
export default function Map() {
|
export default function Map() {
|
||||||
const userLocation = useLocation()
|
const userLocation = useLastOwnLocation()
|
||||||
MapLibreGL.setAccessToken(null)
|
MapLibreGL.setAccessToken(null)
|
||||||
const accuracyCircle = circle([userLocation?.coords.longitude ?? 0, userLocation?.coords.latitude ?? 0], userLocation?.coords.accuracy ?? 0, {steps: 64, units: 'meters'})
|
const accuracyCircle = circle([userLocation?.coords.longitude ?? 0, userLocation?.coords.latitude ?? 0], userLocation?.coords.accuracy ?? 0, {steps: 64, units: 'meters'})
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useLocation } from "@/hooks/useLocation"
|
import { useLastOwnLocation } from "@/hooks/useLocation"
|
||||||
import { circle } from "@turf/circle"
|
import { circle } from "@turf/circle"
|
||||||
import { type Map as MaplibreGLMap } from "maplibre-gl"
|
import { type Map as MaplibreGLMap } from "maplibre-gl"
|
||||||
import { RLayer, RMap, RMarker, RNavigationControl, RSource, useMap } from "maplibre-react-components"
|
import { RLayer, RMap, RMarker, RNavigationControl, RSource, useMap } from "maplibre-react-components"
|
||||||
@ -21,7 +21,7 @@ export default function Map() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function UserLocation() {
|
function UserLocation() {
|
||||||
const userLocation = useLocation()
|
const userLocation = useLastOwnLocation()
|
||||||
const [firstUserPositionFetched, setFirstUserPositionFetched] = useState(false)
|
const [firstUserPositionFetched, setFirstUserPositionFetched] = useState(false)
|
||||||
const map: MaplibreGLMap = useMap()
|
const map: MaplibreGLMap = useMap()
|
||||||
if (userLocation != null && !firstUserPositionFetched) {
|
if (userLocation != null && !firstUserPositionFetched) {
|
||||||
|
3
client/constants/Constants.ts
Normal file
3
client/constants/Constants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const Constants = {
|
||||||
|
MIN_DELAY_LOCATION_SENT: 20
|
||||||
|
}
|
56
client/hooks/mutations/useGeolocationMutation.ts
Normal file
56
client/hooks/mutations/useGeolocationMutation.ts
Normal file
@ -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 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,9 +1,15 @@
|
|||||||
import { LocationObject } from "expo-location"
|
import { LocationObject } from "expo-location"
|
||||||
import { useAppDispatch, useAppSelector } from "./useStore"
|
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 useLastOwnLocation = () => useAppSelector((state) => state.location.lastOwnLocation)
|
||||||
export const useSetLocation = () => {
|
export const useQueuedLocations = () => useAppSelector((state) => state.location.queuedLocations)
|
||||||
|
|
||||||
|
export const useSetLastLocation = () => {
|
||||||
const dispatch = useAppDispatch()
|
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))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
|||||||
import * as SecureStore from '@/utils/SecureStore'
|
import * as SecureStore from '@/utils/SecureStore'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
|
|
||||||
interface AuthState {
|
export interface AuthState {
|
||||||
loggedIn: boolean,
|
loggedIn: boolean,
|
||||||
name: string | null,
|
name: string | null,
|
||||||
token: string | null,
|
token: string | null,
|
||||||
|
@ -1,24 +1,37 @@
|
|||||||
|
import { Constants } from '@/constants/Constants'
|
||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { LocationObject } from 'expo-location'
|
import { LocationObject } from 'expo-location'
|
||||||
|
|
||||||
interface LocationState {
|
interface LocationState {
|
||||||
location: LocationObject | null
|
lastOwnLocation: LocationObject | null
|
||||||
|
lastSentLocation: LocationObject | null
|
||||||
|
queuedLocations: LocationObject[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: LocationState = {
|
const initialState: LocationState = {
|
||||||
location: null
|
lastOwnLocation: null,
|
||||||
|
lastSentLocation: null,
|
||||||
|
queuedLocations: []
|
||||||
}
|
}
|
||||||
|
|
||||||
export const locationSlice = createSlice({
|
export const locationSlice = createSlice({
|
||||||
name: 'location',
|
name: 'location',
|
||||||
initialState: initialState,
|
initialState: initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setLocation: (state, action: PayloadAction<LocationObject>) => {
|
setLastLocation: (state, action: PayloadAction<LocationObject>) => {
|
||||||
state.location = action.payload
|
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<LocationObject>) => {
|
||||||
|
state.queuedLocations.pop()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setLocation } = locationSlice.actions
|
export const { setLastLocation, unqueueLocation } = locationSlice.actions
|
||||||
|
|
||||||
export default locationSlice.reducer
|
export default locationSlice.reducer
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as Location from 'expo-location'
|
import * as Location from 'expo-location'
|
||||||
import * as TaskManager from 'expo-task-manager'
|
import * as TaskManager from 'expo-task-manager'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import { setLocation } from './features/location/locationSlice'
|
import { setLastLocation } from './features/location/locationSlice'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
@ -13,10 +13,7 @@ TaskManager.defineTask(LOCATION_TASK, async ({ data, error }: any) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { locations } = data
|
const { locations } = data
|
||||||
store.dispatch(setLocation(locations.at(-1)))
|
store.dispatch(setLastLocation(locations.at(-1)))
|
||||||
for (let location of locations) {
|
|
||||||
// TODO Envoyer les positions au serveur
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function startGeolocationService(): Promise<void | (() => void)> {
|
export async function startGeolocationService(): Promise<void | (() => void)> {
|
||||||
@ -48,7 +45,7 @@ export async function startGeolocationService(): Promise<void | (() => void)> {
|
|||||||
return async () => await Location.stopLocationUpdatesAsync(LOCATION_TASK)
|
return async () => await Location.stopLocationUpdatesAsync(LOCATION_TASK)
|
||||||
}
|
}
|
||||||
else {
|
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
|
return locationSubscription.remove
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user