88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
|
|
/**
|
|||
|
|
* Tarayıcının "uygulamayı yükle" akışı (PWA install).
|
|||
|
|
*
|
|||
|
|
* `beforeinstallprompt` sayfa yüklenirken erken tetiklenir; React ağacı mount
|
|||
|
|
* olmadan yakalanmazsa olay kaybolur ve buton hiç görünmez. Bu yüzden modül
|
|||
|
|
* import edilir edilmez dinleyiciler kurulur (dosya ~1 KB, entry'de kalması
|
|||
|
|
* sorun değil).
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
type InstallPromptEvent = Event & {
|
|||
|
|
prompt: () => Promise<void>
|
|||
|
|
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export type InstallState = {
|
|||
|
|
/** Tarayıcı kurulum teklifi verdi ve uygulama henüz kurulu değil. */
|
|||
|
|
canInstall: boolean
|
|||
|
|
/** Uygulama standalone modda çalışıyor (yani kurulu). */
|
|||
|
|
installed: boolean
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let deferredPrompt: InstallPromptEvent | undefined
|
|||
|
|
|
|||
|
|
const isStandalone = () => {
|
|||
|
|
try {
|
|||
|
|
return (
|
|||
|
|
window.matchMedia('(display-mode: standalone)').matches ||
|
|||
|
|
window.matchMedia('(display-mode: minimal-ui)').matches ||
|
|||
|
|
(navigator as unknown as { standalone?: boolean }).standalone === true
|
|||
|
|
)
|
|||
|
|
} catch {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let state: InstallState = { canInstall: false, installed: isStandalone() }
|
|||
|
|
|
|||
|
|
const listeners = new Set<(value: InstallState) => void>()
|
|||
|
|
|
|||
|
|
export const getInstallState = () => state
|
|||
|
|
|
|||
|
|
export const subscribeInstallState = (listener: (value: InstallState) => void) => {
|
|||
|
|
listeners.add(listener)
|
|||
|
|
return () => listeners.delete(listener)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function setState(patch: Partial<InstallState>) {
|
|||
|
|
state = { ...state, ...patch }
|
|||
|
|
listeners.forEach((listener) => listener(state))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const promptAppInstall = async (): Promise<'accepted' | 'dismissed' | 'unavailable'> => {
|
|||
|
|
const event = deferredPrompt
|
|||
|
|
if (!event) return 'unavailable'
|
|||
|
|
// Yakalanan olay tek kullanımlıktır.
|
|||
|
|
deferredPrompt = undefined
|
|||
|
|
setState({ canInstall: false })
|
|||
|
|
try {
|
|||
|
|
await event.prompt()
|
|||
|
|
const { outcome } = await event.userChoice
|
|||
|
|
if (outcome === 'accepted') setState({ installed: true })
|
|||
|
|
return outcome
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Uygulama kurulumu başlatılamadı.', error)
|
|||
|
|
return 'unavailable'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (typeof window !== 'undefined') {
|
|||
|
|
window.addEventListener('beforeinstallprompt', (event) => {
|
|||
|
|
// Varsayılan mini bar yerine uygulama içindeki butonu kullanıyoruz.
|
|||
|
|
event.preventDefault()
|
|||
|
|
deferredPrompt = event as InstallPromptEvent
|
|||
|
|
setState({ canInstall: !isStandalone(), installed: isStandalone() })
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
window.addEventListener('appinstalled', () => {
|
|||
|
|
deferredPrompt = undefined
|
|||
|
|
setState({ canInstall: false, installed: true })
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
window
|
|||
|
|
.matchMedia('(display-mode: standalone)')
|
|||
|
|
.addEventListener?.('change', (event) =>
|
|||
|
|
setState({ installed: event.matches, canInstall: event.matches ? false : state.canInstall }),
|
|||
|
|
)
|
|||
|
|
}
|