/** * Theme Context Hook for managing app-wide theme state * Supports Light, Dark, and System (automatic) modes * * IMPORTANT: Requires expo-system-ui plugin in app.json for Android support */ import { Colors, ColorName } from "@/constants/theme"; import { getStorageItem, setStorageItem } from "@/utils/storage"; import { createContext, useContext, useEffect, useState, ReactNode, } from "react"; import { useColorScheme as useSystemColorScheme, Appearance, } from "react-native"; type ThemeMode = "light" | "dark" | "system"; type ColorScheme = "light" | "dark"; interface ThemeContextType { themeMode: ThemeMode; colorScheme: ColorScheme; colors: typeof Colors.light; setThemeMode: (mode: ThemeMode) => Promise; getColor: (colorName: ColorName) => string; } const ThemeContext = createContext(undefined); const THEME_STORAGE_KEY = "theme_mode"; export function ThemeProvider({ children }: { children: ReactNode }) { // State để force re-render khi system theme thay đổi const [systemTheme, setSystemTheme] = useState(() => { const current = Appearance.getColorScheme(); console.log("[Theme] Initial system theme:", current); return current === "dark" ? "dark" : "light"; }); // State lưu user's choice (light/dark/system) const [themeMode, setThemeModeState] = useState("system"); const [isLoaded, setIsLoaded] = useState(false); // Listen vào system theme changes - đăng ký ngay từ đầu useEffect(() => { console.log("[Theme] Registering appearance listener"); const subscription = Appearance.addChangeListener(({ colorScheme }) => { const newScheme = colorScheme === "dark" ? "dark" : "light"; console.log( "[Theme] System theme changed to:", newScheme, "at", new Date().toLocaleTimeString() ); setSystemTheme(newScheme); }); // Double check current theme khi mount const currentScheme = Appearance.getColorScheme(); const current = currentScheme === "dark" ? "dark" : "light"; if (current !== systemTheme) { console.log("[Theme] Syncing system theme on mount:", current); setSystemTheme(current); } return () => { console.log("[Theme] Removing appearance listener"); subscription.remove(); }; }, []); // Xác định colorScheme cuối cùng const colorScheme: ColorScheme = themeMode === "system" ? systemTheme : themeMode; const colors = Colors[colorScheme]; // Log để debug useEffect(() => { console.log( "[Theme] Current state - Mode:", themeMode, "| Scheme:", colorScheme, "| System:", systemTheme ); }, [themeMode, colorScheme, systemTheme]); useEffect(() => { const loadThemeMode = async () => { try { const savedThemeMode = await getStorageItem(THEME_STORAGE_KEY); if ( savedThemeMode && ["light", "dark", "system"].includes(savedThemeMode) ) { setThemeModeState(savedThemeMode as ThemeMode); } } catch (error) { console.warn("Failed to load theme mode:", error); } finally { setIsLoaded(true); } }; loadThemeMode(); }, []); const setThemeMode = async (mode: ThemeMode) => { try { setThemeModeState(mode); await setStorageItem(THEME_STORAGE_KEY, mode); console.log("[Theme] Changed to:", mode); } catch (error) { console.warn("Failed to save theme mode:", error); } }; const getColor = (colorName: ColorName): string => { return colors[colorName] || colors.text; }; // Chờ theme load xong trước khi render if (!isLoaded) { // Render với default theme (system) khi đang load return ( {}, getColor: (colorName: ColorName) => Colors[systemTheme][colorName] || Colors[systemTheme].text, }} > {children} ); } const value: ThemeContextType = { themeMode, colorScheme, colors, setThemeMode, getColor, }; return ( {children} ); } export function useThemeContext(): ThemeContextType { const context = useContext(ThemeContext); if (context === undefined) { throw new Error("useThemeContext must be used within a ThemeProvider"); } return context; } // Legacy hook cho backward compatibility export function useColorScheme(): ColorScheme { const { colorScheme } = useThemeContext(); return colorScheme; }