mirror of https://github.com/msgbyte/tailchat
feat: 增加安装应用按钮
parent
91fe01f247
commit
dcbc148eeb
@ -0,0 +1,217 @@
|
||||
// Fork from https://github.com/piro0919/use-pwa/blob/master/src/hooks/usePwa/index.ts
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { detect } from 'detect-browser';
|
||||
|
||||
type PromiseType<T extends Promise<any>> = T extends Promise<infer P>
|
||||
? P
|
||||
: never;
|
||||
|
||||
type BeforeInstallPromptEvent = Event & {
|
||||
readonly platforms: Array<string>;
|
||||
readonly userChoice: Promise<{
|
||||
outcome: 'accepted' | 'dismissed';
|
||||
platform: string;
|
||||
}>;
|
||||
prompt(): Promise<void>;
|
||||
};
|
||||
|
||||
export type PwaData = {
|
||||
appinstalled: boolean;
|
||||
canInstallprompt: boolean;
|
||||
enabledA2hs: boolean;
|
||||
enabledPwa: boolean;
|
||||
enabledUpdate: boolean;
|
||||
isLoading: boolean;
|
||||
isPwa: boolean;
|
||||
showInstallPrompt: () => void;
|
||||
unregister: () => Promise<boolean | undefined>;
|
||||
userChoice?: PromiseType<BeforeInstallPromptEvent['userChoice']>;
|
||||
};
|
||||
|
||||
export function usePwa(): PwaData {
|
||||
const beforeinstallprompt = useRef<BeforeInstallPromptEvent>();
|
||||
const [appinstalled, setAppinstalled] = useState(false);
|
||||
const [canInstallprompt, setCanInstallprompt] = useState(false);
|
||||
const [enabledA2hs, setEnabledA2hs] = useState(false);
|
||||
const [enabledPwa, setEnabledPwa] = useState(false);
|
||||
const [isPwa, setIsPwa] = useState(false);
|
||||
const [enabledUpdate, setEnabledUpdate] = useState(false);
|
||||
const [userChoice, setUserChoice] = useState<PwaData['userChoice']>();
|
||||
const showInstallPrompt = useCallback(async () => {
|
||||
if (!beforeinstallprompt.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
await beforeinstallprompt.current.prompt();
|
||||
|
||||
if (!beforeinstallprompt.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userChoice = await beforeinstallprompt.current.userChoice;
|
||||
|
||||
setUserChoice(userChoice);
|
||||
}, []);
|
||||
const unregister = useCallback(async () => {
|
||||
if (!('serviceWorker' in window.navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const registration = await window.navigator.serviceWorker.getRegistration();
|
||||
|
||||
if (!registration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await registration.unregister();
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
const handleBeforeInstallPrompt = useCallback(
|
||||
(event: BeforeInstallPromptEvent) => {
|
||||
beforeinstallprompt.current = event;
|
||||
|
||||
setCanInstallprompt(true);
|
||||
},
|
||||
[]
|
||||
);
|
||||
const handleAppinstalled = useCallback(() => {
|
||||
setAppinstalled(true);
|
||||
}, []);
|
||||
const [completed, setCompleted] = useState({
|
||||
appinstalled: false,
|
||||
beforeinstallprompt: false,
|
||||
enabledA2hs: false,
|
||||
enabledPwa: false,
|
||||
enabledUpdate: false,
|
||||
isPwa: false,
|
||||
});
|
||||
const isLoading = useMemo(
|
||||
() => !Object.values(completed).filter((value) => value).length,
|
||||
[completed]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener(
|
||||
'beforeinstallprompt',
|
||||
handleBeforeInstallPrompt as any
|
||||
);
|
||||
|
||||
setCompleted((prevCompleted) => ({
|
||||
...prevCompleted,
|
||||
beforeinstallprompt: true,
|
||||
}));
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
'beforeinstallprompt',
|
||||
handleBeforeInstallPrompt as any
|
||||
);
|
||||
};
|
||||
}, [handleBeforeInstallPrompt]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('appinstalled', handleAppinstalled);
|
||||
|
||||
setCompleted((prevCompleted) => ({
|
||||
...prevCompleted,
|
||||
appinstalled: true,
|
||||
}));
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('appinstalled', handleAppinstalled);
|
||||
};
|
||||
}, [handleAppinstalled]);
|
||||
|
||||
useEffect(() => {
|
||||
setEnabledPwa(
|
||||
'serviceWorker' in window.navigator &&
|
||||
'BeforeInstallPromptEvent' in window
|
||||
);
|
||||
|
||||
setCompleted((prevCompleted) => ({
|
||||
...prevCompleted,
|
||||
enabledPwa: true,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsPwa(
|
||||
'standalone' in window.navigator ||
|
||||
window.matchMedia('(display-mode: standalone)').matches
|
||||
);
|
||||
|
||||
setCompleted((prevCompleted) => ({
|
||||
...prevCompleted,
|
||||
isPwa: true,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const browser = detect();
|
||||
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent.toLowerCase();
|
||||
const isIos =
|
||||
userAgent.indexOf('iphone') >= 0 ||
|
||||
userAgent.indexOf('ipad') >= 0 ||
|
||||
(userAgent.indexOf('macintosh') >= 0 && 'ontouchend' in document);
|
||||
const { name } = browser;
|
||||
|
||||
setEnabledA2hs(isIos && name === 'ios');
|
||||
} finally {
|
||||
setCompleted((prevCompleted) => ({
|
||||
...prevCompleted,
|
||||
enabledA2hs: true,
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const callback = async () => {
|
||||
try {
|
||||
if (!('serviceWorker' in window.navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const registration =
|
||||
await window.navigator.serviceWorker.getRegistration();
|
||||
|
||||
if (!registration) {
|
||||
return;
|
||||
}
|
||||
|
||||
registration.onupdatefound = async () => {
|
||||
await registration.update();
|
||||
|
||||
setEnabledUpdate(true);
|
||||
};
|
||||
} finally {
|
||||
setCompleted((prevCompleted) => ({
|
||||
...prevCompleted,
|
||||
enabledUpdate: true,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
callback();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
appinstalled,
|
||||
canInstallprompt,
|
||||
enabledA2hs,
|
||||
enabledUpdate,
|
||||
enabledPwa,
|
||||
isLoading,
|
||||
isPwa,
|
||||
showInstallPrompt,
|
||||
unregister,
|
||||
userChoice,
|
||||
};
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { usePwa } from '@/hooks/usePwa';
|
||||
import React from 'react';
|
||||
import { Icon } from 'tailchat-design';
|
||||
|
||||
/**
|
||||
* 安装按钮
|
||||
*/
|
||||
export const InstallBtn: React.FC = React.memo(() => {
|
||||
const { canInstallprompt, showInstallPrompt } = usePwa();
|
||||
|
||||
if (!canInstallprompt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Icon
|
||||
className="text-3xl text-gray-600 dark:text-white cursor-pointer"
|
||||
icon="mdi:download"
|
||||
onClick={showInstallPrompt}
|
||||
/>
|
||||
);
|
||||
});
|
||||
InstallBtn.displayName = 'InstallBtn';
|
Loading…
Reference in New Issue