Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 1x 1x 1x 1x 1x 1x 1x 27x 27x 27x 27x 12x 10x 3x 11x 27x 19x 17x 13x 27x 27x 4x 4x 4x 27x 19x 27x 12x 15x 1x 17x 16x 2x 14x | import React, {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { useColorScheme } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { type Theme, darkTheme, lightTheme } from './themes';
export type ThemePreference = 'light' | 'dark' | 'system';
const SECURE_STORE_KEY = 'theme_preference';
interface ThemeContextValue {
/** Resolved theme object for the current color scheme */
theme: Theme;
/** Whether dark mode is currently active */
isDark: boolean;
/** User-persisted preference: 'light' | 'dark' | 'system' */
themePreference: ThemePreference;
/** Persist a new preference and apply immediately */
setThemePreference: (preference: ThemePreference) => Promise<void>;
}
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const systemScheme = useColorScheme();
const [preference, setPreference] = useState<ThemePreference>('system');
const [isHydrated, setIsHydrated] = useState(false);
useEffect(() => {
SecureStore.getItemAsync(SECURE_STORE_KEY)
.then((stored) => {
if (stored === 'light' || stored === 'dark' || stored === 'system') {
setPreference(stored);
}
})
.catch(() => {
// Ignore read errors — fall back to 'system'
})
.finally(() => {
setIsHydrated(true);
});
}, []);
const isDark = useMemo(() => {
if (preference === 'light') return false;
if (preference === 'dark') return true;
return systemScheme === 'dark';
}, [preference, systemScheme]);
const theme = useMemo(() => (isDark ? darkTheme : lightTheme), [isDark]);
const setThemePreference = useCallback(async (next: ThemePreference) => {
setPreference(next);
try {
await SecureStore.setItemAsync(SECURE_STORE_KEY, next);
} catch {
// Persist failure is non-fatal; preference still applies for the session
}
}, []);
const value = useMemo<ThemeContextValue>(
() => ({ theme, isDark, themePreference: preference, setThemePreference }),
[theme, isDark, preference, setThemePreference],
);
// Don't render children until we've read the stored preference so the
// initial render uses the correct theme instead of flickering.
if (!isHydrated) {
return null;
}
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
export function useTheme(): ThemeContextValue {
const ctx = useContext(ThemeContext);
if (!ctx) {
throw new Error('useTheme must be called inside <ThemeProvider>');
}
return ctx;
}
|