xref: /MusicFree/src/core/theme.ts (revision 410a159129b1f6a7a1f44fde7bfad9a46f91e161)
1import Config from '@/core/config';
2
3import {
4    DarkTheme as _DarkTheme,
5    DefaultTheme as _DefaultTheme,
6} from '@react-navigation/native';
7import {GlobalState} from '@/utils/stateMapper';
8import {CustomizedColors} from '@/hooks/useColors';
9import Color from 'color';
10
11export const lightTheme = {
12    id: 'p-light',
13    ..._DefaultTheme,
14    colors: {
15        ..._DefaultTheme.colors,
16        background: 'transparent',
17        text: '#333333',
18        textSecondary: Color('#333333').alpha(0.7).toString(),
19        primary: '#f17d34',
20        pageBackground: '#fafafa',
21        shadow: '#000',
22        appBar: '#f17d34',
23        appBarText: '#fefefe',
24        musicBar: '#f2f2f2',
25        musicBarText: '#333333',
26        divider: 'rgba(0,0,0,0.1)',
27        listActive: 'rgba(0,0,0,0.1)', // 在手机上表现是ripple
28        mask: 'rgba(51,51,51,0.2)',
29        backdrop: '#f0f0f0',
30        tabBar: '#f0f0f0',
31        placeholder: '#eaeaea',
32        success: '#08A34C',
33        danger: '#FC5F5F',
34        info: '#0A95C8',
35        card: '#e2e2e288',
36    },
37};
38
39export const darkTheme = {
40    id: 'p-dark',
41    ..._DarkTheme,
42    colors: {
43        ..._DarkTheme.colors,
44        background: 'transparent',
45        text: '#fcfcfc',
46        textSecondary: Color('#fcfcfc').alpha(0.7).toString(),
47        primary: '#3FA3B5',
48        pageBackground: '#202020',
49        shadow: '#999',
50        appBar: '#262626',
51        appBarText: '#fcfcfc',
52        musicBar: '#262626',
53        musicBarText: '#fcfcfc',
54        divider: 'rgba(255,255,255,0.1)',
55        listActive: 'rgba(255,255,255,0.1)', // 在手机上表现是ripple
56        mask: 'rgba(33,33,33,0.8)',
57        backdrop: '#303030',
58        tabBar: '#303030',
59        placeholder: '#424242',
60        success: '#08A34C',
61        danger: '#FC5F5F',
62        info: '#0A95C8',
63        card: '#33333388',
64    },
65};
66
67interface IBackgroundInfo {
68    url?: string;
69    blur?: number;
70    opacity?: number;
71}
72
73const themeStore = new GlobalState(darkTheme);
74const backgroundStore = new GlobalState<IBackgroundInfo | null>(null);
75
76function setup() {
77    const currentTheme = Config.get('setting.theme.selectedTheme') ?? 'p-dark';
78
79    if (currentTheme === 'p-dark') {
80        themeStore.setValue(darkTheme);
81    } else if (currentTheme === 'p-light') {
82        themeStore.setValue(lightTheme);
83    } else {
84        themeStore.setValue({
85            id: currentTheme,
86            dark: true,
87            // @ts-ignore
88            colors:
89                (Config.get('setting.theme.colors') as CustomizedColors) ??
90                darkTheme.colors,
91        });
92    }
93
94    const bgUrl = Config.get('setting.theme.background');
95    const bgBlur = Config.get('setting.theme.backgroundBlur');
96    const bgOpacity = Config.get('setting.theme.backgroundOpacity');
97
98    backgroundStore.setValue({
99        url: bgUrl,
100        blur: bgBlur ?? 20,
101        opacity: bgOpacity ?? 0.6,
102    });
103}
104
105function setTheme(
106    themeName: string,
107    extra?: {
108        colors?: Partial<CustomizedColors>;
109        background?: IBackgroundInfo;
110    },
111) {
112    if (themeName === 'p-light') {
113        themeStore.setValue(lightTheme);
114    } else if (themeName === 'p-dark') {
115        themeStore.setValue(darkTheme);
116    } else {
117        themeStore.setValue({
118            id: themeName,
119            dark: true,
120            colors: {
121                ...darkTheme.colors,
122                ...(extra?.colors ?? {}),
123            },
124        });
125    }
126
127    Config.set('setting.theme.selectedTheme', themeName);
128    Config.set('setting.theme.colors', themeStore.getValue().colors);
129
130    if (extra?.background) {
131        const currentBg = backgroundStore.getValue();
132        let newBg: IBackgroundInfo = {
133            blur: 20,
134            opacity: 0.6,
135            ...(currentBg ?? {}),
136            url: undefined,
137        };
138        if (typeof extra.background.blur === 'number') {
139            newBg.blur = extra.background.blur;
140        }
141        if (typeof extra.background.opacity === 'number') {
142            newBg.opacity = extra.background.opacity;
143        }
144        if (extra.background.url) {
145            newBg.url = extra.background.url;
146        }
147
148        Config.set('setting.theme.background', newBg.url);
149        Config.set('setting.theme.backgroundBlur', newBg.blur);
150        Config.set('setting.theme.backgroundOpacity', newBg.opacity);
151
152        backgroundStore.setValue(newBg);
153    }
154}
155
156function setColors(colors: Partial<CustomizedColors>) {
157    const currentTheme = themeStore.getValue();
158    if (currentTheme.id !== 'p-light' && currentTheme.id !== 'p-dark') {
159        const newTheme = {
160            ...currentTheme,
161            colors: {
162                ...currentTheme.colors,
163                ...colors,
164            },
165        };
166        Config.set('setting.theme.customColors', newTheme.colors);
167        Config.set('setting.theme.colors', newTheme.colors);
168        themeStore.setValue(newTheme);
169    }
170}
171
172function setBackground(backgroundInfo: Partial<IBackgroundInfo>) {
173    const currentBackgroundInfo = backgroundStore.getValue();
174    let newBgInfo = {
175        ...(currentBackgroundInfo ?? {
176            opacity: 0.6,
177            blur: 20,
178        }),
179    };
180    if (typeof backgroundInfo.blur === 'number') {
181        Config.set('setting.theme.backgroundBlur', backgroundInfo.blur);
182        newBgInfo.blur = backgroundInfo.blur;
183    }
184    if (typeof backgroundInfo.opacity === 'number') {
185        Config.set('setting.theme.backgroundOpacity', backgroundInfo.opacity);
186        newBgInfo.opacity = backgroundInfo.opacity;
187    }
188    if (backgroundInfo.url !== undefined) {
189        Config.set('setting.theme.background', backgroundInfo.url);
190        newBgInfo.url = backgroundInfo.url;
191    }
192    backgroundStore.setValue(newBgInfo);
193}
194
195const configableColorKey: Array<keyof CustomizedColors> = [
196    'primary',
197    'text',
198    'appBar',
199    'appBarText',
200    'musicBar',
201    'musicBarText',
202    'pageBackground',
203    'backdrop',
204    'card',
205    'placeholder',
206];
207
208const colorDesc: Record<string, string> = {
209    text: '文字颜色',
210    primary: '主题色',
211    appBar: '标题栏背景色',
212    appBarText: '标题栏文字颜色',
213    musicBar: '音乐栏背景色',
214    musicBarText: '音乐栏文字颜色',
215    pageBackground: '页面背景色',
216    backdrop: '弹窗、浮层背景色',
217    card: '卡片背景色',
218    placeholder: '输入框背景色',
219};
220
221const Theme = {
222    setup,
223    setTheme,
224    setBackground,
225    setColors,
226    useTheme: themeStore.useValue,
227    getTheme: themeStore.getValue,
228    useBackground: backgroundStore.useValue,
229    configableColorKey,
230    colorDesc,
231};
232
233export default Theme;
234