10 changed files with 184 additions and 9 deletions
-
15src/app/layout.tsx
-
2src/app/providers.tsx
-
19src/app/questions-list/[slug]/question-detail-client.tsx
-
15src/app/questions-list/questions-list-client.tsx
-
28src/components/Componentes/hardware-back-bridge.tsx
-
2src/components/Componentes/question-exit-navigation-button.tsx
-
2src/components/Componentes/question-section-flow.tsx
-
15src/components/Componentes/use-sheet-scroll-lock.ts
-
84src/hooks/use-hardware-back-handler.ts
-
11src/types/window.d.ts
@ -0,0 +1,28 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import { useEffect } from "react"; |
||||
|
import { handleHardwareBack } from "@/hooks/use-hardware-back-handler"; |
||||
|
|
||||
|
/** |
||||
|
* Wires the React hardware-back handler stack to the global |
||||
|
* window.__habibHandleHardwareBack function. |
||||
|
* |
||||
|
* Mount this once in the Providers tree (after React hydration). |
||||
|
* It replaces the bootstrap stub with the real handler that walks |
||||
|
* the registered stack. |
||||
|
*/ |
||||
|
export function HardwareBackBridge() { |
||||
|
useEffect(() => { |
||||
|
window.__habibHandleHardwareBack = handleHardwareBack; |
||||
|
|
||||
|
return () => { |
||||
|
// On unmount (shouldn't happen in practice), restore the stub.
|
||||
|
window.__habibHandleHardwareBack = () => |
||||
|
Promise.resolve({ handled: false }); |
||||
|
}; |
||||
|
}, []); |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
export default HardwareBackBridge; |
||||
@ -0,0 +1,84 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import { useEffect, useRef } from "react"; |
||||
|
|
||||
|
/** |
||||
|
* Global stack of hardware-back handlers. |
||||
|
* |
||||
|
* When Flutter sends a hardware back event, the root handler in layout.tsx |
||||
|
* pops the last handler from this stack and calls it. If the handler returns |
||||
|
* true, it means "I handled it — keep the WebView open". If it returns false |
||||
|
* (or the stack is empty), Flutter should close the WebView screen. |
||||
|
* |
||||
|
* Pages register themselves with useHardwareBackHandler(). Sheets and modals |
||||
|
* also register — the last one wins, matching the visual stacking order. |
||||
|
*/ |
||||
|
const backHandlerStack: Array<() => boolean | Promise<boolean>> = []; |
||||
|
|
||||
|
/** |
||||
|
* Register a hardware-back handler. When the user presses the hardware back |
||||
|
* button (Android), Flutter calls window.__habibHandleHardwareBack(). The |
||||
|
* root handler walks the stack from top to bottom and calls the first handler. |
||||
|
* |
||||
|
* @param handler - Return true if you handled the back (e.g. closed a sheet, |
||||
|
* flushed answers and navigated). Return false if you didn't handle it |
||||
|
* (Flutter should close the WebView). |
||||
|
* @param enabled - When false, the handler is not registered. Useful for |
||||
|
* conditionally enabling back handling. |
||||
|
* |
||||
|
* @example |
||||
|
* // In questions-list (root): back = close service
|
||||
|
* useHardwareBackHandler(() => false); |
||||
|
* |
||||
|
* // In question-detail: back = flush + navigate
|
||||
|
* useHardwareBackHandler(async () => { |
||||
|
* await flushAnswers({ force: true }); |
||||
|
* router.replace(questionsListHref); |
||||
|
* return true; |
||||
|
* }); |
||||
|
*/ |
||||
|
export function useHardwareBackHandler( |
||||
|
handler: () => boolean | Promise<boolean>, |
||||
|
enabled = true, |
||||
|
) { |
||||
|
const handlerRef = useRef(handler); |
||||
|
handlerRef.current = handler; |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (!enabled) return; |
||||
|
|
||||
|
// Wrap in a stable closure so we can remove the exact reference.
|
||||
|
const stableHandler = () => handlerRef.current(); |
||||
|
backHandlerStack.push(stableHandler); |
||||
|
|
||||
|
return () => { |
||||
|
const index = backHandlerStack.lastIndexOf(stableHandler); |
||||
|
if (index !== -1) { |
||||
|
backHandlerStack.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
}, [enabled]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Called by the root bootstrap script when Flutter sends a hardware back event. |
||||
|
* Returns { handled: true } if a web handler consumed the event, or |
||||
|
* { handled: false } if Flutter should close the WebView screen. |
||||
|
*/ |
||||
|
export async function handleHardwareBack(): Promise<{ handled: boolean }> { |
||||
|
if (backHandlerStack.length === 0) { |
||||
|
return { handled: false }; |
||||
|
} |
||||
|
|
||||
|
// Pop the topmost handler (last registered = topmost in visual stack).
|
||||
|
const handler = backHandlerStack[backHandlerStack.length - 1]; |
||||
|
try { |
||||
|
const result = await handler(); |
||||
|
return { handled: result }; |
||||
|
} catch (error) { |
||||
|
console.warn("[HardwareBack] Handler threw:", error); |
||||
|
return { handled: false }; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default useHardwareBackHandler; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue