From 62b18e5bc0f7fa07b9f5caf635490854041cdf3d Mon Sep 17 00:00:00 2001 From: Tran Anh Tuan Date: Wed, 5 Nov 2025 16:23:47 +0700 Subject: [PATCH] =?UTF-8?q?s=E1=BB=ADa=20l=E1=BB=97i=20hi=E1=BB=83n=20th?= =?UTF-8?q?=E1=BB=8B=20polyline,=20polygon=20=E1=BB=9F=20map,=20th=C3=AAm?= =?UTF-8?q?=20component=20ScanQrCode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.json | 6 + app/(tabs)/index.tsx | 41 ++--- app/(tabs)/sensor.tsx | 78 ++++++++- components/ScanQRCode.tsx | 228 +++++++++++++++++++++++++++ components/map/GPSInfoPanel.tsx | 4 +- components/map/PolygonWithLabel.tsx | 4 +- components/map/PolylineWithLabel.tsx | 3 +- components/map/SosButton.tsx | 3 - config/auth.ts | 2 +- package-lock.json | 21 +++ package.json | 1 + 11 files changed, 351 insertions(+), 40 deletions(-) create mode 100644 components/ScanQRCode.tsx diff --git a/app.json b/app.json index 332c6f9..18e91c5 100644 --- a/app.json +++ b/app.json @@ -39,6 +39,12 @@ "backgroundColor": "#000000" } } + ], + [ + "expo-camera", + { + "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera" + } ] ], "experiments": { diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 0ce8735..ae7de51 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -40,8 +40,8 @@ import MapView, { Circle, Marker } from "react-native-maps"; export default function HomeScreen() { - const [gpsData, setGpsData] = useState( - undefined + const [gpsData, setGpsData] = useState( + null ); const [alarmData, setAlarmData] = useState(null); const [entityData, setEntityData] = useState< @@ -81,7 +81,7 @@ export default function HomeScreen() { // console.log("GPS Data: ", gpsData); setGpsData(gpsData); } else { - setGpsData(undefined); + setGpsData(null); setPolygonCoordinates([]); setPolylineCoordinates(null); } @@ -133,23 +133,6 @@ export default function HomeScreen() { // console.log("Unsubscribed EVENT_TRACK_POINTS_DATA"); }; }, []); - - useEffect(() => { - if (polylineCoordinates !== null) { - console.log("Polyline Khac null"); - } else { - console.log("Polyline null"); - } - }, [polylineCoordinates]); - - useEffect(() => { - if (polygonCoordinates.length > 0) { - console.log("Polygon Khac null"); - } else { - console.log("Polygon null"); - } - }, [polygonCoordinates]); - useEffect(() => { setPolylineCoordinates(null); setPolygonCoordinates([]); @@ -336,7 +319,7 @@ export default function HomeScreen() { latitude: point.lat, longitude: point.lon, }} - zIndex={50} + // zIndex={50} // radius={platform === IOS_PLATFORM ? 200 : 50} radius={circleRadius} strokeColor="rgba(16, 85, 201, 0.7)" @@ -347,13 +330,14 @@ export default function HomeScreen() { })} {polylineCoordinates && ( )} {polygonCoordinates.length > 0 && ( @@ -367,22 +351,23 @@ export default function HomeScreen() { return ( ); })} )} - {gpsData !== undefined && ( + {gpsData !== null && ( @@ -418,6 +404,7 @@ export default function HomeScreen() { 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={{ @@ -442,7 +429,7 @@ export default function HomeScreen() { - + ); } diff --git a/app/(tabs)/sensor.tsx b/app/(tabs)/sensor.tsx index 3b2ab4a..d8efbe9 100644 --- a/app/(tabs)/sensor.tsx +++ b/app/(tabs)/sensor.tsx @@ -1,14 +1,51 @@ -import { Platform, ScrollView, StyleSheet, Text, View } from "react-native"; +import ScanQRCode from "@/components/ScanQRCode"; +import { useState } from "react"; +import { + Platform, + Pressable, + ScrollView, + StyleSheet, + Text, + View, +} from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; export default function Sensor() { + const [scanModalVisible, setScanModalVisible] = useState(false); + const [scannedData, setScannedData] = useState(null); + const handleQRCodeScanned = (data: string) => { + setScannedData(data); + // Alert.alert("QR Code Scanned", `Result: ${data}`); + }; + + const handleScanPress = () => { + setScanModalVisible(true); + }; + return ( Cảm biến trên tàu + + + 📱 Scan QR Code + + + {scannedData && ( + + Last Scanned: + {scannedData} + + )} + + setScanModalVisible(false)} + onScanned={handleQRCodeScanned} + /> ); } @@ -25,11 +62,48 @@ const styles = StyleSheet.create({ fontSize: 32, fontWeight: "700", lineHeight: 40, - marginBottom: 10, + marginBottom: 30, fontFamily: Platform.select({ ios: "System", android: "Roboto", default: "System", }), }, + scanButton: { + backgroundColor: "#007AFF", + paddingVertical: 14, + paddingHorizontal: 30, + borderRadius: 10, + marginVertical: 15, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + }, + scanButtonText: { + color: "#fff", + fontSize: 16, + fontWeight: "600", + textAlign: "center", + }, + resultContainer: { + marginTop: 30, + padding: 15, + backgroundColor: "#f0f0f0", + borderRadius: 10, + minWidth: "80%", + }, + resultLabel: { + fontSize: 14, + fontWeight: "600", + color: "#666", + marginBottom: 8, + }, + resultText: { + fontSize: 14, + color: "#333", + fontFamily: "Menlo", + fontWeight: "500", + }, }); diff --git a/components/ScanQRCode.tsx b/components/ScanQRCode.tsx new file mode 100644 index 0000000..fa3cdf4 --- /dev/null +++ b/components/ScanQRCode.tsx @@ -0,0 +1,228 @@ +import { CameraView, useCameraPermissions } from "expo-camera"; +import { useEffect, useRef, useState } from "react"; +import { + ActivityIndicator, + Modal, + Pressable, + StyleSheet, + Text, + View, +} from "react-native"; + +interface ScanQRCodeProps { + visible: boolean; + onClose: () => void; + onScanned: (data: string) => void; +} + +export default function ScanQRCode({ + visible, + onClose, + onScanned, +}: ScanQRCodeProps) { + const [permission, requestPermission] = useCameraPermissions(); + const [scanned, setScanned] = useState(false); + const cameraRef = useRef(null); + + // Request camera permission when component mounts or when visible changes to true + useEffect(() => { + if (visible && !permission?.granted) { + requestPermission(); + } + }, [visible, permission, requestPermission]); + + // Reset scanned state when modal opens + useEffect(() => { + if (visible) { + setScanned(false); + } + }, [visible]); + + const handleBarCodeScanned = ({ + type, + data, + }: { + type: string; + data: string; + }) => { + if (!scanned) { + setScanned(true); + onScanned(data); + onClose(); + } + }; + + if (!permission) { + return ( + + + + + + Requesting camera permission... + + + + + ); + } + + if (!permission.granted) { + return ( + + + + + Camera Permission Required + + + This app needs camera access to scan QR codes. Please allow camera + access in your settings. + + requestPermission()} + > + Request Permission + + + Cancel + + + + + ); + } + + return ( + + + + + + + + + + + + {/* Align QR code within the frame */} + Đặt mã QR vào khung hình + + + ✕ Đóng + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.8)", + justifyContent: "center", + alignItems: "center", + }, + loadingContainer: { + alignItems: "center", + gap: 10, + }, + loadingText: { + color: "#fff", + fontSize: 16, + }, + permissionContainer: { + backgroundColor: "#fff", + marginHorizontal: 20, + borderRadius: 12, + padding: 20, + alignItems: "center", + gap: 15, + }, + permissionTitle: { + fontSize: 18, + fontWeight: "600", + color: "#333", + }, + permissionText: { + fontSize: 14, + color: "#666", + textAlign: "center", + lineHeight: 20, + }, + button: { + backgroundColor: "#007AFF", + paddingVertical: 12, + paddingHorizontal: 30, + borderRadius: 8, + width: "100%", + alignItems: "center", + }, + cancelButton: { + backgroundColor: "#666", + }, + buttonText: { + color: "#fff", + fontSize: 16, + fontWeight: "600", + }, + camera: { + flex: 1, + }, + overlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", + }, + unfocusedContainer: { + flex: 1, + }, + focusedRow: { + height: "80%", + width: "100%", + justifyContent: "center", + alignItems: "center", + }, + focusedContainer: { + aspectRatio: 1, + width: "70%", + borderColor: "#00ff00", + borderWidth: 3, + borderRadius: 10, + }, + bottomContainer: { + flex: 1, + justifyContent: "flex-end", + alignItems: "center", + paddingBottom: 40, + gap: 20, + }, + scanningText: { + color: "#fff", + fontSize: 16, + fontWeight: "500", + }, + closeButton: { + backgroundColor: "rgba(0, 0, 0, 0.6)", + paddingVertical: 10, + paddingHorizontal: 20, + borderRadius: 8, + }, + closeButtonText: { + color: "#fff", + fontSize: 14, + fontWeight: "600", + }, +}); diff --git a/components/map/GPSInfoPanel.tsx b/components/map/GPSInfoPanel.tsx index c368282..f56f317 100644 --- a/components/map/GPSInfoPanel.tsx +++ b/components/map/GPSInfoPanel.tsx @@ -61,10 +61,10 @@ const GPSInfoPanel = ({ gpsData }: GPSInfoPanelProps) => { {/* Nút toggle ở top-right */} diff --git a/components/map/PolygonWithLabel.tsx b/components/map/PolygonWithLabel.tsx index 6aa7543..0268a1d 100644 --- a/components/map/PolygonWithLabel.tsx +++ b/components/map/PolygonWithLabel.tsx @@ -55,7 +55,6 @@ export const PolygonWithLabel: React.FC = ({ const paddingScale = Math.max(Math.pow(2, (zoomLevel - 10) * 0.2), 0.5); const minWidthScale = Math.max(Math.pow(2, (zoomLevel - 10) * 0.25), 0.9); - markerRef.current?.showCallout(); return ( <> = ({ ref={markerRef} coordinate={centerPoint} zIndex={50} - tracksViewChanges={false} + tracksViewChanges={platform === ANDROID_PLATFORM ? false : true} anchor={{ x: 0.5, y: 0.5 }} title={platform === ANDROID_PLATFORM ? label : undefined} description={platform === ANDROID_PLATFORM ? content : undefined} @@ -78,7 +77,6 @@ export const PolygonWithLabel: React.FC = ({ = ({ ? ` (${distance.toFixed(2)}km)` : `${distance.toFixed(2)}km`; } - markerRef.current?.showCallout(); return ( <> = ({ ref={markerRef} coordinate={middlePoint} zIndex={zIndex + 10} - tracksViewChanges={false} + tracksViewChanges={platform === ANDROID_PLATFORM ? false : true} anchor={{ x: 0.5, y: 0.5 }} title={platform === ANDROID_PLATFORM ? label : undefined} description={platform === ANDROID_PLATFORM ? content : undefined} diff --git a/components/map/SosButton.tsx b/components/map/SosButton.tsx index 1dd75ff..579645f 100644 --- a/components/map/SosButton.tsx +++ b/components/map/SosButton.tsx @@ -90,13 +90,11 @@ const SosButton = () => { const handleClickButton = async (isActive: boolean) => { if (isActive) { - console.log("Active"); const resp = await queryDeleteSos(); if (resp.status === 200) { await getSosData(); } } else { - console.log("No Active"); setSelectedSosMessage(11); // Mặc định chọn lý do ma: 11 setShowConfirmSosDialog(true); } @@ -255,7 +253,6 @@ const SosButton = () => { onPress={() => { setSelectedSosMessage(item.ma); setShowDropdown(false); - // Clear custom message nếu chọn khác lý do if (item.ma !== 999) { setCustomMessage(""); diff --git a/config/auth.ts b/config/auth.ts index 2b4fe1f..71770c4 100644 --- a/config/auth.ts +++ b/config/auth.ts @@ -17,7 +17,7 @@ export const setRouterInstance = (router: Router) => { export const handle401 = () => { if (routerInstance) { removeStorageItem(TOKEN); - (routerInstance as any).replace("/login"); + (routerInstance as any).replace("/auth/login"); } else { console.warn("Router instance not set, cannot redirect to login"); } diff --git a/package-lock.json b/package-lock.json index 24286a3..4aa4db1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "dayjs": "^1.11.19", "eventemitter3": "^5.0.1", "expo": "~54.0.20", + "expo-camera": "~17.0.9", "expo-constants": "~18.0.10", "expo-font": "~14.0.9", "expo-haptics": "~15.0.7", @@ -8463,6 +8464,26 @@ "react-native": "*" } }, + "node_modules/expo-camera": { + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-17.0.9.tgz", + "integrity": "sha512-KgticPGurqEsaPBIwbG0T6mzAVnqZasDdM/6OoJt5zPh6tWB09+th6cBF1WafIBMPy8AWbfyUQSqQXqOrNJClg==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, "node_modules/expo-constants": { "version": "18.0.10", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.10.tgz", diff --git a/package.json b/package.json index fbce2ca..35ea97c 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "dayjs": "^1.11.19", "eventemitter3": "^5.0.1", "expo": "~54.0.20", + "expo-camera": "~17.0.9", "expo-constants": "~18.0.10", "expo-font": "~14.0.9", "expo-haptics": "~15.0.7",