forked from akai/readest
feat(reader): custom hardware-button page turning (#4177)
* feat(reader): add custom hardware-button page turning (#4139) Lets users bind hardware remote keys (media keys, D-pad/arrow keys) to previous/next page via a learn-mode capture UI in reader settings — an accessibility feature for page-turner remotes. - New global hardwarePageTurner system setting (enabled + key bindings). - hardwareKeys.ts: key normalization, matching, and page-turn resolution. - deviceStore: reference-counted media-key interception + learn mode. - usePagination: flips pages from bound media keys (native bridge) and D-pad/keyboard keys (DOM keydown), scoped to the active book and suppressed while the toolbar is visible. - Page Turner settings section on all platforms; web/desktop bind keys via DOM keydown only, native media-key interception stays mobile-only. - Android: intercept media + learn-mode keys in dispatchKeyEvent. - iOS: forward media keys via MPRemoteCommandCenter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(i18n): add and translate hardware page turner strings (#4139) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(reader): refine hardware page turner (#4139) - Handle book-iframe key events (iframe-keydown messages) so custom bindings work as soon as a book is open, not only after the settings panel has been shown. - Add Previous/Next Section bindings alongside the page bindings. - Rename the hardwareKeys util to keybinding. - Wire the Page Turner section into the settings Reset action. - Drop the focus ring on the capture buttons; BoxedList gains an optional description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(i18n): translate page turner section and key strings (#4139) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useDeviceControlStore } from '@/store/deviceStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
@@ -14,6 +13,7 @@ import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import { annotationToolQuickActions } from '@/app/reader/components/annotator/AnnotationTools';
|
||||
import { BoxedList, SettingsRow, SettingsSelect, SettingsSwitchRow } from './primitives';
|
||||
import NumberInput from './NumberInput';
|
||||
import PageTurnerSettings from './PageTurnerSettings';
|
||||
|
||||
const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
@@ -22,7 +22,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
const { getBookData } = useBookDataStore();
|
||||
const { settings } = useSettingsStore();
|
||||
const { applyEinkMode } = useEinkMode();
|
||||
const { acquireVolumeKeyInterception, releaseVolumeKeyInterception } = useDeviceControlStore();
|
||||
const bookData = getBookData(bookKey);
|
||||
const viewSettings = getViewSettings(bookKey) || settings.globalViewSettings;
|
||||
|
||||
@@ -30,7 +29,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
const [noContinuousScroll, setNoContinuousScroll] = useState(viewSettings.noContinuousScroll);
|
||||
const [scrollingOverlap, setScrollingOverlap] = useState(viewSettings.scrollingOverlap);
|
||||
const [hideScrollbar, setHideScrollbar] = useState(viewSettings.hideScrollbar || false);
|
||||
const [volumeKeysToFlip, setVolumeKeysToFlip] = useState(viewSettings.volumeKeysToFlip);
|
||||
const [showPaginationButtons, setShowPaginationButtons] = useState(
|
||||
viewSettings.showPaginationButtons,
|
||||
);
|
||||
@@ -53,6 +51,7 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
const [allowScript, setAllowScript] = useState(viewSettings.allowScript);
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
const pageTurnerResetRef = useRef<() => void>(() => {});
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
@@ -60,7 +59,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
noContinuousScroll: setNoContinuousScroll,
|
||||
scrollingOverlap: setScrollingOverlap,
|
||||
hideScrollbar: setHideScrollbar,
|
||||
volumeKeysToFlip: setVolumeKeysToFlip,
|
||||
showPaginationButtons: setShowPaginationButtons,
|
||||
disableClick: setIsDisableClick,
|
||||
swapClickArea: setSwapClickArea,
|
||||
@@ -72,6 +70,7 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
enableAnnotationQuickActions: setEnableAnnotationQuickActions,
|
||||
copyToNotebook: setCopyToNotebook,
|
||||
});
|
||||
pageTurnerResetRef.current();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -113,18 +112,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [scrollingOverlap]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'volumeKeysToFlip', volumeKeysToFlip, false, false);
|
||||
if (appService?.isMobileApp) {
|
||||
if (volumeKeysToFlip) {
|
||||
acquireVolumeKeyInterception();
|
||||
} else {
|
||||
releaseVolumeKeyInterception();
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [volumeKeysToFlip]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(
|
||||
envConfig,
|
||||
@@ -300,13 +287,6 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
onChange={() => setIsDisableDoubleClick(!isDisableDoubleClick)}
|
||||
data-setting-id='settings.control.disableDoubleClick'
|
||||
/>
|
||||
{appService?.isMobileApp && (
|
||||
<SettingsSwitchRow
|
||||
label={_('Volume Keys for Page Flip')}
|
||||
checked={volumeKeysToFlip}
|
||||
onChange={() => setVolumeKeysToFlip(!volumeKeysToFlip)}
|
||||
/>
|
||||
)}
|
||||
<SettingsSwitchRow
|
||||
label={_('Show Page Navigation Buttons')}
|
||||
checked={showPaginationButtons}
|
||||
@@ -315,6 +295,13 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
|
||||
/>
|
||||
</BoxedList>
|
||||
|
||||
<PageTurnerSettings
|
||||
bookKey={bookKey}
|
||||
onRegisterReset={(fn) => {
|
||||
pageTurnerResetRef.current = fn;
|
||||
}}
|
||||
/>
|
||||
|
||||
<BoxedList
|
||||
title={_('Annotation Tools')}
|
||||
data-setting-id='settings.control.enableQuickActions'
|
||||
|
||||
Reference in New Issue
Block a user