/** * Theme Context Hook for managing app-wide theme state. * Supports Light, Dark, and System (automatic) modes across Expo platforms. * * IMPORTANT: Requires expo-system-ui plugin in app.json for Android status bar support. */ import { ColorName, Colors } from "@/constants/theme"; import { getStorageItem, setStorageItem } from "@/utils/storage"; import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { Appearance, AppState, AppStateStatus, useColorScheme as useRNColorScheme, } from "react-native"; export type ThemeMode = "light" | "dark" | "system"; export type ColorScheme = "light" | "dark"; interface ThemeContextType { themeMode: ThemeMode; colorScheme: ColorScheme; colors: typeof Colors.light; setThemeMode: (mode: ThemeMode) => Promise; getColor: (colorName: ColorName) => string; isHydrated: boolean; } const ThemeContext = createContext(undefined); const THEME_STORAGE_KEY = "theme_mode"; const getSystemScheme = (): ColorScheme => { const scheme = Appearance.getColorScheme(); // console.log("[Theme] Appearance.getColorScheme():", scheme); return scheme === "dark" ? "dark" : "light"; }; const isThemeMode = (value: unknown): value is ThemeMode => { return value === "light" || value === "dark" || value === "system"; }; export function ThemeProvider({ children }: { children: ReactNode }) { const [systemScheme, setSystemScheme] = useState(getSystemScheme); const [themeMode, setThemeModeState] = useState("system"); const [isHydrated, setIsHydrated] = useState(false); const syncSystemScheme = useCallback(() => { const next = getSystemScheme(); // console.log("[Theme] syncSystemScheme computed:", next); setSystemScheme((current) => (current === next ? current : next)); }, []); const rnScheme = useRNColorScheme(); useEffect(() => { if (!rnScheme) return; const next = rnScheme === "dark" ? "dark" : "light"; // console.log("[Theme] useColorScheme hook emitted:", rnScheme); setSystemScheme((current) => (current === next ? current : next)); }, [rnScheme]); useEffect(() => { const subscription = Appearance.addChangeListener(({ colorScheme }) => { const next = colorScheme === "dark" ? "dark" : "light"; // console.log("[Theme] Appearance listener fired with:", colorScheme); setSystemScheme((current) => (current === next ? current : next)); }); syncSystemScheme(); return () => { subscription.remove(); }; }, [syncSystemScheme]); useEffect(() => { // console.log("[Theme] System scheme detected:", systemScheme); }, [systemScheme]); useEffect(() => { const handleAppStateChange = (nextState: AppStateStatus) => { if (nextState === "active") { // console.log("[Theme] AppState active → scheduling system scheme sync"); setTimeout(() => { // console.log("[Theme] AppState sync callback running"); syncSystemScheme(); }, 100); } }; const subscription = AppState.addEventListener( "change", handleAppStateChange ); return () => subscription.remove(); }, [syncSystemScheme]); useEffect(() => { let isMounted = true; const hydrateThemeMode = async () => { try { const savedThemeMode = await getStorageItem(THEME_STORAGE_KEY); if (isMounted && isThemeMode(savedThemeMode)) { setThemeModeState(savedThemeMode); } } catch (error) { console.warn("[Theme] Failed to load theme mode:", error); } finally { if (isMounted) { setIsHydrated(true); } } }; hydrateThemeMode(); return () => { isMounted = false; }; }, []); const colorScheme: ColorScheme = themeMode === "system" ? systemScheme : themeMode; const colors = useMemo(() => Colors[colorScheme], [colorScheme]); const setThemeMode = useCallback(async (mode: ThemeMode) => { setThemeModeState(mode); try { await setStorageItem(THEME_STORAGE_KEY, mode); } catch (error) { console.warn("[Theme] Failed to save theme mode:", error); } }, []); useEffect(() => { // console.log("[Theme] window defined:", typeof window !== "undefined"); }, []); const getColor = useCallback( (colorName: ColorName) => colors[colorName] ?? colors.text, [colors] ); useEffect(() => { // console.log("[Theme] Mode:", themeMode); }, [themeMode]); useEffect(() => { // console.log("[Theme] Derived colorScheme:", colorScheme); }, [colorScheme]); const value = useMemo( () => ({ themeMode, colorScheme, colors, setThemeMode, getColor, isHydrated, }), [themeMode, colorScheme, colors, setThemeMode, getColor, isHydrated] ); return ( {children} ); } export function useTheme(): ThemeContextType { const context = useContext(ThemeContext); if (context === undefined) { throw new Error("useTheme must be used within a ThemeProvider"); } return context; } export const useThemeContext = useTheme; export function useColorScheme(): ColorScheme { return useTheme().colorScheme; }