mortezaei 2 weeks ago
parent
commit
0b4913a654
  1. 23
      src/components/ui/report-actions-sheet.tsx
  2. 124
      src/hooks/useFlutterBridge.ts

23
src/components/ui/report-actions-sheet.tsx

@ -36,28 +36,14 @@ export function ReportActionsSheet({ onClose }: ReportActionsSheetProps) {
}; };
// ✅ دکمه دریافت موقعیت مکانی // ✅ دکمه دریافت موقعیت مکانی
// پل اکنون از کانال واقعی HabibApp استفاده می‌کند، پس یک‌بار ارسال کافی است.
const handleGetLocation = () => { const handleGetLocation = () => {
// روش قدیمی
if (typeof window !== "undefined" && window.HabibApp) {
window.HabibApp.postMessage(JSON.stringify({ action: "get_location" }));
}
// روش جدید با bridge
sendToFlutter("REQUEST_LOCATION"); sendToFlutter("REQUEST_LOCATION");
console.log("📍 REQUEST_LOCATION ارسال شد"); console.log("📍 REQUEST_LOCATION ارسال شد");
}; };
// ✅ دکمه مشاور // ✅ دکمه مشاور
const handleOpenConsultant = () => { 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", { sendToFlutter("REQUEST_CONSULTANT", {
consultant: "habib@gmail.com", consultant: "habib@gmail.com",
}); });
@ -225,10 +211,11 @@ export function ReportActionsSheet({ onClose }: ReportActionsSheetProps) {
<p className="text-xs text-yellow-900"> <p className="text-xs text-yellow-900">
<strong>💡 راهنما:</strong> <strong>💡 راهنما:</strong>
<br /> <br />
دکمه "WEB_READY" را بزنید
WEB_READY هنگام باز شدن این شیت بهصورت خودکار از کانال
HabibApp ارسال میشود
<br /> <br />
در Flutter console لاگ را ببینید
<br /> Flutter باید ایونت "INITIAL_CONFIG" بفرستد
نقطهٔ سبز یعنی کانال HabibApp در دسترس است
<br /> برای ارسال دستی، دکمه «ارسال WEB_READY» را بزنید
</p> </p>
</div> </div>
</div> </div>

124
src/hooks/useFlutterBridge.ts

@ -54,6 +54,20 @@ interface UseFlutterBridgeReturn {
logs: string[]; logs: string[];
} }
/**
* نگاشت ایونتهای وب به اکشنهای واقعی Flutter.
* اپ Flutter (najm) فقط کانال `HabibApp` را ثبت میکند و پیامهایی با
* فرمت `{ action, data }` را میفهمد. کانال `FlutterChannel` در Flutter
* وجود ندارد، بنابراین تنها مسیر معتبر ارسال، همین کانال است.
*/
const WEB_EVENT_TO_ACTION: Record<WebEventType, string> = {
WEB_READY: "web_ready",
REQUEST_LOCATION: "get_location",
REQUEST_CONSULTANT: "open_consultant_page",
PAGE_LOADED: "page_loaded",
ERROR_OCCURRED: "error_occurred",
};
export function useFlutterBridge( export function useFlutterBridge(
options: UseFlutterBridgeOptions = {} options: UseFlutterBridgeOptions = {}
): UseFlutterBridgeReturn { ): UseFlutterBridgeReturn {
@ -65,6 +79,7 @@ export function useFlutterBridge(
const [logs, setLogs] = useState<string[]>([]); const [logs, setLogs] = useState<string[]>([]);
const logsRef = useRef<string[]>([]); const logsRef = useRef<string[]>([]);
const hasSentReadyRef = useRef(false);
// تابع کمکی برای اضافه کردن لاگ // تابع کمکی برای اضافه کردن لاگ
const addLog = (message: string, type: 'info' | 'success' | 'error' = 'info') => { const addLog = (message: string, type: 'info' | 'success' | 'error' = 'info') => {
@ -81,37 +96,41 @@ export function useFlutterBridge(
// تابع ارسال ایونت به Flutter // تابع ارسال ایونت به Flutter
const sendToFlutter = (type: WebEventType, payload?: any) => { 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 { 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) { if (window.parent && window.parent !== window) {
window.parent.postMessage(event, '*'); window.parent.postMessage(event, '*');
addLog(`📤 ارسال به Flutter: ${type}`, 'success');
delivered = true;
} }
// روش 2: Flutter WebView interface (Android)
if ((window as any).FlutterChannel) { if ((window as any).FlutterChannel) {
(window as any).FlutterChannel.postMessage(JSON.stringify(event)); (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) { if ((window as any).webkit?.messageHandlers?.FlutterChannel) {
(window as any).webkit.messageHandlers.FlutterChannel.postMessage(event); (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'); addLog(`⚠️ محیط Flutter یافت نشد - در حالت توسعه`, 'info');
} }
} catch (error) { } catch (error) {
@ -185,6 +204,77 @@ export function useFlutterBridge(
}; };
}, [onEvent, enableLogging]); }, [onEvent, enableLogging]);
// تشخیص آمادگی واقعی + ارسال خودکار WEB_READY
// در WebView واقعی Flutter، شیء window.HabibApp توسط addJavaScriptChannel
// تزریق می‌شود؛ وجودش یعنی پل برقرار است. منتظر INITIAL_CONFIG نمی‌مانیم،
// چون Flutter چنین ایونتی نمی‌فرستد.
useEffect(() => {
if (typeof window === 'undefined') return;
let interval: ReturnType<typeof setInterval> | undefined;
let timeout: ReturnType<typeof setTimeout> | 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 { return {
events, events,
lastEvent, lastEvent,

Loading…
Cancel
Save