From bf261e70e40246ca5dde8cf398d48db27e317b4f Mon Sep 17 00:00:00 2001 From: MinhNN Date: Mon, 29 Dec 2025 17:05:05 +0700 Subject: [PATCH] =?UTF-8?q?C=E1=BA=ADp=20nh=E1=BA=ADt=20theme=20cho=20c?= =?UTF-8?q?=E1=BA=A3nh=20b=C3=A1o,=20gi=C3=A1m=20s=C3=A1t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/index.tsx | 29 ++++++-- app/(tabs)/warning.tsx | 7 +- components/DraggablePanel.tsx | 11 +++- components/alarm/AlarmCard.tsx | 101 +++++++++++++++++----------- components/map/AlarmList.tsx | 117 ++++++++++++++++++++++----------- components/map/ShipInfo.tsx | 84 +++++++++++++++-------- 6 files changed, 234 insertions(+), 115 deletions(-) diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 65a090e..bc99c10 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -10,6 +10,7 @@ import ShipSearchForm, { import { ThemedText } from "@/components/themed-text"; import { EVENT_SEARCH_THINGS, IOS_PLATFORM, LIGHT_THEME } from "@/constants"; import { queryBanzoneById } from "@/controller/MapController"; +import { useAppTheme } from "@/hooks/use-app-theme"; import { usePlatform } from "@/hooks/use-platform"; import { useThemeContext } from "@/hooks/use-theme-context"; import { searchThingEventBus } from "@/services/device_events"; @@ -69,6 +70,7 @@ export default function HomeScreen() { const [things, setThings] = useState(null); const platform = usePlatform(); const theme = useThemeContext().colorScheme; + const { colors } = useAppTheme(); const scale = useRef(new Animated.Value(0)).current; const opacity = useRef(new Animated.Value(1)).current; const [shipSearchFormData, setShipSearchFormData] = useState< @@ -85,7 +87,7 @@ export default function HomeScreen() { // Alarm list animation const screenHeight = Dimensions.get("window").height; - const alarmListHeight = Math.round(screenHeight * 0.3); + const alarmListHeight = Math.round(screenHeight * 0.4); const alarmTranslateY = useRef(new Animated.Value(alarmListHeight)).current; const alarmOpacity = useRef(new Animated.Value(0)).current; const uiAnim = useRef(new Animated.Value(1)).current; // 1 visible, 0 hidden @@ -693,6 +695,7 @@ export default function HomeScreen() { fontSize: 20, textAlign: "center", fontWeight: "600", + color: colors.text, }} > Danh sách tàu thuyền @@ -735,7 +738,7 @@ export default function HomeScreen() { style={{ width: "50%", height: 1, - backgroundColor: "#E5E7EB", + backgroundColor: colors.separator, marginVertical: 8, borderRadius: 1, }} @@ -766,22 +769,36 @@ export default function HomeScreen() { zIndex: 10, }} > - + - + Danh sách cảnh báo closeAlarmList()} style={{ padding: 6 }} > - + diff --git a/app/(tabs)/warning.tsx b/app/(tabs)/warning.tsx index 525a98d..592fb2c 100644 --- a/app/(tabs)/warning.tsx +++ b/app/(tabs)/warning.tsx @@ -84,7 +84,12 @@ const WarningScreen = () => { }); const slice = resp.data?.alarms ?? []; - setAlarms((prev) => (append ? [...prev, ...slice] : slice)); + // Sort alarms by level descending (higher level first: SOS > Danger > Warning > Info) + const sortedSlice = [...slice].sort( + (a, b) => (b.level ?? 0) - (a.level ?? 0) + ); + + setAlarms((prev) => (append ? [...prev, ...sortedSlice] : sortedSlice)); setOffset(nextOffset); setHasMore(nextOffset + PAGE_SIZE < resp.data?.total!); } catch (error) { diff --git a/components/DraggablePanel.tsx b/components/DraggablePanel.tsx index 207f9e7..db01218 100644 --- a/components/DraggablePanel.tsx +++ b/components/DraggablePanel.tsx @@ -1,3 +1,4 @@ +import { useAppTheme } from "@/hooks/use-app-theme"; import { Ionicons } from "@expo/vector-icons"; import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"; import React, { useEffect, useState } from "react"; @@ -36,6 +37,7 @@ export default function DraggablePanel({ }: DraggablePanelProps) { const { height: screenHeight } = useWindowDimensions(); const bottomOffset = useBottomTabBarHeight(); + const { colors } = useAppTheme(); const minHeight = screenHeight * minHeightPct; const maxHeight = screenHeight * maxHeightPct; @@ -141,7 +143,12 @@ export default function DraggablePanel({ return ( - + {/* Header với drag handle và nút toggle */} @@ -151,7 +158,7 @@ export default function DraggablePanel({ style={styles.toggleButton} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > - + diff --git a/components/alarm/AlarmCard.tsx b/components/alarm/AlarmCard.tsx index 2050423..5737381 100644 --- a/components/alarm/AlarmCard.tsx +++ b/components/alarm/AlarmCard.tsx @@ -34,40 +34,54 @@ export const AlarmCard: React.FC = ({ alarm, onReload }) => { ); // Determine level and colors based on alarm level + // 0: Thông tin (Xanh lá), 1: Cảnh báo (Vàng), 2: Nguy hiểm (Đỏ), 3: Khẩn cấp SOS (Tím) const getAlarmConfig = (level?: number) => { - if (level === 3) { - // Danger - Red - return { - level: 3, - icon: "warning" as const, - bgColor: "#fee2e2", - borderColor: "#DC0E0E", - iconColor: "#dc2626", - statusBg: "#dcfce7", - statusText: "#166534", - }; - } else if (level === 2) { - // Caution - Yellow/Orange - return { - level: 2, - icon: "alert-circle" as const, - bgColor: "#fef3c7", - borderColor: "#FF6C0C", - iconColor: "#d97706", - statusBg: "#fef08a", - statusText: "#713f12", - }; - } else { - // Info - Green - return { - level: 1, - icon: "information-circle" as const, - bgColor: "#fffefe", - borderColor: "#FF937E", - iconColor: "#FF937E", - statusBg: "#dcfce7", - statusText: "#166534", - }; + switch (level) { + case 3: + // SOS - Khẩn cấp (Purple) + return { + level: 3, + icon: "alert" as const, + bgColor: colors.card, + borderColor: "#9333EA", // Purple + iconColor: "#9333EA", + statusBg: "#dcfce7", + statusText: "#166534", + }; + case 2: + // Nguy hiểm - Đỏ + return { + level: 2, + icon: "warning" as const, + bgColor: colors.card, + borderColor: colors.error, + iconColor: colors.error, + statusBg: "#dcfce7", + statusText: "#166534", + }; + case 1: + // Cảnh báo - Vàng + return { + level: 1, + icon: "alert-circle" as const, + bgColor: colors.card, + borderColor: colors.warning, + iconColor: colors.warning, + statusBg: "#fef08a", + statusText: "#713f12", + }; + case 0: + default: + // Thông tin - Xanh lá + return { + level: 0, + icon: "information-circle" as const, + bgColor: colors.card, + borderColor: colors.success, + iconColor: colors.success, + statusBg: "#dcfce7", + statusText: "#166534", + }; } }; @@ -217,14 +231,16 @@ export const AlarmCard: React.FC = ({ alarm, onReload }) => { style={[ styles.statusBadge, { - backgroundColor: alarm.confirmed ? "#8FD14F" : "#EEEEEE", + backgroundColor: alarm.confirmed + ? "#8FD14F" + : colors.surfaceSecondary, }, ]} > {alarm.confirmed ? "Đã xác nhận" : "Chờ xác nhận"} @@ -262,7 +278,10 @@ export const AlarmCard: React.FC = ({ alarm, onReload }) => { Nhập ghi chú xác nhận = ({ alarm, onReload }) => { /> { setShowModal(false); setNote(""); }} disabled={submitting} > - Hủy + + Hủy + = { +const getAlarmConfig = (isDark: boolean) => ({ entered: { - icon: "warning", + icon: "warning" as keyof typeof Ionicons.glyphMap, label: "Xâm nhập", - bgColor: "bg-red-50", - borderColor: "border-red-200", - iconBgColor: "bg-red-100", - iconColor: "#DC2626", - labelColor: "text-red-600", + bgColor: isDark ? "rgba(220, 38, 38, 0.15)" : "#FEF2F2", + borderColor: isDark ? "rgba(220, 38, 38, 0.3)" : "#FECACA", + iconBgColor: isDark ? "rgba(220, 38, 38, 0.25)" : "#FEE2E2", + iconColor: isDark ? "#F87171" : "#DC2626", + labelColor: isDark ? "#F87171" : "#DC2626", }, approaching: { - icon: "alert-circle", + icon: "alert-circle" as keyof typeof Ionicons.glyphMap, label: "Tiếp cận", - bgColor: "bg-amber-50", - borderColor: "border-amber-200", - iconBgColor: "bg-amber-100", - iconColor: "#D97706", - labelColor: "text-amber-600", + bgColor: isDark ? "rgba(217, 119, 6, 0.15)" : "#FFFBEB", + borderColor: isDark ? "rgba(217, 119, 6, 0.3)" : "#FDE68A", + iconBgColor: isDark ? "rgba(217, 119, 6, 0.25)" : "#FEF3C7", + iconColor: isDark ? "#FBBF24" : "#D97706", + labelColor: isDark ? "#FBBF24" : "#D97706", }, fishing: { - icon: "fish", + icon: "fish" as keyof typeof Ionicons.glyphMap, label: "Đánh bắt", - bgColor: "bg-orange-50", - borderColor: "border-orange-200", - iconBgColor: "bg-orange-100", - iconColor: "#EA580C", - labelColor: "text-orange-600", + bgColor: isDark ? "rgba(234, 88, 12, 0.15)" : "#FFF7ED", + borderColor: isDark ? "rgba(234, 88, 12, 0.3)" : "#FED7AA", + iconBgColor: isDark ? "rgba(234, 88, 12, 0.25)" : "#FFEDD5", + iconColor: isDark ? "#FB923C" : "#EA580C", + labelColor: isDark ? "#FB923C" : "#EA580C", }, -}; +}); // ============ AlarmCard Component ============ const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => { - const config = ALARM_CONFIG[alarm.type]; + const { colors, utils } = useAppTheme(); + const isDark = utils.isDark; + const alarmConfig = getAlarmConfig(isDark); + const config = alarmConfig[alarm.type]; return ( {/* Icon Container */} @@ -77,12 +83,31 @@ const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => { {/* Header: Ship name + Badge */} - + {alarm.ship_name || alarm.thing_id} - + {config.label} @@ -90,15 +115,27 @@ const AlarmCard = ({ alarm, onPress }: AlarmCardProps) => { {/* Zone Info */} - + {alarm.zone.message || alarm.zone.zone_name} {/* Footer: Zone ID + Time */} - - + + {formatTimestamp(alarm.zone.gps_time)} diff --git a/components/map/ShipInfo.tsx b/components/map/ShipInfo.tsx index 6a2b067..6f82ce2 100644 --- a/components/map/ShipInfo.tsx +++ b/components/map/ShipInfo.tsx @@ -13,28 +13,31 @@ interface ShipInfoProps { const ShipInfo = ({ thingMetadata }: ShipInfoProps) => { const { t } = useI18n(); - const { colors } = useAppTheme(); - // Định nghĩa màu sắc theo trạng thái + const { colors, utils } = useAppTheme(); + const isDark = utils.isDark; + + // Định nghĩa màu sắc theo trạng thái - hỗ trợ cả light và dark theme const statusConfig = { normal: { - dotColor: "bg-green-500", - badgeColor: "bg-green-100", - badgeTextColor: "text-green-700", + dotColor: colors.success, + badgeColor: isDark ? "rgba(52, 199, 89, 0.2)" : "#dcfce7", + badgeTextColor: isDark ? "#30D158" : "#15803d", badgeText: "Bình thường", }, warning: { - dotColor: "bg-yellow-500", - badgeColor: "bg-yellow-100", - badgeTextColor: "text-yellow-700", + dotColor: colors.warning, + badgeColor: isDark ? "rgba(255, 102, 0, 0.2)" : "#fef3c7", + badgeTextColor: isDark ? "#ff9500" : "#b45309", badgeText: "Cảnh báo", }, danger: { - dotColor: "bg-red-500", - badgeColor: "bg-red-100", - badgeTextColor: "text-red-700", + dotColor: colors.error, + badgeColor: isDark ? "rgba(255, 69, 58, 0.2)" : "#fee2e2", + badgeTextColor: isDark ? "#FF453A" : "#b91c1c", badgeText: "Nguy hiểm", }, }; + const getThingStatus = () => { switch (thingMetadata?.state_level) { case STATUS_NORMAL: @@ -67,57 +70,82 @@ const ShipInfo = ({ thingMetadata }: ShipInfoProps) => { {/* Status dot */} - + {/* Tên tàu */} - + {thingMetadata?.ship_name} {/* Badge trạng thái */} - + {currentStatus.badgeText} - {/* Mã tàu */} - {/* {shipCode} */} + {/* Tốc độ và hướng */} - - + + {kmhToKnot(gpsData.s || 0)} {t("home.speed_units")} - - {gpsData.h}° + + + {gpsData.h}° + + {/* Tọa độ */} - - + + {convertToDMS(gpsData.lat || 0, true)}, {convertToDMS(gpsData.lon || 0, false)} - - + + {formatRelativeTime(gpsData.t)} - {/* - - */} + + {/* Trạng thái */} {thingMetadata?.state !== "" && (