update theme dark, light mode

This commit is contained in:
2025-11-17 17:01:42 +07:00
parent e725819c01
commit 862c4e42a4
25 changed files with 1274 additions and 442 deletions

131
hooks/use-theme-context.tsx Normal file
View File

@@ -0,0 +1,131 @@
/**
* 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 } 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<void>;
getColor: (colorName: ColorName) => string;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
const THEME_STORAGE_KEY = "theme_mode";
export function ThemeProvider({ children }: { children: ReactNode }) {
// Dùng useColorScheme từ react-native để detect theme thiết bị
const deviceColorScheme = useSystemColorScheme(); // "light" | "dark" | null | undefined
// State lưu user's choice (light/dark/system)
const [themeMode, setThemeModeState] = useState<ThemeMode>("system");
const [isLoaded, setIsLoaded] = useState(false);
// Xác định colorScheme cuối cùng
const colorScheme: ColorScheme =
themeMode === "system"
? deviceColorScheme === "dark"
? "dark"
: "light"
: themeMode;
const colors = Colors[colorScheme];
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
const defaultScheme: ColorScheme =
deviceColorScheme === "dark" ? "dark" : "light";
return (
<ThemeContext.Provider
value={{
themeMode: "system",
colorScheme: defaultScheme,
colors: Colors[defaultScheme],
setThemeMode: async () => {},
getColor: (colorName: ColorName) =>
Colors[defaultScheme][colorName] || Colors[defaultScheme].text,
}}
>
{children}
</ThemeContext.Provider>
);
}
const value: ThemeContextType = {
themeMode,
colorScheme,
colors,
setThemeMode,
getColor,
};
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
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;
}