feat(reader): swipe-to-adjust brightness gesture on mobile (#3021) (#4356)

* docs: design spec for gesture-based brightness control (#3021)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: revise brightness-gesture spec per /autoplan review (#3021)

CEO+Design+Eng dual-voice review. Key fixes: capture-phase listener
(bubble-phase could not suppress foliate paginator), opt-out toggle,
18px threshold, selection guard, brightness seed race, rAF teardown,
e-ink stepped overlay, contrast capsule, perceptual curve reuse,
listener-level test harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(reader): swipe-to-adjust brightness gesture on mobile (#3021)

Left-edge vertical swipe adjusts screen brightness on iOS/Android, with a
Sun-icon progress overlay. Capture-phase non-passive listener suppresses the
foliate paginator / page-flip / UI-toggle handlers; selection guard, strip
reservation in scrolled mode, eager brightness seed, rAF throttle + teardown.
Opt-out toggle in Settings > Behavior > Device (default on). Perceptual curve
shared with the menu slider. Pure-helper + listener-level tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(reader): detect brightness-swipe edge by screenX; i18n + shorter label (#3021)

On-device fix: paginated mode lays the iframe doc out as wide side-by-side
columns, so clientX/documentElement.clientWidth are document coordinates and a
left-edge touch on a later page never fell inside the strip (armed stayed false).
Detect with screenX against the parent window width, matching usePagination.

Also: translate the two new setting strings across all locales and shorten the
toggle description to 'Slide along the left edge'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-05-29 21:28:42 +08:00
committed by GitHub
parent 89723b421e
commit 36e11de332
43 changed files with 1013 additions and 33 deletions
@@ -48,6 +48,9 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
const [isEink, setIsEink] = useState(viewSettings.isEink);
const [isColorEink, setIsColorEink] = useState(viewSettings.isColorEink);
const [autoScreenBrightness, setAutoScreenBrightness] = useState(settings.autoScreenBrightness);
const [swipeBrightnessGesture, setSwipeBrightnessGesture] = useState(
settings.swipeBrightnessGesture,
);
const [screenWakeLock, setScreenWakeLock] = useState(settings.screenWakeLock);
const [allowScript, setAllowScript] = useState(viewSettings.allowScript);
@@ -191,6 +194,12 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autoScreenBrightness]);
useEffect(() => {
if (swipeBrightnessGesture === settings.swipeBrightnessGesture) return;
saveSysSettings(envConfig, 'swipeBrightnessGesture', swipeBrightnessGesture);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [swipeBrightnessGesture]);
useEffect(() => {
if (screenWakeLock === settings.screenWakeLock) return;
saveSysSettings(envConfig, 'screenWakeLock', screenWakeLock);
@@ -382,6 +391,15 @@ const ControlPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterRes
onChange={() => setAutoScreenBrightness(!autoScreenBrightness)}
/>
)}
{appService?.hasScreenBrightness && (
<SettingsSwitchRow
label={_('Swipe for Brightness')}
description={_('Slide along the left edge')}
checked={swipeBrightnessGesture}
onChange={() => setSwipeBrightnessGesture(!swipeBrightnessGesture)}
data-setting-id='settings.control.swipeBrightnessGesture'
/>
)}
<SettingsSwitchRow
label={_('Keep Screen Awake')}
checked={screenWakeLock}