hooks/useGeolocation.ts

hook·mobile·3.6 KB · 117 lignes· Voir l'itinéraire
Annotation non disponible

Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.

2 exports

useGeolocationUseGeolocationReturn

Code source· typescript

/**
 * Sprint International Foundation V1 — 2026-05-17
 *
 * Hook autour de `expo-location` + `useGeoStore`.
 *
 * Pattern :
 *   1. Au mount : si la position en cache est encore fraîche (< 1h), on
 *      l'utilise sans demander la permission ni interroger le GPS.
 *   2. Sinon, `requestPermission()` doit être appelé explicitement (geste UX
 *      — bouton "Activer la géoloc" dans le futur banner Home V1.8).
 *   3. `getCurrentPosition()` interroge le GPS et alimente le store.
 *
 * V1 : pas de branchement UI. Hook prêt à l'emploi pour V1.8 quand on
 * branchera la home sur `/api/mobile/vitrines?near=`.
 */
import { useCallback, useEffect, useState } from 'react';
import * as Location from 'expo-location';
import { useGeoStore } from '@/store/geoStore';

export type UseGeolocationReturn = {
  lat: number | null;
  lng: number | null;
  accuracy: number | null;
  loading: boolean;
  error: string | null;
  hasPermission: boolean | null; // null = pas encore vérifié
  requestPermission: () => Promise<boolean>;
  getCurrentPosition: () => Promise<void>;
  clearPosition: () => void;
};

export function useGeolocation(): UseGeolocationReturn {
  const position = useGeoStore((s) => s.position);
  const setPosition = useGeoStore((s) => s.setPosition);
  const clearPosition = useGeoStore((s) => s.clearPosition);
  const isFresh = useGeoStore((s) => s.isFresh);

  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [hasPermission, setHasPermission] = useState<boolean | null>(null);

  // Vérifie le statut de permission au mount (sans rien demander à l'utilisateur)
  useEffect(() => {
    let alive = true;
    Location.getForegroundPermissionsAsync()
      .then((s) => {
        if (alive) setHasPermission(s.granted);
      })
      .catch(() => {
        if (alive) setHasPermission(false);
      });
    return () => {
      alive = false;
    };
  }, []);

  const requestPermission = useCallback(async (): Promise<boolean> => {
    try {
      const res = await Location.requestForegroundPermissionsAsync();
      setHasPermission(res.granted);
      if (!res.granted) {
        setError('Permission de géolocalisation refusée.');
      } else {
        setError(null);
      }
      return res.granted;
    } catch (e: any) {
      setError(e?.message || 'Erreur permission géoloc');
      setHasPermission(false);
      return false;
    }
  }, []);

  const getCurrentPosition = useCallback(async (): Promise<void> => {
    setError(null);
    // Si on a déjà un fix frais, on ne re-fetch pas le GPS
    if (isFresh()) return;
    setLoading(true);
    try {
      // S'assure d'avoir la permission (sans bloquer si déjà accordée)
      const status = await Location.getForegroundPermissionsAsync();
      if (!status.granted) {
        const granted = await requestPermission();
        if (!granted) {
          setLoading(false);
          return;
        }
      }
      const loc = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.Balanced,
      });
      setPosition({
        lat: loc.coords.latitude,
        lng: loc.coords.longitude,
        accuracy: loc.coords.accuracy ?? null,
        timestamp: Date.now(),
      });
    } catch (e: any) {
      setError(e?.message || 'Impossible de récupérer la position.');
    } finally {
      setLoading(false);
    }
  }, [isFresh, requestPermission, setPosition]);

  return {
    lat: position?.lat ?? null,
    lng: position?.lng ?? null,
    accuracy: position?.accuracy ?? null,
    loading,
    error,
    hasPermission,
    requestPermission,
    getCurrentPosition,
    clearPosition,
  };
}