From 7c8ea006f69f5c7e26996ed4d387080a37066a26 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sat, 21 Feb 2026 15:22:17 +0100 Subject: [PATCH] chore: fix gps --- frontend-admin/.expo/README.md | 13 - frontend-admin/.expo/devices.json | 3 - .../favicon-48.png | Bin 2919 -> 0 bytes frontend-admin/.gitignore | 2 +- frontend-admin/app.json | 4 +- frontend-admin/src/api/tomtom.ts | 2 +- frontend-admin/src/components/TomTomMap.tsx | 241 ++++--- .../src/screens/admin/DeliveryScreen.tsx | 45 +- .../src/screens/delivery/DashboardScreen.tsx | 636 ++++++------------ 9 files changed, 383 insertions(+), 563 deletions(-) delete mode 100644 frontend-admin/.expo/README.md delete mode 100644 frontend-admin/.expo/devices.json delete mode 100644 frontend-admin/.expo/web/cache/production/images/favicon/favicon-24272cdaeff82cc5facdaccd982a6f05b60c4504704bbf94c19a6388659880bb-contain-transparent/favicon-48.png diff --git a/frontend-admin/.expo/README.md b/frontend-admin/.expo/README.md deleted file mode 100644 index ce8c4b6f..00000000 --- a/frontend-admin/.expo/README.md +++ /dev/null @@ -1,13 +0,0 @@ -> Why do I have a folder named ".expo" in my project? - -The ".expo" folder is created when an Expo project is started using "expo start" command. - -> What do the files contain? - -- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds. -- "settings.json": contains the server configuration that is used to serve the application manifest. - -> Should I commit the ".expo" folder? - -No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine. -Upon project creation, the ".expo" folder is already added to your ".gitignore" file. diff --git a/frontend-admin/.expo/devices.json b/frontend-admin/.expo/devices.json deleted file mode 100644 index 5efff6c8..00000000 --- a/frontend-admin/.expo/devices.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "devices": [] -} diff --git a/frontend-admin/.expo/web/cache/production/images/favicon/favicon-24272cdaeff82cc5facdaccd982a6f05b60c4504704bbf94c19a6388659880bb-contain-transparent/favicon-48.png b/frontend-admin/.expo/web/cache/production/images/favicon/favicon-24272cdaeff82cc5facdaccd982a6f05b60c4504704bbf94c19a6388659880bb-contain-transparent/favicon-48.png deleted file mode 100644 index c99b4f2291c114216521d2da5eb3b8f7de2383ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2919 zcmXYz2|UzY7ssa(Lx@tAF(yk?WJ$}5YMGxq2${1PU&Ne$py+W~H3K_=dY9<=*aznKzVPet z<6@-Vvu~ZXXGu$C?z#Elq_L~V&L|m6#VLop6YVbR=Z=8|Cg+^=Y%zkHojMgB>-2oT z;o3K0N6ULWfg;pdC8O+NzP_?Oi;}S*PS>pX-mN%7W@aV~gTYvYZ1zf>97s`Gy=6!E znJ9PB#Md`ARaN1we0>=NFCN^S=KTEpadvigB4NF8hgC3E zk7QzFV{BkxFmvnrzS-pXcn!1$DOF)jx68`TK1iTaRqf~7!n3$kY#RI%I-~hji2K`P z`tI)T&JY6iliPdu?MvCiH3c;_%+?Qu7IVCUf~YrJftqqnOh@Ma&K%J$ARr*6d%>iB z=DSZzHy1@B92VO}glh%+w{%3E)`d+*$HaX3mX~gBSL<{B=R2xmXbO#Hh$S&_&4m3w znWNt%lb$`()7;x==g{OsAmlVRd&KA0n|8e@Q}m{zd(kjQN5@%UyV#SHBT={6ar)NH z+S=NafsL~v95#lLk&)wSek*1RAB*m?y-@QbS5#c>iiPRp@yZSk4mf=q8@O{hb*BEo z!p^SKjdn0SUDvBA-h&zp&T5~ZYgR%+LS}zOaEIK<^kt3JV-UeJyqb7Gzyt?3WW@Gj zvnnc{Y6ks$^7Ls(pIoz%fdNPLXP?^fh5$uXRn@)Ut0g!+N={f#PL4e3wxwl{pI3EI zYTJ4NpKbuEWb9z@?%$2M#Fc`)3f#G{bC)m2T9rGOI`<3V*T%JNZAG&6VzU(4PKro~ zi-#hJdkMu*(eIIDvT05D+qVWB92_-hXr?K%8f9W)^0%zzn+zfv9cPu?vr?}k6WSEv2PZ0=dyxudJ6$C^&~eNp>&ZCTmK@qG4ABe{k~G-a-c_EtYa zbF=Od$M*L2YAH#{uJzf*-S6t_4T;|)qoWtH2eSU2^Re4O1rs$HE zBJoQdy@^tAk$C;03mot?jR1~N6R(!W?tyS_kzQho`jne9ShleH7#x7CkpcO?Wd|djncK?Ne1EOg-$E)3l7D2_3F|<`t@6o z^ka6zasAG_L7txU@v^cj3!qwl?Cw|O4ZBW*qLWTZN!fH2ucqEtrXA#%7YZtS2>31y zEcPUbC-*x$VlaXl{_=2DVeVP~1+I@JcJUJ)&b^fL%Q!VPA$faOSrCuj$$u(>orAY8{=8d8V1T9cS<8-_i z6ZWLr^U0Kki!zO^(CpG-xnjS*qYA|8jf{>K6c;X_L&n~6@XYlK|v}YO`N1ra!axT2FlLv zu=%C;ZH3$DZ{jY!x)+_tP^h0Fu)Og-QI>T{~0^-4+oZQ@SZiH5_>mhCX z7Dwt1X=?}}Gr2T#O$&P7y^~NFq;ZPGtHMGqU_uOH^s9`n ztgim&VaakBOXSuT4!qRDkFj#vgLR4#P#`UI$NSdr5w5#eTCd{N*a|w74M7PKQocY&0!jfPW$c<#+uD$u3`SIZd|ru? zl2WPrhfDu*=cTx4m=1(Ix6WUIkrxdLge!Y3%gu7Q2k^w*|I5E zwRvyz#G{*@&>IOL3zuCxB6%%k&TF$v0RZbC&WYF^%Kd1gy4EpJDhzW!) zRFwCj4osA#wPxu>&;?mpNlnc((1**xw1IhuvHM>T3J)@icIxzL;2!g- zj|?E+>(?@`UcFlO^s1iHg_QLzUXqrT6#?Amy+YVy{!?Ub=mHwNJk>~mlT|HyY!%>} z8%X)|`Sa1cVq#+4N-8RHEFL%{YjMrJ;s?MJdVRXqw_=KJE)uj>%7db~mgMBHB=&~2 z+Gj{7B_#prye$0vnS#&zwk^P^W-ks2CrZ1yx-vcbQOMY{|DvRCKj2v>c17$J193{QUZMi4whq7Ysn*-aB%Y zSAsgAq%It{4<@S*7VC-spS=KM3?@5py2cyCQs-D{U1}+Vi!d+T&+A)ES=$cWIZ}iR zH5zlL{*9C+E? zG{Bfw@$1)D*QToFWJN_q-@9HN5&WM)rnGxyUFQ;GzhsJzK92T{kQvI&nA zeME?vIbZndYjf$d(cy7OB=W7M$RI9#(1N96$PgRYv+yIebi#wyeAtG+&(!M!?DoHL zXn$i}bhri_?Cb^GfsKO!jk7Wtaki4na1*_3?}%YnVFWm8T?R$4R(P-zQfgssK_Pgz pig(-F+BnwmL0nI`-j8(uL3BnK+XPC!?Eo8S2uc^NQ;f7H{TIY;s#pL3 diff --git a/frontend-admin/.gitignore b/frontend-admin/.gitignore index cac38cad..85aebbc3 100644 --- a/frontend-admin/.gitignore +++ b/frontend-admin/.gitignore @@ -1,5 +1,5 @@ node_modules -.expo +.expo/ dist build *.log diff --git a/frontend-admin/app.json b/frontend-admin/app.json index 0740dfbe..1f3aa80a 100644 --- a/frontend-admin/app.json +++ b/frontend-admin/app.json @@ -10,7 +10,7 @@ "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", - "backgroundColor": "#000" + "backgroundColor": "#000000" }, "ios": { "supportsTablet": true, @@ -20,7 +20,7 @@ "package": "com.uberstup.adminpanel", "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#000" + "backgroundColor": "#000000" }, "edgeToEdgeEnabled": true, "permissions": [ diff --git a/frontend-admin/src/api/tomtom.ts b/frontend-admin/src/api/tomtom.ts index 9bb3976b..7f058bfb 100644 --- a/frontend-admin/src/api/tomtom.ts +++ b/frontend-admin/src/api/tomtom.ts @@ -84,7 +84,7 @@ export async function geocodeAddress( address: string, ): Promise { try { - const url = `https://api.tomtom.com/search/2/geocode/${encodeURIComponent(address)}.json?key=${TOMTOM_API_KEY}&countrySet=FR&limit=1`; + const url = `https://api.tomtom.com/search/2/geocode/${encodeURIComponent(address)}.json?key=${TOMTOM_API_KEY}&limit=1`; const { data } = await axios.get(url); if (data.results && data.results.length > 0) { const pos = data.results[0].position; diff --git a/frontend-admin/src/components/TomTomMap.tsx b/frontend-admin/src/components/TomTomMap.tsx index 7945b633..0fece98c 100644 --- a/frontend-admin/src/components/TomTomMap.tsx +++ b/frontend-admin/src/components/TomTomMap.tsx @@ -7,6 +7,7 @@ import React, { } from "react"; import { StyleSheet } from "react-native"; import { WebView } from "react-native-webview"; +import { geocodeAddress, calculateRoute } from "../api/tomtom"; const TOMTOM_API_KEY = "MERY8I7LMeYVSLKO5WuV73W9rKJpBLoB"; @@ -20,29 +21,25 @@ export interface TomTomMarker { isSelected?: boolean; } -export interface TomTomRoute { - coordinates: { latitude: number; longitude: number }[]; - color?: string; -} - -export interface TomTomDestination { - latitude: number; - longitude: number; - color?: string; -} - export interface TomTomMapRef { fitAllMarkers: () => void; fitToCoordinates: ( coords: { latitude: number; longitude: number }[], ) => void; + calcRoute: ( + origin: { latitude: number; longitude: number }, + destination: { latitude: number; longitude: number }, + ) => void; + calcRouteFromAddress: ( + origin: { latitude: number; longitude: number }, + address: string, + ) => void; + clearRoute: () => void; } interface TomTomMapProps { style?: any; markers?: TomTomMarker[]; - route?: TomTomRoute | null; - destination?: TomTomDestination | null; initialCenter?: { latitude: number; longitude: number }; initialZoom?: number; onMarkerPress?: (markerId: string) => void; @@ -53,10 +50,8 @@ const TomTomMap = forwardRef( { style, markers = [], - route, - destination, initialCenter, - initialZoom = 12, + initialZoom = 14, onMarkerPress, }, ref, @@ -69,7 +64,7 @@ const TomTomMap = forwardRef( const json = JSON.stringify(msg); if (isReady.current) { webViewRef.current?.injectJavaScript( - `handleMessage(${json}); true;`, + `(function(){ try { handleMessage(${json}); } catch(e) {} })(); true;`, ); } else { pendingMessages.current.push(json); @@ -82,6 +77,53 @@ const TomTomMap = forwardRef( fitAllMarkers: () => sendMessage({ type: "fitAll" }), fitToCoordinates: (coords) => sendMessage({ type: "fitCoords", coords }), + clearRoute: () => sendMessage({ type: "clearRoute" }), + calcRoute: (origin, dest) => { + calculateRoute(origin, dest).then((result) => { + if (result) { + sendMessage({ + type: "drawRoute", + coordinates: result.route.coordinates, + destination: dest, + }); + } else { + // Fallback: ligne droite + sendMessage({ + type: "drawRoute", + coordinates: [origin, dest], + destination: dest, + }); + } + }).catch(() => { + sendMessage({ + type: "drawRoute", + coordinates: [origin, dest], + destination: dest, + }); + }); + }, + calcRouteFromAddress: async (origin, address) => { + try { + const dest = await geocodeAddress(address); + if (!dest) return; + const result = await calculateRoute(origin, dest); + if (result) { + sendMessage({ + type: "drawRoute", + coordinates: result.route.coordinates, + destination: dest, + }); + } else { + sendMessage({ + type: "drawRoute", + coordinates: [origin, dest], + destination: dest, + }); + } + } catch { + /* silent */ + } + }, }), [sendMessage], ); @@ -90,30 +132,25 @@ const TomTomMap = forwardRef( sendMessage({ type: "updateMarkers", markers }); }, [markers, sendMessage]); - useEffect(() => { - sendMessage({ - type: "updateRoute", - route: route || null, - destination: destination || null, - }); - }, [route, destination, sendMessage]); - const onMessage = useCallback( (event: any) => { try { const data = JSON.parse(event.nativeEvent.data); if (data.type === "ready") { isReady.current = true; - // flush pending - for (const msg of pendingMessages.current) { + // Rejouer les messages en attente + const pending = pendingMessages.current.slice(); + pendingMessages.current = []; + for (const msg of pending) { webViewRef.current?.injectJavaScript( - `handleMessage(${msg}); true;`, + `(function(){ try { handleMessage(${msg}); } catch(e) {} })(); true;`, ); } - pendingMessages.current = []; - // Auto fit markers after ready + flush + // Fit sur tous les markers après un court délai setTimeout(() => { - sendMessage({ type: "fitAll" }); + webViewRef.current?.injectJavaScript( + `(function(){ try { handleMessage(${JSON.stringify({ type: "fitAll" })}); } catch(e) {} })(); true;`, + ); }, 500); } else if (data.type === "markerPress" && onMarkerPress) { onMarkerPress(data.id); @@ -134,7 +171,7 @@ const TomTomMap = forwardRef( } : { latitude: 48.8566, longitude: 2.3522 }); - const html = buildHtml(center, initialZoom); + const html = buildHtml(center, initialZoom, TOMTOM_API_KEY); return ( ( scrollEnabled={false} bounces={false} originWhitelist={["*"]} + mixedContentMode="always" /> ); }, @@ -155,6 +193,7 @@ const TomTomMap = forwardRef( function buildHtml( center: { latitude: number; longitude: number }, zoom: number, + apiKey: string, ) { return ` @@ -163,13 +202,13 @@ function buildHtml( @@ -190,39 +226,82 @@ function buildHtml(
diff --git a/frontend-admin/src/screens/admin/DeliveryScreen.tsx b/frontend-admin/src/screens/admin/DeliveryScreen.tsx index 8e5a9acb..ec88b18a 100644 --- a/frontend-admin/src/screens/admin/DeliveryScreen.tsx +++ b/frontend-admin/src/screens/admin/DeliveryScreen.tsx @@ -23,7 +23,7 @@ import { getCommandByID, } from "../../api/api_admin"; import { geocodeAddress, calculateRoute } from "../../api/tomtom"; -import type { RouteInfo, LatLng } from "../../api/tomtom"; +import type { RouteInfo } from "../../api/tomtom"; import type { DeliveryPerson } from "../../api/types"; import { STATUS_LABELS, getStatusColors } from "../../utils/constants"; import LoadingSpinner from "../../components/ui/LoadingSpinner"; @@ -32,8 +32,6 @@ import Badge from "../../components/ui/Badge"; import TomTomMap, { TomTomMapRef, TomTomMarker, - TomTomRoute, - TomTomDestination, } from "../../components/TomTomMap"; const MAP_HEIGHT = 280; @@ -60,9 +58,6 @@ export default function DeliveryScreen() { const [selectedLivreur, setSelectedLivreur] = useState(null); const [routeInfo, setRouteInfo] = useState(null); - const [destinationCoords, setDestinationCoords] = useState( - null, - ); const [routeLoading, setRouteLoading] = useState(false); const loadData = useCallback(async () => { @@ -111,30 +106,13 @@ export default function DeliveryScreen() { })); }, [livreursWithGPS, selectedLivreur, statusColors, colors.textMuted]); - // Route TomTom - const tomtomRoute: TomTomRoute | null = useMemo(() => { - if (!routeInfo || routeInfo.coordinates.length === 0) return null; - return { coordinates: routeInfo.coordinates, color: "#4285F4" }; - }, [routeInfo]); - - // Destination TomTom - const tomtomDestination: TomTomDestination | null = useMemo(() => { - if (!destinationCoords) return null; - return { - latitude: destinationCoords.latitude, - longitude: destinationCoords.longitude, - color: colors.danger, - }; - }, [destinationCoords, colors.danger]); - // -------------------------------------------------- - // Track livreur — calcul de route + // Track livreur — calcul de route via ref TomTomMap // -------------------------------------------------- const trackLivreur = useCallback( async (livreur: DeliveryPerson) => { setSelectedLivreur(livreur); setRouteInfo(null); - setDestinationCoords(null); if (!livreur.stats.current_command) return; @@ -154,24 +132,16 @@ export default function DeliveryScreen() { setRouteLoading(false); return; } - setDestinationCoords(dest); - const origin: LatLng = { + const origin = { latitude: livreur.location.latitude, longitude: livreur.location.longitude, }; const result = await calculateRoute(origin, dest); if (result) { setRouteInfo(result.route); - // Fit la map sur les deux points - const ref = mapFullscreen ? fullscreenMapRef : mapRef; - ref.current?.fitToCoordinates([ - { - latitude: origin.latitude, - longitude: origin.longitude, - }, - { latitude: dest.latitude, longitude: dest.longitude }, - ]); + const activeRef = mapFullscreen ? fullscreenMapRef : mapRef; + activeRef.current?.calcRoute(origin, dest); } } catch { /* silent */ @@ -184,7 +154,6 @@ export default function DeliveryScreen() { const clearRoute = () => { setSelectedLivreur(null); setRouteInfo(null); - setDestinationCoords(null); }; // -------------------------------------------------- @@ -599,8 +568,6 @@ export default function DeliveryScreen() { ref={mapRef} style={styles.map} markers={tomtomMarkers} - route={tomtomRoute} - destination={tomtomDestination} initialCenter={{ latitude: livreursWithGPS[0].location.latitude, longitude: livreursWithGPS[0].location.longitude, @@ -708,8 +675,6 @@ export default function DeliveryScreen() { ref={fullscreenMapRef} style={styles.fullscreenMap} markers={tomtomMarkers} - route={tomtomRoute} - destination={tomtomDestination} initialCenter={{ latitude: livreursWithGPS[0].location.latitude, longitude: diff --git a/frontend-admin/src/screens/delivery/DashboardScreen.tsx b/frontend-admin/src/screens/delivery/DashboardScreen.tsx index c3be60fb..c0565348 100644 --- a/frontend-admin/src/screens/delivery/DashboardScreen.tsx +++ b/frontend-admin/src/screens/delivery/DashboardScreen.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useRef, useMemo, + RefObject, } from "react"; import { View, @@ -33,17 +34,8 @@ import { updateDeliveryStatus, updateMyLocation, } from "../../api/api_delivery"; -import { - geocodeAddress, - calculateRoute, - maneuverIcons, - maneuverTranslations, -} from "../../api/tomtom"; -import type { - RouteInfo, - NavigationInstruction, - LatLng, -} from "../../api/tomtom"; +import { geocodeAddress, calculateRoute } from "../../api/tomtom"; +import type { RouteInfo } from "../../api/tomtom"; import type { DeliveryStatus, DeliveryItem, QueueInfo } from "../../api/types"; import LoadingSpinner from "../../components/ui/LoadingSpinner"; import Card from "../../components/ui/Card"; @@ -54,10 +46,10 @@ import { useAlert } from "../../hooks/useAlert"; import TomTomMap, { TomTomMapRef, TomTomMarker, - TomTomRoute, - TomTomDestination, } from "../../components/TomTomMap"; + + const { width: SCREEN_WIDTH } = Dimensions.get("window"); const MAP_HEIGHT = 260; const LOCATION_INTERVAL_MS = 15000; @@ -91,6 +83,7 @@ export default function DashboardScreen() { lat: number; lng: number; } | null>(null); + const lastCoordsRef = useRef<{ lat: number; lng: number } | null>(null); const [lastUpdate, setLastUpdate] = useState(null); const locationInterval = useRef | null>( null, @@ -102,17 +95,9 @@ export default function DashboardScreen() { const fullscreenMapRef = useRef(null); const [mapFullscreen, setMapFullscreen] = useState(false); - // TomTom routing - const [routeInfo, setRouteInfo] = useState(null); - const [instructions, setInstructions] = useState( - [], - ); - const [destinationCoords, setDestinationCoords] = useState( - null, - ); - const [showInstructions, setShowInstructions] = useState(false); - const [currentInstructionIdx, setCurrentInstructionIdx] = useState(0); const [routeLoading, setRouteLoading] = useState(false); + const [routeInfo, setRouteInfo] = useState(null); + const pendingRouteAddress = useRef(null); const { alert, showError, showSuccess, hideAlert } = useAlert(); const STATUS_COLORS: Record = useMemo( @@ -124,10 +109,6 @@ export default function DashboardScreen() { [colors], ); - // -------------------------------------------------- - // Construire les props pour TomTomMap - // -------------------------------------------------- - // Marker du livreur (position courante) const driverMarkers: TomTomMarker[] = useMemo(() => { if (!lastCoords) return []; @@ -148,21 +129,42 @@ export default function DashboardScreen() { ]; }, [lastCoords, lastUpdate, colors.success]); - // Route TomTom - const tomtomRoute: TomTomRoute | null = useMemo(() => { - if (!routeInfo || routeInfo.coordinates.length === 0) return null; - return { coordinates: routeInfo.coordinates, color: "#4285F4" }; - }, [routeInfo]); + // -------------------------------------------------- + // TomTom Route calculation + // Geocode + calcul dans DashboardScreen pour récupérer routeInfo + // -------------------------------------------------- + const calcRoute = useCallback(async (address: string, targetRef?: RefObject) => { + const coords = lastCoordsRef.current; + if (!coords) { + pendingRouteAddress.current = address; + return; + } + pendingRouteAddress.current = address; + const ref = targetRef ?? mapRef; + if (!ref.current) return; - // Destination TomTom - const tomtomDestination: TomTomDestination | null = useMemo(() => { - if (!destinationCoords) return null; - return { - latitude: destinationCoords.latitude, - longitude: destinationCoords.longitude, - color: colors.danger, - }; - }, [destinationCoords, colors.danger]); + setRouteLoading(true); + try { + const origin = { latitude: coords.lat, longitude: coords.lng }; + const dest = await geocodeAddress(address); + if (!dest) { + setRouteLoading(false); + return; + } + const result = await calculateRoute(origin, dest); + if (result) { + setRouteInfo(result.route); + ref.current?.calcRoute(origin, dest); + } else { + setRouteInfo(null); + ref.current?.calcRoute(origin, dest); + } + } catch { + /* silent */ + } finally { + setRouteLoading(false); + } + }, []); // -------------------------------------------------- // Data @@ -245,64 +247,45 @@ export default function DashboardScreen() { (d) => d.status === "in_progress" || d.status === "en_route", ) || enriched.find((d) => d.status === "assigned"); - if (activeDelivery && activeDelivery.adresse && lastCoords) { + if (activeDelivery && activeDelivery.adresse) { calcRoute(activeDelivery.adresse); } } catch { /* ignore */ } setLoading(false); - }, [lastCoords]); + }, [calcRoute]); useEffect(() => { loadData(); }, [loadData]); + // Quand GPS devient disponible, rejouer la route en attente + useEffect(() => { + if (lastCoords && pendingRouteAddress.current) { + calcRoute(pendingRouteAddress.current); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [lastCoords]); + + // Quand le fullscreen s'ouvre, rejouer la route sur la carte fullscreen + useEffect(() => { + if (!mapFullscreen) return; + const addr = pendingRouteAddress.current; + const coords = lastCoordsRef.current; + if (!addr || !coords) return; + setTimeout(() => { + calcRoute(addr, fullscreenMapRef); + }, 1200); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mapFullscreen]); + const onRefresh = async () => { setRefreshing(true); await loadData(); setRefreshing(false); }; - // -------------------------------------------------- - // TomTom Route calculation - // -------------------------------------------------- - const calcRoute = useCallback( - async (address: string) => { - if (!lastCoords) return; - setRouteLoading(true); - try { - const dest = await geocodeAddress(address); - if (!dest) { - setRouteLoading(false); - return; - } - setDestinationCoords(dest); - - const result = await calculateRoute( - { latitude: lastCoords.lat, longitude: lastCoords.lng }, - dest, - ); - if (result) { - setRouteInfo(result.route); - setInstructions(result.instructions); - setCurrentInstructionIdx(0); - - // Fit la map pour afficher driver + destination - const coords = [ - { latitude: lastCoords.lat, longitude: lastCoords.lng }, - { latitude: dest.latitude, longitude: dest.longitude }, - ]; - mapRef.current?.fitToCoordinates(coords); - } - } catch { - /* silent */ - } - setRouteLoading(false); - }, - [lastCoords], - ); - // -------------------------------------------------- // Location tracking // -------------------------------------------------- @@ -327,18 +310,26 @@ export default function DashboardScreen() { !("coords" in loc) || !loc.coords ) { - console.log("GPS invalide — update ignoré"); return; } const coords = (loc as Location.LocationObject).coords; const { latitude, longitude } = coords; + const wasNull = !lastCoordsRef.current; + lastCoordsRef.current = { lat: latitude, lng: longitude }; setLastCoords({ lat: latitude, lng: longitude }); setLastUpdate(new Date()); await updateMyLocation(latitude, longitude); - } catch (err) { - console.log("GPS error:", err); + // Si c'est la première position GPS et qu'une adresse était en attente, rejouer + if (wasNull && pendingRouteAddress.current) { + const addr = pendingRouteAddress.current; + setTimeout(() => { + calcRoute(addr); + }, 1000); + } + } catch { + /* silent */ } finally { sendingRef.current = false; } @@ -435,9 +426,6 @@ export default function DashboardScreen() { const res = await updateDeliveryStatus(deliveryId, "livre", lat, lng); if (res.success) { showSuccess("Succès", "Livraison terminée"); - setRouteInfo(null); - setDestinationCoords(null); - setInstructions([]); loadData(); } else { showError("Erreur", res.error || "Erreur"); @@ -450,98 +438,6 @@ export default function DashboardScreen() { Linking.openURL(url); }; - // -------------------------------------------------- - // Render: Navigation instruction bar - // -------------------------------------------------- - const renderInstructionBar = () => { - if (instructions.length === 0 || !routeInfo) return null; - const current = instructions[currentInstructionIdx]; - const next = instructions[currentInstructionIdx + 1]; - const iconName = - maneuverIcons[current?.maneuver] || maneuverIcons.DEFAULT; - - return ( - - - - - - - - {current?.instruction || "Suivez l'itinéraire"} - - {current?.streetName && ( - - {current.streetName} - - )} - - - {current?.distance} - - - - {next && ( - - Puis - - - {next.instruction} - - - )} - - - - - - {routeInfo.distance} - - - - - - {routeInfo.duration} - - - setShowInstructions(!showInstructions)} - > - - - {showInstructions ? "Masquer" : "Étapes"} ( - {instructions.length}) - - - - - ); - }; - // -------------------------------------------------- // Render delivery card // -------------------------------------------------- @@ -713,23 +609,22 @@ export default function DashboardScreen() { // -------------------------------------------------- const renderHeader = () => ( - {/* Carte TomTom */} - {lastCoords ? ( - - + {/* Carte TomTom — toujours montée pour que le ref soit disponible */} + + - {/* GPS overlay */} + {/* Overlay GPS */} + {lastCoords ? ( )} - - {/* Bouton plein écran */} - setMapFullscreen(true)} - activeOpacity={0.7} - > + ) : ( + - - - {/* Indicateur calcul de route */} - {routeLoading && ( - - - Calcul de l'itinéraire... - - - )} - - ) : ( - - - - Récupération de la position GPS... - - - )} - - {/* Instructions navigation */} - {renderInstructionBar()} - - {/* Liste complète des étapes */} - {showInstructions && instructions.length > 0 && ( - - - - Étapes de l'itinéraire + + Récupération GPS... - setShowInstructions(false)} - > - - - - {instructions.map((inst, idx) => { - const icoName = - maneuverIcons[inst.maneuver] || - maneuverIcons.DEFAULT; - const isCurrent = idx === currentInstructionIdx; - const isPassed = idx < currentInstructionIdx; - return ( - - - - - - - {inst.instruction} - - {inst.streetName && ( - - {inst.streetName} - - )} - - - {inst.distance} - - - ); - })} - - - )} + )} + + {/* Overlay infos trajet */} + {routeInfo && !routeLoading && ( + + + + {routeInfo.duration} + + + + {routeInfo.distance} + + + )} + + {/* Bouton plein écran */} + setMapFullscreen(true)} + activeOpacity={0.7} + > + + + + {/* Indicateur calcul de route */} + {routeLoading && ( + + + Calcul de l'itinéraire... + + + )} + {/* Queue info */} {queue && queue.queue_size > 0 && ( @@ -1004,6 +828,28 @@ export default function DashboardScreen() { alignItems: "center", }, + routeInfoOverlay: { + position: "absolute", + top: spacing.s, + left: spacing.s, + flexDirection: "row", + gap: spacing.xs, + }, + routeInfoChip: { + flexDirection: "row", + alignItems: "center", + gap: 4, + backgroundColor: "rgba(0,0,0,0.7)", + paddingVertical: 4, + paddingHorizontal: spacing.s, + borderRadius: 12, + }, + routeInfoOverlayText: { + color: colors.white, + fontSize: fontSize.xs, + fontWeight: "700", + }, + // Instructions instructionBar: { backgroundColor: colors.bgSecondary, @@ -1069,20 +915,6 @@ export default function DashboardScreen() { borderTopWidth: 1, borderTopColor: colors.borderSubtle, }, - routeInfoChip: { - flexDirection: "row", - alignItems: "center", - gap: 4, - backgroundColor: colors.accent + "15", - paddingVertical: 4, - paddingHorizontal: spacing.s, - borderRadius: 12, - }, - routeInfoValue: { - color: colors.accent, - fontSize: fontSize.xs, - fontWeight: "600", - }, showStepsBtn: { flexDirection: "row", alignItems: "center", @@ -1410,20 +1242,17 @@ export default function DashboardScreen() { >