64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import { StatusBar } from "expo-status-bar";
|
|
import { Appearance, useColorScheme } from "react-native";
|
|
import {
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
type ReactNode,
|
|
} from "react";
|
|
|
|
import { useSettingsStore, type ThemeMode } from "@/lib/settings";
|
|
|
|
type ThemeContextValue = {
|
|
/** "light" | "dark" — the scheme actually in effect (honors our override). */
|
|
colorScheme: "light" | "dark";
|
|
isDark: boolean;
|
|
mode: ThemeMode;
|
|
};
|
|
|
|
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
|
|
/**
|
|
* Drives the whole-app theme.
|
|
*
|
|
* NativeWind's `dark:` variants read the React Native `Appearance` module, so
|
|
* calling `Appearance.setColorScheme` with our chosen mode is what makes every
|
|
* `dark:` class apply app-wide — no `darkMode` config key is needed (the
|
|
* default `media` strategy already reads Appearance). The inline-styled spots
|
|
* that can't use Tailwind classes (map style, BottomSheet, tab bar, spinners)
|
|
* consume `useTheme().isDark` instead.
|
|
*/
|
|
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
|
|
const mode = useSettingsStore((state) => state.mode);
|
|
|
|
useEffect(() => {
|
|
// `null` clears the override and falls back to the device scheme.
|
|
Appearance.setColorScheme(mode === "system" ? null : mode);
|
|
}, [mode]);
|
|
|
|
const deviceScheme = useColorScheme();
|
|
const resolved = deviceScheme === "dark" ? "dark" : "light";
|
|
|
|
const value = useMemo<ThemeContextValue>(
|
|
() => ({ colorScheme: resolved, isDark: resolved === "dark", mode }),
|
|
[resolved, mode],
|
|
);
|
|
|
|
return (
|
|
<ThemeContext.Provider value={value}>
|
|
{children}
|
|
<StatusBar style={resolved === "dark" ? "light" : "dark"} />
|
|
</ThemeContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useTheme = (): ThemeContextValue => {
|
|
const context = useContext(ThemeContext);
|
|
|
|
if (!context) {
|
|
throw new Error("useTheme must be used within ThemeProvider.");
|
|
}
|
|
|
|
return context;
|
|
}; |