From 37d56b320502ce3c71b71109050de12a701d11ad Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Tue, 25 Nov 2025 15:01:17 +0800 Subject: [PATCH] fix(android): support back key in menu, dialog and alert widgets, closes #2454 (#2539) --- .../src/app/library/components/Bookshelf.tsx | 81 +++------------ .../src/app/library/components/ImportMenu.tsx | 1 + .../library/components/SelectModeActions.tsx | 98 +++++++++++++++++++ .../app/library/components/SettingsMenu.tsx | 1 + .../src/app/library/components/ViewMenu.tsx | 5 +- .../src/app/reader/components/Reader.tsx | 13 ++- .../src/app/reader/components/ViewMenu.tsx | 1 + .../components/footerbar/MobileFooterBar.tsx | 1 + .../components/footerbar/NavigationPanel.tsx | 9 +- .../reader/components/sidebar/BookMenu.tsx | 1 + apps/readest-app/src/components/Alert.tsx | 4 + apps/readest-app/src/components/Menu.tsx | 6 +- .../src/hooks/useKeyDownActions.ts | 67 +++++++++++++ 13 files changed, 211 insertions(+), 77 deletions(-) create mode 100644 apps/readest-app/src/app/library/components/SelectModeActions.tsx create mode 100644 apps/readest-app/src/hooks/useKeyDownActions.ts diff --git a/apps/readest-app/src/app/library/components/Bookshelf.tsx b/apps/readest-app/src/app/library/components/Bookshelf.tsx index db80a4da..bf409ef4 100644 --- a/apps/readest-app/src/app/library/components/Bookshelf.tsx +++ b/apps/readest-app/src/app/library/components/Bookshelf.tsx @@ -2,8 +2,6 @@ import clsx from 'clsx'; import * as React from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { MdDelete, MdOpenInNew, MdOutlineCancel, MdInfoOutline } from 'react-icons/md'; -import { LuFolderPlus } from 'react-icons/lu'; import { PiPlus } from 'react-icons/pi'; import { Book } from '@/types/book'; import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings'; @@ -17,12 +15,12 @@ import { navigateToLibrary, navigateToReader, showReaderWindow } from '@/utils/n import { createBookFilter, createBookSorter } from '../utils/libraryUtils'; import { formatTitle } from '@/utils/book'; import { eventDispatcher } from '@/utils/event'; -import { isMd5 } from '@/utils/md5'; import Alert from '@/components/Alert'; import Spinner from '@/components/Spinner'; import ModalPortal from '@/components/ModalPortal'; import BookshelfItem, { generateBookshelfItems } from './BookshelfItem'; +import SelectModeActions from './SelectModeActions'; import GroupingModal from './GroupingModal'; interface BookshelfProps { @@ -372,72 +370,17 @@ const Bookshelf: React.FC = ({ )} -
- {isSelectMode && showSelectModeActions && ( -
- - - - - -
- )} -
+ {isSelectMode && showSelectModeActions && ( + handleSetSelectMode(false)} + /> + )} {showGroupingModal && ( = ({ setIsDropdownOpen, onImportBook 'dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow', appService?.isMobile ? 'no-triangle' : 'dropdown-center', )} + onCancel={() => setIsDropdownOpen?.(false)} > diff --git a/apps/readest-app/src/app/library/components/SelectModeActions.tsx b/apps/readest-app/src/app/library/components/SelectModeActions.tsx new file mode 100644 index 00000000..7644224b --- /dev/null +++ b/apps/readest-app/src/app/library/components/SelectModeActions.tsx @@ -0,0 +1,98 @@ +import clsx from 'clsx'; +import { MdDelete, MdOpenInNew, MdOutlineCancel, MdInfoOutline } from 'react-icons/md'; +import { LuFolderPlus } from 'react-icons/lu'; +import { useKeyDownActions } from '@/hooks/useKeyDownActions'; +import { useTranslation } from '@/hooks/useTranslation'; +import { isMd5 } from '@/utils/md5'; + +interface SelectModeActionsProps { + selectedBooks: string[]; + safeAreaBottom: number; + onOpen: () => void; + onGroup: () => void; + onDetails: () => void; + onDelete: () => void; + onCancel: () => void; +} + +const SelectModeActions: React.FC = ({ + selectedBooks, + safeAreaBottom, + onOpen, + onGroup, + onDetails, + onDelete, + onCancel, +}) => { + const _ = useTranslation(); + + const hasSelection = selectedBooks.length > 0; + const hasValidBooks = selectedBooks.every((id) => isMd5(id)); + const hasSingleSelection = selectedBooks.length === 1; + const divRef = useKeyDownActions({ onCancel }); + + return ( +
+
+ + + + + +
+
+ ); +}; + +export default SelectModeActions; diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index 896570f0..1433f402 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -231,6 +231,7 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { 'settings-menu dropdown-content no-triangle border-base-100', 'z-20 mt-2 max-w-[90vw] shadow-2xl', )} + onCancel={() => setIsDropdownOpen?.(false)} > {user ? ( = ({ setIsDropdownOpen }) => { }; return ( - + setIsDropdownOpen?.(false)} + > {viewOptions.map((option) => ( = ({ ids }) => { const router = useRouter(); const { envConfig, appService } = useEnv(); const { setLibrary } = useLibraryStore(); - const { hoveredBookKey } = useReaderStore(); + const { hoveredBookKey, getView } = useReaderStore(); const { settings, setSettings } = useSettingsStore(); + const { sideBarBookKey } = useSidebarStore(); const { isSideBarVisible, getIsSideBarVisible, setSideBarVisible } = useSidebarStore(); const { isNotebookVisible, getIsNotebookVisible, setNotebookVisible } = useNotebookStore(); const { isDarkMode, systemUIAlwaysHidden, isRoundedWindow } = useThemeStore(); @@ -76,11 +77,14 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => { }, []); const handleKeyDown = (event: CustomEvent) => { + const view = getView(sideBarBookKey!); if (event.detail.keyName === 'Back') { if (getIsSideBarVisible()) { setSideBarVisible(false); } else if (getIsNotebookVisible()) { setNotebookVisible(false); + } else if (view?.history.canGoBack) { + view.history.back(); } else { eventDispatcher.dispatch('close-reader'); router.back(); @@ -100,15 +104,16 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => { }, [appService?.isAndroidApp]); useEffect(() => { - if (!appService?.isAndroidApp) return; - eventDispatcher.onSync('native-key-down', handleKeyDown); + if (appService?.isAndroidApp) { + eventDispatcher.onSync('native-key-down', handleKeyDown); + } return () => { if (appService?.isAndroidApp) { eventDispatcher.offSync('native-key-down', handleKeyDown); } }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appService?.isAndroidApp, isSideBarVisible, isNotebookVisible]); + }, [appService?.isAndroidApp, sideBarBookKey, isSideBarVisible, isNotebookVisible]); useEffect(() => { if (isInitiating.current) return; diff --git a/apps/readest-app/src/app/reader/components/ViewMenu.tsx b/apps/readest-app/src/app/reader/components/ViewMenu.tsx index ef7059ee..ed9d7e3f 100644 --- a/apps/readest-app/src/app/reader/components/ViewMenu.tsx +++ b/apps/readest-app/src/app/reader/components/ViewMenu.tsx @@ -153,6 +153,7 @@ const ViewMenu: React.FC = ({ bookKey, setIsDropdownOpen }) => { maxWidth: `${window.innerWidth - 40}px`, marginRight: window.innerWidth < 640 ? '-36px' : '0px', }} + onCancel={() => setIsDropdownOpen?.(false)} > {bookData.bookDoc?.rendition?.layout === 'pre-paginated' && ( <> diff --git a/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx b/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx index 1bb2c1d1..b2ee0779 100644 --- a/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx +++ b/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx @@ -25,6 +25,7 @@ const MobileFooterBar: React.FC = ({ <> = ({ + bookKey, actionTab, progressFraction, progressValid, @@ -30,6 +33,8 @@ export const NavigationPanel: React.FC = ({ sliderHeight, }) => { const _ = useTranslation(); + const { getView } = useReaderStore(); + const view = getView(bookKey); const [progressValue, setProgressValue] = React.useState( progressValid ? progressFraction * 100 : 0, @@ -99,7 +104,7 @@ export const NavigationPanel: React.FC = ({ )} onClick={navigationHandlers.onGoBack} label={_('Go Back')} - disabled={!navigationHandlers.onGoBack} + disabled={!view?.history.canGoBack} />