Android PWA-Installationshinweis mit direktem Install-Button hinzugefügt
This commit is contained in:
@@ -1,10 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
interface BeforeInstallPromptEvent extends Event {
|
||||||
|
prompt: () => Promise<void>;
|
||||||
|
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Navigator { standalone?: boolean }
|
interface Navigator { standalone?: boolean }
|
||||||
|
interface WindowEventMap {
|
||||||
|
beforeinstallprompt: BeforeInstallPromptEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LAST_SHOWN_KEY = 'pwaInstallPrompt_lastShown';
|
const LAST_SHOWN_KEY = 'pwaInstallPrompt_lastShown';
|
||||||
|
const ANDROID_DISMISSED_KEY = 'pwaInstallPrompt_androidDismissed';
|
||||||
|
|
||||||
function isIOS(): boolean {
|
function isIOS(): boolean {
|
||||||
if (typeof navigator === 'undefined') return false;
|
if (typeof navigator === 'undefined') return false;
|
||||||
@@ -31,59 +40,144 @@ function isStandalone(): boolean {
|
|||||||
export function PWAInstallPrompt({ hidden = false }: { hidden?: boolean }) {
|
export function PWAInstallPrompt({ hidden = false }: { hidden?: boolean }) {
|
||||||
if (hidden) return null;
|
if (hidden) return null;
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
|
const [promptType, setPromptType] = useState<'ios' | 'android' | null>(null);
|
||||||
|
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
|
||||||
const [initialized, setInitialized] = useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only run on client
|
// Only run on client
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
const eligible = isIOS() && isSafari() && !isStandalone();
|
// Check if already in standalone mode
|
||||||
if (!eligible) {
|
if (isStandalone()) {
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastShown = 0;
|
// Handle iOS
|
||||||
try {
|
const isIOSDevice = isIOS() && isSafari();
|
||||||
lastShown = Number(localStorage.getItem(LAST_SHOWN_KEY) || 0);
|
if (isIOSDevice) {
|
||||||
} catch {}
|
let lastShown = 0;
|
||||||
const oneWeek = 7 * 24 * 60 * 60 * 1000;
|
try {
|
||||||
const shouldShow = !lastShown || Date.now() - lastShown > oneWeek;
|
lastShown = Number(localStorage.getItem(LAST_SHOWN_KEY) || 0);
|
||||||
|
} catch {}
|
||||||
|
const oneWeek = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
const shouldShow = !lastShown || Date.now() - lastShown > oneWeek;
|
||||||
|
|
||||||
setShow(shouldShow);
|
if (shouldShow) {
|
||||||
|
setPromptType('ios');
|
||||||
|
setShow(true);
|
||||||
|
}
|
||||||
|
setInitialized(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Android (beforeinstallprompt)
|
||||||
|
const handleBeforeInstall = (e: BeforeInstallPromptEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Check if user has dismissed Android prompt before
|
||||||
|
let dismissed = false;
|
||||||
|
try {
|
||||||
|
dismissed = localStorage.getItem(ANDROID_DISMISSED_KEY) === 'true';
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (!dismissed) {
|
||||||
|
setDeferredPrompt(e);
|
||||||
|
setPromptType('android');
|
||||||
|
setShow(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', handleBeforeInstall);
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeinstallprompt', handleBeforeInstall);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const dismiss = () => {
|
const dismiss = () => {
|
||||||
try { localStorage.setItem(LAST_SHOWN_KEY, String(Date.now())); } catch {}
|
if (promptType === 'ios') {
|
||||||
|
try { localStorage.setItem(LAST_SHOWN_KEY, String(Date.now())); } catch {}
|
||||||
|
} else if (promptType === 'android') {
|
||||||
|
try { localStorage.setItem(ANDROID_DISMISSED_KEY, 'true'); } catch {}
|
||||||
|
}
|
||||||
setShow(false);
|
setShow(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!initialized || !show) return null;
|
const handleAndroidInstall = async () => {
|
||||||
|
if (!deferredPrompt) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deferredPrompt.prompt();
|
||||||
|
const choiceResult = await deferredPrompt.userChoice;
|
||||||
|
|
||||||
|
if (choiceResult.outcome === 'accepted') {
|
||||||
|
console.log('PWA installation accepted');
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeferredPrompt(null);
|
||||||
|
setShow(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during PWA installation:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!initialized || !show || !promptType) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label="PWA Installation Anleitung"
|
aria-label={promptType === 'android' ? 'PWA Installation' : 'PWA Installation Anleitung'}
|
||||||
className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-4"
|
className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-4"
|
||||||
style={{ paddingBottom: `max(env(safe-area-inset-bottom), 1rem)` }}
|
style={{ paddingBottom: `max(env(safe-area-inset-bottom), 1rem)` }}
|
||||||
>
|
>
|
||||||
<div className="relative max-w-3xl mx-auto rounded-xl shadow-lg bg-gradient-to-r from-pink-500 to-purple-600 text-white p-4 sm:p-6">
|
<div className="relative max-w-3xl mx-auto rounded-xl shadow-lg bg-gradient-to-r from-pink-500 to-purple-600 text-white p-4 sm:p-6">
|
||||||
<button aria-label="Hinweis schließen" onClick={dismiss} className="absolute top-2 right-2 text-white/90 hover:text-white text-2xl leading-none">×</button>
|
<button aria-label="Hinweis schließen" onClick={dismiss} className="absolute top-2 right-2 text-white/90 hover:text-white text-2xl leading-none">×</button>
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className="text-2xl">📱</div>
|
{promptType === 'android' ? (
|
||||||
<div className="flex-1">
|
// Android: Direct install button
|
||||||
<h3 className="text-lg sm:text-xl font-bold mb-2">App installieren</h3>
|
<div className="flex items-center gap-4">
|
||||||
<p className="text-white/90 mb-2">So installierst du Stargirlnails als App auf deinem iPhone/iPad:</p>
|
<div className="text-3xl">📱</div>
|
||||||
<ol className="list-decimal pl-5 space-y-1 text-white/95">
|
<div className="flex-1">
|
||||||
<li>Öffne diese Seite in Safari (nicht Chrome oder andere Browser).</li>
|
<h3 className="text-lg sm:text-xl font-bold mb-2">App installieren</h3>
|
||||||
<li>Tippe auf das Teilen-Symbol (□↑) unten in der Mitte.</li>
|
<p className="text-white/90 mb-3">
|
||||||
<li>Scrolle nach unten und wähle „Zum Home-Bildschirm".</li>
|
Installiere Stargirlnails als App für schnellen Zugriff direkt vom Startbildschirm!
|
||||||
<li>Tippe auf „Hinzufügen".</li>
|
</p>
|
||||||
</ol>
|
<div className="flex gap-2">
|
||||||
<p className="mt-3 text-sm text-white/90">✨ Schneller Zugriff, keine App-Store-Installation nötig, automatische Updates.</p>
|
<button
|
||||||
|
onClick={handleAndroidInstall}
|
||||||
|
className="bg-white text-pink-600 px-4 py-2 rounded-lg font-semibold hover:bg-pink-50 transition-colors"
|
||||||
|
>
|
||||||
|
Jetzt installieren
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={dismiss}
|
||||||
|
className="bg-white/20 text-white px-4 py-2 rounded-lg font-semibold hover:bg-white/30 transition-colors"
|
||||||
|
>
|
||||||
|
Später
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
// iOS: Manual instructions
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="text-2xl">📱</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-lg sm:text-xl font-bold mb-2">App installieren</h3>
|
||||||
|
<p className="text-white/90 mb-2">So installierst du Stargirlnails als App auf deinem iPhone/iPad:</p>
|
||||||
|
<ol className="list-decimal pl-5 space-y-1 text-white/95">
|
||||||
|
<li>Öffne diese Seite in Safari (nicht Chrome oder andere Browser).</li>
|
||||||
|
<li>Tippe auf das Teilen-Symbol (□↑) unten in der Mitte.</li>
|
||||||
|
<li>Scrolle nach unten und wähle „Zum Home-Bildschirm".</li>
|
||||||
|
<li>Tippe auf „Hinzufügen".</li>
|
||||||
|
</ol>
|
||||||
|
<p className="mt-3 text-sm text-white/90">✨ Schneller Zugriff, keine App-Store-Installation nötig, automatische Updates.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user