/* * Copyright (C) Contributors to the Suwayomi project * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import IconButton from '@mui/material/IconButton'; import SkipPreviousIcon from '@mui/icons-material/SkipPrevious'; import SkipNextIcon from '@mui/icons-material/SkipNext'; import Stack from '@mui/material/Stack'; import { alpha } from '@mui/material/styles'; import Box from '@mui/material/Box'; import { ComponentProps, memo, useCallback, useLayoutEffect, useMemo, useState } from 'react'; import Slide, { SlideProps } from '@mui/material/Slide'; import { ReaderProgressBar } from '@/features/reader/components/overlay/progress-bar/ReaderProgressBar.tsx'; import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx'; import { ReaderService } from '@/features/reader/services/ReaderService.ts'; import { getPage, getProgressBarPositionInfo } from '@/features/reader/utils/ReaderProgressBar.utils.tsx'; import { getOptionForDirection } from '@/features/theme/services/ThemeCreator.ts'; import { IReaderSettings, ProgressBarPosition, ReaderStateChapters, TReaderScrollbarContext, } from '@/features/reader/types/Reader.types.ts'; import { ReaderProgressBarDirectionWrapper } from '@/features/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx'; import { useReaderProgressBarContext } from '@/features/reader/contexts/ReaderProgressBarContext.tsx'; import { useReaderOverlayContext } from '@/features/reader/contexts/ReaderOverlayContext.tsx'; import { withPropsFrom } from '@/features/core/hoc/withPropsFrom.tsx'; import { TReaderOverlayContext } from '@/features/reader/types/ReaderOverlay.types.ts'; import { ReaderProgressBarProps, TReaderProgressBarContext } from '@/features/reader/types/ReaderProgressBar.types.ts'; import { ReaderProgressBarSlotMobile } from '@/features/reader/components/overlay/progress-bar/mobile/ReaderProgressBarSlotMobile.tsx'; import { userReaderStatePagesContext } from '@/features/reader/contexts/state/ReaderStatePagesContext.tsx'; import { applyStyles } from '@/features/core/utils/ApplyStyles.ts'; import { useResizeObserver } from '@/features/core/hooks/useResizeObserver.tsx'; import { getProgressBarPosition } from '@/features/reader/utils/ReaderSettings.utils.tsx'; import { useReaderScrollbarContext } from '@/features/reader/contexts/ReaderScrollbarContext.tsx'; import { ReaderControls } from '@/features/reader/services/ReaderControls.ts'; const PROGRESS_BAR_POSITION_TO_SLIDE_DIRECTION: Record = { [ProgressBarPosition.BOTTOM]: 'up', [ProgressBarPosition.LEFT]: 'right', [ProgressBarPosition.RIGHT]: 'left', // should never get accessed [ProgressBarPosition.AUTO]: 'left', }; const BaseMobileReaderProgressBar = ({ previousChapter, nextChapter, isVisible, setIsMaximized, isDragging, currentPageIndex, pages, direction: readerDirection, progressBarPosition, progressBarPositionAutoVertical, topOffset = 0, bottomOffset = 0, scrollbarXSize, }: Pick & Pick & Pick & Pick & Pick & Pick & { direction: ReturnType; topOffset?: number; bottomOffset?: number; }) => { const openChapter = ReaderControls.useOpenChapter(); const [, setRefreshProgressBarPosition] = useState({}); useResizeObserver( window.document.documentElement, useCallback(() => setRefreshProgressBarPosition({}), []), ); const finalProgressBarPosition = getProgressBarPosition( progressBarPosition, progressBarPositionAutoVertical, // scrollbar x size is already included in the top/bottom offset due to the progress bar being placed in the reader mobile bottom bar topOffset + bottomOffset, scrollbarXSize, ); const { isLeft, isRight, isVertical, isHorizontal } = getProgressBarPositionInfo(finalProgressBarPosition); const finalReaderDirection = isHorizontal ? readerDirection : 'ltr'; const currentPagesIndex = useMemo(() => getPage(currentPageIndex, pages).pagesIndex, [currentPageIndex, pages]); const progressBarSlotProps: ComponentProps['slotProps'] = useMemo( () => ({ container: { sx: { flexGrow: 1, position: 'relative', display: 'flex', justifyItems: 'center', alignItems: 'stretch', backgroundColor: (theme) => alpha(theme.palette.background.paper, 0.85), borderRadius: 100, boxShadow: 2, }, }, progressBarRoot: { sx: { flexGrow: 1, gap: 0, ...applyStyles(isVertical, { flexDirection: 'column', }), ...applyStyles(isHorizontal, { alignItems: 'stretch', }), }, }, progressBarSlotsActionArea: { sx: { alignItems: 'center', cursor: 'pointer', ...applyStyles(isVertical, { width: '100%', flexDirection: 'column', px: 2, }), ...applyStyles(isHorizontal, { height: '100%', py: 2, }), }, }, progressBarSlotsContainer: { sx: { borderRadius: 100, backgroundColor: 'background.default', ...applyStyles(isVertical, { flexDirection: 'column', py: 1, }), ...applyStyles(isHorizontal, { px: 1, }), }, }, progressBarSlot: { sx: { ...applyStyles(isVertical, { width: '20px', }), ...applyStyles(isHorizontal, { height: '20px', }), }, }, progressBarCurrentPageSlot: { sx: { display: 'flex', zIndex: 1, cursor: 'inherit', ...applyStyles(isVertical, { justifyContent: 'center', }), ...applyStyles(isHorizontal, { alignItems: 'center', }), }, }, progressBarPageTexts: { base: { sx: { ...applyStyles(isVertical, { py: 1, }), ...applyStyles(isHorizontal, { px: 1, }), }, }, }, }), [isVertical, isHorizontal], ); const progressBarCurrentPage = useMemo( () => ({ progressBarCurrentPage: ( ), }), [currentPagesIndex, pages.length, isDragging, isVertical, isHorizontal], ); useLayoutEffect(() => { setIsMaximized(isVisible); return () => setIsMaximized(false); }, [isVisible]); return ( openChapter('previous')} disabled={!previousChapter} sx={{ backgroundColor: (theme) => alpha(theme.palette.background.paper, 0.85), boxShadow: 2, }} > {getOptionForDirection( , , finalReaderDirection, )} ( ), [isVertical, isHorizontal], )} slotProps={{ ...progressBarSlotProps, progressBarReadPages: { sx: { backgroundColor: 'primary.main', ...applyStyles(isVertical, { width: '20px', height: `${(Math.max(0, getPage(currentPageIndex, pages).pagesIndex) / (pages.length - 1)) * 100}%`, borderRadius: '400px 400px 0 0', }), ...applyStyles(isHorizontal, { width: `${(Math.max(0, getPage(currentPageIndex, pages).pagesIndex) / (pages.length - 1)) * 100}%`, height: '20px', borderRadius: '400px 0 0 400px', }), }, }, }} slots={progressBarCurrentPage} /> openChapter('next')} disabled={!nextChapter} sx={{ backgroundColor: (theme) => alpha(theme.palette.background.paper, 0.85), boxShadow: 2 }} > {getOptionForDirection( , , finalReaderDirection, )} ); }; export const MobileReaderProgressBar = withPropsFrom( memo(BaseMobileReaderProgressBar), [ useReaderStateChaptersContext, useReaderOverlayContext, useReaderProgressBarContext, userReaderStatePagesContext, () => ({ direction: ReaderService.useGetThemeDirection() }), ReaderService.useSettingsWithoutDefaultFlag, useReaderScrollbarContext, ], [ 'previousChapter', 'nextChapter', 'isVisible', 'setIsMaximized', 'isDragging', 'currentPageIndex', 'pages', 'direction', 'progressBarPosition', 'progressBarPositionAutoVertical', 'scrollbarXSize', ], );