From 252c741180a8fd06e2d216f6ade342dd11cdf816 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 28 Jan 2021 20:14:36 -0600 Subject: [PATCH] add app install button --- src/actions/app.ts | 51 ++++++++++++++++++++++++++ src/components/SettingsDrawer.tsx | 29 ++++++++++++++- src/components/settings-i18n.en.json | 3 ++ src/components/settings-i18n.ts | 1 + src/reducers/app.ts | 30 +++++++++++++++ src/sagas/app.test.ts | 55 ++++++++++++++++++++++++++-- src/sagas/app.ts | 52 +++++++++++++++++++++++++- src/utils/dom.ts | 13 +++++++ 8 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 src/utils/dom.ts diff --git a/src/actions/app.ts b/src/actions/app.ts index 34e92b2a..cd09b533 100644 --- a/src/actions/app.ts +++ b/src/actions/app.ts @@ -4,6 +4,7 @@ // Actions for the app in general. import { Action } from 'redux'; +import { BeforeInstallPromptEvent } from '../utils/dom'; /** App action types. */ export enum AppActionType { @@ -13,6 +14,14 @@ export enum AppActionType { CheckForUpdate = 'app.action.checkForUpdate', /** Indicates that checking for update finished. */ DidCheckForUpdate = 'app.action.didCheckForUpdate', + /* Indicates the browser wants to prompt the use to install the app. */ + DidBeforeInstallPrompt = 'app.action.didBeforeInstallPrompt', + /* Requests to prompt the user to install the app. */ + InstallPrompt = 'app.action.installPrompt', + /* Indicates that the user responded to the install prompt. */ + DidInstallPrompt = 'app.action.didInstallPrompt', + /* Indicates that the app was installed. */ + DidInstall = 'app.action.didInstallPrompt', /** The app has just ben started. */ DidStart = 'app.action.didStart', /** Open settings dialog. */ @@ -61,6 +70,44 @@ export function didCheckForUpdate(updateFound: boolean): AppDidCheckForUpdateAct return { type: AppActionType.DidCheckForUpdate, updateFound }; } +/* Action that indicates the browser wants to prompt the use to install the app. */ +export type AppDidBeforeInstallPromptAction = Action & { + event: BeforeInstallPromptEvent; +}; + +/* Action that indicates the browser wants to prompt the use to install the app. */ +export function didBeforeInstallPrompt( + event: BeforeInstallPromptEvent, +): AppDidBeforeInstallPromptAction { + return { type: AppActionType.DidBeforeInstallPrompt, event }; +} + +/* Action that requests to prompt the user to install the app. */ +export type AppInstallPromptAction = Action & { + event: BeforeInstallPromptEvent; +}; + +/* Action that requests to prompt the user to install the app. */ +export function installPrompt(event: BeforeInstallPromptEvent): AppInstallPromptAction { + return { type: AppActionType.InstallPrompt, event }; +} + +/* Action that indicates that the user responded to the install prompt. */ +export type AppDidInstallPromptAction = Action; + +/* Action that indicates that the user responded to the install prompt. */ +export function didInstallPrompt(): AppDidInstallPromptAction { + return { type: AppActionType.DidInstallPrompt }; +} + +/* Action that indicates app was installed. */ +export type AppDidInstallAction = Action; + +/* Action that indicates app was installed. */ +export function didInstall(): AppDidInstallAction { + return { type: AppActionType.DidInstall }; +} + /** Action that indicates the app has just started. */ export type AppDidStartAction = Action; @@ -122,6 +169,10 @@ export type AppAction = | AppReloadAction | AppCheckForUpdatesAction | AppDidCheckForUpdateAction + | AppDidBeforeInstallPromptAction + | AppInstallPromptAction + | AppDidInstallPromptAction + | AppDidInstallAction | AppDidStartAction | AppOpenSettingsAction | AppCloseSettingsAction diff --git a/src/components/SettingsDrawer.tsx b/src/components/SettingsDrawer.tsx index 8020fdad..a9192332 100644 --- a/src/components/SettingsDrawer.tsx +++ b/src/components/SettingsDrawer.tsx @@ -19,7 +19,13 @@ import { WithI18nProps, withI18n } from '@shopify/react-i18n'; import React from 'react'; import { connect } from 'react-redux'; import { Action, Dispatch } from '../actions'; -import { checkForUpdate, closeSettings, openAboutDialog, reload } from '../actions/app'; +import { + checkForUpdate, + closeSettings, + installPrompt, + openAboutDialog, + reload, +} from '../actions/app'; import { setBoolean, toggleBoolean } from '../actions/settings'; import { RootState } from '../reducers'; import { pseudolocalize } from '../settings/i18n'; @@ -31,6 +37,7 @@ import { tooltipDelay, } from '../settings/ui'; import { SettingId } from '../settings/user'; +import { BeforeInstallPromptEvent } from '../utils/dom'; import { isMacOS } from '../utils/os'; import AboutDialog from './AboutDialog'; import ExternalLinkIcon from './ExternalLinkIcon'; @@ -45,6 +52,8 @@ type StateProps = { serviceWorker: ServiceWorkerRegistration | null; checkingForUpdate: boolean; updateAvailable: boolean; + beforeInstallPrompt: BeforeInstallPromptEvent | null; + promptingInstall: boolean; }; type DispatchProps = { @@ -56,6 +65,7 @@ type DispatchProps = { onToggleDocs: () => void; onCheckForUpdate: (registration: ServiceWorkerRegistration) => void; onReload: (registration: ServiceWorkerRegistration) => void; + onInstallPrompt: (event: BeforeInstallPromptEvent) => void; }; type SettingsProps = StateProps & DispatchProps & WithI18nProps; @@ -71,6 +81,8 @@ class SettingsDrawer extends React.PureComponent { flashCurrentProgram, checkingForUpdate, updateAvailable, + beforeInstallPrompt, + promptingInstall, onClose, onShowDocsChanged, onDarkModeChanged, @@ -78,6 +90,7 @@ class SettingsDrawer extends React.PureComponent { onAbout, onCheckForUpdate, onReload, + onInstallPrompt: onInstall, i18n, } = this.props; return ( @@ -219,6 +232,17 @@ class SettingsDrawer extends React.PureComponent { vertical={true} alignText="left" > + {beforeInstallPrompt && ( + + )} {serviceWorker && !updateAvailable && (