compat: add support for WebView down to version 92, closes #2139 (#2145)

This commit is contained in:
Huang Xin
2025-10-01 01:14:35 +08:00
committed by GitHub
parent 2230741779
commit cdd274e5ca
15 changed files with 118 additions and 43 deletions
+1 -1
View File
@@ -345,7 +345,7 @@ export default function AuthPage() {
<div
className={clsx(
'bg-base-100 inset-0 flex select-none flex-col items-center overflow-hidden',
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
'h-[100vh] supports-[height:100dvh]:h-[100dvh]',
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
)}
>
+2 -2
View File
@@ -4,7 +4,7 @@ import posthog from 'posthog-js';
import { useEffect, useState } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
import { parseWebViewVersion } from '@/utils/ua';
import { parseWebViewInfo } from '@/utils/ua';
import { handleGlobalError } from '@/utils/error';
interface ErrorPageProps {
@@ -18,7 +18,7 @@ export default function Error({ error, reset }: ErrorPageProps) {
const [browserInfo, setBrowserInfo] = useState('');
useEffect(() => {
setBrowserInfo(parseWebViewVersion(appService));
setBrowserInfo(parseWebViewInfo(appService));
posthog.captureException(error);
handleGlobalError(error);
}, [appService, error]);
@@ -353,16 +353,21 @@ const Bookshelf: React.FC<BookshelfProps> = ({
/>
))}
{viewMode === 'grid' && !navBooksGroup && allBookshelfItems.length > 0 && (
<button
aria-label={_('Import Books')}
className={clsx(
'border-1 bg-base-100 hover:bg-base-300/50 flex items-center justify-center',
'mx-0 my-4 aspect-[28/41] sm:mx-4',
)}
onClick={handleImportBooks}
>
<PiPlus className='size-10' color='gray' />
</button>
<div className={clsx('mx-0 my-4 sm:mx-4')}>
<button
aria-label={_('Import Books')}
className={clsx(
'border-1 bg-base-100 hover:bg-base-300/50',
'flex items-center justify-center',
'aspect-[28/41] w-full',
)}
onClick={handleImportBooks}
>
<div className='flex items-center justify-center'>
<PiPlus className='size-10' color='gray' />
</div>
</button>
</div>
)}
</div>
{loading && (
@@ -342,7 +342,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
onKeyDown={handleKeyDown}
{...handlers}
>
<div className='flex-grow'>
<div className='flex h-full flex-col justify-end'>
{'format' in item ? (
<BookItem
mode={mode}
@@ -96,12 +96,7 @@ const GroupItem: React.FC<GroupItemProps> = ({ mode, group, isSelectMode, groupS
};
return (
<div
className={clsx(
'group-item flex h-full flex-col justify-end',
appService?.hasContextMenu ? 'cursor-pointer' : '',
)}
>
<div className={clsx('group-item', appService?.hasContextMenu ? 'cursor-pointer' : '')}>
<div
className={clsx(
'relative flex overflow-hidden',
+1 -1
View File
@@ -631,7 +631,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
aria-label='Your Library'
className={clsx(
'library-page bg-base-200 text-base-content flex select-none flex-col overflow-hidden',
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
'h-[100vh] supports-[height:100dvh]:h-[100dvh]',
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
)}
>
@@ -142,7 +142,7 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
<div
className={clsx(
`reader-page bg-base-100 text-base-content select-none overflow-hidden`,
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
'h-[100vh] supports-[height:100dvh]:h-[100dvh]',
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
)}
>
@@ -1,6 +1,5 @@
'use client';
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
@@ -185,7 +184,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
setTimeout(() => setLoading(true), 200);
return (
loading && (
<div className={clsx('hero hero-content', appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh')}>
<div className='hero hero-content h-[100vh] supports-[height:100dvh]:h-[100dvh]'>
<Spinner loading={true} />
</div>
)
@@ -193,7 +192,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
}
return (
<div className={clsx('reader-content flex', appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh')}>
<div className='reader-content flex h-[100vh] supports-[height:100dvh]:h-[100dvh]'>
<SideBar onGoToLibrary={handleCloseBooksToLibrary} />
<BooksGrid bookKeys={bookKeys} onCloseBook={handleCloseBook} />
<Notebook />
+1 -1
View File
@@ -341,7 +341,7 @@ const ProfilePage = () => {
<div
className={clsx(
'bg-base-100 inset-0 select-none overflow-hidden',
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
'h-[100vh] supports-[height:100dvh]:h-[100dvh]',
appService?.hasRoundedWindow && isRoundedWindow && 'window-border rounded-window',
)}
>
@@ -3,7 +3,7 @@ import Image from 'next/image';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
import { checkForAppUpdates, checkAppReleaseNotes } from '@/helpers/updater';
import { parseWebViewVersion } from '@/utils/ua';
import { parseWebViewInfo } from '@/utils/ua';
import { getAppVersion } from '@/utils/version';
import SupportLinks from './SupportLinks';
import LegalLinks from './LegalLinks';
@@ -30,7 +30,7 @@ export const AboutWindow = () => {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
setBrowserInfo(parseWebViewVersion(appService));
setBrowserInfo(parseWebViewInfo(appService));
const handleCustomEvent = (event: CustomEvent) => {
setIsOpen(event.detail.visible);
+7
View File
@@ -0,0 +1,7 @@
declare module '*.css' {
interface IClassNames {
[className: string]: string;
}
const classNames: IClassNames;
export default classNames;
}
+20 -3
View File
@@ -1,10 +1,11 @@
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useThemeStore } from '@/store/themeStore';
import { useSettingsStore } from '@/store/settingsStore';
import { applyCustomTheme, Palette } from '@/styles/themes';
import { themes, applyCustomTheme, Palette } from '@/styles/themes';
import { getStatusBarHeight, setSystemUIVisibility } from '@/utils/bridge';
import { getOSPlatform } from '@/utils/misc';
import { parseWebViewVersion } from '@/utils/ua';
type UseThemeProps = {
systemUIVisible?: boolean;
@@ -28,6 +29,8 @@ export const useTheme = ({
setSystemUIAlwaysHidden,
} = useThemeStore();
const useFallbackColors = useRef(false);
useEffect(() => {
updateAppTheme(appThemeColor);
if (appService?.isAndroidApp) {
@@ -88,10 +91,24 @@ export const useTheme = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleSystemUIVisibility]);
useEffect(() => {
if (!appService?.isAndroidApp) return;
const webViewVersion = parseWebViewVersion(appService);
// OKLCH color model is supported in Chromium 111+
useFallbackColors.current = webViewVersion < 111;
}, [appService]);
useEffect(() => {
if (!themeColor || !themes.find((t) => t.name === themeColor)) return;
if (useFallbackColors.current) {
applyCustomTheme(undefined, themeColor, true);
}
}, [themeColor]);
useEffect(() => {
const customThemes = settings.globalReadSettings?.customThemes ?? [];
customThemes.forEach((customTheme) => {
applyCustomTheme(customTheme);
applyCustomTheme(customTheme, undefined, useFallbackColors.current);
});
localStorage.setItem('customThemes', JSON.stringify(customThemes));
}, [settings.globalReadSettings?.customThemes]);
+53 -11
View File
@@ -1,6 +1,6 @@
import tinycolor from 'tinycolor2';
import { stubTranslation as _ } from '../utils/misc';
import { getContrastOklch, hexToOklch } from '../utils/color';
import { getContrastHex, getContrastOklch, hexToOklch } from '../utils/color';
export type BaseColor = {
bg: string;
@@ -160,8 +160,8 @@ export const themes = [
},
] as Theme[];
const generateCustomThemeVariables = (palette: Palette): string => {
return `
const generateCustomThemeVariables = (palette: Palette, fallbackIncluded = false): string => {
const colors = `
--b1: ${hexToOklch(palette['base-100'])};
--b2: ${hexToOklch(palette['base-200'])};
--b3: ${hexToOklch(palette['base-300'])};
@@ -188,22 +188,64 @@ const generateCustomThemeVariables = (palette: Palette): string => {
--er: 70.9% 0.184 22;
--erc: 100% 0 0;
`;
const fallbackColors = `
--fallback-b1: ${palette['base-100']};
--fallback-b2: ${palette['base-200']};
--fallback-b3: ${palette['base-300']};
--fallback-bc: ${palette['base-content']};
--fallback-p: ${palette.primary};
--fallback-pc: ${getContrastHex(palette.primary)};
--fallback-s: ${palette.secondary};
--fallback-sc: ${getContrastHex(palette.secondary)};
--fallback-a: ${palette.accent};
--fallback-ac: ${getContrastHex(palette.accent)};
--fallback-n: ${palette.neutral};
--fallback-nc: ${palette['neutral-content']};
--fallback-in: #ff0000;
--fallback-inc: #ffffff;
--fallback-su: #00ff00;
--fallback-suc: #000000;
--fallback-wa: #ffff00;
--fallback-wac: #000000;
--fallback-er: #ff8000;
--fallback-erc: #000000;
`;
return colors + (fallbackIncluded ? fallbackColors : '');
};
export const applyCustomTheme = (customTheme: CustomTheme) => {
const lightPalette = generateLightPalette(customTheme.colors.light);
const darkPalette = generateDarkPalette(customTheme.colors.dark);
export const applyCustomTheme = (
customTheme?: CustomTheme,
themeName?: string,
fallbackIncluded = false,
) => {
if (!customTheme && !themeName) return;
const lightThemeName = `${customTheme.name}-light`;
const darkThemeName = `${customTheme.name}-dark`;
const lightThemeName = customTheme ? `${customTheme.name}-light` : `${themeName}-light`;
const darkThemeName = customTheme ? `${customTheme.name}-dark` : `${themeName}-dark`;
console.log('themeName', themeName);
const lightPalette = customTheme
? generateLightPalette(customTheme.colors.light)
: (themes.find((t) => t.name === themeName) || themes[0]!).colors.light;
const darkPalette = customTheme
? generateDarkPalette(customTheme.colors.dark)
: (themes.find((t) => t.name === themeName) || themes[0]!).colors.dark;
const css = `
[data-theme="${lightThemeName}"] {
${generateCustomThemeVariables(lightPalette)}
${generateCustomThemeVariables(lightPalette, fallbackIncluded)}
}
[data-theme="${darkThemeName}"] {
${generateCustomThemeVariables(darkPalette)}
${generateCustomThemeVariables(darkPalette, fallbackIncluded)}
}
:root {
@@ -213,7 +255,7 @@ export const applyCustomTheme = (customTheme: CustomTheme) => {
`;
const styleElement = document.createElement('style');
styleElement.id = `theme-${lightThemeName}-styles`;
styleElement.id = `theme-${customTheme ? customTheme.name : themeName}-styles`;
styleElement.textContent = css;
const existingStyle = document.getElementById(styleElement.id);
+4
View File
@@ -38,3 +38,7 @@ export function hexToOklch(hexColor: string): string {
export const getContrastOklch = (hexColor: string): string => {
return tinycolor(hexColor).isDark() ? '100% 0 0' : '0% 0 0';
};
export const getContrastHex = (hex: string): string => {
return tinycolor(hex).isDark() ? '#FFFFFF' : '#000000';
};
+7 -1
View File
@@ -1,6 +1,6 @@
import { AppService } from '@/types/system';
export const parseWebViewVersion = (appService: AppService | null): string => {
export const parseWebViewInfo = (appService: AppService | null): string => {
const ua = navigator.userAgent;
if (appService?.isAndroidApp) {
@@ -63,3 +63,9 @@ export const parseWebViewVersion = (appService: AppService | null): string => {
return 'Unknown';
}
};
export const parseWebViewVersion = (appService: AppService | null): number => {
const webViewInfo = parseWebViewInfo(appService);
const versionMatch = webViewInfo.match(/([0-9]+)\./);
return versionMatch ? parseFloat(versionMatch[1]!) : 0;
};