Fix reader page scaling with applied safe area inset
The safe area inset wasn't applied everywhere, which potentially caused the scrollbar to appear while the pages should be fitting the screen fixes #1102
This commit is contained in:
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
|
|
||||||
## [Unreleased] (Preview)
|
## [Unreleased] (Preview)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- (**Reader**) Fix scrollbar appearing with "fit to widt/height/screen" page scale mode and applied safe area insets
|
||||||
|
|
||||||
## [20260509.01] (r3147) - 2026-05-09
|
## [20260509.01] (r3147) - 2026-05-09
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ export interface ReaderPagerProps
|
|||||||
| 'customFilter'
|
| 'customFilter'
|
||||||
| 'shouldStretchPage'
|
| 'shouldStretchPage'
|
||||||
| 'readerWidth'
|
| 'readerWidth'
|
||||||
|
| 'safeAreaInset'
|
||||||
>,
|
>,
|
||||||
Pick<NavbarContextType, 'readerNavBarWidth'> {
|
Pick<NavbarContextType, 'readerNavBarWidth'> {
|
||||||
onLoad?: (pagesIndex: number, url: string, isPrimary?: boolean) => void;
|
onLoad?: (pagesIndex: number, url: string, isPrimary?: boolean) => void;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const BaseReaderRGBAFilter = ({ readerNavBarWidth }: Pick<NavbarContextType, 're
|
|||||||
value: { red, green, blue, alpha, blendMode },
|
value: { red, green, blue, alpha, blendMode },
|
||||||
enabled,
|
enabled,
|
||||||
} = useReaderSettingsStore((state) => state.customFilter.rgba);
|
} = useReaderSettingsStore((state) => state.customFilter.rgba);
|
||||||
|
const safeAreaInset = useReaderSettingsStore('safeAreaInset');
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return null;
|
return null;
|
||||||
@@ -26,10 +27,10 @@ const BaseReaderRGBAFilter = ({ readerNavBarWidth }: Pick<NavbarContextType, 're
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: 0,
|
top: safeAreaInset.top ? 'env(safe-area-inset-top)' : 0,
|
||||||
left: readerNavBarWidth,
|
left: `calc(${readerNavBarWidth}px - ${safeAreaInset.left ? 'env(safe-area-inset-left)' : '0px'})`,
|
||||||
right: 0,
|
right: safeAreaInset.right ? 'env(safe-area-inset-right)' : 0,
|
||||||
bottom: 0,
|
bottom: safeAreaInset.bottom ? 'env(safe-area-inset-bottom)' : 0,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
background: `rgba(${red} ${green} ${blue} / ${alpha}%)`,
|
background: `rgba(${red} ${green} ${blue} / ${alpha}%)`,
|
||||||
mixBlendMode: `${blendMode}`,
|
mixBlendMode: `${blendMode}`,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
IReaderSettingsWithDefaultFlag,
|
IReaderSettingsWithDefaultFlag,
|
||||||
ProgressBarPositionAutoVertical,
|
ProgressBarPositionAutoVertical,
|
||||||
ReaderPagerProps,
|
ReaderPagerProps,
|
||||||
|
SafeAreaInset,
|
||||||
} from '@/features/reader/Reader.types.ts';
|
} from '@/features/reader/Reader.types.ts';
|
||||||
import { ProgressBarPosition, ReaderPageScaleMode, ReadingMode } from '@/features/reader/Reader.types.ts';
|
import { ProgressBarPosition, ReaderPageScaleMode, ReadingMode } from '@/features/reader/Reader.types.ts';
|
||||||
import type { MangaGenreInfo, MangaSourceNameInfo } from '@/features/manga/Manga.types.ts';
|
import type { MangaGenreInfo, MangaSourceNameInfo } from '@/features/manga/Manga.types.ts';
|
||||||
@@ -20,6 +21,7 @@ import { ReaderPagedPager } from '@/features/reader/viewer/pager/components/Read
|
|||||||
import { ReaderDoublePagedPager } from '@/features/reader/viewer/pager/components/ReaderDoublePagedPager.tsx';
|
import { ReaderDoublePagedPager } from '@/features/reader/viewer/pager/components/ReaderDoublePagedPager.tsx';
|
||||||
import { ReaderVerticalPager } from '@/features/reader/viewer/pager/components/ReaderVerticalPager.tsx';
|
import { ReaderVerticalPager } from '@/features/reader/viewer/pager/components/ReaderVerticalPager.tsx';
|
||||||
import { ReaderHorizontalPager } from '@/features/reader/viewer/pager/components/ReaderHorizontalPager.tsx';
|
import { ReaderHorizontalPager } from '@/features/reader/viewer/pager/components/ReaderHorizontalPager.tsx';
|
||||||
|
import { ScrollDirection } from '@/base/Base.types.ts';
|
||||||
|
|
||||||
export const isOffsetDoubleSpreadPagesEditable = (readingMode: IReaderSettings['readingMode']): boolean =>
|
export const isOffsetDoubleSpreadPagesEditable = (readingMode: IReaderSettings['readingMode']): boolean =>
|
||||||
readingMode === ReadingMode.DOUBLE_PAGE;
|
readingMode === ReadingMode.DOUBLE_PAGE;
|
||||||
@@ -96,3 +98,13 @@ export const getProgressBarPosition = (
|
|||||||
|
|
||||||
return ProgressBarPosition.BOTTOM;
|
return ProgressBarPosition.BOTTOM;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getSafeAreaInsets = (
|
||||||
|
safeAreaInset: SafeAreaInset,
|
||||||
|
direction: Exclude<ScrollDirection, ScrollDirection.XY>,
|
||||||
|
): string[] => [
|
||||||
|
...(direction === ScrollDirection.Y && safeAreaInset.top ? ['env(safe-area-inset-top)'] : []),
|
||||||
|
...(direction === ScrollDirection.X && safeAreaInset.right ? ['env(safe-area-inset-right)'] : []),
|
||||||
|
...(direction === ScrollDirection.Y && safeAreaInset.bottom ? ['env(safe-area-inset-bottom)'] : []),
|
||||||
|
...(direction === ScrollDirection.X && safeAreaInset.left ? ['env(safe-area-inset-left)'] : []),
|
||||||
|
];
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const BaseReaderChapterViewer = ({
|
|||||||
retryFailedPagesKeyPrefix,
|
retryFailedPagesKeyPrefix,
|
||||||
readingMode,
|
readingMode,
|
||||||
readerWidth,
|
readerWidth,
|
||||||
|
safeAreaInset,
|
||||||
pageScaleMode,
|
pageScaleMode,
|
||||||
shouldOffsetDoubleSpreads,
|
shouldOffsetDoubleSpreads,
|
||||||
readingDirection,
|
readingDirection,
|
||||||
@@ -427,6 +428,7 @@ const BaseReaderChapterViewer = ({
|
|||||||
customFilter={customFilter}
|
customFilter={customFilter}
|
||||||
shouldStretchPage={shouldStretchPage}
|
shouldStretchPage={shouldStretchPage}
|
||||||
readerWidth={readerWidth}
|
readerWidth={readerWidth}
|
||||||
|
safeAreaInset={safeAreaInset}
|
||||||
readerNavBarWidth={readerNavBarWidth}
|
readerNavBarWidth={readerNavBarWidth}
|
||||||
isPreloadMode={isPreloadMode}
|
isPreloadMode={isPreloadMode}
|
||||||
resumeMode={resumeMode}
|
resumeMode={resumeMode}
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ const BaseReaderViewer = ({
|
|||||||
shouldStretchPage: state.shouldStretchPage.value,
|
shouldStretchPage: state.shouldStretchPage.value,
|
||||||
isStaticNav: state.isStaticNav,
|
isStaticNav: state.isStaticNav,
|
||||||
}));
|
}));
|
||||||
|
const safeAreaInset = useReaderSettingsStore('safeAreaInset');
|
||||||
const { resumeMode = ReaderResumeMode.START } = useLocation<ReaderOpenChapterLocationState>().state ?? {
|
const { resumeMode = ReaderResumeMode.START } = useLocation<ReaderOpenChapterLocationState>().state ?? {
|
||||||
resumeMode: ReaderResumeMode.START,
|
resumeMode: ReaderResumeMode.START,
|
||||||
};
|
};
|
||||||
@@ -337,6 +338,7 @@ const BaseReaderViewer = ({
|
|||||||
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
|
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
|
||||||
readingMode={readingMode}
|
readingMode={readingMode}
|
||||||
readerWidth={readerWidth}
|
readerWidth={readerWidth}
|
||||||
|
safeAreaInset={safeAreaInset}
|
||||||
pageScaleMode={pageScaleMode}
|
pageScaleMode={pageScaleMode}
|
||||||
shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads}
|
shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads}
|
||||||
readingDirection={readingDirection}
|
readingDirection={readingDirection}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import { applyStyles } from '@/base/utils/ApplyStyles.ts';
|
|||||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||||
import type { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts';
|
import type { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts';
|
||||||
import { useReaderScrollbarStore } from '@/features/reader/stores/ReaderStore.ts';
|
import { useReaderScrollbarStore } from '@/features/reader/stores/ReaderStore.ts';
|
||||||
|
import { ScrollDirection } from '@/base/Base.types.ts';
|
||||||
|
import { getSafeAreaInsets } from '@/features/reader/settings/ReaderSettings.utils.tsx';
|
||||||
|
|
||||||
const getCustomFilterString = (customFilter: ReaderCustomFilter): string =>
|
const getCustomFilterString = (customFilter: ReaderCustomFilter): string =>
|
||||||
Object.keys(customFilter)
|
Object.keys(customFilter)
|
||||||
@@ -63,6 +65,7 @@ const BaseReaderPage = ({
|
|||||||
pageScaleMode,
|
pageScaleMode,
|
||||||
shouldStretchPage,
|
shouldStretchPage,
|
||||||
readerWidth,
|
readerWidth,
|
||||||
|
safeAreaInset,
|
||||||
onLoad,
|
onLoad,
|
||||||
onError,
|
onError,
|
||||||
setRef,
|
setRef,
|
||||||
@@ -70,7 +73,10 @@ const BaseReaderPage = ({
|
|||||||
isLoaded,
|
isLoaded,
|
||||||
...props
|
...props
|
||||||
}: Omit<SpinnerImageProps, 'spinnerStyle' | 'imgStyle' | 'onLoad' | 'onError'> &
|
}: Omit<SpinnerImageProps, 'spinnerStyle' | 'imgStyle' | 'onLoad' | 'onError'> &
|
||||||
Pick<IReaderSettings, 'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth'> &
|
Pick<
|
||||||
|
IReaderSettings,
|
||||||
|
'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth' | 'safeAreaInset'
|
||||||
|
> &
|
||||||
Pick<NavbarContextType, 'readerNavBarWidth'> & {
|
Pick<NavbarContextType, 'readerNavBarWidth'> & {
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
pagesIndex: number;
|
pagesIndex: number;
|
||||||
@@ -115,8 +121,8 @@ const BaseReaderPage = ({
|
|||||||
shouldStretchPage,
|
shouldStretchPage,
|
||||||
pageScaleMode,
|
pageScaleMode,
|
||||||
readerWidth,
|
readerWidth,
|
||||||
readerNavBarWidth + scrollbar.ySize,
|
[readerNavBarWidth, scrollbar.ySize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.X)],
|
||||||
scrollbar.xSize,
|
[scrollbar.xSize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.Y)],
|
||||||
doublePage,
|
doublePage,
|
||||||
isTabletWidth,
|
isTabletWidth,
|
||||||
),
|
),
|
||||||
@@ -132,8 +138,8 @@ const BaseReaderPage = ({
|
|||||||
pageScaleMode,
|
pageScaleMode,
|
||||||
doublePage,
|
doublePage,
|
||||||
readerWidth,
|
readerWidth,
|
||||||
readerNavBarWidth + scrollbar.ySize,
|
[readerNavBarWidth, scrollbar.ySize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.X)],
|
||||||
scrollbar.xSize,
|
[scrollbar.xSize, ...getSafeAreaInsets(safeAreaInset, ScrollDirection.Y)],
|
||||||
),
|
),
|
||||||
filter: getCustomFilterString(customFilter),
|
filter: getCustomFilterString(customFilter),
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
|
|||||||
@@ -56,25 +56,28 @@ const getPageWidthPercentage = (
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const joinOffsetsForCssCalc = (offsets: (number | string)[], operator: '-' | '+' = '-'): string =>
|
||||||
|
offsets.map((offset) => (typeof offset === 'number' ? `${offset}px` : offset)).join(` ${operator} `);
|
||||||
|
|
||||||
export const getImagePlaceholderStyling = (
|
export const getImagePlaceholderStyling = (
|
||||||
readingMode: IReaderSettings['readingMode'],
|
readingMode: IReaderSettings['readingMode'],
|
||||||
shouldStretchPage: IReaderSettings['shouldStretchPage'],
|
shouldStretchPage: IReaderSettings['shouldStretchPage'],
|
||||||
pageScaleMode: IReaderSettings['pageScaleMode'],
|
pageScaleMode: IReaderSettings['pageScaleMode'],
|
||||||
readerWidth: IReaderSettings['readerWidth'],
|
readerWidth: IReaderSettings['readerWidth'],
|
||||||
widthOffset: number,
|
widthOffset: (number | string)[],
|
||||||
heightOffset: number,
|
heightOffset: (number | string)[],
|
||||||
isDoublePage?: boolean,
|
isDoublePage?: boolean,
|
||||||
isTabletWidth?: boolean,
|
isTabletWidth?: boolean,
|
||||||
): CSSObject => {
|
): CSSObject => {
|
||||||
const OVER_9000 = 9000;
|
const OVER_9000 = 9000;
|
||||||
|
|
||||||
const getMaxWidth = (width: string) => `calc(${width} - ${widthOffset}px)`;
|
const getMaxWidth = (width: string) => `calc(${width} - ${joinOffsetsForCssCalc(widthOffset)})`;
|
||||||
const getDesktopWidth = (width: number, readerWidthValue?: number) =>
|
const getDesktopWidth = (width: number, readerWidthValue?: number) =>
|
||||||
getMaxWidth(`${coerceIn(readerWidthValue ?? width, Math.min(width, readerWidthValue ?? OVER_9000), width)}vw`);
|
getMaxWidth(`${coerceIn(readerWidthValue ?? width, Math.min(width, readerWidthValue ?? OVER_9000), width)}vw`);
|
||||||
|
|
||||||
const setReaderWidth = getSetReaderWidth(readerWidth, pageScaleMode);
|
const setReaderWidth = getSetReaderWidth(readerWidth, pageScaleMode);
|
||||||
const fullWidth = getMaxWidth(`${Math.min(100, setReaderWidth ?? OVER_9000)}vw`);
|
const fullWidth = getMaxWidth(`${Math.min(100, setReaderWidth ?? OVER_9000)}vw`);
|
||||||
const fullHeight = `calc(100vh - ${heightOffset}px)`;
|
const fullHeight = `calc(100vh - ${joinOffsetsForCssCalc(heightOffset)})`;
|
||||||
|
|
||||||
const DEFAULT_SINGLE_PAGE_WIDTH = isTabletWidth ? fullWidth : getDesktopWidth(40, setReaderWidth);
|
const DEFAULT_SINGLE_PAGE_WIDTH = isTabletWidth ? fullWidth : getDesktopWidth(40, setReaderWidth);
|
||||||
const DEFAULT_SINGLE_PAGE_HEIGHT = isTabletWidth ? fullHeight : '85vh';
|
const DEFAULT_SINGLE_PAGE_HEIGHT = isTabletWidth ? fullHeight : '85vh';
|
||||||
@@ -151,11 +154,11 @@ const getReaderDimensionStyling = (
|
|||||||
readingMode: IReaderSettings['readingMode'],
|
readingMode: IReaderSettings['readingMode'],
|
||||||
shouldStretchPage: IReaderSettings['shouldStretchPage'],
|
shouldStretchPage: IReaderSettings['shouldStretchPage'],
|
||||||
pageScaleMode: IReaderSettings['pageScaleMode'],
|
pageScaleMode: IReaderSettings['pageScaleMode'],
|
||||||
widthOffset: number,
|
widthOffset: (number | string)[],
|
||||||
heightOffset: number,
|
heightOffset: (number | string)[],
|
||||||
): CSSObject => {
|
): CSSObject => {
|
||||||
const fullWidth = `calc((100vw - ${widthOffset}px) * ${widthPercentage})`;
|
const fullWidth = `calc((100vw - ${joinOffsetsForCssCalc(widthOffset)}) * ${widthPercentage})`;
|
||||||
const fullHeight = `calc(100vh - ${heightOffset}px)`;
|
const fullHeight = `calc(100vh - ${joinOffsetsForCssCalc(heightOffset)})`;
|
||||||
|
|
||||||
switch (pageScaleMode) {
|
switch (pageScaleMode) {
|
||||||
case ReaderPageScaleMode.WIDTH:
|
case ReaderPageScaleMode.WIDTH:
|
||||||
@@ -216,8 +219,8 @@ export const getReaderImageStyling = (
|
|||||||
pageScaleMode: IReaderSettings['pageScaleMode'],
|
pageScaleMode: IReaderSettings['pageScaleMode'],
|
||||||
isDoublePage: boolean,
|
isDoublePage: boolean,
|
||||||
readerWidth: IReaderSettings['readerWidth'],
|
readerWidth: IReaderSettings['readerWidth'],
|
||||||
widthOffset: number,
|
widthOffset: (number | string)[],
|
||||||
heightOffset: number,
|
heightOffset: (number | string)[],
|
||||||
): CSSObject => {
|
): CSSObject => {
|
||||||
const widthPercentage = getPageWidthPercentage(pageScaleMode, isDoublePage, readerWidth, true);
|
const widthPercentage = getPageWidthPercentage(pageScaleMode, isDoublePage, readerWidth, true);
|
||||||
return getReaderDimensionStyling(
|
return getReaderDimensionStyling(
|
||||||
@@ -292,6 +295,7 @@ export const createReaderPage = (
|
|||||||
pageScaleMode: IReaderSettings['pageScaleMode'],
|
pageScaleMode: IReaderSettings['pageScaleMode'],
|
||||||
shouldStretchPage: IReaderSettings['shouldStretchPage'],
|
shouldStretchPage: IReaderSettings['shouldStretchPage'],
|
||||||
readerWidth: IReaderSettings['readerWidth'],
|
readerWidth: IReaderSettings['readerWidth'],
|
||||||
|
safeAreaInset: IReaderSettings['safeAreaInset'],
|
||||||
readerNavBarWidth: NavbarContextType['readerNavBarWidth'],
|
readerNavBarWidth: NavbarContextType['readerNavBarWidth'],
|
||||||
retryKeyPrefix?: string,
|
retryKeyPrefix?: string,
|
||||||
position?: 'left' | 'right',
|
position?: 'left' | 'right',
|
||||||
@@ -322,6 +326,7 @@ export const createReaderPage = (
|
|||||||
pageScaleMode={pageScaleMode}
|
pageScaleMode={pageScaleMode}
|
||||||
shouldStretchPage={shouldStretchPage}
|
shouldStretchPage={shouldStretchPage}
|
||||||
readerWidth={readerWidth}
|
readerWidth={readerWidth}
|
||||||
|
safeAreaInset={safeAreaInset}
|
||||||
readerNavBarWidth={readerNavBarWidth}
|
readerNavBarWidth={readerNavBarWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const BaseBasePager = ({
|
|||||||
pageScaleMode,
|
pageScaleMode,
|
||||||
shouldStretchPage,
|
shouldStretchPage,
|
||||||
readerWidth,
|
readerWidth,
|
||||||
|
safeAreaInset,
|
||||||
readerNavBarWidth,
|
readerNavBarWidth,
|
||||||
resumeMode,
|
resumeMode,
|
||||||
handleAsInitialRender,
|
handleAsInitialRender,
|
||||||
@@ -52,6 +53,7 @@ const BaseBasePager = ({
|
|||||||
pageScaleMode: ReaderPagerProps['pageScaleMode'],
|
pageScaleMode: ReaderPagerProps['pageScaleMode'],
|
||||||
shouldStretchPage: ReaderPagerProps['shouldStretchPage'],
|
shouldStretchPage: ReaderPagerProps['shouldStretchPage'],
|
||||||
readerWidth: ReaderPagerProps['readerWidth'],
|
readerWidth: ReaderPagerProps['readerWidth'],
|
||||||
|
safeAreaInset: ReaderPagerProps['safeAreaInset'],
|
||||||
readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'],
|
readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'],
|
||||||
) => ReactNode;
|
) => ReactNode;
|
||||||
slots?: { boxProps?: BoxProps };
|
slots?: { boxProps?: BoxProps };
|
||||||
@@ -125,6 +127,7 @@ const BaseBasePager = ({
|
|||||||
pageScaleMode,
|
pageScaleMode,
|
||||||
shouldStretchPage,
|
shouldStretchPage,
|
||||||
readerWidth,
|
readerWidth,
|
||||||
|
safeAreaInset,
|
||||||
readerNavBarWidth,
|
readerNavBarWidth,
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user