diff --git a/app/(tabs)/tripInfo.tsx b/app/(tabs)/tripInfo.tsx index afc1ddf..629f76f 100644 --- a/app/(tabs)/tripInfo.tsx +++ b/app/(tabs)/tripInfo.tsx @@ -5,17 +5,19 @@ import CrewListTable from "@/components/tripInfo/CrewListTable"; import FishingToolsTable from "@/components/tripInfo/FishingToolsList"; import NetListTable from "@/components/tripInfo/NetListTable"; import TripCostTable from "@/components/tripInfo/TripCostTable"; +import { useI18n } from "@/hooks/use-i18n"; import { useThemeContext } from "@/hooks/use-theme-context"; import { Platform, ScrollView, StyleSheet, Text, View } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; export default function TripInfoScreen() { + const { t } = useI18n(); const { colors } = useThemeContext(); return ( - Thông Tin Chuyến Đi + {t("trip.infoTrip")} diff --git a/app/auth/login.tsx b/app/auth/login.tsx index 72141a2..18c305c 100644 --- a/app/auth/login.tsx +++ b/app/auth/login.tsx @@ -12,6 +12,7 @@ import { useI18n } from "@/hooks/use-i18n"; import { ColorScheme as ThemeColorScheme, useTheme, + useThemeContext, } from "@/hooks/use-theme-context"; import { showErrorToast, showWarningToast } from "@/services/toast_service"; import { @@ -44,6 +45,7 @@ export default function LoginScreen() { const [isShowingQRScanner, setIsShowingQRScanner] = useState(false); const { t, setLocale, locale } = useI18n(); const { colors, colorScheme } = useTheme(); + const { setThemeMode } = useThemeContext(); const styles = useMemo( () => createStyles(colors, colorScheme), [colors, colorScheme] @@ -312,6 +314,10 @@ export default function LoginScreen() { inactiveBackgroundColor={colors.surface} inactiveOverlayColor={colors.textSecondary} activeOverlayColor={colors.background} + value={colorScheme === "light"} + onChange={(val) => { + setThemeMode(val ? "light" : "dark"); + }} /> diff --git a/components/Select.tsx b/components/Select.tsx index 9aeeea7..784304c 100644 --- a/components/Select.tsx +++ b/components/Select.tsx @@ -1,3 +1,4 @@ +import { useThemeContext } from "@/hooks/use-theme-context"; import { AntDesign } from "@expo/vector-icons"; import React, { useEffect, useState } from "react"; import { @@ -93,6 +94,12 @@ const Select: React.FC = ({ const sz = sizeMap[size]; + // Theme colors from context (consistent with other components) + const { colors } = useThemeContext(); + const selectBackgroundColor = disabled + ? colors.backgroundSecondary + : colors.surface; + return ( = ({ { height: sz.height, paddingHorizontal: sz.paddingHorizontal, - opacity: disabled ? 0.6 : 1, + backgroundColor: selectBackgroundColor, + borderColor: disabled ? colors.border : colors.primary, }, style, ]} @@ -112,14 +120,18 @@ const Select: React.FC = ({ > {loading ? ( - + ) : ( = ({ {allowClear && selectedValue && !loading ? ( - + ) : null} {isOpen && ( - + {showSearch && ( = ({ key={item.value} style={[ styles.option, + { + borderBottomColor: colors.separator, + }, item.disabled && styles.optionDisabled, - selectedValue === item.value && styles.optionSelected, + selectedValue === item.value && { + backgroundColor: colors.primary + "20", // Add transparency to primary color + }, ]} onPress={() => !item.disabled && handleSelect(item.value)} disabled={item.disabled} @@ -169,14 +203,22 @@ const Select: React.FC = ({ {item.label} {selectedValue === item.value && ( - + )} ))} @@ -193,9 +235,7 @@ const styles = StyleSheet.create({ }, container: { borderWidth: 1, - borderColor: "#e6e6e6", borderRadius: 8, - backgroundColor: "#fff", flexDirection: "row", alignItems: "center", justifyContent: "space-between", @@ -204,7 +244,7 @@ const styles = StyleSheet.create({ flex: 1, }, text: { - color: "#111", + // Color is set dynamically via theme }, suffix: { flexDirection: "row", @@ -220,9 +260,7 @@ const styles = StyleSheet.create({ position: "absolute", left: 0, right: 0, - backgroundColor: "#fff", borderWidth: 1, - borderColor: "#e6e6e6", borderTopWidth: 0, borderRadius: 10, borderBottomLeftRadius: 8, @@ -236,7 +274,6 @@ const styles = StyleSheet.create({ }, searchInput: { borderWidth: 1, - borderColor: "#e6e6e6", borderRadius: 4, padding: 8, margin: 8, @@ -247,7 +284,6 @@ const styles = StyleSheet.create({ option: { padding: 12, borderBottomWidth: 1, - borderBottomColor: "#f0f0f0", flexDirection: "row", justifyContent: "space-between", alignItems: "center", @@ -255,20 +291,11 @@ const styles = StyleSheet.create({ optionDisabled: { opacity: 0.5, }, - optionSelected: { - backgroundColor: "#f6ffed", - }, + // optionSelected is handled dynamically via inline styles optionText: { fontSize: 16, - color: "#111", - }, - optionTextDisabled: { - color: "#999", - }, - optionTextSelected: { - color: "#4ecdc4", - fontWeight: "600", }, + // optionTextDisabled and optionTextSelected are handled dynamically via inline styles }); export default Select; diff --git a/components/map/Description.tsx b/components/map/Description.tsx index d626e3f..78a8d8d 100644 --- a/components/map/Description.tsx +++ b/components/map/Description.tsx @@ -1,4 +1,6 @@ -import { Text, View } from "react-native"; +import { ThemedText } from "@/components/themed-text"; +import { useAppTheme } from "@/hooks/use-app-theme"; +import { View } from "react-native"; interface DescriptionProps { title?: string; @@ -8,10 +10,15 @@ export const Description = ({ title = "", description = "", }: DescriptionProps) => { + const { colors } = useAppTheme(); return ( - {title}: - {description} + + {title}: + + {description} ); }; diff --git a/components/map/GPSInfoPanel.tsx b/components/map/GPSInfoPanel.tsx index 80e9094..8e895c6 100644 --- a/components/map/GPSInfoPanel.tsx +++ b/components/map/GPSInfoPanel.tsx @@ -1,3 +1,4 @@ +import { useAppTheme } from "@/hooks/use-app-theme"; import { useI18n } from "@/hooks/use-i18n"; import { convertToDMS, kmhToKnot } from "@/utils/geom"; import { MaterialIcons } from "@expo/vector-icons"; @@ -15,6 +16,8 @@ const GPSInfoPanel = ({ gpsData }: GPSInfoPanelProps) => { const translateY = useRef(new Animated.Value(0)).current; const blockBottom = useRef(new Animated.Value(0)).current; const { t } = useI18n(); + const { colors, styles } = useAppTheme(); + useEffect(() => { Animated.timing(translateY, { toValue: isExpanded ? 0 : 200, // Dịch chuyển xuống 200px khi thu gọn @@ -44,45 +47,35 @@ const GPSInfoPanel = ({ gpsData }: GPSInfoPanelProps) => { position: "absolute", bottom: blockBottom, left: 5, - // width: 48, - // height: 48, - // backgroundColor: "blue", borderRadius: 4, zIndex: 30, }} > - {/* { - // showInfoToast("oad"); - showWarningToast("This is a warning toast!"); - }} - className="absolute top-2 right-2 z-10 bg-white rounded-full p-1" - > - - */} setPanelHeight(event.nativeEvent.layout.height)} > {/* Nút toggle ở top-right */} @@ -120,9 +113,10 @@ const GPSInfoPanel = ({ gpsData }: GPSInfoPanelProps) => { {!isExpanded && ( - + )} diff --git a/components/map/SosButton.tsx b/components/map/SosButton.tsx index 6c00299..ee35d21 100644 --- a/components/map/SosButton.tsx +++ b/components/map/SosButton.tsx @@ -12,6 +12,7 @@ import { StyleSheet, Text, TextInput, View } from "react-native"; import IconButton from "../IconButton"; import Select from "../Select"; import Modal from "../ui/modal"; +import { useThemeColor } from "@/hooks/use-theme-color"; const SosButton = () => { const [sosData, setSosData] = useState(); @@ -22,6 +23,16 @@ const SosButton = () => { const [customMessage, setCustomMessage] = useState(""); const [errors, setErrors] = useState<{ [key: string]: string }>({}); const { t } = useI18n(); + + // Theme colors + const textColor = useThemeColor({}, 'text'); + const borderColor = useThemeColor({}, 'border'); + const errorColor = useThemeColor({}, 'error'); + const backgroundColor = useThemeColor({}, 'background'); + + // Dynamic styles + const styles = SosButtonStyles(textColor, borderColor, errorColor, backgroundColor); + const sosOptions = [ ...sosMessage.map((msg) => ({ ma: msg.ma, @@ -176,7 +187,7 @@ const SosButton = () => { errors.customMessage ? styles.errorInput : {}, ]} placeholder={t("home.sos.enterStatus")} - placeholderTextColor="#999" + placeholderTextColor={textColor + '99'} // Add transparency value={customMessage} onChangeText={(text) => { setCustomMessage(text); @@ -201,7 +212,7 @@ const SosButton = () => { ); }; -const styles = StyleSheet.create({ +const SosButtonStyles = (textColor: string, borderColor: string, errorColor: string, backgroundColor: string) => StyleSheet.create({ formGroup: { marginBottom: 16, }, @@ -209,26 +220,27 @@ const styles = StyleSheet.create({ fontSize: 14, fontWeight: "600", marginBottom: 8, - color: "#333", + color: textColor, }, errorBorder: { - borderColor: "#ff4444", + borderColor: errorColor, }, input: { borderWidth: 1, - borderColor: "#ddd", + borderColor: borderColor, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 12, fontSize: 14, - color: "#333", + color: textColor, + backgroundColor: backgroundColor, textAlignVertical: "top", }, errorInput: { - borderColor: "#ff4444", + borderColor: errorColor, }, errorText: { - color: "#ff4444", + color: errorColor, fontSize: 12, marginTop: 4, }, diff --git a/components/tripInfo/modal/components/InfoSection.tsx b/components/tripInfo/modal/components/InfoSection.tsx index 06a343a..6e2934e 100644 --- a/components/tripInfo/modal/components/InfoSection.tsx +++ b/components/tripInfo/modal/components/InfoSection.tsx @@ -15,7 +15,7 @@ export const InfoSection: React.FC = ({ const { t } = useI18n(); const { colors } = useThemeContext(); const styles = React.useMemo(() => createStyles(colors), [colors]); - + if (!fishingLog) { return null; } @@ -80,7 +80,7 @@ const createStyles = (colors: any) => borderRadius: 8, padding: 12, marginBottom: 12, - backgroundColor: colors.backgroundSecondary, + backgroundColor: colors.surfaceSecondary, }, infoRow: { flexDirection: "row", diff --git a/components/tripInfo/modal/style/CreateOrUpdateHaulModal.styles.ts b/components/tripInfo/modal/style/CreateOrUpdateHaulModal.styles.ts index 23b87ac..c7a0fc4 100644 --- a/components/tripInfo/modal/style/CreateOrUpdateHaulModal.styles.ts +++ b/components/tripInfo/modal/style/CreateOrUpdateHaulModal.styles.ts @@ -58,7 +58,7 @@ export const createStyles = (colors: typeof Colors.light) => marginBottom: 15, }, fishCard: { - backgroundColor: colors.card, + backgroundColor: colors.surfaceSecondary, borderRadius: 12, padding: 16, marginBottom: 16, @@ -95,7 +95,7 @@ export const createStyles = (colors: typeof Colors.light) => }, input: { borderWidth: 1, - borderColor: colors.border, + borderColor: colors.primary, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, @@ -106,7 +106,7 @@ export const createStyles = (colors: typeof Colors.light) => inputDisabled: { backgroundColor: colors.backgroundSecondary, color: colors.textSecondary, - borderColor: colors.separator, + borderColor: colors.border, }, errorText: { color: colors.error, diff --git a/components/ui/slice-switch.tsx b/components/ui/slice-switch.tsx index 86da13b..04214da 100644 --- a/components/ui/slice-switch.tsx +++ b/components/ui/slice-switch.tsx @@ -1,6 +1,6 @@ import { Ionicons } from "@expo/vector-icons"; import type { ComponentProps } from "react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Animated, OpaqueColorValue, @@ -29,7 +29,7 @@ const PRESS_FEEDBACK_DURATION = 120; type IoniconName = ComponentProps["name"]; -type RotateSwitchProps = { +type SliceSwitchProps = { size?: SwitchSize; leftIcon?: IoniconName; leftIconColor?: string | OpaqueColorValue | undefined; @@ -42,6 +42,7 @@ type RotateSwitchProps = { activeOverlayColor?: string; style?: StyleProp; onChange?: (value: boolean) => void; + value?: boolean; }; const SliceSwitch = ({ @@ -57,19 +58,28 @@ const SliceSwitch = ({ activeOverlayColor = "#000", style, onChange, -}: RotateSwitchProps) => { + value, +}: SliceSwitchProps) => { const { width: containerWidth, height: containerHeight } = SIZE_PRESETS[size] ?? SIZE_PRESETS.md; const animationDuration = Math.max(duration ?? DEFAULT_TOGGLE_DURATION, 0); - const [isOn, setIsOn] = useState(false); - const [bgOn, setBgOn] = useState(false); - const progress = useRef(new Animated.Value(0)).current; + const [isOn, setIsOn] = useState(value ?? false); + const [bgOn, setBgOn] = useState(value ?? false); + const progress = useRef(new Animated.Value(value ? 1 : 0)).current; const pressScale = useRef(new Animated.Value(1)).current; - const overlayTranslateX = useRef(new Animated.Value(0)).current; + const overlayTranslateX = useRef( + new Animated.Value(value ? containerWidth / 2 : 0) + ).current; const listenerIdRef = useRef(null); - const handleToggle = () => { - const next = !isOn; + // Sync with external value prop if provided + useEffect(() => { + if (value !== undefined && value !== isOn) { + animateToValue(value); + } + }, [value]); + + const animateToValue = (next: boolean) => { const targetValue = next ? 1 : 0; const overlayTarget = next ? containerWidth / 2 : 0; @@ -81,7 +91,6 @@ const SliceSwitch = ({ overlayTranslateX.setValue(overlayTarget); setIsOn(next); setBgOn(next); - onChange?.(next); return; } @@ -100,7 +109,6 @@ const SliceSwitch = ({ }), ]).start(() => { setBgOn(next); - onChange?.(next); }); // Remove any previous listener @@ -132,6 +140,14 @@ const SliceSwitch = ({ }); }; + const handleToggle = () => { + const next = !isOn; + if (value === undefined) { + animateToValue(next); + } + onChange?.(next); + }; + const handlePressIn = () => { pressScale.stopAnimation(); Animated.timing(pressScale, { diff --git a/hooks/use-app-theme.ts b/hooks/use-app-theme.ts index a718237..858237a 100644 --- a/hooks/use-app-theme.ts +++ b/hooks/use-app-theme.ts @@ -3,9 +3,9 @@ * Provides styled components and theme utilities */ +import { useThemeContext } from "@/hooks/use-theme-context"; import { useMemo } from "react"; import { StyleSheet, TextStyle, ViewStyle } from "react-native"; -import { useThemeContext } from "@/hooks/use-theme-context"; export function useAppTheme() { const { colors, colorScheme, themeMode, setThemeMode, getColor } = diff --git a/locales/en.json b/locales/en.json index 02afb06..5e4c81e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -58,6 +58,7 @@ } }, "trip": { + "infoTrip": "Trip Information", "createNewTrip": "Create New Trip", "endTrip": "End Trip", "cancelTrip": "Cancel Trip", diff --git a/locales/vi.json b/locales/vi.json index 434b264..362509b 100644 --- a/locales/vi.json +++ b/locales/vi.json @@ -58,6 +58,7 @@ } }, "trip": { + "infoTrip": "Thông Tin Chuyến Đi", "createNewTrip": "Tạo chuyến mới", "endTrip": "Kết thúc chuyến", "cancelTrip": "Hủy chuyến",