diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx
index d8270b76..d0ce76c3 100644
--- a/apps/readest-app/src/app/library/page.tsx
+++ b/apps/readest-app/src/app/library/page.tsx
@@ -39,8 +39,8 @@ const LibraryPage = () => {
checkOpenWithBooks,
clearOpenWithBooks,
} = useLibraryStore();
- useTheme();
const _ = useTranslation();
+ const { updateAppTheme } = useTheme();
const { setSettings, saveSettings } = useSettingsStore();
const [loading, setLoading] = useState(false);
const isInitiating = useRef(false);
@@ -49,6 +49,7 @@ const LibraryPage = () => {
const demoBooks = useDemoBooks();
useEffect(() => {
+ updateAppTheme('base-200');
const doAppUpdates = async () => {
if (hasUpdater()) {
await checkForAppUpdates(_);
@@ -237,7 +238,7 @@ const LibraryPage = () => {
return (
diff --git a/apps/readest-app/src/app/reader/components/FooterBar.tsx b/apps/readest-app/src/app/reader/components/FooterBar.tsx
index 96997f4c..95adef94 100644
--- a/apps/readest-app/src/app/reader/components/FooterBar.tsx
+++ b/apps/readest-app/src/app/reader/components/FooterBar.tsx
@@ -8,7 +8,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { useTranslation } from '@/hooks/useTranslation';
import { eventDispatcher } from '@/utils/event';
-import { isTauriAppPlatform } from '@/services/environment';
+import { isPWA, isTauriAppPlatform } from '@/services/environment';
import Button from '@/components/Button';
interface FooterBarProps {
@@ -62,6 +62,7 @@ const FooterBar: React.FC
= ({ bookKey, pageinfo, isHoveredAnim
className={clsx(
'footer-bar absolute bottom-0 z-10 flex h-12 w-full items-center gap-x-4 px-4',
'shadow-xs bg-base-100 transition-opacity duration-300',
+ isPWA() ? 'pb-[env(safe-area-inset-bottom)]' : '',
isTauriAppPlatform() && 'rounded-window-bottom-right',
!isSideBarVisible && isTauriAppPlatform() && 'rounded-window-bottom-left',
isHoveredAnim && 'hover-bar-anim',
diff --git a/apps/readest-app/src/app/reader/components/Reader.tsx b/apps/readest-app/src/app/reader/components/Reader.tsx
index a9935666..e2ac88d4 100644
--- a/apps/readest-app/src/app/reader/components/Reader.tsx
+++ b/apps/readest-app/src/app/reader/components/Reader.tsx
@@ -5,6 +5,7 @@ import * as React from 'react';
import { useEffect, Suspense, useRef } from 'react';
import { useEnv } from '@/context/EnvContext';
+import { useTheme } from '@/hooks/useTheme';
import { useLibraryStore } from '@/store/libraryStore';
import { useSettingsStore } from '@/store/settingsStore';
import { isTauriAppPlatform } from '@/services/environment';
@@ -18,7 +19,10 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
const { library, setLibrary } = useLibraryStore();
const isInitiating = useRef(false);
+ const { updateAppTheme } = useTheme();
+
useEffect(() => {
+ updateAppTheme('base-100');
if (isInitiating.current) return;
isInitiating.current = true;
const initLibrary = async () => {
diff --git a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx
index acbd1f2b..b333039e 100644
--- a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx
+++ b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx
@@ -7,6 +7,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { useNotebookStore } from '@/store/notebookStore';
import { useTranslation } from '@/hooks/useTranslation';
+import { useTheme } from '@/hooks/useTheme';
import { useEnv } from '@/context/EnvContext';
import { TextSelection } from '@/utils/sel';
import { BookNote } from '@/types/book';
@@ -23,6 +24,7 @@ const MAX_NOTEBOOK_WIDTH = 0.45;
const Notebook: React.FC = ({}) => {
const _ = useTranslation();
+ const { updateAppTheme } = useTheme();
const { envConfig } = useEnv();
const { settings } = useSettingsStore();
const { sideBarBookKey } = useSidebarStore();
@@ -41,6 +43,15 @@ const Notebook: React.FC = ({}) => {
}
};
+ useEffect(() => {
+ if (isNotebookVisible) {
+ updateAppTheme('base-200');
+ } else {
+ updateAppTheme('base-100');
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isNotebookVisible]);
+
useEffect(() => {
setNotebookWidth(settings.globalReadSettings.notebookWidth);
setNotebookPin(settings.globalReadSettings.isNotebookPinned);
diff --git a/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx b/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx
index 5899f6bb..1d14a1d8 100644
--- a/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx
+++ b/apps/readest-app/src/app/reader/components/sidebar/BookCard.tsx
@@ -22,7 +22,7 @@ const BookCard = ({ book }: { book: Book }) => {
alt={_('Book Cover')}
width={56}
height={80}
- className='mr-4 aspect-auto max-h-20 w-[15%] max-w-14 rounded-sm object-cover shadow-md'
+ className='mr-4 aspect-auto max-h-16 w-[15%] max-w-12 rounded-sm object-cover shadow-md'
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
diff --git a/apps/readest-app/src/app/reader/components/sidebar/Content.tsx b/apps/readest-app/src/app/reader/components/sidebar/Content.tsx
index 92fb8b8d..d2dd8d64 100644
--- a/apps/readest-app/src/app/reader/components/sidebar/Content.tsx
+++ b/apps/readest-app/src/app/reader/components/sidebar/Content.tsx
@@ -53,7 +53,7 @@ const SidebarContent: React.FC<{
const handleTabChange = (tab: string) => {
setFade(true);
const timeout = setTimeout(() => {
- setFade(false)
+ setFade(false);
setTargetTab(tab);
setConfig(sideBarBookKey!, config);
clearTimeout(timeout);
@@ -76,7 +76,7 @@ const SidebarContent: React.FC<{
ref={scrollContainerRef}
className={clsx(
'scroll-container overflow-y-auto transition-opacity duration-300 ease-in-out',
- { 'opacity-0': fade, 'opacity-100': !fade }
+ { 'opacity-0': fade, 'opacity-100': !fade },
)}
>
{targetTab === 'toc' && bookDoc.toc && (
diff --git a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx
index 4ddd2970..cabe2360 100644
--- a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx
+++ b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx
@@ -8,6 +8,7 @@ import { useSidebarStore } from '@/store/sidebarStore';
import { BookSearchResult } from '@/types/book';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
+import { useTheme } from '@/hooks/useTheme';
import SidebarHeader from './Header';
import SidebarContent from './Content';
import BookCard from './BookCard';
@@ -23,6 +24,7 @@ const MAX_SIDEBAR_WIDTH = 0.45;
const SideBar: React.FC<{
onGoToLibrary: () => void;
}> = ({ onGoToLibrary }) => {
+ const { updateAppTheme } = useTheme();
const { settings } = useSettingsStore();
const { sideBarBookKey } = useSidebarStore();
const { getBookData } = useBookDataStore();
@@ -57,6 +59,15 @@ const SideBar: React.FC<{
}
};
+ useEffect(() => {
+ if (isSideBarVisible) {
+ updateAppTheme('base-200');
+ } else {
+ updateAppTheme('base-100');
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isSideBarVisible]);
+
useEffect(() => {
eventDispatcher.on('search', onSearchEvent);
eventDispatcher.on('navigate', onNavigateEvent);
diff --git a/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx b/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx
index 6433f254..0903fa6a 100644
--- a/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx
+++ b/apps/readest-app/src/app/reader/components/sidebar/TabNavigation.tsx
@@ -3,6 +3,7 @@ import React from 'react';
import { MdToc, MdEditNote, MdBookmarkBorder } from 'react-icons/md';
import { useTranslation } from '@/hooks/useTranslation';
+import { isPWA } from '@/services/environment';
const TabNavigation: React.FC<{
activeTab: string;
@@ -13,7 +14,12 @@ const TabNavigation: React.FC<{
const tabs = ['toc', 'annotations', 'bookmarks'];
return (
-
+
{
const [panelPosition, setPanelPosition] = useState
();
const [trianglePosition, setTrianglePosition] = useState();
+ const popupWidth = useResponsiveSize(POPUP_WIDTH);
+ const popupHeight = useResponsiveSize(POPUP_HEIGHT);
+ const popupPadding = useResponsiveSize(POPUP_PADDING);
+
const iconRef = useRef(null);
const ttsControllerRef = useRef(null);
@@ -212,9 +219,9 @@ const TTSControl = () => {
const popupPos = getPopupPosition(
trianglePos,
windowRect,
- POPUP_WIDTH,
- POPUP_HEIGHT,
- POPUP_PADDING,
+ popupWidth,
+ popupHeight,
+ popupPadding,
);
setPanelPosition(popupPos);
@@ -241,14 +248,20 @@ const TTSControl = () => {
/>
)}
{showIndicator && (
-
+
)}
{showPanel && panelPosition && trianglePosition && (
{
bg: defaultPalette['base-100'],
fg: defaultPalette['base-content'],
primary: defaultPalette.primary,
+ palette: defaultPalette,
};
});
@@ -83,12 +84,18 @@ export const useTheme = () => {
if (isWebAppPlatform()) {
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', bg);
}
- setThemeCode({ bg, fg, primary });
+ setThemeCode({ bg, fg, primary, palette });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [themeMode, themeColor, isDarkMode]);
const updateThemeMode = (mode: ThemeMode) => setThemeMode(mode);
const updateThemeColor = (color: string) => setThemeColor(color);
+ const updateAppTheme = (color: keyof Palette) => {
+ if (isWebAppPlatform()) {
+ const { palette } = themeCode;
+ document.querySelector('meta[name="theme-color"]')?.setAttribute('content', palette[color]);
+ }
+ };
return {
themeMode,
@@ -97,5 +104,6 @@ export const useTheme = () => {
isDarkMode,
updateThemeMode,
updateThemeColor,
+ updateAppTheme,
};
};
diff --git a/apps/readest-app/src/services/environment.ts b/apps/readest-app/src/services/environment.ts
index 1ba2edf0..b03cca44 100644
--- a/apps/readest-app/src/services/environment.ts
+++ b/apps/readest-app/src/services/environment.ts
@@ -13,6 +13,7 @@ export const isWebAppPlatform = () => process.env['NEXT_PUBLIC_APP_PLATFORM'] ==
export const hasUpdater = () =>
window.__READEST_UPDATER_ACCESS === true && !process.env['NEXT_PUBLIC_DISABLE_UPDATER'];
export const hasCli = () => window.__READEST_CLI_ACCESS === true;
+export const isPWA = () => window.matchMedia('(display-mode: standalone)').matches;
// Dev API only in development mode and web platform
// with command `pnpm dev-web`
diff --git a/apps/readest-app/src/styles/themes.ts b/apps/readest-app/src/styles/themes.ts
index 854f3805..a57aebea 100644
--- a/apps/readest-app/src/styles/themes.ts
+++ b/apps/readest-app/src/styles/themes.ts
@@ -7,6 +7,18 @@ type BaseColor = {
primary: string;
};
+export type Palette = {
+ 'base-100': string;
+ 'base-200': string;
+ 'base-300': string;
+ 'base-content': string;
+ neutral: string;
+ 'neutral-content': string;
+ primary: string;
+ secondary: string;
+ accent: string;
+};
+
const generateLightPalette = ({ bg, fg, primary }: BaseColor) => {
return {
'base-100': bg, // Main background
@@ -18,7 +30,7 @@ const generateLightPalette = ({ bg, fg, primary }: BaseColor) => {
primary: primary,
secondary: tinycolor(primary).lighten(20).toHexString(), // Lighter secondary
accent: tinycolor(primary).analogous()[1]!.toHexString(), // Analogous accent
- };
+ } as Palette;
};
const generateDarkPalette = ({ bg, fg, primary }: BaseColor) => {
@@ -32,7 +44,7 @@ const generateDarkPalette = ({ bg, fg, primary }: BaseColor) => {
primary: primary,
secondary: tinycolor(primary).darken(20).toHexString(), // Darker secondary
accent: tinycolor(primary).triad()[1]!.toHexString(), // Triad accent
- };
+ } as Palette;
};
export const themes = [
diff --git a/apps/readest-app/src/utils/style.ts b/apps/readest-app/src/utils/style.ts
index 9dd061bc..d3e32a95 100644
--- a/apps/readest-app/src/utils/style.ts
+++ b/apps/readest-app/src/utils/style.ts
@@ -5,6 +5,7 @@ import {
FALLBACK_FONTS,
} from '@/services/constants';
import { ViewSettings } from '@/types/book';
+import { Palette } from '@/styles/themes';
import fontfacesCSS from '!!raw-loader!../styles/fonts.css';
@@ -193,6 +194,7 @@ export interface ThemeCode {
bg: string;
fg: string;
primary: string;
+ palette: Palette;
}
export const getStyles = (viewSettings: ViewSettings, themeCode: ThemeCode) => {