feat(reader): add e-ink screen refresh page-turner action (#4687) (#4822)

Add a bindable "Refresh Page" action to Settings > Behavior > Page Turner
that triggers a deep e-ink full refresh (GC16) to clear screen ghosting,
gated to e-ink mode on Android.

It reuses the existing hardware page-turner key-binding machinery: a new
'refresh' slot in HardwarePageTurnerSettings, shown only when isAndroidApp
and the e-ink view setting is on. Pressing the bound key calls a new native
bridge command instead of paginating.

The native side is device-agnostic: EinkRefreshController probes each vendor
mechanism via reflection and stops at the first that works, covering Onyx
BOOX (Qualcomm View.refreshScreen), Tolino/Nook (NTX postInvalidateDelayed)
and Boyue-style Rockchip (requestEpdMode) without bundling any vendor SDK.
A success:false result is a soft no-op on non-e-ink hardware. iOS gets a stub.

Verified on an Onyx BOOX Leaf5: the Onyx path fires and performs a visible
full GC16 refresh.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-06-27 17:18:11 +08:00
committed by GitHub
parent f8916e128e
commit 324bb8a366
55 changed files with 470 additions and 41 deletions
@@ -9,6 +9,7 @@ import { useSettingsStore } from '@/store/settingsStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { eventDispatcher } from '@/utils/event';
import { resolvePageTurn, normalizeDomKeyEvent, KeyCandidate } from '@/utils/keybinding';
import { refreshEinkScreen } from '@/utils/bridge';
import { isTauriAppPlatform } from '@/services/environment';
import { tauriGetWindowLogicalPosition } from '@/utils/window';
import { getReadingRulerMoveDirection } from '../utils/readingRuler';
@@ -359,6 +360,15 @@ export const usePagination = (
const action = resolvePageTurn(settings, candidate);
if (!action) return false;
// E-ink full screen refresh (Android only) — clears ghosting without
// turning the page. The native bridge no-ops on non-e-ink hardware.
if (action === 'refresh') {
if (appService?.isAndroidApp) {
refreshEinkScreen().catch(() => {});
}
return true;
}
const viewSettings = getViewSettings(bookKey);
const side = action === 'pagePrev' || action === 'sectionPrev' ? 'up' : 'down';
const mode = action === 'sectionPrev' || action === 'sectionNext' ? 'section' : 'page';
@@ -455,7 +465,8 @@ export const usePagination = (
hardwarePageTurner?.bindings.pagePrev?.source === 'native' ||
hardwarePageTurner?.bindings.pageNext?.source === 'native' ||
hardwarePageTurner?.bindings.sectionPrev?.source === 'native' ||
hardwarePageTurner?.bindings.sectionNext?.source === 'native';
hardwarePageTurner?.bindings.sectionNext?.source === 'native' ||
hardwarePageTurner?.bindings.refresh?.source === 'native';
const needsNativeInterception =
!!appService?.isMobileApp && !!hardwarePageTurner?.enabled && hasNativeBinding;
@@ -485,6 +496,7 @@ export const usePagination = (
hardwarePageTurner?.bindings.pageNext?.source,
hardwarePageTurner?.bindings.sectionPrev?.source,
hardwarePageTurner?.bindings.sectionNext?.source,
hardwarePageTurner?.bindings.refresh?.source,
]);
// Touch swipe page flip for fixed-layout books — registered as a touch interceptor