import DraggablePanel from "@/components/DraggablePanel"; import type { PolygonWithLabelProps } from "@/components/map/PolygonWithLabel"; import type { PolylineWithLabelProps } from "@/components/map/PolylineWithLabel"; import ShipInfo from "@/components/map/ShipInfo"; import { TagState, TagStateCallbackPayload } from "@/components/map/TagState"; import { EVENT_SEARCH_THINGS, IOS_PLATFORM, LIGHT_THEME } from "@/constants"; import { usePlatform } from "@/hooks/use-platform"; import { useThemeContext } from "@/hooks/use-theme-context"; import { searchThingEventBus } from "@/services/device_events"; import { getShipIcon } from "@/services/map_service"; import eventBus from "@/utils/eventBus"; import { useEffect, useRef, useState } from "react"; import { Animated, Image, ScrollView, StyleSheet, Text, View, } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import MapView, { Marker } from "react-native-maps"; export default function HomeScreen() { const [alarmData, setAlarmData] = useState(null); const [banzoneData, setBanzoneData] = useState(null); const [trackPointsData, setTrackPointsData] = useState< Model.ShipTrackPoint[] | null >(null); const [circleRadius, setCircleRadius] = useState(100); const [zoomLevel, setZoomLevel] = useState(10); const [isFirstLoad, setIsFirstLoad] = useState(true); const [polylineCoordinates, setPolylineCoordinates] = useState< PolylineWithLabelProps[] >([]); const [polygonCoordinates, setPolygonCoordinates] = useState< PolygonWithLabelProps[] >([]); const [things, setThings] = useState(null); const platform = usePlatform(); const theme = useThemeContext().colorScheme; const scale = useRef(new Animated.Value(0)).current; const opacity = useRef(new Animated.Value(1)).current; // Thêm state để quản lý tracksViewChanges const [tracksViewChanges, setTracksViewChanges] = useState(true); const bodySearchThings: Model.SearchThingBody = { offset: 0, limit: 50, order: "name", sort: "asc", metadata: { not_empty: "ship_id", }, }; useEffect(() => { searchThingEventBus(bodySearchThings); const querySearchThingsData = (thingsData: Model.ThingsResponse) => { const sortedThings: Model.Thing[] = (thingsData.things ?? []).sort( (a, b) => { const stateLevelA = a.metadata?.state_level || 0; const stateLevelB = b.metadata?.state_level || 0; return stateLevelB - stateLevelA; // Giảm dần } ); const sortedThingsResponse: Model.ThingsResponse = { ...thingsData, things: sortedThings, }; console.log("Things Updated: ", sortedThingsResponse.things?.length); setThings(sortedThingsResponse); }; eventBus.on(EVENT_SEARCH_THINGS, querySearchThingsData); return () => { eventBus.off(EVENT_SEARCH_THINGS, querySearchThingsData); }; }, []); useEffect(() => { if (things) { // console.log("Things Updated: ", things.things?.length); // const gpsDatas: Model.GPSResponse[] = []; // for (const thing of things.things || []) { // if (thing.metadata?.gps) { // const gps: Model.GPSResponse = JSON.parse(thing.metadata.gps); // gpsDatas.push(gps); // } // } // console.log("GPS Lenght: ", gpsDatas.length); // setGpsData(gpsDatas); } }, [things]); // useEffect(() => { // setPolylineCoordinates([]); // setPolygonCoordinates([]); // if (!entityData) return; // if (!banzoneData) return; // for (const entity of entityData) { // if (entity.id !== ENTITY.ZONE_ALARM_LIST) { // continue; // } // let zones: any[] = []; // try { // zones = entity.valueString ? JSON.parse(entity.valueString) : []; // } catch (parseError) { // console.error("Error parsing zone list:", parseError); // continue; // } // // Nếu danh sách zone rỗng, clear tất cả // if (zones.length === 0) { // setPolylineCoordinates([]); // setPolygonCoordinates([]); // return; // } // let polylines: PolylineWithLabelProps[] = []; // let polygons: PolygonWithLabelProps[] = []; // for (const zone of zones) { // // console.log("Zone Data: ", zone); // const geom = banzoneData.find((b) => b.id === zone.zone_id); // if (!geom) { // continue; // } // const { geom_type, geom_lines, geom_poly } = geom.geom || {}; // if (typeof geom_type !== "number") { // continue; // } // if (geom_type === 2) { // // if(oldEntityData.find(e => e.id === )) // // foundPolyline = true; // const coordinates = convertWKTLineStringToLatLngArray( // geom_lines || "" // ); // if (coordinates.length > 0) { // polylines.push({ // coordinates: coordinates.map((coord) => ({ // latitude: coord[0], // longitude: coord[1], // })), // label: zone?.zone_name ?? "", // content: zone?.message ?? "", // }); // } else { // console.log("Không tìm thấy polyline trong alarm"); // } // } else if (geom_type === 1) { // // foundPolygon = true; // const coordinates = convertWKTtoLatLngString(geom_poly || ""); // if (coordinates.length > 0) { // // console.log("Polygon Coordinate: ", coordinates); // const zonePolygons = coordinates.map((polygon) => ({ // coordinates: polygon.map((coord) => ({ // latitude: coord[0], // longitude: coord[1], // })), // label: zone?.zone_name ?? "", // content: zone?.message ?? "", // })); // polygons.push(...zonePolygons); // } else { // console.log("Không tìm thấy polygon trong alarm"); // } // } // } // setPolylineCoordinates(polylines); // setPolygonCoordinates(polygons); // } // }, [banzoneData, entityData]); // Hàm tính radius cố định khi zoom change const calculateRadiusFromZoom = (zoom: number) => { const baseZoom = 10; const baseRadius = 100; const zoomDifference = baseZoom - zoom; const calculatedRadius = baseRadius * Math.pow(2, zoomDifference); // console.log("Caculate Radius: ", calculatedRadius); return Math.max(calculatedRadius, 50); }; // Xử lý khi region (zoom) thay đổi const handleRegionChangeComplete = (newRegion: any) => { // Tính zoom level từ latitudeDelta // zoom = log2(360 / (latitudeDelta * 2)) + 8 const zoom = Math.round(Math.log2(360 / (newRegion.latitudeDelta * 2)) + 8); const newRadius = calculateRadiusFromZoom(zoom); setCircleRadius(newRadius); setZoomLevel(zoom); // console.log("Zoom level:", zoom, "Circle radius:", newRadius); }; // Tính toán region để focus vào marker của tàu (chỉ lần đầu tiên) const getMapRegion = () => { if (!isFirstLoad) { // Sau lần đầu, return undefined để không force region return undefined; } if (things?.things?.length === 0) { return { latitude: 15.70581, longitude: 116.152685, latitudeDelta: 0.05, longitudeDelta: 0.05, }; } return { latitude: things?.things?.[0]?.metadata?.gps ? JSON.parse(things.things[0].metadata.gps).lat : 15.70581, longitude: things?.things?.[0]?.metadata?.gps ? JSON.parse(things.things[0].metadata.gps).lon : 116.152685, latitudeDelta: 0.05, longitudeDelta: 0.05, }; }; const handleMapReady = () => { setTimeout(() => { setIsFirstLoad(false); }, 2000); }; useEffect(() => { if (alarmData?.level === 3) { const loop = Animated.loop( Animated.sequence([ Animated.parallel([ Animated.timing(scale, { toValue: 3, // nở to 3 lần duration: 1500, useNativeDriver: true, }), Animated.timing(opacity, { toValue: 0, // mờ dần duration: 1500, useNativeDriver: true, }), ]), Animated.parallel([ Animated.timing(scale, { toValue: 0, duration: 0, useNativeDriver: true, }), Animated.timing(opacity, { toValue: 1, duration: 0, useNativeDriver: true, }), ]), ]) ); loop.start(); return () => loop.stop(); } }, [things, scale, opacity]); // Tắt tracksViewChanges sau khi map đã load xong useEffect(() => { if (!isFirstLoad) { // Delay một chút để đảm bảo markers đã render xong const timer = setTimeout(() => { setTracksViewChanges(false); }, 3000); return () => clearTimeout(timer); } }, [isFirstLoad]); const handleOnPressState = (state: TagStateCallbackPayload) => { // Xây dựng query state dựa trên logic bạn cung cấp const stateNormalQuery = state.isNormal ? "normal" : ""; const stateSosQuery = state.isSos ? "sos" : ""; const stateWarningQuery = state.isWarning ? stateNormalQuery + ",warning" : stateNormalQuery; const stateCriticalQuery = state.isDangerous ? stateWarningQuery + ",critical" : stateWarningQuery; // Nếu bật tất cả filter thì không cần truyền stateQuery const stateQuery = state.isNormal && state.isWarning && state.isDangerous && state.isSos ? "" : [stateCriticalQuery, stateSosQuery].filter(Boolean).join(","); let metaFormQuery = {}; if (state.isDisconected) metaFormQuery = { ...metaFormQuery, connected: false }; // Tạo metadata query const metaStateQuery = stateQuery !== "" ? { state_level: stateQuery } : null; // Tạo body search với filter const searchParams: Model.SearchThingBody = { offset: 0, limit: 50, order: "name", sort: "asc", metadata: { ...metaFormQuery, ...metaStateQuery, not_empty: "ship_id", }, }; // Gọi API tìm kiếm searchThingEventBus(searchParams); }; return ( {things?.things && things.things.length > 0 && ( <> {things.things .filter((thing) => thing.metadata?.gps) // Filter trước để tránh null check .map((thing, index) => { const gpsData: Model.GPSResponse = JSON.parse( thing.metadata!.gps! ); // Tạo unique key dựa trên thing.id hoặc tọa độ const uniqueKey = thing.id ? `marker-${thing.id}-${index}` : `marker-${gpsData.lat.toFixed(6)}-${gpsData.lon.toFixed( 6 )}-${index}`; return ( {thing.metadata?.state_level === 3 && ( )} { const icon = getShipIcon( thing.metadata?.state_level || 0, gpsData.fishing ); // console.log("Ship icon:", icon, "for level:", alarmData?.level, "fishing:", gpsData.fishing); return typeof icon === "string" ? { uri: icon } : icon; })()} style={{ width: 32, height: 32, transform: [ { rotate: `${ typeof gpsData.h === "number" && !isNaN(gpsData.h) ? gpsData.h : 0 }deg`, }, ], }} /> ); })} )} {/* */} {/* Draggable Panel */} { console.log("Panel expanded:", expanded); }} > <> Danh sách tàu thuyền {things && things.things && things.things.length > 0 && ( <> {things.things.map((thing, index) => { return ( {index < (things.things?.length ?? 0) - 1 && ( )} ); })} )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, map: { flex: 1, }, button: { // display: "none", position: "absolute", top: 50, right: 20, backgroundColor: "#007AFF", paddingHorizontal: 16, paddingVertical: 12, borderRadius: 8, elevation: 5, shadowColor: "#000", shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, }, buttonText: { color: "#fff", fontSize: 16, fontWeight: "600", }, pingContainer: { width: 32, height: 32, alignItems: "center", justifyContent: "center", overflow: "visible", }, pingCircle: { position: "absolute", width: 40, height: 40, borderRadius: 20, backgroundColor: "#ED3F27", }, centerDot: { width: 20, height: 20, borderRadius: 10, backgroundColor: "#0096FF", }, });