Add the Install the app bottom sheet for mobile browsers
- New InstallSheet mounted in the root layout: appears ~1s after load on iOS/Android phones and tablets, over any screen including the key gate, with a dimmed backdrop. Never on desktop, never in standalone mode, and at most once per 7 days after a dismissal (localStorage). - Platform/browser detection (lib/install.ts): iPhone/iPad incl. iPadOS reporting as Mac with touch, Android, Safari vs Chrome vs other vs in-app browsers, with step 1 adapted accordingly. - iOS: numbered steps with chips (Safari, Share, ... , More, Add to Home Screen) and the accent Add pill. Android: captured beforeinstallprompt turns into a native Install button; fallback steps use Install app / Add to Home screen chips and an accent Install pill. - Close via X, backdrop, Escape or swipe-down on the handle; the site is untouched afterwards. Anchored to 100dvh so browser toolbars never hide the last step; pb-safe respects the home indicator. - Dark sheet by default; light only when the app theme was explicitly set to light. Settings gains a mobile-only Install app row that reopens it. - The inline head script captures beforeinstallprompt before hydration and records appinstalled. Old InstallHint banner removed. - Tests: new install-sheet suite (45 checks across iPhone Safari/Chrome/ in-app, Android Chrome/Samsung, desktop, prompt path, dismissal, 7-day expiry, standalone, Settings row, both themes); pwa suite updated.
This commit is contained in:
parent
969f7a82a0
commit
26e36bc0c0
8 changed files with 632 additions and 79 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import type { Metadata, Viewport } from "next";
|
||||
|
||||
import { InstallSheet } from "@/components/InstallSheet";
|
||||
import { ServiceWorkerRegistrar } from "@/components/ServiceWorkerRegistrar";
|
||||
import "./globals.css";
|
||||
|
||||
|
|
@ -51,15 +52,26 @@ export const viewport: Viewport = {
|
|||
/** Applies the saved theme before paint to avoid a flash. */
|
||||
const themeScript = `(function(){try{var s=localStorage.getItem('freezmo.theme');var d=s?s==='dark':window.matchMedia('(prefers-color-scheme: dark)').matches;if(d)document.documentElement.classList.add('dark');}catch(e){}})();`;
|
||||
|
||||
/**
|
||||
* Captures Chrome's `beforeinstallprompt` (it can fire before React hydrates)
|
||||
* and records an install so the prompt never comes back. Static, no user data.
|
||||
*/
|
||||
const installScript = `(function(){try{
|
||||
window.addEventListener('beforeinstallprompt',function(e){e.preventDefault();window.__freezmoInstallPrompt=e;window.dispatchEvent(new Event('freezmo:installprompt'));});
|
||||
window.addEventListener('appinstalled',function(){try{localStorage.setItem('freezmo.install.installed','1')}catch(e){}window.dispatchEvent(new Event('freezmo:installed'));});
|
||||
}catch(e){}})();`;
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
<script dangerouslySetInnerHTML={{ __html: installScript }} />
|
||||
</head>
|
||||
<body className="min-h-full antialiased">
|
||||
<div className="app-backdrop" aria-hidden />
|
||||
{children}
|
||||
<InstallSheet />
|
||||
<ServiceWorkerRegistrar />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { ApiKeyGate } from "@/components/ApiKeyGate";
|
||||
import { AppHeader } from "@/components/AppHeader";
|
||||
import { InstallHint } from "@/components/InstallHint";
|
||||
import { LibraryScreen } from "@/components/LibraryScreen";
|
||||
import { MobileSidebar, Sidebar, SidebarOffset } from "@/components/Sidebar";
|
||||
import { TabBar } from "@/components/TabBar";
|
||||
|
|
@ -15,9 +14,6 @@ export default function HomePage() {
|
|||
<main className="min-w-0 flex-1">
|
||||
<AppHeader />
|
||||
<div className="mx-auto w-full max-w-6xl px-4 pb-tabbar lg:px-10 lg:pb-16">
|
||||
<div className="mb-6">
|
||||
<InstallHint />
|
||||
</div>
|
||||
<LibraryScreen />
|
||||
</div>
|
||||
<TabBar />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue