/** * The native bits Phase 1 did in native.js: haptics and the hardware back * button. No-ops in a browser, so the dev server behaves normally. */ import { useEffect } from "react"; import { App as CapacitorApp } from "@capacitor/app"; import { Capacitor } from "@capacitor/core"; import { Haptics, ImpactStyle, NotificationType } from "@capacitor/haptics"; const isNative = (): boolean => Capacitor.isNativePlatform(); /** * Swallowing these was a mistake: haptics did nothing on the device and the * reason had been discarded. Report once per distinct failure — enough to read * in logcat, quiet enough not to spam on every key press. */ const reported = new Set(); function report(where: string, error: unknown): void { const message = error instanceof Error ? error.message : String(error); const key = where + ":" + message; if (reported.has(key)) return; reported.add(key); console.error("haptics " + where + " failed:", message); } /** A light tick under every key press, as the reference's buzz() did. */ export function tapFeedback(): void { if (!isNative()) return; Haptics.impact({ style: ImpactStyle.Light }).catch((e) => report("impact", e)); } /** Shaped feedback on a graded answer: right and wrong should feel different. */ export function gradeFeedback(correct: boolean): void { if (!isNative()) return; Haptics.notification({ type: correct ? NotificationType.Success : NotificationType.Error, }).catch((e) => report("notification", e)); } export function exitApp(): void { if (!isNative()) return; void CapacitorApp.exitApp(); } /** * Hardware back. Registering a listener suppresses Capacitor's default history * navigation, so `onBack` owns the whole stack: sheet, then tab, then exit. */ export function useBackButton(onBack: () => void): void { useEffect(() => { if (!isNative()) return; const handle = CapacitorApp.addListener("backButton", () => onBack()); return () => { void handle.then((h) => h.remove()); }; }, [onBack]); }