From 0b4913a654033e78f5d9b96087d91e9904dcef5f Mon Sep 17 00:00:00 2001 From: mortezaei Date: Sat, 13 Jun 2026 20:16:15 +0330 Subject: [PATCH] fix --- src/components/ui/report-actions-sheet.tsx | 23 +--- src/hooks/useFlutterBridge.ts | 130 +++++++++++++++++---- 2 files changed, 115 insertions(+), 38 deletions(-) diff --git a/src/components/ui/report-actions-sheet.tsx b/src/components/ui/report-actions-sheet.tsx index 0a68792..d9575ba 100644 --- a/src/components/ui/report-actions-sheet.tsx +++ b/src/components/ui/report-actions-sheet.tsx @@ -36,28 +36,14 @@ export function ReportActionsSheet({ onClose }: ReportActionsSheetProps) { }; // ✅ دکمه دریافت موقعیت مکانی + // پل اکنون از کانال واقعی HabibApp استفاده می‌کند، پس یک‌بار ارسال کافی است. const handleGetLocation = () => { - // روش قدیمی - if (typeof window !== "undefined" && window.HabibApp) { - window.HabibApp.postMessage(JSON.stringify({ action: "get_location" })); - } - // روش جدید با bridge sendToFlutter("REQUEST_LOCATION"); console.log("📍 REQUEST_LOCATION ارسال شد"); }; // ✅ دکمه مشاور const handleOpenConsultant = () => { - // روش قدیمی - if (typeof window !== "undefined" && window.HabibApp) { - window.HabibApp.postMessage( - JSON.stringify({ - action: "open_consultant_page", - data: { consultant: "habib@gmail.com" }, - }) - ); - } - // روش جدید با bridge sendToFlutter("REQUEST_CONSULTANT", { consultant: "habib@gmail.com", }); @@ -225,10 +211,11 @@ export function ReportActionsSheet({ onClose }: ReportActionsSheetProps) {

💡 راهنما:
- • دکمه "WEB_READY" را بزنید + • WEB_READY هنگام باز شدن این شیت به‌صورت خودکار از کانال + HabibApp ارسال می‌شود
- • در Flutter console لاگ را ببینید -
• Flutter باید ایونت "INITIAL_CONFIG" بفرستد + • نقطهٔ سبز یعنی کانال HabibApp در دسترس است +
• برای ارسال دستی، دکمه «ارسال WEB_READY» را بزنید

