Compare commits

..

No commits in common. "113d20efe10ebdfdba6954fe407d8243eaafc34d" and "59ccc13836cc82c16770a7b3ef9317a28b5c220a" have entirely different histories.

10 changed files with 57 additions and 54 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -10,7 +10,6 @@ import markerRedDisabled from "~/assets/img/marker-red-disabled.png";
import markerYellowDisabled from "~/assets/img/marker-yellow-disabled.png"; import markerYellowDisabled from "~/assets/img/marker-yellow-disabled.png";
import markerGreenDisabled from "~/assets/img/marker-green-disabled.png"; import markerGreenDisabled from "~/assets/img/marker-green-disabled.png";
import markerOrigin from "~/assets/img/marker-origin.png"; import markerOrigin from "~/assets/img/marker-origin.png";
import markerDae from "~/assets/img/marker-dae.png";
const images = { const images = {
red: markerRed, red: markerRed,
@ -21,7 +20,6 @@ const images = {
yellowDisabled: markerYellowDisabled, yellowDisabled: markerYellowDisabled,
greenDisabled: markerGreenDisabled, greenDisabled: markerGreenDisabled,
origin: markerOrigin, origin: markerOrigin,
dae: markerDae,
}; };
export default function FeatureImages() { export default function FeatureImages() {

View file

@ -17,10 +17,11 @@ const iconStyle = {
iconSize: 0.5, iconSize: 0.5,
}; };
const defibStyle = { const defibCircleStyle = {
iconImage: "dae", circleRadius: 8,
iconSize: 0.5, circleColor: ["get", "defibColor"],
iconAllowOverlap: true, circleStrokeColor: "#FFFFFF",
circleStrokeWidth: 2,
}; };
const useStyles = createStyles(({ theme: { colors } }) => ({ const useStyles = createStyles(({ theme: { colors } }) => ({
@ -65,12 +66,12 @@ export default function ShapePoints({ shape, children, ...shapeSourceProps }) {
/> />
{/* Defibrillators (DAE) separate layer (non-clustered) */} {/* Defibrillators (DAE) separate layer (non-clustered) */}
<Maplibre.SymbolLayer <Maplibre.CircleLayer
filter={["==", ["get", "isDefib"], true]} filter={["==", ["get", "isDefib"], true]}
key="points-defib" key="points-defib"
id="points-defib" id="points-defib"
aboveLayerID="points-origin" aboveLayerID="points-origin"
style={defibStyle} style={defibCircleStyle}
/> />
{children} {children}

View file

@ -7,8 +7,6 @@ import {
} from "@expo/vector-icons"; } from "@expo/vector-icons";
import { useNavigation, CommonActions } from "@react-navigation/native"; import { useNavigation, CommonActions } from "@react-navigation/native";
import FontAwesome6 from "@expo/vector-icons/FontAwesome6";
import DrawerContent from "~/navigation/DrawerNav/DrawerContent"; import DrawerContent from "~/navigation/DrawerNav/DrawerContent";
import { useDrawerState } from "~/navigation/Context"; import { useDrawerState } from "~/navigation/Context";
import getDefaultDrawerWidth from "~/navigation/DrawerNav/getDefaultDrawerWidth"; import getDefaultDrawerWidth from "~/navigation/DrawerNav/getDefaultDrawerWidth";
@ -377,16 +375,11 @@ export default React.memo(function DrawerNav() {
options={{ options={{
drawerLabel: "Défibrillateurs", drawerLabel: "Défibrillateurs",
drawerIcon: ({ focused }) => ( drawerIcon: ({ focused }) => (
<FontAwesome6 <MaterialCommunityIcons
name="heart-circle-bolt" name="heart-pulse"
{...iconProps} {...iconProps}
{...(focused ? iconFocusedProps : {})} {...(focused ? iconFocusedProps : {})}
/> />
// <MaterialCommunityIcons
// name="heart-flash"
// {...iconProps}
// {...(focused ? iconFocusedProps : {})}
// />
), ),
unmountOnBlur: true, unmountOnBlur: true,
}} }}

View file

@ -130,6 +130,7 @@ export default function useFeatures({
id, id,
properties: { properties: {
id, id,
defibColor: "#4CAF50",
defib, defib,
isDefib: true, isDefib: true,
}, },

View file

@ -22,6 +22,7 @@ import IconTouchTarget from "~/components/IconTouchTarget";
import { useTheme } from "~/theme"; import { useTheme } from "~/theme";
import { useDefibsState, useNetworkState } from "~/stores"; import { useDefibsState, useNetworkState } from "~/stores";
import useLocation from "~/hooks/useLocation"; import useLocation from "~/hooks/useLocation";
import { getDefibAvailability } from "~/utils/dae/getDefibAvailability";
import { import {
osmProfileUrl, osmProfileUrl,
profileDefaultModes, profileDefaultModes,
@ -32,7 +33,6 @@ import {
setA11yFocusAfterInteractions, setA11yFocusAfterInteractions,
} from "~/lib/a11y"; } from "~/lib/a11y";
import markerDae from "~/assets/img/marker-dae.png";
import RoutingSteps from "~/scenes/AlertCurMap/RoutingSteps"; import RoutingSteps from "~/scenes/AlertCurMap/RoutingSteps";
import MapHeadRouting from "~/scenes/AlertCurMap/MapHeadRouting"; import MapHeadRouting from "~/scenes/AlertCurMap/MapHeadRouting";
@ -42,6 +42,11 @@ import {
STATE_CALCULATING_LOADING, STATE_CALCULATING_LOADING,
} from "~/scenes/AlertCurMap/constants"; } from "~/scenes/AlertCurMap/constants";
const STATUS_COLORS = {
open: "#4CAF50",
closed: "#F44336",
unknown: "#9E9E9E",
};
export default React.memo(function DAEItemCarte() { export default React.memo(function DAEItemCarte() {
const { colors } = useTheme(); const { colors } = useTheme();
@ -201,6 +206,10 @@ export default React.memo(function DAEItemCarte() {
// Defib marker GeoJSON // Defib marker GeoJSON
const defibGeoJSON = useMemo(() => { const defibGeoJSON = useMemo(() => {
if (!hasDefibCoords) return null; if (!hasDefibCoords) return null;
const { status } = getDefibAvailability(
defib.horaires_std,
defib.disponible_24h,
);
return { return {
type: "FeatureCollection", type: "FeatureCollection",
features: [ features: [
@ -213,6 +222,7 @@ export default React.memo(function DAEItemCarte() {
properties: { properties: {
id: defib.id, id: defib.id,
nom: defib.nom || "Défibrillateur", nom: defib.nom || "Défibrillateur",
color: STATUS_COLORS[status],
}, },
}, },
], ],
@ -360,17 +370,22 @@ export default React.memo(function DAEItemCarte() {
</Maplibre.ShapeSource> </Maplibre.ShapeSource>
)} )}
<Maplibre.Images images={{ dae: markerDae }} />
{/* Defib marker */} {/* Defib marker */}
{defibGeoJSON && ( {defibGeoJSON && (
<Maplibre.ShapeSource id="defibItemSource" shape={defibGeoJSON}> <Maplibre.ShapeSource id="defibItemSource" shape={defibGeoJSON}>
<Maplibre.SymbolLayer <Maplibre.CircleLayer
id="defibItemSymbol" id="defibItemCircle"
style={{
circleRadius: 10,
circleColor: ["get", "color"],
circleStrokeColor: "#FFFFFF",
circleStrokeWidth: 2.5,
}}
/>
<Maplibre.SymbolLayer
id="defibItemLabel"
aboveLayerID="defibItemCircle"
style={{ style={{
iconImage: "dae",
iconSize: 0.5,
iconAllowOverlap: true,
textField: ["get", "nom"], textField: ["get", "nom"],
textSize: 12, textSize: 12,
textOffset: [0, 1.8], textOffset: [0, 1.8],

View file

@ -20,15 +20,21 @@ import Text from "~/components/Text";
import Loader from "~/components/Loader"; import Loader from "~/components/Loader";
import { useTheme } from "~/theme"; import { useTheme } from "~/theme";
import { defibsActions } from "~/stores"; import { defibsActions } from "~/stores";
import { getDefibAvailability } from "~/utils/dae/getDefibAvailability";
import markerDae from "~/assets/img/marker-dae.png";
import useNearbyDefibs from "./useNearbyDefibs"; import useNearbyDefibs from "./useNearbyDefibs";
const STATUS_COLORS = {
open: "#4CAF50",
closed: "#F44336",
unknown: "#9E9E9E",
};
function defibsToGeoJSON(defibs) { function defibsToGeoJSON(defibs) {
return { return {
type: "FeatureCollection", type: "FeatureCollection",
features: defibs.map((d) => { features: defibs.map((d) => {
const { status } = getDefibAvailability(d.horaires_std, d.disponible_24h);
return { return {
type: "Feature", type: "Feature",
id: d.id, id: d.id,
@ -39,6 +45,8 @@ function defibsToGeoJSON(defibs) {
properties: { properties: {
id: d.id, id: d.id,
nom: d.nom || "Défibrillateur", nom: d.nom || "Défibrillateur",
status,
color: STATUS_COLORS[status],
}, },
}; };
}), }),
@ -168,19 +176,27 @@ export default React.memo(function DAEListCarte() {
detached={false} detached={false}
/> />
<Maplibre.Images images={{ dae: markerDae }} />
{geoJSON.features.length > 0 && ( {geoJSON.features.length > 0 && (
<Maplibre.ShapeSource <Maplibre.ShapeSource
id="defibSource" id="defibSource"
shape={geoJSON} shape={geoJSON}
onPress={onMarkerPress} onPress={onMarkerPress}
> >
<Maplibre.CircleLayer
id="defibCircleLayer"
style={{
circleRadius: 8,
circleColor: ["get", "color"],
circleStrokeColor: "#FFFFFF",
circleStrokeWidth: 2,
}}
/>
<Maplibre.SymbolLayer <Maplibre.SymbolLayer
id="defibSymbolLayer" id="defibSymbolLayer"
aboveLayerID="defibCircleLayer"
style={{ style={{
iconImage: "dae", iconImage: "heart-pulse",
iconSize: 0.5, iconSize: 0.6,
iconAllowOverlap: true, iconAllowOverlap: true,
textField: ["get", "nom"], textField: ["get", "nom"],
textSize: 11, textSize: 11,

View file

@ -70,7 +70,7 @@ function EmptyError({ error, onRetry }) {
]} ]}
> >
Impossible de charger les défibrillateurs.{"\n"} Impossible de charger les défibrillateurs.{"\n"}
Veuillez réessayer ultérieurement. {error?.message || "Veuillez réessayer."}
</Text> </Text>
{onRetry && ( {onRetry && (
<Button mode="contained" onPress={onRetry} style={styles.retryButton}> <Button mode="contained" onPress={onRetry} style={styles.retryButton}>

View file

@ -15,27 +15,6 @@ const DEFAULT_LIMIT = 200;
const AUTO_DISMISS_DELAY = 4_000; const AUTO_DISMISS_DELAY = 4_000;
/**
* Convert a technical DAE update error into a user-friendly French message.
* The raw technical details are already logged via console.warn in updateDaeDb.
*/
function userFriendlyDaeError(error) {
const msg = error?.message || "";
if (msg.includes("Network") || msg.includes("network")) {
return "Impossible de contacter le serveur. Vérifiez votre connexion internet et réessayez.";
}
if (msg.includes("HTTP")) {
return "Le serveur a rencontré un problème. Veuillez réessayer ultérieurement.";
}
if (msg.includes("Download failed") || msg.includes("file is empty")) {
return "Le téléchargement a échoué. Veuillez réessayer.";
}
if (msg.includes("failed validation")) {
return "Le fichier téléchargé est corrompu. Veuillez réessayer.";
}
return "La mise à jour a échoué. Veuillez réessayer ultérieurement.";
}
export default createAtom(({ merge, reset }) => { export default createAtom(({ merge, reset }) => {
const actions = { const actions = {
reset, reset,
@ -174,7 +153,7 @@ export default createAtom(({ merge, reset }) => {
if (!result.success) { if (!result.success) {
merge({ merge({
daeUpdateState: "error", daeUpdateState: "error",
daeUpdateError: userFriendlyDaeError(result.error), daeUpdateError: result.error?.message || "Erreur inconnue",
}); });
return; return;
} }