remove gluestackk-ui
This commit is contained in:
@@ -1,26 +1,30 @@
|
||||
/**
|
||||
* Theme Context Hook for managing app-wide theme state
|
||||
* Supports Light, Dark, and System (automatic) modes
|
||||
* 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 support
|
||||
* IMPORTANT: Requires expo-system-ui plugin in app.json for Android status bar support.
|
||||
*/
|
||||
|
||||
import { Colors, ColorName } from "@/constants/theme";
|
||||
import { ColorName, Colors } from "@/constants/theme";
|
||||
import { getStorageItem, setStorageItem } from "@/utils/storage";
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
useColorScheme as useSystemColorScheme,
|
||||
Appearance,
|
||||
AppState,
|
||||
AppStateStatus,
|
||||
useColorScheme as useRNColorScheme,
|
||||
} from "react-native";
|
||||
|
||||
type ThemeMode = "light" | "dark" | "system";
|
||||
type ColorScheme = "light" | "dark";
|
||||
export type ThemeMode = "light" | "dark" | "system";
|
||||
export type ColorScheme = "light" | "dark";
|
||||
|
||||
interface ThemeContextType {
|
||||
themeMode: ThemeMode;
|
||||
@@ -28,146 +32,162 @@ interface ThemeContextType {
|
||||
colors: typeof Colors.light;
|
||||
setThemeMode: (mode: ThemeMode) => Promise<void>;
|
||||
getColor: (colorName: ColorName) => string;
|
||||
isHydrated: boolean;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(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 }) {
|
||||
// State để force re-render khi system theme thay đổi
|
||||
const [systemTheme, setSystemTheme] = useState<ColorScheme>(() => {
|
||||
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 [systemScheme, setSystemScheme] =
|
||||
useState<ColorScheme>(getSystemScheme);
|
||||
const [themeMode, setThemeModeState] = useState<ThemeMode>("system");
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [isHydrated, setIsHydrated] = useState(false);
|
||||
|
||||
// Listen vào system theme changes - đăng ký ngay từ đầu
|
||||
const syncSystemScheme = useCallback(() => {
|
||||
const next = getSystemScheme();
|
||||
// console.log("[Theme] syncSystemScheme computed:", next);
|
||||
setSystemScheme((current) => (current === next ? current : next));
|
||||
}, []);
|
||||
|
||||
const rnScheme = useRNColorScheme();
|
||||
useEffect(() => {
|
||||
console.log("[Theme] Registering appearance listener");
|
||||
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 newScheme = colorScheme === "dark" ? "dark" : "light";
|
||||
console.log(
|
||||
"[Theme] System theme changed to:",
|
||||
newScheme,
|
||||
"at",
|
||||
new Date().toLocaleTimeString()
|
||||
);
|
||||
setSystemTheme(newScheme);
|
||||
const next = colorScheme === "dark" ? "dark" : "light";
|
||||
// console.log("[Theme] Appearance listener fired with:", colorScheme);
|
||||
setSystemScheme((current) => (current === next ? current : next));
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
syncSystemScheme();
|
||||
|
||||
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]);
|
||||
}, [syncSystemScheme]);
|
||||
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
loadThemeMode();
|
||||
|
||||
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 setThemeMode = async (mode: ThemeMode) => {
|
||||
const colorScheme: ColorScheme =
|
||||
themeMode === "system" ? systemScheme : themeMode;
|
||||
|
||||
const colors = useMemo(() => Colors[colorScheme], [colorScheme]);
|
||||
|
||||
const setThemeMode = useCallback(async (mode: ThemeMode) => {
|
||||
setThemeModeState(mode);
|
||||
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);
|
||||
console.warn("[Theme] Failed to save theme mode:", error);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getColor = (colorName: ColorName): string => {
|
||||
return colors[colorName] || colors.text;
|
||||
};
|
||||
useEffect(() => {
|
||||
// console.log("[Theme] window defined:", typeof window !== "undefined");
|
||||
}, []);
|
||||
|
||||
// Chờ theme load xong trước khi render
|
||||
if (!isLoaded) {
|
||||
// Render với default theme (system) khi đang load
|
||||
return (
|
||||
<ThemeContext.Provider
|
||||
value={{
|
||||
themeMode: "system",
|
||||
colorScheme: systemTheme,
|
||||
colors: Colors[systemTheme],
|
||||
setThemeMode: async () => {},
|
||||
getColor: (colorName: ColorName) =>
|
||||
Colors[systemTheme][colorName] || Colors[systemTheme].text,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
const getColor = useCallback(
|
||||
(colorName: ColorName) => colors[colorName] ?? colors.text,
|
||||
[colors]
|
||||
);
|
||||
|
||||
const value: ThemeContextType = {
|
||||
themeMode,
|
||||
colorScheme,
|
||||
colors,
|
||||
setThemeMode,
|
||||
getColor,
|
||||
};
|
||||
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 (
|
||||
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useThemeContext(): ThemeContextType {
|
||||
export function useTheme(): ThemeContextType {
|
||||
const context = useContext(ThemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useThemeContext must be used within a ThemeProvider");
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
// Legacy hook cho backward compatibility
|
||||
export const useThemeContext = useTheme;
|
||||
|
||||
export function useColorScheme(): ColorScheme {
|
||||
const { colorScheme } = useThemeContext();
|
||||
return colorScheme;
|
||||
return useTheme().colorScheme;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user