You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

319 lines
11 KiB

"use client";
import { useEffect, useState } from "react";
import Button from "./button";
import { useFlutterBridge } from "@/hooks/useFlutterBridge";
import {
copyToClipboard,
downloadFile,
isInFlutterWebView,
openExternalUrl,
requestAutoLocation,
pickManualLocation,
} from "@/lib/webview-actions";
const EXIT_ANIMATION_MS = 220;
type ReportActionsSheetProps = {
onClose?: () => void;
};
export function ReportActionsSheet({ onClose }: ReportActionsSheetProps) {
const [isVisible, setIsVisible] = useState(true);
const [isEntering, setIsEntering] = useState(true);
const [isClosing, setIsClosing] = useState(false);
const [showLogs, setShowLogs] = useState(false);
const { sendToFlutter, logs, lastEvent, isReady } = useFlutterBridge({
enableLogging: true,
});
const closeSheet = () => {
if (isClosing) return;
setIsClosing(true);
};
// ✅ دکمه WEB_READY
const handleSendWebReady = () => {
sendToFlutter("WEB_READY", {
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
});
console.log("✅ WEB_READY ارسال شد");
};
// ✅ دکمه دریافت خودکار موقعیت مکانی GPS (Auto)
const handleAutoLocation = async () => {
try {
const data = await requestAutoLocation();
alert(`📍 Auto Location: ${data.city || ""}, ${data.country || ""} (${data.latitude}, ${data.longitude})`);
} catch (e: any) {
alert(`❌ Auto Location Error: ${e.message}`);
}
};
// ✅ دکمه انتخاب دستی از روی نقشه فلاتر (Manual)
const handleManualLocation = async () => {
try {
const data = await pickManualLocation({ latitude: 35.6892, longitude: 51.3890 });
if (data) {
alert(`🗺️ Selected Location: ${data.city || ""}, ${data.country || ""} (${data.latitude}, ${data.longitude})`);
} else {
alert("⚠️ Map selection cancelled");
}
} catch (e: any) {
alert(`❌ Map Location Error: ${e.message}`);
}
};
// ✅ دکمه مشاور
const handleOpenConsultant = () => {
sendToFlutter("REQUEST_CONSULTANT", {
consultant: "habib@gmail.com",
});
console.log("👨‍⚕️ REQUEST_CONSULTANT ارسال شد");
};
// ✅ تست download_file
const handleTestDownload = () => {
const sent = downloadFile({
url: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
fileName: "test-document.pdf",
title: "Test PDF Document",
mimeType: "application/pdf",
fileType: "document",
});
console.log(
sent ? "📥 download_file ارسال شد" : "⚠️ Flutter WebView یافت نشد",
);
};
// ✅ تست copy_to_clipboard
const handleTestCopy = async () => {
const success = await copyToClipboard(
"سلام! این یک متن تست برای کپی است. 🎉",
"test_copy",
);
console.log(success ? "📋 copy_to_clipboard ارسال شد" : "⚠️ کپی انجام نشد");
};
// ✅ تست open_external_url
const handleTestOpenUrl = () => {
const sent = openExternalUrl({
url: "https://habibapp.com",
mode: "externalApplication",
title: "test_open_url",
});
console.log(
sent ? "🔗 open_external_url ارسال شد" : "⚠️ Flutter WebView یافت نشد",
);
};
useEffect(() => {
const frameId = window.requestAnimationFrame(() => {
setIsEntering(false);
});
return () => window.cancelAnimationFrame(frameId);
}, []);
useEffect(() => {
if (!isVisible) return;
const previousBodyOverflow = document.body.style.overflow;
const previousHtmlOverflow = document.documentElement.style.overflow;
document.body.style.overflow = "hidden";
document.documentElement.style.overflow = "hidden";
return () => {
document.body.style.overflow = previousBodyOverflow;
document.documentElement.style.overflow = previousHtmlOverflow;
};
}, [isVisible]);
useEffect(() => {
if (!isClosing) return;
const timeoutId = window.setTimeout(() => {
setIsVisible(false);
onClose?.();
}, EXIT_ANIMATION_MS);
return () => window.clearTimeout(timeoutId);
}, [isClosing, onClose]);
useEffect(() => {
const handleFlutterResponse: NonNullable<Window["onFlutterResponse"]> = (
event,
) => {
if (event.action === "get_location" && event.success && event.data) {
const message = `Location: ${event.data.latitude}, ${event.data.longitude}`;
alert(message);
}
// Log responses for download/clipboard/external actions
if (event.action === "download_file") {
console.log(`📥 download_file response: ${event.status}`, event);
}
if (event.action === "copy_to_clipboard") {
console.log(`📋 copy_to_clipboard response: ${event.success}`, event);
}
if (event.action === "open_external_url") {
console.log(`🔗 open_external_url response: ${event.status}`, event);
}
};
const unsubscribe = window.addFlutterResponseListener?.(
handleFlutterResponse,
);
return () => {
unsubscribe?.();
};
}, []);
if (!isVisible) return null;
return (
<div
className={[
"fixed inset-0 z-50 flex items-end justify-center transition-all duration-[220ms]",
isClosing || isEntering
? "bg-[#171717]/0 opacity-0"
: "bg-[#171717]/55 opacity-100",
]
.filter(Boolean)
.join(" ")}
role="dialog"
aria-modal="true"
onClick={(event) => {
if (event.target === event.currentTarget) closeSheet();
}}
>
<section
className={[
"w-full sm:max-w-[375px] rounded-t-[15px] bg-[#F9F8F8] shadow-[0_20px_60px_rgba(15,23,42,0.08)] transition-transform duration-[220ms] ease-out max-h-[85vh] overflow-y-auto",
isClosing || isEntering ? "translate-y-full" : "translate-y-0",
]
.filter(Boolean)
.join(" ")}
>
{/* Header */}
<div className="sticky top-0 bg-[#F9F8F8] p-3.5 border-b border-gray-200/50">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-gray-800">
Settings & Support
</h3>
<div
className={`w-2 h-2 rounded-full ${isReady ? "bg-green-500" : "bg-gray-400"}`}
title={isReady ? "Connected to Flutter" : "Inactive"}
/>
</div>
<button
onClick={closeSheet}
className="text-2xl text-gray-500 hover:text-gray-700 leading-none"
aria-label="Close"
>
×
</button>
</div>
</div>
<div className="p-3.5 flex flex-col gap-3">
{/* Main buttons */}
<div className="flex flex-col gap-2">
<Button onClick={handleAutoLocation}>📍 Auto Location (GPS)</Button>
<Button onClick={handleManualLocation}>🗺 Manual Location (Map)</Button>
<Button onClick={handleOpenConsultant}>
👨 Habib Consultation
</Button>
</div>
{/* Test WebView Actions */}
<div className="bg-purple-50 rounded-lg p-3 border border-purple-200">
<p className="text-xs text-purple-800 mb-2 font-semibold">
🧪 Test WebView Utility Actions:
</p>
<div className="flex flex-col gap-2">
<Button onClick={handleTestDownload}>📥 download_file</Button>
<Button onClick={handleTestCopy}>📋 copy_to_clipboard</Button>
<Button onClick={handleTestOpenUrl}>🔗 open_external_url</Button>
</div>
{!isInFlutterWebView() && (
<p className="text-[10px] text-purple-600 mt-2">
Outside WebView copy_to_clipboard uses browser fallback,
others require Flutter.
</p>
)}
</div>
{/* Test WEB_READY */}
<div className="bg-blue-50 rounded-lg p-3 border border-blue-200">
<p className="text-xs text-blue-800 mb-2 font-semibold">
🧪 Test Flutter Connection:
</p>
<Button onClick={handleSendWebReady}>🚀 Send WEB_READY</Button>
</div>
{/* Last Received Event */}
{lastEvent && (
<div className="bg-green-50 rounded-lg p-3 border border-green-200">
<p className="text-xs text-green-800 font-semibold mb-2">
📥 Last Event from Flutter:
</p>
<div className="bg-white rounded p-2 text-xs font-mono break-all">
<div className="text-green-600 font-bold">{lastEvent.type}</div>
<div className="text-gray-600 mt-1">
{JSON.stringify(lastEvent.payload, null, 2)}
</div>
</div>
</div>
)}
{/* Logs */}
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
<div className="flex items-center justify-between mb-2">
<p className="text-xs text-gray-700 font-semibold">
📋 Logs ({logs.length})
</p>
<button
onClick={() => setShowLogs(!showLogs)}
className="text-xs text-blue-600 hover:text-blue-800"
>
{showLogs ? "Hide" : "Show"}
</button>
</div>
{showLogs && (
<div className="max-h-[150px] overflow-y-auto space-y-1">
{logs.length === 0 ? (
<p className="text-xs text-gray-500 text-center py-2">
هنوز لاگی ثبت نشده
</p>
) : (
logs.slice(-10).map((log, index) => (
<div
key={index}
className="text-[10px] font-mono bg-white p-1.5 rounded border border-gray-300 break-all"
>
{log}
</div>
))
)}
</div>
)}
</div>
{/* راهنما */}
<div className="bg-yellow-50 rounded-lg p-3 border border-yellow-200">
<p className="text-xs text-yellow-900">
<strong>💡 راهنما:</strong>
<br /> WEB_READY هنگام باز شدن این شیت بهصورت خودکار از کانال
HabibApp ارسال میشود
<br /> نقطهٔ سبز یعنی کانال HabibApp در دسترس است
<br /> برای ارسال دستی، دکمه «ارسال WEB_READY» را بزنید
</p>
</div>
</div>
</section>
</div>
);
}
export default ReportActionsSheet;