Files
localesp/src/hooks/usePwaInstall.js
T
edgar.friendly 257fb9c622
deploy-branch / deploy (push) Successful in 2m36s
deploy-branch / teardown (push) Skipped
refactor(ui): modulariza App.jsx y unifica branding y caché PWA
- Extrae App.jsx en módulos reutilizables: constants/ (categorias,
  provincias), utils/geo, styles/shared, hooks/usePwaInstall y
  components/ (StarRating, LocalCard, CampoUbicacion, BannerMovil,
  CategoriaBadge)
- AdminPanel reutiliza constantes y componentes compartidos y corrige
  la limpieza de suscripciones de Firebase al desmontar
- Añade logos de marca (logo-localesp.png + @2x) y regenera los iconos
  PWA con tamaños menores
- sw.js: sube la caché del shell a v2 e incluye los nuevos iconos y logos
2026-08-05 19:07:00 +02:00

73 lines
3.1 KiB
JavaScript

import { useCallback, useEffect, useState } from "react";
// Detecta si la web se está ejecutando ya como app instalada (modo standalone),
// tanto en Chrome/Android (display-mode: standalone) como en Safari/iOS
// (navigator.standalone) o dentro de una TWA de Android (referrer android-app://).
function calcularSiEsStandalone() {
if (typeof window === "undefined") return false;
const enModoStandalone = window.matchMedia?.("(display-mode: standalone)")?.matches ?? false;
const enIosStandalone = window.navigator?.standalone === true;
const enTwaAndroid = document.referrer?.startsWith("android-app://") ?? false;
return enModoStandalone || enIosStandalone || enTwaAndroid;
}
function detectarIos() {
if (typeof window === "undefined") return false;
return /iphone|ipad|ipod/i.test(window.navigator.userAgent) && !window.MSStream;
}
/**
* Hook que centraliza el estado de instalación de la PWA:
* - isInstalled: la app ya se está ejecutando en modo standalone (o el
* navegador confirma que está instalada mediante getInstalledRelatedApps).
* - canPromptInstall: el navegador ha ofrecido el evento nativo `beforeinstallprompt`,
* así que se puede lanzar el diálogo de instalación con promptInstall().
* - isIos: Safari/iOS no soporta beforeinstallprompt, así que ahí solo cabe
* mostrar instrucciones manuales ("Compartir → Añadir a pantalla de inicio").
*/
export default function usePwaInstall() {
const [installPromptEvent, setInstallPromptEvent] = useState(null);
const [isInstalled, setIsInstalled] = useState(calcularSiEsStandalone);
const [isIos] = useState(detectarIos);
useEffect(() => {
const alCapturarPrompt = (evento) => {
evento.preventDefault();
setInstallPromptEvent(evento);
};
const alInstalar = () => {
setInstallPromptEvent(null);
setIsInstalled(true);
};
window.addEventListener("beforeinstallprompt", alCapturarPrompt);
window.addEventListener("appinstalled", alInstalar);
const consultaDisplayMode = window.matchMedia?.("(display-mode: standalone)");
const alCambiarDisplayMode = () => setIsInstalled(calcularSiEsStandalone());
consultaDisplayMode?.addEventListener?.("change", alCambiarDisplayMode);
// Progressive enhancement: en navegadores compatibles (Chrome/Android),
// confirma si la PWA ya está instalada aunque se esté viendo en una pestaña normal.
navigator.getInstalledRelatedApps?.()
.then((apps) => { if (apps.length > 0) setIsInstalled(true); })
.catch(() => {});
return () => {
window.removeEventListener("beforeinstallprompt", alCapturarPrompt);
window.removeEventListener("appinstalled", alInstalar);
consultaDisplayMode?.removeEventListener?.("change", alCambiarDisplayMode);
};
}, []);
const promptInstall = useCallback(async () => {
if (!installPromptEvent) return "unavailable";
installPromptEvent.prompt();
const { outcome } = await installPromptEvent.userChoice;
setInstallPromptEvent(null);
return outcome; // "accepted" | "dismissed"
}, [installPromptEvent]);
return { isInstalled, canPromptInstall: Boolean(installPromptEvent), promptInstall, isIos };
}