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