fix(pwa): guarantee mobile install banner visibility, capture early beforeinstallprompt in head, and provide install guide modal
This commit is contained in:
1 parent
4d4b8c860d
commit
704d3b9eca
2 files changed
+62
-18
No files matched your search
@@ -42,6 +42,23 @@ export default function RootLayout({
|
|||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
window.__everclone_install_prompt = null;
|
||||||
|
window.addEventListener('beforeinstallprompt', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
window.__everclone_install_prompt = e;
|
||||||
|
window.dispatchEvent(new CustomEvent('everclone:installable', { detail: true }));
|
||||||
|
console.log('[Everclone PWA] Early beforeinstallprompt captured!');
|
||||||
|
});
|
||||||
|
window.addEventListener('appinstalled', function() {
|
||||||
|
window.__everclone_install_prompt = null;
|
||||||
|
window.dispatchEvent(new CustomEvent('everclone:installable', { detail: false }));
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<PwaRegister />
|
<PwaRegister />
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export default function EvercloneWorkspace() {
|
|||||||
const [newUserEmail, setNewUserEmail] = useState('');
|
const [newUserEmail, setNewUserEmail] = useState('');
|
||||||
const [triageModalOpen, setTriageModalOpen] = useState(false);
|
const [triageModalOpen, setTriageModalOpen] = useState(false);
|
||||||
const [canInstallPwa, setCanInstallPwa] = useState(false);
|
const [canInstallPwa, setCanInstallPwa] = useState(false);
|
||||||
|
const [installHelpModalOpen, setInstallHelpModalOpen] = useState(false);
|
||||||
const [shareToast, setShareToast] = useState<string | null>(null);
|
const [shareToast, setShareToast] = useState<string | null>(null);
|
||||||
const [historyStack, setHistoryStack] = useState<string[]>([]);
|
const [historyStack, setHistoryStack] = useState<string[]>([]);
|
||||||
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
||||||
@@ -122,12 +123,37 @@ export default function EvercloneWorkspace() {
|
|||||||
// PWA In-App Installation Listener
|
// PWA In-App Installation Listener
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
if (window.__everclone_install_prompt) {
|
|
||||||
setCanInstallPwa(true);
|
const checkInstallState = () => {
|
||||||
}
|
const isStandalone =
|
||||||
|
window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
|
(navigator as unknown as { standalone?: boolean }).standalone === true;
|
||||||
|
|
||||||
|
if (isStandalone) {
|
||||||
|
setCanInstallPwa(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dismissed = localStorage.getItem('evc_dismiss_install');
|
||||||
|
if (dismissed === '1') {
|
||||||
|
if (window.__everclone_install_prompt) {
|
||||||
|
setCanInstallPwa(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.innerWidth < 768 || window.__everclone_install_prompt) {
|
||||||
|
setCanInstallPwa(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkInstallState();
|
||||||
|
|
||||||
const onInstallable = (e: Event) => {
|
const onInstallable = (e: Event) => {
|
||||||
const detail = (e as CustomEvent).detail;
|
const detail = (e as CustomEvent).detail;
|
||||||
setCanInstallPwa(Boolean(detail));
|
if (detail) {
|
||||||
|
setCanInstallPwa(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('everclone:installable', onInstallable);
|
window.addEventListener('everclone:installable', onInstallable);
|
||||||
return () => window.removeEventListener('everclone:installable', onInstallable);
|
return () => window.removeEventListener('everclone:installable', onInstallable);
|
||||||
@@ -135,21 +161,22 @@ export default function EvercloneWorkspace() {
|
|||||||
|
|
||||||
const handleInstallPwa = async () => {
|
const handleInstallPwa = async () => {
|
||||||
const promptEvent = window.__everclone_install_prompt;
|
const promptEvent = window.__everclone_install_prompt;
|
||||||
if (!promptEvent) {
|
if (promptEvent) {
|
||||||
alert("To install Everclone on Android, tap your browser's menu (⋮) and select 'Install app' or 'Add to Home screen'.");
|
try {
|
||||||
return;
|
await promptEvent.prompt();
|
||||||
}
|
const choice = await promptEvent.userChoice;
|
||||||
try {
|
if (choice.outcome === 'accepted') {
|
||||||
await promptEvent.prompt();
|
setCanInstallPwa(false);
|
||||||
const choice = await promptEvent.userChoice;
|
window.__everclone_install_prompt = null;
|
||||||
if (choice.outcome === 'accepted') {
|
setShareToast('Everclone installed! Native Share Target active.');
|
||||||
setCanInstallPwa(false);
|
setTimeout(() => setShareToast(null), 5000);
|
||||||
window.__everclone_install_prompt = null;
|
}
|
||||||
setShareToast('Everclone installed! You can now share directly to Everclone.');
|
} catch (err) {
|
||||||
setTimeout(() => setShareToast(null), 5000);
|
console.error('Install prompt failed:', err);
|
||||||
|
setInstallHelpModalOpen(true);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} else {
|
||||||
console.error('Install prompt failed:', err);
|
setInstallHelpModalOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user