diff --git a/public/locales/en.json b/public/locales/en.json
index d32d346a..ff4eb221 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -754,6 +754,7 @@
},
"reader": {
"button": {
+ "chapter_list": "Chapter list",
"close_menu": "Close menu",
"exit": "Exit reader",
"next_chapter": "Next Chapter",
@@ -786,6 +787,8 @@
"offset_first_page": "Offset first page",
"reader_type": "Reader type",
"reader_width": "Reader width",
+ "reading_direction": "Reading direction",
+ "reading_mode": "Reading mode",
"scale_page": "Scale small pages",
"show_page_number": "Show page number",
"skip_dup_chapters": "Skip duplicate chapters",
@@ -793,16 +796,17 @@
},
"reader_type": {
"label": {
- "continuous_horizontal_ltr": "Horizontal (LTR) ➡️",
- "continuous_horizontal_rtl": "Horizontal (RTL) ⬅️",
- "continuous_vertical": "Continues vertical",
- "double_page_ltr": "Double page (LTR) ➡️",
- "double_page_rtl": "Double page (RTL) ⬅️",
- "single_page_ltr": "Single page (LTR) ➡️",
- "single_page_rtl": "Single page (RTL) ⬅️",
+ "continuous_horizontal": "Continuous horizontal",
+ "continuous_vertical": "Continuous vertical",
+ "double_page": "Double page",
+ "single_page": "Single page",
"webtoon": "Webtoon"
}
},
+ "reading_direction": {
+ "ltr": "Left to right",
+ "rtl": "Right to left"
+ },
"title": {
"default_reader_settings": "Default Reader Settings",
"reader_settings": "Reader Settings"
diff --git a/src/assets/icons/svg/ContinuousHorizontalPageIcon.tsx b/src/assets/icons/svg/ContinuousHorizontalPageIcon.tsx
new file mode 100644
index 00000000..673da9f4
--- /dev/null
+++ b/src/assets/icons/svg/ContinuousHorizontalPageIcon.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 SvgIcon from '@mui/material/SvgIcon';
+
+export const ContinuousHorizontalPageIcon = () => (
+
+
+
+);
diff --git a/src/assets/icons/svg/ContinuousVerticalPageIcon.tsx b/src/assets/icons/svg/ContinuousVerticalPageIcon.tsx
new file mode 100644
index 00000000..07885056
--- /dev/null
+++ b/src/assets/icons/svg/ContinuousVerticalPageIcon.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 SvgIcon from '@mui/material/SvgIcon';
+
+export const ContinuousVerticalPageIcon = () => (
+
+
+
+);
diff --git a/src/assets/icons/svg/DoublePageIcon.tsx b/src/assets/icons/svg/DoublePageIcon.tsx
new file mode 100644
index 00000000..68da4f55
--- /dev/null
+++ b/src/assets/icons/svg/DoublePageIcon.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 SvgIcon from '@mui/material/SvgIcon';
+
+export const DoublePageIcon = () => (
+
+
+
+);
diff --git a/src/assets/icons/svg/OffsetDoubleSpreadIcon.tsx b/src/assets/icons/svg/OffsetDoubleSpreadIcon.tsx
new file mode 100644
index 00000000..9ac182bf
--- /dev/null
+++ b/src/assets/icons/svg/OffsetDoubleSpreadIcon.tsx
@@ -0,0 +1,17 @@
+/*
+ * 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 SvgIcon from '@mui/material/SvgIcon';
+
+export const OffsetDoubleSpreadIcon = () => (
+
+
+
+);
diff --git a/src/assets/icons/svg/SinglePageIcon.tsx b/src/assets/icons/svg/SinglePageIcon.tsx
new file mode 100644
index 00000000..6571c2ab
--- /dev/null
+++ b/src/assets/icons/svg/SinglePageIcon.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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 SvgIcon from '@mui/material/SvgIcon';
+
+export const SinglePageIcon = () => (
+
+
+
+);
diff --git a/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx b/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx
index e2c91a68..3e72f09d 100644
--- a/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx
+++ b/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx
@@ -40,7 +40,7 @@ import {
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
-type BaseProps = { onClose: () => void };
+type BaseProps = { onClose: () => void; selectable?: boolean };
type TChapter = ChapterIdInfo &
ChapterMangaInfo &
@@ -71,6 +71,7 @@ export const ChapterActionMenuItems = ({
canBeDownloaded = false,
selectedChapters = [],
onClose,
+ selectable = true,
}: Props) => {
const { t } = useTranslation();
@@ -158,7 +159,7 @@ export const ChapterActionMenuItems = ({
return (
<>
- {isSingleMode && (
+ {isSingleMode && selectable && (
)}
{isSingleMode && (
diff --git a/src/modules/chapter/components/cards/ChapterCard.tsx b/src/modules/chapter/components/cards/ChapterCard.tsx
index 2031773f..9d84dbe4 100644
--- a/src/modules/chapter/components/cards/ChapterCard.tsx
+++ b/src/modules/chapter/components/cards/ChapterCard.tsx
@@ -50,12 +50,15 @@ type TChapter = ChapterIdInfo &
Pick;
interface IProps {
+ mode?: 'manga.page' | 'reader';
chapter: TChapter;
allChapters: TChapter[];
downloadChapter: ChapterDownloadStatus | undefined;
showChapterNumber: boolean;
onSelect: (selected: boolean, isShiftKey?: boolean) => void;
selected: boolean | null;
+ selectable?: boolean;
+ isActiveChapter?: boolean; // reader
}
export const ChapterCard: React.FC = (props: IProps) => {
@@ -64,7 +67,17 @@ export const ChapterCard: React.FC = (props: IProps) => {
const menuButtonRef = useRef(null);
- const { chapter, allChapters, downloadChapter: dc, showChapterNumber, onSelect, selected } = props;
+ const {
+ mode = 'manga.page',
+ chapter,
+ allChapters,
+ downloadChapter: dc,
+ showChapterNumber,
+ onSelect,
+ selected,
+ selectable = true,
+ isActiveChapter = false,
+ } = props;
const isSelecting = selected !== null;
const { isDownloaded } = chapter;
@@ -98,102 +111,115 @@ export const ChapterCard: React.FC = (props: IProps) => {
});
return (
-
-
- {(popupState) => (
-
-
-
+ {(popupState) => (
+
+
+ handleClick(e)}
+ {...longPressBind(popupState.open)}
+ >
+ handleClick(e)}
- {...longPressBind(popupState.open)}
>
-
-
- {chapter.isBookmarked && }
-
- {showChapterNumber
- ? `${t('chapter.title_one')} ${chapter.chapterNumber}`
- : chapter.name}
-
-
- {chapter.scanlator}
-
- {getDateString(Number(chapter.uploadDate ?? 0), true)}
- {isDownloaded && ` • ${t('chapter.status.label.downloaded')}`}
-
-
-
- {dc && }
-
-
- {selected === null ? (
-
- handleClickOpenMenu(e, popupState.open)}
- onTouchStart={(e) => handleClickOpenMenu(e, popupState.open)}
- aria-label="more"
- size="large"
- >
-
-
-
- ) : (
-
-
-
+ {chapter.isBookmarked && (
+
)}
+
+ {showChapterNumber
+ ? `${t('chapter.title_one')} ${chapter.chapterNumber}`
+ : chapter.name}
+
-
-
-
- {!isSelecting && popupState.isOpen && (
-
- )}
-
- )}
-
-
+ {chapter.scanlator}
+
+ {getDateString(Number(chapter.uploadDate ?? 0), true)}
+ {isDownloaded && ` • ${t('chapter.status.label.downloaded')}`}
+
+
+
+ {dc && }
+
+
+ {selected === null ? (
+
+ handleClickOpenMenu(e, popupState.open)}
+ onTouchStart={(e) => handleClickOpenMenu(e, popupState.open)}
+ aria-label="more"
+ size="large"
+ >
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+ {!isSelecting && popupState.isOpen && (
+
+ )}
+
+ )}
+
);
};
diff --git a/src/modules/chapter/services/Chapters.ts b/src/modules/chapter/services/Chapters.ts
index 795eb04a..d3a31252 100644
--- a/src/modules/chapter/services/Chapters.ts
+++ b/src/modules/chapter/services/Chapters.ts
@@ -117,7 +117,7 @@ export class Chapters {
}
static getReaderUrl(chapter: Chapter): string {
- return `manga/${chapter.mangaId}/chapter/${chapter.sourceOrder}`;
+ return `/manga/${chapter.mangaId}/chapter/${chapter.sourceOrder}`;
}
static isDownloading(id: number): boolean {
diff --git a/src/modules/core/Core.types.ts b/src/modules/core/Core.types.ts
index b3d4b792..26b1bb64 100644
--- a/src/modules/core/Core.types.ts
+++ b/src/modules/core/Core.types.ts
@@ -6,8 +6,19 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
+import { ReactNode } from 'react';
+import { TranslationKey } from '@/Base.types.ts';
+
export enum GridLayout {
Compact = 0,
Comfortable = 1,
List = 2,
}
+
+export type ValueToDisplayData = Record<
+ Value,
+ {
+ title: TranslationKey;
+ icon: ReactNode;
+ }
+>;
diff --git a/src/modules/core/components/buttons/ButtonSelect.tsx b/src/modules/core/components/buttons/ButtonSelect.tsx
new file mode 100644
index 00000000..60148c1f
--- /dev/null
+++ b/src/modules/core/components/buttons/ButtonSelect.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 Stack from '@mui/material/Stack';
+import { useTranslation } from 'react-i18next';
+import Button from '@mui/material/Button';
+import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
+
+export const ButtonSelect = ({
+ value,
+ values,
+ setValue,
+ valueToDisplayData,
+}: {
+ value: Value;
+ values: Value[];
+ setValue: (value: Value) => void;
+ valueToDisplayData: ValueToDisplayData;
+}) => {
+ const { t } = useTranslation();
+
+ return (
+
+ {values.map((displayValue) => (
+
+ ))}
+
+ );
+};
diff --git a/src/modules/core/components/inputs/ButtonSelectInput.tsx b/src/modules/core/components/inputs/ButtonSelectInput.tsx
new file mode 100644
index 00000000..4517817b
--- /dev/null
+++ b/src/modules/core/components/inputs/ButtonSelectInput.tsx
@@ -0,0 +1,23 @@
+/*
+ * 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 Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import { ComponentProps } from 'react';
+
+import { ButtonSelect } from '@/modules/core/components/buttons/ButtonSelect.tsx';
+
+export const ButtonSelectInput = ({
+ label,
+ ...buttonSelectProps
+}: ComponentProps> & { label: string }) => (
+
+ {label}
+
+
+);
diff --git a/src/modules/metadata/Metadata.constants.ts b/src/modules/metadata/Metadata.constants.ts
index cd410c49..dfa937fb 100644
--- a/src/modules/metadata/Metadata.constants.ts
+++ b/src/modules/metadata/Metadata.constants.ts
@@ -7,6 +7,7 @@
*/
import { AppMetadataKeys, IMetadataMigration } from '@/modules/metadata/Metadata.types.ts';
+import { ReadingMode } from '@/modules/reader/types/Reader.types.ts';
export const APP_METADATA_KEY_PREFIX = 'webUI_';
@@ -60,6 +61,7 @@ const APP_METADATA_OBJECT: Record = {
progressBarType: undefined,
progressBarSize: undefined,
progressBarPosition: undefined,
+ readingMode: undefined,
};
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_OBJECT);
@@ -121,6 +123,7 @@ export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
'customThemes',
'mangaThumbnailBackdrop',
];
+
/**
* Once all changes have been done in the current branch, a new migration for all changes should be
* created.
@@ -184,4 +187,56 @@ export const METADATA_MIGRATIONS: IMetadataMigration[] = [
{
keys: [{ oldKey: 'deleteChaptersAutoMarkedRead', newKey: 'deleteChaptersWhileReading' }],
},
+ {
+ keys: [
+ {
+ oldKey: 'readerType',
+ newKey: 'readingMode',
+ },
+ ],
+ values: [
+ // START: readerType
+ {
+ key: 'readerType',
+ oldValue: 'ContinuesVertical',
+ newValue: `${ReadingMode.CONTINUOUS_VERTICAL}`,
+ },
+ {
+ key: 'readerType',
+ oldValue: 'Webtoon',
+ newValue: `${ReadingMode.CONTINUOUS_VERTICAL}`,
+ },
+ {
+ key: 'readerType',
+ oldValue: 'SingleRTL',
+ newValue: `${ReadingMode.SINGLE_PAGE}`,
+ },
+ {
+ key: 'readerType',
+ oldValue: 'SingleLTR',
+ newValue: `${ReadingMode.SINGLE_PAGE}`,
+ },
+ {
+ key: 'readerType',
+ oldValue: 'DoubleRTL',
+ newValue: `${ReadingMode.DOUBLE_PAGE}`,
+ },
+ {
+ key: 'readerType',
+ oldValue: 'DoubleLTR',
+ newValue: `${ReadingMode.DOUBLE_PAGE}`,
+ },
+ {
+ key: 'readerType',
+ oldValue: 'ContinuesHorizontalLTR',
+ newValue: `${ReadingMode.CONTINUOUS_HORIZONTAL}`,
+ },
+ {
+ key: 'readerType',
+ oldValue: 'ContinuesHorizontalRTL',
+ newValue: `${ReadingMode.CONTINUOUS_HORIZONTAL}`,
+ },
+ // END: readerType
+ ],
+ },
];
diff --git a/src/modules/reader/components/overlay/navigation/ReaderChapterList.tsx b/src/modules/reader/components/overlay/navigation/ReaderChapterList.tsx
new file mode 100644
index 00000000..405f88db
--- /dev/null
+++ b/src/modules/reader/components/overlay/navigation/ReaderChapterList.tsx
@@ -0,0 +1,69 @@
+/*
+ * 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 { Virtuoso } from 'react-virtuoso';
+import { useMemo } from 'react';
+import { IChapterWithMeta } from '@/modules/chapter/components/ChapterList.tsx';
+import { ChapterCard } from '@/modules/chapter/components/cards/ChapterCard.tsx';
+import { requestManager } from '@/lib/requests/RequestManager.ts';
+import { ReaderStateChapters } from '@/modules/reader/types/Reader.types.ts';
+
+export const ReaderChapterList = ({
+ currentChapter,
+ chapters,
+}: Required>) => {
+ const { data: downloaderData } = requestManager.useGetDownloadStatus();
+ const queue = downloaderData?.downloadStatus.queue ?? [];
+
+ const currentChapterIndex = useMemo(
+ () => currentChapter && chapters.findIndex((chapter) => chapter.id === currentChapter.id),
+ [currentChapter, chapters],
+ );
+
+ const chaptersWithMeta: IChapterWithMeta[] = useMemo(
+ () =>
+ chapters.map((chapter) => {
+ const downloadChapter = queue?.find((cd) => cd.chapter.id === chapter.id);
+
+ return {
+ chapter,
+ downloadChapter,
+ selected: null,
+ };
+ }),
+ [queue, chapters],
+ );
+
+ return (
+ chaptersWithMeta[index].chapter.id}
+ itemContent={(index) => (
+ undefined}
+ selectable={false}
+ isActiveChapter={index === currentChapterIndex}
+ />
+ )}
+ increaseViewportBy={400}
+ />
+ );
+};
diff --git a/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx
new file mode 100644
index 00000000..01eb1c3c
--- /dev/null
+++ b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx
@@ -0,0 +1,106 @@
+/*
+ * 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 SettingsIcon from '@mui/icons-material/Settings';
+import Stack from '@mui/material/Stack';
+import AppSettingsAltIcon from '@mui/icons-material/AppSettingsAlt';
+import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
+import { alpha } from '@mui/material/styles';
+import { bindDialog, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks';
+import Dialog from '@mui/material/Dialog';
+import DialogContent from '@mui/material/DialogContent';
+import Tooltip from '@mui/material/Tooltip';
+import { useTranslation } from 'react-i18next';
+import Slide from '@mui/material/Slide';
+import { useLayoutEffect } from 'react';
+import { ReaderBottomBarMobileProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
+import { MobileReaderProgressBar } from '@/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx';
+import { ReaderChapterList } from '@/modules/reader/components/overlay/navigation/ReaderChapterList.tsx';
+import { ReaderBottomBarMobileQuickSettings } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx';
+import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
+import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx';
+
+export const ReaderBottomBarMobile = ({ openSettings, isVisible }: ReaderBottomBarMobileProps) => {
+ const { t } = useTranslation();
+ const { currentChapter, chapters } = useReaderStateChaptersContext();
+ const { scrollbarXSize, scrollbarYSize } = useReaderScrollbarContext();
+
+ const chapterListPopupState = usePopupState({ variant: 'dialog', popupId: 'reader-chapter-list-dialog' });
+ const quickSettingsPopupState = usePopupState({ variant: 'dialog', popupId: 'reader-quick-settings-dialog' });
+
+ useLayoutEffect(() => {
+ chapterListPopupState.close();
+ }, [currentChapter?.id]);
+
+ return (
+ <>
+
+
+
+ alpha(theme.palette.background.paper, 0.95),
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {chapterListPopupState.isOpen && (
+
+ )}
+ {quickSettingsPopupState.isOpen && (
+
+ )}
+ >
+ );
+};
diff --git a/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx
new file mode 100644
index 00000000..30634956
--- /dev/null
+++ b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx
@@ -0,0 +1,45 @@
+/*
+ * 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 Stack from '@mui/material/Stack';
+import { useTranslation } from 'react-i18next';
+import { ReaderBottomBarMobileReadingMode } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx';
+import { ReaderBottomBarMobileReadingDirection } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx';
+import { createUpdateReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
+import { makeToast } from '@/modules/core/utils/Toast.ts';
+import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
+import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
+import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
+
+export const ReaderBottomBarMobileQuickSettings = () => {
+ const { t } = useTranslation();
+
+ const { manga } = useReaderStateMangaContext();
+ const { readingMode, readingDirection } = ReaderService.useSettings();
+
+ const updateReaderSettings = createUpdateReaderSettings<
+ keyof Pick
+ >(manga ?? { id: -1 }, () => makeToast(t('reader.settings.error.label.failed_to_save_settings')));
+
+ if (!manga) {
+ return null;
+ }
+
+ return (
+
+ updateReaderSettings('readingMode', value)}
+ />
+ updateReaderSettings('readingDirection', value)}
+ />
+
+ );
+};
diff --git a/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx
new file mode 100644
index 00000000..8c840ff4
--- /dev/null
+++ b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft';
+import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
+import { useTranslation } from 'react-i18next';
+import { IReaderSettings, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
+import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
+import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
+
+const VALUE_TO_DISPLAY_DATA: ValueToDisplayData = {
+ [ReadingDirection.LTR]: {
+ title: 'reader.settings.reading_direction.ltr',
+ icon: ,
+ },
+ [ReadingDirection.RTL]: {
+ title: 'reader.settings.reading_direction.rtl',
+ icon: ,
+ },
+};
+
+const READING_DIRECTION_VALUES = Object.values(ReadingDirection).filter((value) => typeof value === 'number');
+
+export const ReaderBottomBarMobileReadingDirection = ({
+ readingDirection,
+ setReadingDirection,
+}: Pick & {
+ setReadingDirection: (readingDirection: ReadingDirection) => void;
+}) => {
+ const { t } = useTranslation();
+
+ return (
+
+ );
+};
diff --git a/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx
new file mode 100644
index 00000000..b785e5dc
--- /dev/null
+++ b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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 { useTranslation } from 'react-i18next';
+import { IReaderSettings, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
+import { SinglePageIcon } from '@/assets/icons/svg/SinglePageIcon.tsx';
+import { DoublePageIcon } from '@/assets/icons/svg/DoublePageIcon.tsx';
+import { ContinuousVerticalPageIcon } from '@/assets/icons/svg/ContinuousVerticalPageIcon.tsx';
+import { ContinuousHorizontalPageIcon } from '@/assets/icons/svg/ContinuousHorizontalPageIcon.tsx';
+import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
+import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
+
+const VALUE_TO_DISPLAY_DATA: ValueToDisplayData = {
+ [ReadingMode.SINGLE_PAGE]: {
+ title: 'reader.settings.reader_type.label.single_page',
+ icon: ,
+ },
+ [ReadingMode.DOUBLE_PAGE]: {
+ title: 'reader.settings.reader_type.label.double_page',
+ icon: ,
+ },
+ [ReadingMode.CONTINUOUS_VERTICAL]: {
+ title: 'reader.settings.reader_type.label.continuous_vertical',
+ icon: ,
+ },
+ [ReadingMode.CONTINUOUS_HORIZONTAL]: {
+ title: 'reader.settings.reader_type.label.continuous_horizontal',
+ icon: ,
+ },
+};
+
+const READING_MODE_VALUES = Object.values(ReadingMode).filter((value) => typeof value === 'number');
+
+export const ReaderBottomBarMobileReadingMode = ({
+ readingMode,
+ setReadingMode,
+}: Pick & {
+ setReadingMode: (mode: ReadingMode) => void;
+}) => {
+ const { t } = useTranslation();
+
+ return (
+
+ );
+};
diff --git a/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx b/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx
index 36e9c270..45aaecd2 100644
--- a/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx
+++ b/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx
@@ -18,7 +18,7 @@ import { ReaderProgressBarSlot } from '@/modules/reader/components/overlay/progr
import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx';
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
-import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils';
+import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx';
import { getOptionForDirection } from '@/theme.tsx';
import { ProgressBarPosition } from '@/modules/reader/types/Reader.types.ts';
import { ReaderProgressBarDirectionWrapper } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx';
diff --git a/src/modules/reader/constants/Reader.constants.ts b/src/modules/reader/constants/Reader.constants.ts
index 3550c078..cc5ad1f6 100644
--- a/src/modules/reader/constants/Reader.constants.ts
+++ b/src/modules/reader/constants/Reader.constants.ts
@@ -11,6 +11,7 @@ import {
ProgressBarPosition,
ProgressBarType,
ReadingDirection,
+ ReadingMode,
} from '@/modules/reader/types/Reader.types.ts';
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
@@ -29,4 +30,5 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = {
progressBarSize: 4,
progressBarPosition: ProgressBarPosition.BOTTOM,
readingDirection: ReadingDirection.LTR,
+ readingMode: ReadingMode.SINGLE_PAGE,
};
diff --git a/src/modules/reader/types/Reader.types.ts b/src/modules/reader/types/Reader.types.ts
index 82953e5b..cc153611 100644
--- a/src/modules/reader/types/Reader.types.ts
+++ b/src/modules/reader/types/Reader.types.ts
@@ -25,6 +25,13 @@ export enum ReadingDirection {
RTL,
}
+export enum ReadingMode {
+ SINGLE_PAGE,
+ DOUBLE_PAGE,
+ CONTINUOUS_VERTICAL,
+ CONTINUOUS_HORIZONTAL,
+}
+
export interface ReaderStateChapters {
chapters: TChapterReader[];
currentChapter?: TChapterReader | null;
@@ -48,4 +55,5 @@ export interface IReaderSettings {
progressBarSize: number;
progressBarPosition: ProgressBarPosition;
readingDirection: ReadingDirection;
+ readingMode: ReadingMode;
}
diff --git a/src/modules/reader/types/ReaderOverlay.types.ts b/src/modules/reader/types/ReaderOverlay.types.ts
index 47a91fae..5707d3d6 100644
--- a/src/modules/reader/types/ReaderOverlay.types.ts
+++ b/src/modules/reader/types/ReaderOverlay.types.ts
@@ -6,6 +6,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-export interface MobileHeaderProps {
+export interface BaseReaderOverlayProps {
isVisible: boolean;
}
+
+export interface MobileHeaderProps extends BaseReaderOverlayProps {}
+
+export interface ReaderBottomBarMobileProps extends BaseReaderOverlayProps {
+ openSettings: () => void;
+}