chore: fix gps
This commit is contained in:
@@ -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<DeliveryPerson | null>(null);
|
||||
const [routeInfo, setRouteInfo] = useState<RouteInfo | null>(null);
|
||||
const [destinationCoords, setDestinationCoords] = useState<LatLng | null>(
|
||||
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:
|
||||
|
||||
@@ -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<Date | null>(null);
|
||||
const locationInterval = useRef<ReturnType<typeof setInterval> | null>(
|
||||
null,
|
||||
@@ -102,17 +95,9 @@ export default function DashboardScreen() {
|
||||
const fullscreenMapRef = useRef<TomTomMapRef>(null);
|
||||
const [mapFullscreen, setMapFullscreen] = useState(false);
|
||||
|
||||
// TomTom routing
|
||||
const [routeInfo, setRouteInfo] = useState<RouteInfo | null>(null);
|
||||
const [instructions, setInstructions] = useState<NavigationInstruction[]>(
|
||||
[],
|
||||
);
|
||||
const [destinationCoords, setDestinationCoords] = useState<LatLng | null>(
|
||||
null,
|
||||
);
|
||||
const [showInstructions, setShowInstructions] = useState(false);
|
||||
const [currentInstructionIdx, setCurrentInstructionIdx] = useState(0);
|
||||
const [routeLoading, setRouteLoading] = useState(false);
|
||||
const [routeInfo, setRouteInfo] = useState<RouteInfo | null>(null);
|
||||
const pendingRouteAddress = useRef<string | null>(null);
|
||||
const { alert, showError, showSuccess, hideAlert } = useAlert();
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = 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<TomTomMapRef | null>) => {
|
||||
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 (
|
||||
<View style={styles.instructionBar}>
|
||||
<View style={styles.currentInstruction}>
|
||||
<View style={styles.instructionIconBox}>
|
||||
<Ionicons
|
||||
name={iconName as any}
|
||||
size={22}
|
||||
color={colors.white}
|
||||
/>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.instructionText} numberOfLines={2}>
|
||||
{current?.instruction || "Suivez l'itinéraire"}
|
||||
</Text>
|
||||
{current?.streetName && (
|
||||
<Text style={styles.instructionStreet}>
|
||||
{current.streetName}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.instructionDist}>
|
||||
{current?.distance}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{next && (
|
||||
<View style={styles.nextInstruction}>
|
||||
<Text style={styles.nextLabel}>Puis</Text>
|
||||
<Ionicons
|
||||
name={
|
||||
(maneuverIcons[next.maneuver] ||
|
||||
maneuverIcons.DEFAULT) as any
|
||||
}
|
||||
size={14}
|
||||
color={colors.textMuted}
|
||||
/>
|
||||
<Text style={styles.nextText} numberOfLines={1}>
|
||||
{next.instruction}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.routeSummary}>
|
||||
<View style={styles.routeInfoChip}>
|
||||
<Ionicons
|
||||
name="speedometer-outline"
|
||||
size={14}
|
||||
color={colors.accent}
|
||||
/>
|
||||
<Text style={styles.routeInfoValue}>
|
||||
{routeInfo.distance}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.routeInfoChip}>
|
||||
<Ionicons
|
||||
name="time-outline"
|
||||
size={14}
|
||||
color={colors.accent}
|
||||
/>
|
||||
<Text style={styles.routeInfoValue}>
|
||||
{routeInfo.duration}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.showStepsBtn}
|
||||
onPress={() => setShowInstructions(!showInstructions)}
|
||||
>
|
||||
<Ionicons
|
||||
name="list-outline"
|
||||
size={14}
|
||||
color={colors.white}
|
||||
/>
|
||||
<Text style={styles.showStepsBtnText}>
|
||||
{showInstructions ? "Masquer" : "Étapes"} (
|
||||
{instructions.length})
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// --------------------------------------------------
|
||||
// Render delivery card
|
||||
// --------------------------------------------------
|
||||
@@ -713,23 +609,22 @@ export default function DashboardScreen() {
|
||||
// --------------------------------------------------
|
||||
const renderHeader = () => (
|
||||
<View>
|
||||
{/* Carte TomTom */}
|
||||
{lastCoords ? (
|
||||
<View style={styles.mapContainer}>
|
||||
<TomTomMap
|
||||
ref={mapRef}
|
||||
style={styles.map}
|
||||
markers={driverMarkers}
|
||||
route={tomtomRoute}
|
||||
destination={tomtomDestination}
|
||||
initialCenter={{
|
||||
latitude: lastCoords.lat,
|
||||
longitude: lastCoords.lng,
|
||||
}}
|
||||
initialZoom={14}
|
||||
/>
|
||||
{/* Carte TomTom — toujours montée pour que le ref soit disponible */}
|
||||
<View style={styles.mapContainer}>
|
||||
<TomTomMap
|
||||
ref={mapRef}
|
||||
style={styles.map}
|
||||
markers={driverMarkers}
|
||||
initialCenter={
|
||||
lastCoords
|
||||
? { latitude: lastCoords.lat, longitude: lastCoords.lng }
|
||||
: { latitude: 48.8566, longitude: 2.3522 }
|
||||
}
|
||||
initialZoom={14}
|
||||
/>
|
||||
|
||||
{/* GPS overlay */}
|
||||
{/* Overlay GPS */}
|
||||
{lastCoords ? (
|
||||
<View style={styles.gpsOverlay}>
|
||||
<View
|
||||
style={[
|
||||
@@ -754,126 +649,55 @@ export default function DashboardScreen() {
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Bouton plein écran */}
|
||||
<TouchableOpacity
|
||||
style={styles.expandBtn}
|
||||
onPress={() => setMapFullscreen(true)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
) : (
|
||||
<View style={styles.gpsOverlay}>
|
||||
<Ionicons
|
||||
name="expand-outline"
|
||||
size={20}
|
||||
color={colors.white}
|
||||
name="location-outline"
|
||||
size={16}
|
||||
color={colors.textMuted}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Indicateur calcul de route */}
|
||||
{routeLoading && (
|
||||
<View style={styles.routeLoadingOverlay}>
|
||||
<Text style={styles.routeLoadingText}>
|
||||
Calcul de l'itinéraire...
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.noMapBox}>
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
size={32}
|
||||
color={colors.textMuted}
|
||||
/>
|
||||
<Text style={styles.noMapText}>
|
||||
Récupération de la position GPS...
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Instructions navigation */}
|
||||
{renderInstructionBar()}
|
||||
|
||||
{/* Liste complète des étapes */}
|
||||
{showInstructions && instructions.length > 0 && (
|
||||
<View style={styles.allInstructionsBox}>
|
||||
<View style={styles.allInstructionsHeader}>
|
||||
<Text style={styles.allInstructionsTitle}>
|
||||
Étapes de l'itinéraire
|
||||
<Text style={styles.gpsOverlayText}>
|
||||
Récupération GPS...
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowInstructions(false)}
|
||||
>
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={20}
|
||||
color={colors.textMuted}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView style={{ maxHeight: 250 }} nestedScrollEnabled>
|
||||
{instructions.map((inst, idx) => {
|
||||
const icoName =
|
||||
maneuverIcons[inst.maneuver] ||
|
||||
maneuverIcons.DEFAULT;
|
||||
const isCurrent = idx === currentInstructionIdx;
|
||||
const isPassed = idx < currentInstructionIdx;
|
||||
return (
|
||||
<View
|
||||
key={idx}
|
||||
style={[
|
||||
styles.stepRow,
|
||||
isCurrent && styles.stepRowActive,
|
||||
isPassed && styles.stepRowPassed,
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.stepIcon,
|
||||
isCurrent && {
|
||||
backgroundColor:
|
||||
colors.accent + "30",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name={icoName as any}
|
||||
size={16}
|
||||
color={
|
||||
isCurrent
|
||||
? colors.accent
|
||||
: isPassed
|
||||
? colors.textMuted
|
||||
: colors.textSecondary
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text
|
||||
style={[
|
||||
styles.stepText,
|
||||
isPassed && {
|
||||
color: colors.textMuted,
|
||||
},
|
||||
]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{inst.instruction}
|
||||
</Text>
|
||||
{inst.streetName && (
|
||||
<Text style={styles.stepStreet}>
|
||||
{inst.streetName}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.stepDist}>
|
||||
{inst.distance}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Overlay infos trajet */}
|
||||
{routeInfo && !routeLoading && (
|
||||
<View style={styles.routeInfoOverlay}>
|
||||
<View style={styles.routeInfoChip}>
|
||||
<Ionicons name="time-outline" size={13} color={colors.white} />
|
||||
<Text style={styles.routeInfoOverlayText}>{routeInfo.duration}</Text>
|
||||
</View>
|
||||
<View style={styles.routeInfoChip}>
|
||||
<Ionicons name="navigate-outline" size={13} color={colors.white} />
|
||||
<Text style={styles.routeInfoOverlayText}>{routeInfo.distance}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Bouton plein écran */}
|
||||
<TouchableOpacity
|
||||
style={styles.expandBtn}
|
||||
onPress={() => setMapFullscreen(true)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons
|
||||
name="expand-outline"
|
||||
size={20}
|
||||
color={colors.white}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Indicateur calcul de route */}
|
||||
{routeLoading && (
|
||||
<View style={styles.routeLoadingOverlay}>
|
||||
<Text style={styles.routeLoadingText}>
|
||||
Calcul de l'itinéraire...
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 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() {
|
||||
>
|
||||
<StatusBar hidden={mapFullscreen} />
|
||||
<View style={styles.fullscreenContainer}>
|
||||
{lastCoords && (
|
||||
<TomTomMap
|
||||
ref={fullscreenMapRef}
|
||||
style={styles.fullscreenMap}
|
||||
markers={driverMarkers}
|
||||
route={tomtomRoute}
|
||||
destination={tomtomDestination}
|
||||
initialCenter={{
|
||||
latitude: lastCoords.lat,
|
||||
longitude: lastCoords.lng,
|
||||
}}
|
||||
initialZoom={15}
|
||||
/>
|
||||
)}
|
||||
<TomTomMap
|
||||
ref={fullscreenMapRef}
|
||||
style={styles.fullscreenMap}
|
||||
markers={driverMarkers}
|
||||
initialCenter={
|
||||
lastCoords
|
||||
? { latitude: lastCoords.lat, longitude: lastCoords.lng }
|
||||
: { latitude: 48.8566, longitude: 2.3522 }
|
||||
}
|
||||
initialZoom={15}
|
||||
/>
|
||||
|
||||
{/* Top bar */}
|
||||
<View style={styles.fullscreenTopBar}>
|
||||
@@ -1438,54 +1267,45 @@ export default function DashboardScreen() {
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.fullscreenTitle}>
|
||||
{routeInfo
|
||||
? `${routeInfo.distance} · ${routeInfo.duration}`
|
||||
: "GPS en direct"}
|
||||
{pendingRouteAddress.current ? "Itinéraire en cours" : "GPS en direct"}
|
||||
</Text>
|
||||
<View style={{ width: 40 }} />
|
||||
</View>
|
||||
|
||||
{/* Instruction bar en plein écran */}
|
||||
{instructions.length > 0 && (
|
||||
<View style={styles.fullscreenInstructionBar}>
|
||||
<View style={styles.instructionIconBox}>
|
||||
<Ionicons
|
||||
name={
|
||||
(maneuverIcons[
|
||||
instructions[currentInstructionIdx]
|
||||
?.maneuver
|
||||
] || maneuverIcons.DEFAULT) as any
|
||||
}
|
||||
size={22}
|
||||
color={colors.white}
|
||||
/>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text
|
||||
style={styles.instructionText}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{instructions[currentInstructionIdx]
|
||||
?.instruction || "Suivez l'itinéraire"}
|
||||
</Text>
|
||||
{instructions[currentInstructionIdx]
|
||||
?.streetName && (
|
||||
<Text style={styles.instructionStreet}>
|
||||
{
|
||||
instructions[currentInstructionIdx]
|
||||
.streetName
|
||||
}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.instructionDist}>
|
||||
{instructions[currentInstructionIdx]?.distance}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Bottom info */}
|
||||
<View style={styles.fullscreenBottomBar}>
|
||||
{/* Adresse de destination */}
|
||||
{pendingRouteAddress.current && (
|
||||
<View style={styles.fullscreenInfoRow}>
|
||||
<Ionicons
|
||||
name="location"
|
||||
size={16}
|
||||
color={colors.danger}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.fullscreenInfoText, { flex: 1 }]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{pendingRouteAddress.current}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Infos trajet */}
|
||||
{routeInfo && (
|
||||
<View style={styles.fullscreenInfoRow}>
|
||||
<View style={styles.routeInfoChip}>
|
||||
<Ionicons name="time-outline" size={14} color={colors.white} />
|
||||
<Text style={styles.routeInfoOverlayText}>{routeInfo.duration}</Text>
|
||||
</View>
|
||||
<View style={[styles.routeInfoChip, { marginLeft: spacing.s }]}>
|
||||
<Ionicons name="navigate-outline" size={14} color={colors.white} />
|
||||
<Text style={styles.routeInfoOverlayText}>{routeInfo.distance}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* GPS status */}
|
||||
<View style={styles.fullscreenInfoRow}>
|
||||
<View
|
||||
style={[
|
||||
@@ -1500,41 +1320,25 @@ export default function DashboardScreen() {
|
||||
<Text style={styles.fullscreenInfoText}>
|
||||
GPS {locationEnabled ? "actif" : "inactif"}
|
||||
</Text>
|
||||
</View>
|
||||
{lastCoords && (
|
||||
<Text style={styles.fullscreenCoords}>
|
||||
{lastCoords.lat.toFixed(6)},{" "}
|
||||
{lastCoords.lng.toFixed(6)}
|
||||
</Text>
|
||||
)}
|
||||
{status && (
|
||||
<View
|
||||
style={[
|
||||
styles.fullscreenStatusBadge,
|
||||
{
|
||||
backgroundColor:
|
||||
STATUS_COLORS[status.status] + "30",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.dot,
|
||||
{
|
||||
backgroundColor:
|
||||
STATUS_COLORS[status.status],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.fullscreenStatusText,
|
||||
{ color: STATUS_COLORS[status.status] },
|
||||
]}
|
||||
>
|
||||
{STATUS_LABELS[status.status]}
|
||||
{lastCoords && (
|
||||
<Text style={[styles.fullscreenCoords, { marginLeft: 8 }]}>
|
||||
{lastCoords.lat.toFixed(4)}, {lastCoords.lng.toFixed(4)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Bouton recalculer */}
|
||||
{pendingRouteAddress.current && lastCoords && (
|
||||
<TouchableOpacity
|
||||
style={styles.calcRouteBtn}
|
||||
onPress={() => calcRoute(pendingRouteAddress.current!, fullscreenMapRef)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name="navigate" size={16} color={colors.white} />
|
||||
<Text style={styles.calcRouteBtnText}>
|
||||
Recalculer l'itinéraire
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user