-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathapp-theme-provider.tsx
More file actions
87 lines (73 loc) · 2.62 KB
/
app-theme-provider.tsx
File metadata and controls
87 lines (73 loc) · 2.62 KB
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
import { DarkTheme, DefaultTheme, ThemeProvider as ReactNavigationThemeProvider } from '@react-navigation/native';
import { colorScheme } from 'nativewind';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { type ColorSchemeName, useColorScheme as useSystemColorScheme } from 'react-native';
import { getThemePreference, setThemePreference } from '@/lib/appearance';
type ThemeSetting = 'light' | 'dark' | 'system';
type ResolvedTheme = 'light' | 'dark';
type Props = {
children: React.ReactNode;
};
type ThemeContextValue = {
/**
* 主题模式设置,'light' | 'dark' | 'system'
*/
themeSetting: ThemeSetting;
/**
* 当前实际使用的主题,'light' | 'dark'
*/
currentTheme: ResolvedTheme;
isDarkTheme: boolean;
setThemeSetting: (theme: ThemeSetting) => Promise<void>;
};
const AppThemeContext = createContext<ThemeContextValue | null>(null);
const resolveTheme = (setting: ThemeSetting, systemTheme: ColorSchemeName): ResolvedTheme => {
if (setting === 'system') {
return systemTheme === 'dark' ? 'dark' : 'light';
}
return setting;
};
export const AppThemeProvider = ({ children }: Props) => {
const systemTheme = useSystemColorScheme();
const [themeSetting, setThemeSettingState] = useState<ThemeSetting>('system');
const resolvedTheme = useMemo(() => resolveTheme(themeSetting, systemTheme), [themeSetting, systemTheme]);
const setThemeSetting = useCallback(
async (nextTheme: ThemeSetting) => {
setThemeSettingState(nextTheme);
await setThemePreference(nextTheme);
colorScheme.set(nextTheme);
},
[setThemeSettingState],
);
const syncThemeFromStorage = useCallback(async () => {
const storedTheme = await getThemePreference();
setThemeSettingState(storedTheme);
colorScheme.set(storedTheme);
}, []);
useEffect(() => {
syncThemeFromStorage().catch(() => undefined);
}, [syncThemeFromStorage]);
const contextValue = useMemo<ThemeContextValue>(
() => ({
themeSetting,
currentTheme: resolvedTheme,
isDarkTheme: resolvedTheme === 'dark',
setThemeSetting,
}),
[themeSetting, resolvedTheme, setThemeSetting],
);
return (
<AppThemeContext.Provider value={contextValue}>
<ReactNavigationThemeProvider value={resolvedTheme === 'dark' ? DarkTheme : DefaultTheme}>
{children}
</ReactNavigationThemeProvider>
</AppThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(AppThemeContext);
if (!context) {
throw new Error('useTheme must be used within AppThemeProvider');
}
return context;
};