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