diff --git a/src/hooks/useFlutterBridge.ts b/src/hooks/useFlutterBridge.ts index 42f7b4a..cce3a73 100644 --- a/src/hooks/useFlutterBridge.ts +++ b/src/hooks/useFlutterBridge.ts @@ -43,17 +43,31 @@ interface UseFlutterBridgeReturn { // دریافت ایونت‌ها events: FlutterEvent[]; lastEvent: FlutterEvent | null; - + // ارسال ایونت‌ها sendToFlutter: (type: WebEventType, payload?: any) => void; - + // آماده بودن isReady: boolean; - + // لاگ‌ها logs: string[]; } +/** + * نگاشت ایونت‌های وب به اکشن‌های واقعی Flutter. + * اپ Flutter (najm) فقط کانال `HabibApp` را ثبت می‌کند و پیام‌هایی با + * فرمت `{ action, data }` را می‌فهمد. کانال `FlutterChannel` در Flutter + * وجود ندارد، بنابراین تنها مسیر معتبر ارسال، همین کانال است. + */ +const WEB_EVENT_TO_ACTION: Record = { + WEB_READY: "web_ready", + REQUEST_LOCATION: "get_location", + REQUEST_CONSULTANT: "open_consultant_page", + PAGE_LOADED: "page_loaded", + ERROR_OCCURRED: "error_occurred", +}; + export function useFlutterBridge( options: UseFlutterBridgeOptions = {} ): UseFlutterBridgeReturn { @@ -65,6 +79,7 @@ export function useFlutterBridge( const [logs, setLogs] = useState([]); const logsRef = useRef([]); + const hasSentReadyRef = useRef(false); // تابع کمکی برای اضافه کردن لاگ const addLog = (message: string, type: 'info' | 'success' | 'error' = 'info') => { @@ -81,37 +96,41 @@ export function useFlutterBridge( // تابع ارسال ایونت به Flutter const sendToFlutter = (type: WebEventType, payload?: any) => { - const event: WebEvent = { - type, - payload, - timestamp: Date.now(), - }; + if (typeof window === 'undefined') return; + + const action = WEB_EVENT_TO_ACTION[type]; + let delivered = false; try { - // روش 1: postMessage (استاندارد) + // روش اصلی و واقعی: کانال HabibApp با فرمت { action, data } + // این تنها کانالی است که اپ Flutter ثبت کرده و به آن گوش می‌دهد. + if (window.HabibApp?.postMessage) { + const message = + payload !== undefined ? { action, data: payload } : { action }; + window.HabibApp.postMessage(JSON.stringify(message)); + delivered = true; + addLog(`📤 ارسال به Flutter (HabibApp): ${type} → ${action}`, 'success'); + } + + // مسیرهای fallback فقط برای محیط توسعه/iframe (در WebView واقعی Flutter وجود ندارند) + const event: WebEvent = { type, payload, timestamp: Date.now() }; + if (window.parent && window.parent !== window) { window.parent.postMessage(event, '*'); - addLog(`📤 ارسال به Flutter: ${type}`, 'success'); + delivered = true; } - // روش 2: Flutter WebView interface (Android) if ((window as any).FlutterChannel) { (window as any).FlutterChannel.postMessage(JSON.stringify(event)); - addLog(`📤 ارسال به Flutter (Android): ${type}`, 'success'); + delivered = true; } - // روش 3: iOS WebKit message handler if ((window as any).webkit?.messageHandlers?.FlutterChannel) { (window as any).webkit.messageHandlers.FlutterChannel.postMessage(event); - addLog(`📤 ارسال به Flutter (iOS): ${type}`, 'success'); + delivered = true; } - // اگر هیچ کدام موجود نبود - if ( - (!window.parent || window.parent === window) && - !(window as any).FlutterChannel && - !(window as any).webkit?.messageHandlers?.FlutterChannel - ) { + if (!delivered) { addLog(`⚠️ محیط Flutter یافت نشد - در حالت توسعه`, 'info'); } } catch (error) { @@ -185,6 +204,77 @@ export function useFlutterBridge( }; }, [onEvent, enableLogging]); + // تشخیص آمادگی واقعی + ارسال خودکار WEB_READY + // در WebView واقعی Flutter، شیء window.HabibApp توسط addJavaScriptChannel + // تزریق می‌شود؛ وجودش یعنی پل برقرار است. منتظر INITIAL_CONFIG نمی‌مانیم، + // چون Flutter چنین ایونتی نمی‌فرستد. + useEffect(() => { + if (typeof window === 'undefined') return; + + let interval: ReturnType | undefined; + let timeout: ReturnType | undefined; + + const markReadyAndAnnounce = () => { + if (!window.HabibApp?.postMessage) return false; + + setIsReady(true); + + if (!hasSentReadyRef.current) { + hasSentReadyRef.current = true; + sendToFlutter('WEB_READY', { + url: window.location.href, + userAgent: navigator.userAgent, + timestamp: Date.now(), + }); + addLog('✅ WEB_READY به‌صورت خودکار ارسال شد', 'success'); + } + + return true; + }; + + // اگر کانال هنوز تزریق نشده، کمی صبر می‌کنیم (تزریق ممکن است با تأخیر باشد) + if (!markReadyAndAnnounce()) { + interval = setInterval(() => { + if (markReadyAndAnnounce() && interval) clearInterval(interval); + }, 100); + timeout = setTimeout(() => { + if (interval) clearInterval(interval); + }, 5000); + } + + return () => { + if (interval) clearInterval(interval); + if (timeout) clearTimeout(timeout); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // گوش‌دادن به پاسخ‌های واقعی Flutter از مسیر window.onFlutterResponse. + // رسیدن هر پاسخ یعنی ارتباط دوطرفه برقرار است. + useEffect(() => { + if (typeof window === 'undefined') return; + + const unsubscribe = window.addFlutterResponseListener?.((response) => { + setIsReady(true); + + const flutterEvent: FlutterEvent = { + type: 'INITIAL_CONFIG', + payload: response, + timestamp: response?.data?.timestamp || Date.now(), + }; + + addLog(`📥 پاسخ از Flutter: ${response?.action}`, 'success'); + setEvents((prev) => [...prev, flutterEvent].slice(-20)); + setLastEvent(flutterEvent); + onEvent?.(flutterEvent); + }); + + return () => { + unsubscribe?.(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [onEvent]); + return { events, lastEvent,