diff --git a/apps/readest-app/src/app/reader/components/BooksGrid.tsx b/apps/readest-app/src/app/reader/components/BooksGrid.tsx index a108122c..8a023953 100644 --- a/apps/readest-app/src/app/reader/components/BooksGrid.tsx +++ b/apps/readest-app/src/app/reader/components/BooksGrid.tsx @@ -29,7 +29,8 @@ interface BooksGridProps { const BooksGrid: React.FC = ({ bookKeys, onCloseBook }) => { const { appService } = useEnv(); const { getConfig, getBookData } = useBookDataStore(); - const { getProgress, getViewState, getViewSettings, hoveredBookKey } = useReaderStore(); + const { getProgress, getViewState, getViewSettings } = useReaderStore(); + const { getGridInsets, setGridInsets, hoveredBookKey } = useReaderStore(); const { isSideBarVisible, sideBarBookKey } = useSidebarStore(); const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useSettingsStore(); @@ -45,6 +46,21 @@ const BooksGrid: React.FC = ({ bookKeys, onCloseBook }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [sideBarBookKey]); + useEffect(() => { + if (!screenInsets) return; + bookKeys.forEach((bookKey, index) => { + const { top, right, bottom, left } = getInsetEdges(index, bookKeys.length, aspectRatio); + const gridInsets = { + top: top ? screenInsets.top : 0, + right: right ? screenInsets.right : 0, + bottom: bottom ? screenInsets.bottom : 0, + left: left ? screenInsets.left : 0, + }; + setGridInsets(bookKey, gridInsets); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bookKeys, screenInsets]); + if (!screenInsets) return null; return ( @@ -56,26 +72,18 @@ const BooksGrid: React.FC = ({ bookKeys, onCloseBook }) => { }} > {bookKeys.map((bookKey, index) => { - const { top, right, bottom, left } = getInsetEdges(index, bookKeys.length, aspectRatio); const bookData = getBookData(bookKey); const config = getConfig(bookKey); const progress = getProgress(bookKey); const viewSettings = getViewSettings(bookKey); + const gridInsets = getGridInsets(bookKey); const { book, bookDoc } = bookData || {}; - if (!book || !config || !bookDoc || !viewSettings) return null; + if (!book || !config || !bookDoc || !viewSettings || !gridInsets) return null; const { section, pageinfo, timeinfo, sectionLabel } = progress || {}; const isBookmarked = getViewState(bookKey)?.ribbonVisible; const horizontalGapPercent = viewSettings.gapPercent; const viewInsets = getViewInsets(viewSettings); - // insets for the grid cell - const gridInsets = { - top: top ? screenInsets.top : 0, - right: right ? screenInsets.right : 0, - bottom: bottom ? screenInsets.bottom : 0, - left: left ? screenInsets.left : 0, - }; - // insets for the content inside the grid cell const contentInsets = { top: gridInsets.top + viewInsets.top, right: gridInsets.right + viewInsets.right, @@ -110,7 +118,7 @@ const BooksGrid: React.FC = ({ bookKeys, onCloseBook }) => { bookKey={bookKey} bookDoc={bookDoc} config={config} - insets={contentInsets} + contentInsets={contentInsets} /> {viewSettings.vertical && viewSettings.scrolled && ( <> diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index fdec9f2e..4085057f 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { BookDoc, getDirection } from '@/libs/document'; import { BookConfig } from '@/types/book'; import { FoliateView, wrappedFoliateView } from '@/types/view'; +import { Insets } from '@/types/misc'; import { useEnv } from '@/context/EnvContext'; import { useThemeStore } from '@/store/themeStore'; import { useReaderStore } from '@/store/readerStore'; @@ -38,8 +39,8 @@ const FoliateViewer: React.FC<{ bookKey: string; bookDoc: BookDoc; config: BookConfig; - insets: { top: number; right: number; bottom: number; left: number }; -}> = ({ bookKey, bookDoc, config, insets }) => { + contentInsets: Insets; +}> = ({ bookKey, bookDoc, config, contentInsets: insets }) => { const { getView, setView: setFoliateView, setProgress } = useReaderStore(); const { getViewSettings, setViewSettings } = useReaderStore(); const { getParallels } = useParallelViewStore(); @@ -248,11 +249,16 @@ const FoliateViewer: React.FC<{ const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder; const showDoubleBorderHeader = showDoubleBorder && viewSettings.showHeader; const showDoubleBorderFooter = showDoubleBorder && viewSettings.showFooter; + const showTopHeader = viewSettings.showHeader && !viewSettings.vertical; + const showBottomFooter = viewSettings.showFooter && !viewSettings.vertical; + + const moreTopInset = showTopHeader ? Math.max(0, 44 - insets.top) : 0; + const moreBottomInset = showBottomFooter ? Math.max(0, 44 - insets.bottom) : 0; const moreRightInset = showDoubleBorderHeader ? 32 : 0; const moreLeftInset = showDoubleBorderFooter ? 32 : 0; - viewRef.current?.renderer.setAttribute('margin-top', `${insets.top}px`); + viewRef.current?.renderer.setAttribute('margin-top', `${insets.top + moreTopInset}px`); viewRef.current?.renderer.setAttribute('margin-right', `${insets.right + moreRightInset}px`); - viewRef.current?.renderer.setAttribute('margin-bottom', `${insets.bottom}px`); + viewRef.current?.renderer.setAttribute('margin-bottom', `${insets.bottom + moreBottomInset}px`); viewRef.current?.renderer.setAttribute('margin-left', `${insets.left + moreLeftInset}px`); viewRef.current?.renderer.setAttribute('gap', `${viewSettings.gapPercent}%`); if (viewSettings.scrolled) { diff --git a/apps/readest-app/src/app/reader/components/HintInfo.tsx b/apps/readest-app/src/app/reader/components/HintInfo.tsx index 57d93618..4e6163f9 100644 --- a/apps/readest-app/src/app/reader/components/HintInfo.tsx +++ b/apps/readest-app/src/app/reader/components/HintInfo.tsx @@ -1,6 +1,8 @@ import clsx from 'clsx'; import React, { useEffect, useRef } from 'react'; import { Insets } from '@/types/misc'; +import { useEnv } from '@/context/EnvContext'; +import { useThemeStore } from '@/store/themeStore'; import { eventDispatcher } from '@/utils/event'; interface SectionInfoProps { @@ -22,6 +24,13 @@ const HintInfo: React.FC = ({ contentInsets, gridInsets, }) => { + const { appService } = useEnv(); + const { systemUIVisible, statusBarHeight } = useThemeStore(); + const topInset = Math.max( + gridInsets.top, + appService?.isAndroidApp && systemUIVisible ? statusBarHeight / 2 : 0, + ); + const [hintMessage, setHintMessage] = React.useState(null); const hintTimeout = useRef(2000); const dismissTimeout = useRef | null>(null); @@ -57,7 +66,7 @@ const HintInfo: React.FC = ({ hintMessage ? 'bg-base-100' : 'bg-transparent', )} style={{ - height: `${gridInsets.top}px`, + height: `${topInset}px`, }} />
= ({ width: showDoubleBorder ? '30px' : `${horizontalGap}%`, } : { - top: `${gridInsets.top}px`, + top: `${topInset}px`, insetInlineEnd: `calc(${horizontalGap / 2}% + ${contentInsets.right}px)`, } } diff --git a/apps/readest-app/src/app/reader/components/SectionInfo.tsx b/apps/readest-app/src/app/reader/components/SectionInfo.tsx index ff7532b3..de726634 100644 --- a/apps/readest-app/src/app/reader/components/SectionInfo.tsx +++ b/apps/readest-app/src/app/reader/components/SectionInfo.tsx @@ -1,6 +1,8 @@ import clsx from 'clsx'; import React from 'react'; import { Insets } from '@/types/misc'; +import { useEnv } from '@/context/EnvContext'; +import { useThemeStore } from '@/store/themeStore'; interface SectionInfoProps { section?: string; @@ -21,6 +23,13 @@ const SectionInfo: React.FC = ({ contentInsets, gridInsets, }) => { + const { appService } = useEnv(); + const { systemUIVisible, statusBarHeight } = useThemeStore(); + const topInset = Math.max( + gridInsets.top, + appService?.isAndroidApp && systemUIVisible ? statusBarHeight / 2 : 0, + ); + return ( <>
= ({ isScrolled && !isVertical && 'bg-base-100', )} style={{ - height: `${gridInsets.top}px`, + height: `${topInset}px`, }} />
= ({ height: `calc(100% - ${contentInsets.top + contentInsets.bottom}px)`, } : { - top: `${gridInsets.top}px`, + top: `${topInset}px`, insetInlineStart: `calc(${horizontalGap / 2}% + ${contentInsets.left}px)`, width: `calc(100% - ${contentInsets.left + contentInsets.right}px)`, } diff --git a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx index b8a53f4e..91705984 100644 --- a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx @@ -18,11 +18,12 @@ import NumberInput from './NumberInput'; const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const _ = useTranslation(); const { envConfig } = useEnv(); - const { getView, getViewSettings, setViewSettings } = useReaderStore(); + const { getView, getViewSettings, getGridInsets, setViewSettings } = useReaderStore(); const { getBookData } = useBookDataStore(); const view = getView(bookKey); const bookData = getBookData(bookKey)!; const viewSettings = getViewSettings(bookKey)!; + const gridInsets = getGridInsets(bookKey)!; const [paragraphMargin, setParagraphMargin] = useState(viewSettings.paragraphMargin!); const [lineHeight, setLineHeight] = useState(viewSettings.lineHeight!); @@ -273,7 +274,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { useEffect(() => { if (showHeader === viewSettings.showHeader) return; if (showHeader && !viewSettings.vertical) { - viewSettings.marginTopPx = Math.max(viewSettings.marginTopPx, 44); + const minMarginTop = Math.max(0, Math.round((44 - gridInsets.top) / 4) * 4); + viewSettings.marginTopPx = Math.max(viewSettings.marginTopPx, minMarginTop); setMarginTopPx(viewSettings.marginTopPx); setViewSettings(bookKey, viewSettings); } @@ -285,7 +287,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { useEffect(() => { if (showFooter === viewSettings.showFooter) return; if (showFooter && !viewSettings.vertical) { - viewSettings.marginBottomPx = Math.max(viewSettings.marginBottomPx, 44); + const minMarginBottom = Math.max(0, Math.round((44 - gridInsets.bottom) / 4) * 4); + viewSettings.marginBottomPx = Math.max(viewSettings.marginBottomPx, minMarginBottom); setMarginBottomPx(viewSettings.marginBottomPx); setViewSettings(bookKey, viewSettings); } @@ -462,7 +465,11 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { label={_('Top Margin (px)')} value={showHeader && !isVertical ? marginTopPx : compactMarginTopPx} onChange={showHeader && !isVertical ? setMarginTopPx : setCompactMarginTopPx} - min={0} + min={ + showHeader && !isVertical + ? Math.max(0, Math.round((44 - gridInsets.top) / 4) * 4) + : 0 + } max={88} step={4} /> @@ -470,7 +477,11 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { label={_('Bottom Margin (px)')} value={showFooter && !isVertical ? marginBottomPx : compactMarginBottomPx} onChange={showFooter && !isVertical ? setMarginBottomPx : setCompactMarginBottomPx} - min={0} + min={ + showFooter && !isVertical + ? Math.max(0, Math.round((44 - gridInsets.bottom) / 4) * 4) + : 0 + } max={88} step={4} /> diff --git a/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx b/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx index 03a70632..ff813f98 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/BooknoteItem.tsx @@ -133,7 +133,9 @@ const BooknoteItem: React.FC = ({ bookKey, item }) => { >
- {dayjs(item.createdAt).fromNow()} + + {dayjs(item.createdAt).fromNow()} +
{item.note && ( diff --git a/apps/readest-app/src/components/Dialog.tsx b/apps/readest-app/src/components/Dialog.tsx index f982d76b..23844e87 100644 --- a/apps/readest-app/src/components/Dialog.tsx +++ b/apps/readest-app/src/components/Dialog.tsx @@ -192,7 +192,7 @@ const Dialog: React.FC = ({ >
-
+
{header ? ( header ) : ( diff --git a/apps/readest-app/src/store/readerStore.ts b/apps/readest-app/src/store/readerStore.ts index 9ab391e5..227ef16a 100644 --- a/apps/readest-app/src/store/readerStore.ts +++ b/apps/readest-app/src/store/readerStore.ts @@ -8,6 +8,7 @@ import { ViewSettings, TimeInfo, } from '@/types/book'; +import { Insets } from '@/types/misc'; import { EnvConfigType } from '@/services/environment'; import { FoliateView } from '@/types/view'; import { DocumentLoader, TOCItem } from '@/libs/document'; @@ -27,6 +28,7 @@ interface ViewState { progress: BookProgress | null; ribbonVisible: boolean; ttsEnabled: boolean; + gridInsets: Insets | null; /* View settings for the view: generally view settings have a hierarchy of global settings < book settings < view settings view settings for primary view are saved to book config which is persisted to config file @@ -67,6 +69,8 @@ interface ReaderStore { ) => Promise; clearViewState: (key: string) => void; getViewState: (key: string) => ViewState | null; + getGridInsets: (key: string) => Insets | null; + setGridInsets: (key: string, insets: Insets | null) => void; } export const useReaderStore = create((set, get) => ({ @@ -114,6 +118,7 @@ export const useReaderStore = create((set, get) => ({ progress: null, ribbonVisible: false, ttsEnabled: false, + gridInsets: null, viewSettings: null, }, }, @@ -159,6 +164,7 @@ export const useReaderStore = create((set, get) => ({ progress: null, ribbonVisible: false, ttsEnabled: false, + gridInsets: null, viewSettings: { ...globalViewSettings, ...configViewSettings }, }, }, @@ -178,6 +184,7 @@ export const useReaderStore = create((set, get) => ({ progress: null, ribbonVisible: false, ttsEnabled: false, + gridInsets: null, viewSettings: null, }, }, @@ -306,4 +313,16 @@ export const useReaderStore = create((set, get) => ({ }, }, })), + + getGridInsets: (key: string) => get().viewStates[key]?.gridInsets || null, + setGridInsets: (key: string, insets: Insets | null) => + set((state) => ({ + viewStates: { + ...state.viewStates, + [key]: { + ...state.viewStates[key]!, + gridInsets: insets, + }, + }, + })), }));