import { useEffect, useRef, useState } from "react"; import { Platform, StyleSheet } from "react-native"; import MapView, { Marker, PROVIDER_DEFAULT } from "react-native-maps"; import MapViewDirections from "react-native-maps-directions"; import { icons } from "@/constants"; import { useFetch } from "@/lib/fetch"; import { tr } from "@/lib/i18n"; import { calculateDriverTimes, calculateRegion, generateMarkersFromData, } from "@/lib/map"; import { useTheme } from "@/lib/theme"; import { useDriverStore, useLocationStore, useServiceStore } from "@/store"; import type { Driver, MarkerData } from "@/types/type"; // react-native-maps sizes itself from a real style object, so give it explicit // dimensions rather than relying on percentage classNames resolving to 0. const styles = StyleSheet.create({ map: { ...StyleSheet.absoluteFillObject, borderRadius: 16 }, }); // "mutedStandard" is an Apple Maps type. Android's MapManager looks the value // up in a fixed table and unboxes the result into an int, so an unrecognised // name is a null Integer -> NullPointerException, and the map never draws. const MAP_TYPE = Platform.OS === "ios" ? "mutedStandard" : "standard"; // showsPointsOfInterest is iOS-only; on Android the same muting is done with a // style array, so both platforms get the same clean base map. const MUTED_POI_STYLE = [ { featureType: "poi", elementType: "labels", stylers: [{ visibility: "off" }], }, { featureType: "transit", elementType: "labels.icon", stylers: [{ visibility: "off" }], }, ]; export const Map = () => { const { userLatitude, userLongitude, destinationLatitude, destinationLongitude, } = useLocationStore(); const { service } = useServiceStore(); const { selectedDriver, setDrivers } = useDriverStore(); const { isDark } = useTheme(); // Online drivers of the selected service near the rider. Falls back to a // Beirut center when the rider's position isn't resolved yet so the map // still populates instead of sitting empty. const lat = userLatitude ?? 33.8938; const lng = userLongitude ?? 35.5018; const { data: drivers, error } = useFetch( `/(api)/driver/nearby?service=${service}&lat=${lat}&lng=${lng}`, ); const [markers, setMarkers] = useState([]); const mapRef = useRef(null); const region = calculateRegion({ userLatitude, userLongitude, destinationLatitude, destinationLongitude, }); // `initialRegion` is read once, at mount. The map mounts before the location // fix arrives, so it would sit on the Beirut fallback forever and never zoom // out to fit a destination the rider picks later. Animate on every real // change instead. Keyed on the coordinates so the repeated setUserLocation // from reverse geocoding (same coords, new address) doesn't yank the camera // back while the rider is panning. const regionKey = `${region.latitude},${region.longitude},${region.latitudeDelta},${region.longitudeDelta}`; const lastRegionKey = useRef(regionKey); useEffect(() => { if (lastRegionKey.current === regionKey) return; lastRegionKey.current = regionKey; mapRef.current?.animateToRegion(region, 500); // eslint-disable-next-line react-hooks/exhaustive-deps }, [regionKey]); useEffect(() => { if (Array.isArray(drivers)) { if (!userLatitude || !userLongitude) return; const newMarkers = generateMarkersFromData({ data: drivers, userLatitude, userLongitude, }); setMarkers(newMarkers); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [drivers, userLatitude, userLongitude]); useEffect(() => { if (markers.length > 0 && destinationLatitude && destinationLongitude) { calculateDriverTimes({ markers, userLatitude, userLongitude, destinationLatitude, destinationLongitude, service, }).then((driversWithTimes) => { setDrivers((driversWithTimes as MarkerData[]) ?? []); }); } }, [ markers, destinationLatitude, destinationLongitude, userLatitude, userLongitude, setDrivers, service, ]); // The map itself never waits on the driver list or the location fix: drivers // are an overlay, and calculateRegion falls back to Beirut without coords. // Previously either one failing replaced the whole map with a spinner or an // error line, which read as "the map didn't load". if (error) console.log("[MAP_DRIVERS]: ", error); return ( {markers.map((marker) => ( ))} {userLatitude && userLongitude && destinationLatitude && destinationLongitude && ( <> )} ); };