import GPSInfoPanel from "@/components/map/GPSInfoPanel"; import type { PolygonWithLabelProps } from "@/components/map/PolygonWithLabel"; import { PolygonWithLabel } from "@/components/map/PolygonWithLabel"; import type { PolylineWithLabelProps } from "@/components/map/PolylineWithLabel"; import { PolylineWithLabel } from "@/components/map/PolylineWithLabel"; import SosButton from "@/components/map/SosButton"; import { ENTITY, EVENT_ALARM_DATA, EVENT_BANZONE_DATA, EVENT_ENTITY_DATA, EVENT_GPS_DATA, EVENT_TRACK_POINTS_DATA, IOS_PLATFORM, LIGHT_THEME, } from "@/constants"; import { useColorScheme } from "@/hooks/use-color-scheme.web"; import { usePlatform } from "@/hooks/use-platform"; import { getAlarmEventBus, getBanzonesEventBus, getEntitiesEventBus, getGpsEventBus, getTrackPointsEventBus, } from "@/services/device_events"; import { getShipIcon } from "@/services/map_service"; import eventBus from "@/utils/eventBus"; import { convertWKTLineStringToLatLngArray, convertWKTtoLatLngString, } from "@/utils/geom"; import { useEffect, useRef, useState } from "react"; import { Animated, Image as RNImage, StyleSheet, View } from "react-native"; import MapView, { Circle, Marker } from "react-native-maps"; export default function HomeScreen() { const [gpsData, setGpsData] = useState(null); const [alarmData, setAlarmData] = useState(null); const [entityData, setEntityData] = useState< Model.TransformedEntity[] | null >(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 platform = usePlatform(); const theme = useColorScheme(); const scale = useRef(new Animated.Value(0)).current; const opacity = useRef(new Animated.Value(1)).current; useEffect(() => { getGpsEventBus(); getAlarmEventBus(); getEntitiesEventBus(); getBanzonesEventBus(); getTrackPointsEventBus(); const queryGpsData = (gpsData: Model.GPSResponse) => { if (gpsData) { // console.log("GPS Data: ", gpsData); setGpsData(gpsData); } else { setGpsData(null); setPolygonCoordinates([]); setPolylineCoordinates([]); } }; const queryAlarmData = (alarmData: Model.AlarmResponse) => { // console.log("Alarm Data: ", alarmData.alarms.length); setAlarmData(alarmData); }; const queryEntityData = (entityData: Model.TransformedEntity[]) => { // console.log("Entities Length Data: ", entityData.length); setEntityData(entityData); }; const queryBanzonesData = (banzoneData: Model.Zone[]) => { // console.log("Banzone Data: ", banzoneData.length); setBanzoneData(banzoneData); }; const queryTrackPointsData = (TrackPointsData: Model.ShipTrackPoint[]) => { // console.log("TrackPoints Data: ", TrackPointsData.length); if (TrackPointsData && TrackPointsData.length > 0) { setTrackPointsData(TrackPointsData); } else { setTrackPointsData(null); } }; eventBus.on(EVENT_GPS_DATA, queryGpsData); // console.log("Registering event handlers in HomeScreen"); eventBus.on(EVENT_GPS_DATA, queryGpsData); // console.log("Subscribed to EVENT_GPS_DATA"); eventBus.on(EVENT_ALARM_DATA, queryAlarmData); // console.log("Subscribed to EVENT_ALARM_DATA"); eventBus.on(EVENT_ENTITY_DATA, queryEntityData); // console.log("Subscribed to EVENT_ENTITY_DATA"); eventBus.on(EVENT_TRACK_POINTS_DATA, queryTrackPointsData); // console.log("Subscribed to EVENT_TRACK_POINTS_DATA"); eventBus.once(EVENT_BANZONE_DATA, queryBanzonesData); // console.log("Subscribed once to EVENT_BANZONE_DATA"); return () => { // console.log("Unregistering event handlers in HomeScreen"); eventBus.off(EVENT_GPS_DATA, queryGpsData); // console.log("Unsubscribed EVENT_GPS_DATA"); eventBus.off(EVENT_ALARM_DATA, queryAlarmData); // console.log("Unsubscribed EVENT_ALARM_DATA"); eventBus.off(EVENT_ENTITY_DATA, queryEntityData); // console.log("Unsubscribed EVENT_ENTITY_DATA"); eventBus.off(EVENT_TRACK_POINTS_DATA, queryTrackPointsData); // console.log("Unsubscribed EVENT_TRACK_POINTS_DATA"); }; }, []); 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 (!gpsData) { return { latitude: 15.70581, longitude: 116.152685, latitudeDelta: 0.05, longitudeDelta: 0.05, }; } return { latitude: gpsData.lat, longitude: gpsData.lon, 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(); } }, [alarmData?.level, scale, opacity]); return ( {trackPointsData && trackPointsData.length > 0 && trackPointsData.map((point, index) => { // console.log(`Rendering circle ${index}:`, point); return ( ); })} {polylineCoordinates.length > 0 && ( <> {polylineCoordinates.map((polyline, index) => ( ))} )} {polygonCoordinates.length > 0 && ( <> {polygonCoordinates.map((polygon, index) => { return ( ); })} )} {gpsData !== null && ( {alarmData?.level === 3 && ( )} { const icon = getShipIcon( alarmData?.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`, }, ], }} /> )} ); } 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", }, });