as-app/src/scenes/DAEList/Carte.js

229 lines
5.9 KiB
JavaScript

import React, {
useCallback,
useMemo,
useRef,
useState,
useEffect,
} from "react";
import { View, StyleSheet } from "react-native";
import Maplibre from "@maplibre/maplibre-react-native";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native";
import MapView from "~/containers/Map/MapView";
import Camera from "~/containers/Map/Camera";
import LastKnownLocationMarker from "~/containers/Map/LastKnownLocationMarker";
import { BoundType, DEFAULT_ZOOM_LEVEL } from "~/containers/Map/constants";
import StepZoomButtonGroup from "~/containers/Map/StepZoomButtonGroup";
import Text from "~/components/Text";
import Loader from "~/components/Loader";
import { useTheme } from "~/theme";
import { defibsActions } from "~/stores";
import { getDefibAvailability } from "~/utils/dae/getDefibAvailability";
import useNearbyDefibs from "./useNearbyDefibs";
const STATUS_COLORS = {
open: "#4CAF50",
closed: "#F44336",
unknown: "#9E9E9E",
};
function defibsToGeoJSON(defibs) {
return {
type: "FeatureCollection",
features: defibs.map((d) => {
const { status } = getDefibAvailability(d.horaires_std, d.disponible_24h);
return {
type: "Feature",
id: d.id,
geometry: {
type: "Point",
coordinates: [d.longitude, d.latitude],
},
properties: {
id: d.id,
nom: d.nom || "Défibrillateur",
status,
color: STATUS_COLORS[status],
},
};
}),
};
}
function EmptyNoLocation() {
const { colors } = useTheme();
return (
<View style={styles.emptyContainer}>
<MaterialCommunityIcons
name="crosshairs-off"
size={56}
color={colors.onSurfaceVariant || colors.grey}
style={styles.emptyIcon}
/>
<Text style={styles.emptyTitle}>Localisation indisponible</Text>
<Text
style={[
styles.emptyText,
{ color: colors.onSurfaceVariant || colors.grey },
]}
>
Activez la géolocalisation pour afficher les défibrillateurs sur la
carte.
</Text>
</View>
);
}
export default React.memo(function DAEListCarte() {
const { colors } = useTheme();
const navigation = useNavigation();
const {
defibs,
loading,
noLocation,
hasLocation,
isLastKnown,
lastKnownTimestamp,
coords,
} = useNearbyDefibs();
const mapRef = useRef();
const cameraRef = useRef();
const [cameraKey, setCameraKey] = useState(1);
const refreshCamera = useCallback(() => {
setCameraKey(`${Date.now()}`);
}, []);
const hasCoords =
coords && coords.latitude !== null && coords.longitude !== null;
// Camera state — simple follow user
const [followUserLocation] = useState(true);
const [followUserMode] = useState(Maplibre.UserTrackingMode.Follow);
const [zoomLevel, setZoomLevel] = useState(DEFAULT_ZOOM_LEVEL);
const geoJSON = useMemo(() => defibsToGeoJSON(defibs), [defibs]);
const onMarkerPress = useCallback(
(e) => {
const feature = e?.features?.[0];
if (!feature) return;
const defibId = feature.properties?.id;
const defib = defibs.find((d) => d.id === defibId);
if (defib) {
defibsActions.setSelectedDefib(defib);
navigation.navigate("DAEItem");
}
},
[defibs, navigation],
);
if (noLocation && !hasLocation) {
return <EmptyNoLocation />;
}
if (loading && defibs.length === 0 && !hasCoords) {
return <Loader />;
}
return (
<View style={styles.container}>
<MapView
mapRef={mapRef}
compassViewPosition={1}
compassViewMargin={{ x: 10, y: 10 }}
>
<Camera
cameraKey={cameraKey}
setCameraKey={setCameraKey}
refreshCamera={refreshCamera}
cameraRef={cameraRef}
followUserLocation={followUserLocation}
followUserMode={followUserMode}
followPitch={0}
zoomLevel={zoomLevel}
bounds={null}
detached={false}
/>
{geoJSON.features.length > 0 && (
<Maplibre.ShapeSource
id="defibSource"
shape={geoJSON}
onPress={onMarkerPress}
>
<Maplibre.CircleLayer
id="defibCircleLayer"
style={{
circleRadius: 8,
circleColor: ["get", "color"],
circleStrokeColor: "#FFFFFF",
circleStrokeWidth: 2,
}}
/>
<Maplibre.SymbolLayer
id="defibSymbolLayer"
aboveLayerID="defibCircleLayer"
style={{
iconImage: "heart-pulse",
iconSize: 0.6,
iconAllowOverlap: true,
textField: ["get", "nom"],
textSize: 11,
textOffset: [0, 1.5],
textAnchor: "top",
textMaxWidth: 12,
textColor: colors.onSurface,
textHaloColor: colors.surface,
textHaloWidth: 1,
textOptional: true,
}}
/>
</Maplibre.ShapeSource>
)}
{isLastKnown && hasCoords ? (
<LastKnownLocationMarker
coordinates={coords}
timestamp={lastKnownTimestamp}
id="lastKnownLocation_daeList"
/>
) : (
<Maplibre.UserLocation visible showsUserHeadingIndicator />
)}
</MapView>
<StepZoomButtonGroup mapRef={mapRef} setZoomLevel={setZoomLevel} />
</View>
);
});
const styles = StyleSheet.create({
container: {
flex: 1,
},
emptyContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 32,
},
emptyIcon: {
marginBottom: 16,
},
emptyTitle: {
fontSize: 18,
fontWeight: "600",
textAlign: "center",
marginBottom: 8,
},
emptyText: {
fontSize: 14,
textAlign: "center",
lineHeight: 20,
},
});