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
+15
View File
@@ -113,6 +113,11 @@ export interface GetStorefrontRegionCodeResponse {
error?: string;
}
export interface RefreshEinkScreenResponse {
success: boolean;
error?: string;
}
export async function copyURIToPath(request: CopyURIRequest): Promise<CopyURIResponse> {
const result = await invoke<CopyURIResponse>('plugin:native-bridge|copy_uri_to_path', {
payload: request,
@@ -240,6 +245,16 @@ export async function getStorefrontRegionCode(): Promise<GetStorefrontRegionCode
return result;
}
/**
* Trigger a deep e-ink full screen refresh (GC / GC16 waveform) to clear
* ghosting. Android-only; the native side probes several vendor mechanisms
* via reflection and returns `success: false` on devices with no e-ink
* controller. Other platforms reject with an unsupported-platform error.
*/
export async function refreshEinkScreen(): Promise<RefreshEinkScreenResponse> {
return await invoke<RefreshEinkScreenResponse>('plugin:native-bridge|refresh_eink_screen');
}
// ── Sync passphrase keychain ────────────────────────────────────────────
// Tauri-only. Wired into the TauriPassphraseStore (src/libs/crypto/
// passphrase.ts) so the user's sync passphrase persists across app
+8 -3
View File
@@ -2,13 +2,16 @@ import { HardwarePageTurnerSettings, KeyBinding } from '@/types/settings';
import { stubTranslation as _ } from '@/utils/misc';
export type KeyCandidate = { source: 'native' | 'dom'; id: string };
export type PageTurnAction = 'pagePrev' | 'pageNext' | 'sectionPrev' | 'sectionNext';
// `refresh` is an e-ink-only action (full screen refresh to clear ghosting);
// the others navigate. All share the same key-binding machinery.
export type PageTurnAction = 'pagePrev' | 'pageNext' | 'sectionPrev' | 'sectionNext' | 'refresh';
export const PAGE_TURN_ACTIONS: PageTurnAction[] = [
'pagePrev',
'pageNext',
'sectionPrev',
'sectionNext',
'refresh',
];
const NATIVE_KEY_LABELS: Record<string, string> = {
@@ -53,8 +56,10 @@ export const normalizeDomKeyEvent = (event: KeyboardEvent): KeyBinding => {
};
/** True when `candidate` is the key described by `binding`. */
export const matchesBinding = (binding: KeyBinding | null, candidate: KeyCandidate): boolean =>
!!binding && binding.source === candidate.source && binding.id === candidate.id;
export const matchesBinding = (
binding: KeyBinding | null | undefined,
candidate: KeyCandidate,
): boolean => !!binding && binding.source === candidate.source && binding.id === candidate.id;
/**
* Decide which page-turn action an incoming key triggers. Returns the