diff --git a/client/components/Map.tsx b/client/components/Map.tsx index 1af026b..0446c10 100644 --- a/client/components/Map.tsx +++ b/client/components/Map.tsx @@ -1,27 +1,52 @@ -import { StyleSheet } from 'react-native' -import MapLibreGL, { Camera, FillLayer, LineLayer, MapView, PointAnnotation, RasterLayer, RasterSource, ShapeSource, UserLocation, UserTrackingMode } from '@maplibre/maplibre-react-native' import { FontAwesome5, MaterialIcons } from '@expo/vector-icons' +import MapLibreGL, { Camera, FillLayer, LineLayer, MapView, PointAnnotation, RasterLayer, RasterSource, ShapeSource, UserLocation, UserTrackingMode } from '@maplibre/maplibre-react-native' +import { useQuery } from '@tanstack/react-query' import { circle } from '@turf/circle' -import { useLastOwnLocation, useLastPlayerLocations } from '@/hooks/useLocation' import React, { useMemo, useState } from 'react' -import { PlayerLocation } from '@/utils/features/location/locationSlice' +import { StyleSheet } from 'react-native' +import { Button, Dialog, FAB, Portal, Text } from 'react-native-paper' +import { useAuth } from '@/hooks/useAuth' import { useGame } from '@/hooks/useGame' -import { FAB } from 'react-native-paper' +import { useLastOwnLocation, useLastPlayerLocations } from '@/hooks/useLocation' +import { isAuthValid } from '@/utils/features/auth/authSlice' +import { Player } from '@/utils/features/game/gameSlice' +import { PlayerLocation } from '@/utils/features/location/locationSlice' export default function Map() { const [followUser, setFollowUser] = useState(true) + return ( <> - - } - onPress={() => setFollowUser(followUser => !followUser)} /> + + ) } -function MapComponent({ followUser, setFollowUser }: { followUser?: boolean, setFollowUser: React.Dispatch> }) { +type FollowUserProps = { + followUser: boolean, + setFollowUser: React.Dispatch>, +} + +function MapWrapper({ followUser, setFollowUser }: FollowUserProps) { + const [displayedPlayerId, setDisplayedPlayerId] = useState(null) + return ( + <> + + + setDisplayedPlayerId(null)} /> + + + ) +} + +type MapComponentProps = { + followUser?: boolean, + setFollowUser: React.Dispatch>, + setDisplayedPlayerId: React.Dispatch> +} + +function MapComponent({ followUser, setFollowUser, setDisplayedPlayerId }: MapComponentProps) { MapLibreGL.setAccessToken(null) const userLocation = useLastOwnLocation() return ( @@ -44,22 +69,22 @@ function MapComponent({ followUser, setFollowUser }: { followUser?: boolean, set - + ) } -function PlayerLocationsMarkers() { +function PlayerLocationsMarkers({ setDisplayedPlayerId }: { setDisplayedPlayerId: React.Dispatch> }) { const game = useGame() const lastPlayerLocations = useLastPlayerLocations() return lastPlayerLocations ? lastPlayerLocations .filter(() => game.currentRunner === true || !game.gameStarted) .filter(playerLoc => playerLoc.playerId !== game.playerId) - .map(playerLoc => ) : <> + .map(playerLoc => ) : <> } -function PlayerLocationMarker({ playerLocation }: { playerLocation: PlayerLocation }) { +function PlayerLocationMarker({ playerLocation, setDisplayedPlayerId }: { playerLocation: PlayerLocation, setDisplayedPlayerId: React.Dispatch> }) { const accuracyCircle = useMemo(() => circle([playerLocation.longitude, playerLocation.latitude], playerLocation.accuracy, {steps: 64, units: 'meters'}), [playerLocation]) return <> + coordinate={[playerLocation.longitude, playerLocation.latitude]} + onSelected={() => { setDisplayedPlayerId(playerLocation.playerId) }}> @@ -88,3 +114,56 @@ const styles = StyleSheet.create({ alignSelf: 'stretch', } }) + +function FollowUserButton({ followUser, setFollowUser }: FollowUserProps) { + return ( + } + onPress={() => setFollowUser(followUser => !followUser)} /> + ) +} + +function PlayerLocationDialog({ displayedPlayerId, onDismiss }: { displayedPlayerId: number | null, onDismiss: () => void }) { + const auth = useAuth() + const lastPlayerLocations = useLastPlayerLocations() + const playersQuery = useQuery({ + queryKey: ['get-players', auth.token], + queryFn: () => fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/players/`, { + headers: { "Authorization": `Bearer ${auth.token}` }} + ).then(resp => resp.json()), + enabled: isAuthValid(auth), + initialData: { data: [], meta: { currentPage: 0, lastPage: 0, nextPage: 0, prevPage: 0, total: 0, totalPerPage: 0 } }, + }) + const displayedPlayerLoc = useMemo(() => { + return lastPlayerLocations.find(loc => loc.playerId === displayedPlayerId) + }, [displayedPlayerId, lastPlayerLocations]) + const displayedPlayerName = useMemo(() => { + if (!playersQuery.isSuccess || !displayedPlayerId) + return "Chargement…" + const player: Player | undefined = playersQuery.data.data.find((player: Player) => player.id === displayedPlayerId) + if (!player) + return "Chargement…" + return player.name + }, [displayedPlayerId, playersQuery]) + + return ( + + {displayedPlayerName} + + + Dernière position : {new Date(displayedPlayerLoc?.timestamp ?? 0).toLocaleString()} + + + Précision : {displayedPlayerLoc?.accuracy.toPrecision(3)} m + + + + + + + ) +} diff --git a/client/utils/features/game/gameSlice.ts b/client/utils/features/game/gameSlice.ts index a91b2ce..9ef7205 100644 --- a/client/utils/features/game/gameSlice.ts +++ b/client/utils/features/game/gameSlice.ts @@ -20,6 +20,13 @@ export interface PenaltyPayload { penaltyEnd: number | null } +export interface Player { + id: number + name: string + money: number + activeChallengeId: number | null +} + export interface GameState { playerId: number | null runId: number | null