From c10739630c6f69fd17da06f312550d838452eacf Mon Sep 17 00:00:00 2001 From: Pecacheu <3608878+Pecacheu@users.noreply.github.com> Date: Sat, 21 Feb 2026 15:26:33 -0500 Subject: [PATCH 1/2] feat: TryPWA install dialog Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --- packages/client/components/client/index.tsx | 45 +++++++++++------ packages/client/components/modal/modals.tsx | 3 ++ .../client/components/modal/modals/TryPWA.tsx | 49 +++++++++++++++++++ packages/client/components/modal/types.ts | 3 ++ packages/client/components/state/index.tsx | 5 ++ .../components/state/stores/Settings.ts | 6 +++ packages/client/src/index.tsx | 4 ++ 7 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 packages/client/components/modal/modals/TryPWA.tsx diff --git a/packages/client/components/client/index.tsx b/packages/client/components/client/index.tsx index 47741e438..007780dc9 100644 --- a/packages/client/components/client/index.tsx +++ b/packages/client/components/client/index.tsx @@ -26,30 +26,43 @@ const clientContext = createContext(null! as ClientController); * Mount the modal controller */ export function ClientContext(props: { state: State; children: JSXElement }) { - const { openModal } = useModals(); + const { openModal, isOpen } = useModals(); // eslint-disable-next-line solid/reactivity const controller = new ClientController(props.state); onCleanup(() => controller.dispose()); createEffect(() => { - const lastIndex = props.state.settings.getValue("changelog:last_index"); - if (controller.lifecycle.state() === LifecycleState.Ready) return; + const cycleState = controller.lifecycle.state(); + + //Show Changelog modal + if (cycleState !== LifecycleState.Ready) { + const lastIndex = props.state.settings.getValue("changelog:last_index"); + + if ( + lastIndex !== CHANGELOG_MODAL_CONST.index && + new Date() < CHANGELOG_MODAL_CONST.until + ) { + openModal({ + type: "changelog", + initial: CHANGELOG_MODAL_CONST.index, + }); + + props.state.settings.setValue( + "changelog:last_index", + CHANGELOG_MODAL_CONST.index, + ); + } + } + //Show TryPWA modal if ( - lastIndex !== CHANGELOG_MODAL_CONST.index && - new Date() < CHANGELOG_MODAL_CONST.until - ) { - openModal({ - type: "changelog", - initial: CHANGELOG_MODAL_CONST.index, - }); - - props.state.settings.setValue( - "changelog:last_index", - CHANGELOG_MODAL_CONST.index, - ); - } + props.state.isMobile && + cycleState === LifecycleState.Connected && + !props.state.settings.getValue("pwa:shown") && + !isOpen("try_pwa") + ) + openModal({ type: "try_pwa" }); }); createEffect( diff --git a/packages/client/components/modal/modals.tsx b/packages/client/components/modal/modals.tsx index 9da2b50ee..0ddacd3be 100644 --- a/packages/client/components/modal/modals.tsx +++ b/packages/client/components/modal/modals.tsx @@ -53,6 +53,7 @@ import { ServerInfoModal } from "./modals/ServerInfo"; import { SettingsModal } from "./modals/Settings"; import { SignOutSessionsModal } from "./modals/SignOutSessions"; import { SignedOutModal } from "./modals/SignedOut"; +import { TryPWAModal } from "./modals/TryPWA"; import { UserProfileModal } from "./modals/UserProfile"; import { UserProfileMutualFriendsModal } from "./modals/UserProfileMutualFriends"; import { UserProfileMutualGroupsModal } from "./modals/UserProfileMutualGroups"; @@ -183,6 +184,8 @@ export function RenderModal(props: ActiveModal & { onClose: () => void }) { return ; case "edit_category": return ; + case "try_pwa": + return ; default: console.error( diff --git a/packages/client/components/modal/modals/TryPWA.tsx b/packages/client/components/modal/modals/TryPWA.tsx new file mode 100644 index 000000000..62ead9097 --- /dev/null +++ b/packages/client/components/modal/modals/TryPWA.tsx @@ -0,0 +1,49 @@ +import { Trans } from "@lingui-solid/solid/macro"; + +import { Dialog, DialogProps, iconSize } from "@revolt/ui"; + +import MdInfo from "@material-design-icons/svg/outlined/error.svg?component-solid"; + +import { t } from "@lingui/core/macro"; +import { useState } from "@revolt/state"; +import { Modals } from "../types"; + +export function TryPWAModal(props: DialogProps & Modals & { type: "try_pwa" }) { + const state = useState(); + + return ( + } + show={props.show} + onClose={() => { + state.settings.setValue("pwa:shown", true); + props.onClose(); + }} + title={Did you know?} + actions={[ + { text: Close }, + { + text: Install, + onClick: () => { + // @ts-expect-error PWA event not recognized + if (state.pwaPrompt) state.pwaPrompt.prompt(); + else + alert( + t`Sorry, your device doesn't support auto-install. Check your browser's menu for the 'Add to Home Screen' option.`, + ); + }, + }, + ]} + > + + You can install this Web App on your devive for conveient access + from the Home Screen, just like a real app. It even hides the huge menu + bar! +
+
+ To install, tap the button below or open your browser's menu and choose + Add to Home Screen. +
+
+ ); +} diff --git a/packages/client/components/modal/types.ts b/packages/client/components/modal/types.ts index a6c6a020e..48c7cc048 100644 --- a/packages/client/components/modal/types.ts +++ b/packages/client/components/modal/types.ts @@ -314,4 +314,7 @@ export type Modals = type: "edit_category"; server: Server; category: CategoryData; + } + | { + type: "try_pwa"; }; diff --git a/packages/client/components/state/index.tsx b/packages/client/components/state/index.tsx index bfdbb55db..ab32d8b09 100644 --- a/packages/client/components/state/index.tsx +++ b/packages/client/components/state/index.tsx @@ -11,6 +11,7 @@ import { SetStoreFunction, createStore } from "solid-js/store"; import equal from "fast-deep-equal"; import localforage from "localforage"; +import { isMobileBrowser } from "@livekit/components-core"; import { AbstractStore, Store } from "./stores"; import { Auth } from "./stores/Auth"; import { Draft } from "./stores/Draft"; @@ -47,6 +48,9 @@ export class State { private setStore: SetStoreFunction; private writeQueue: Record; + isMobile: boolean; + pwaPrompt: Event | undefined; + // define all stores auth = new Auth(this); draft = new Draft(this); @@ -99,6 +103,7 @@ export class State { this.store = store as never; this.setStore = setStore; this.writeQueue = {}; + this.isMobile = isMobileBrowser(); } /** diff --git a/packages/client/components/state/stores/Settings.ts b/packages/client/components/state/stores/Settings.ts index 1aee0a92e..8748d890d 100644 --- a/packages/client/components/state/stores/Settings.ts +++ b/packages/client/components/state/stores/Settings.ts @@ -68,6 +68,11 @@ interface SettingsDefinition { * Last read changelog index */ "changelog:last_index": number; + + /** + * Whether the user has seen the TryPWA dialog + */ + "pwa:shown": boolean; } /** @@ -95,6 +100,7 @@ const EXPECTED_TYPES: { [K in keyof SettingsDefinition]: ValueType } = { "advanced:copy_id": "boolean", "advanced:admin_panel": "boolean", "changelog:last_index": "number", + "pwa:shown": "boolean", }; /** diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx index 23a11e744..9115416eb 100644 --- a/packages/client/src/index.tsx +++ b/packages/client/src/index.tsx @@ -108,8 +108,12 @@ function BotRedirect() { return ; } +let pwaPrompt: Event; +addEventListener("beforeinstallprompt", (e) => (pwaPrompt = e)); + function MountContext(props: { children?: JSX.Element }) { const state = useState(); + state.pwaPrompt = pwaPrompt; /** * Tanstack Query client From e482c2169d31f63847cbce53980adfe77178ea46 Mon Sep 17 00:00:00 2001 From: Pecacheu <3608878+Pecacheu@users.noreply.github.com> Date: Sat, 21 Mar 2026 23:02:35 -0400 Subject: [PATCH 2/2] fix: Make dialog less wordy Signed-off-by: Pecacheu <3608878+Pecacheu@users.noreply.github.com> --- packages/client/components/modal/modals/TryPWA.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/client/components/modal/modals/TryPWA.tsx b/packages/client/components/modal/modals/TryPWA.tsx index 62ead9097..b4591f6bf 100644 --- a/packages/client/components/modal/modals/TryPWA.tsx +++ b/packages/client/components/modal/modals/TryPWA.tsx @@ -37,12 +37,8 @@ export function TryPWAModal(props: DialogProps & Modals & { type: "try_pwa" }) { > You can install this Web App on your devive for conveient access - from the Home Screen, just like a real app. It even hides the huge menu - bar! -
-
- To install, tap the button below or open your browser's menu and choose - Add to Home Screen. + from the Home Screen. Tap the button below or open your browser's menu + and choose Add to Home Screen.
);