/**
* Install state for the App settings page.
*
* `beforeinstallprompt` fires once and early, and only if nobody has called
* preventDefault() on it — a listener registered when the panel mounts is
* already too late. So it lives here, at module scope, and main.js imports
* this module so the listener is armed on every start.
*/
import { computed, ref } from 'vue'
import { bridgeAvailable } from './nativeBridge.js'
function standalone() {
if (typeof window === 'undefined') return false
if (window.matchMedia?.('(display-mode: standalone)')?.matches) return true
return window.navigator?.standalone === true // iOS Safari
}
/** iPadOS reports itself as a Mac; the touch points give it away. */
export const isIos =
typeof navigator !== 'undefined' &&
(/iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1))
const deferred = ref(null)
const installed = ref(standalone())
if (typeof window !== 'undefined') {
window.addEventListener('beforeinstallprompt', (event) => {
// Keep the event instead of letting the browser show its own banner: the
// button on the App page replays it on a real user gesture.
event.preventDefault()
deferred.value = event
})
window.addEventListener('appinstalled', () => {
deferred.value = null
installed.value = true
})
window
.matchMedia?.('(display-mode: standalone)')
?.addEventListener?.('change', (event) => {
installed.value = event.matches
})
}
export function usePwa() {
/** 'android-app' | 'installed' | 'prompt' | 'manual' */
const state = computed(() => {
if (bridgeAvailable()) return 'android-app'
if (installed.value) return 'installed'
if (deferred.value) return 'prompt'
return 'manual'
})
/** Replay the captured prompt. Resolves to 'accepted'/'dismissed', or null. */
async function install() {
const event = deferred.value
if (!event) return null
event.prompt()
const choice = await event.userChoice
// Whatever the answer, the event is spent — never keep it for a second try.
deferred.value = null
if (choice?.outcome === 'accepted') installed.value = true
return choice?.outcome ?? null
}
return { state, install, isIos }
}