xref: /MusicFree/src/core/config.ts (revision 41ddce918e1138d8f16e522cc7c19ac86ceca698)
1import { CustomizedColors } from "@/hooks/useColors";
2import { getStorage, removeStorage } from "@/utils/storage";
3import { ResumeMode, SortType } from "@/constants/commonConst.ts";
4import getOrCreateMMKV from "@/utils/getOrCreateMMKV.ts";
5import safeStringify from "@/utils/safeStringify.ts";
6import { useMMKVObject } from "react-native-mmkv";
7
8
9const configStore = getOrCreateMMKV('App.config');
10
11// 新版本配置类型(扁平化结构)
12interface IConfig {
13    "$schema": "1";
14    // Basic
15    "basic.autoPlayWhenAppStart": boolean;
16    "basic.useCelluarNetworkPlay": boolean;
17    "basic.useCelluarNetworkDownload": boolean;
18    "basic.maxDownload": number | string;
19    "basic.clickMusicInSearch": '播放歌曲' | '播放歌曲并替换播放列表';
20    "basic.clickMusicInAlbum": '播放专辑' | '播放单曲';
21    "basic.downloadPath": string;
22    "basic.notInterrupt": boolean;
23    "basic.tempRemoteDuck": '暂停' | '降低音量';
24    "basic.autoStopWhenError": boolean;
25    "basic.pluginCacheControl": string;
26    "basic.maxCacheSize": number;
27    "basic.defaultPlayQuality": IMusic.IQualityKey;
28    "basic.playQualityOrder": 'asc' | 'desc';
29    "basic.defaultDownloadQuality": IMusic.IQualityKey;
30    "basic.downloadQualityOrder": 'asc' | 'desc';
31    "basic.musicDetailDefault": 'album' | 'lyric';
32    "basic.musicDetailAwake": boolean;
33    "basic.maxHistoryLen": number;
34    "basic.autoUpdatePlugin": boolean;
35    "basic.notCheckPluginVersion": boolean;
36    "basic.associateLyricType": 'input' | 'search';
37    "basic.showExitOnNotification": boolean;
38    "basic.musicOrderInLocalSheet": SortType;
39    "basic.tryChangeSourceWhenPlayFail": boolean;
40
41    // Lyric
42    "lyric.showStatusBarLyric": boolean;
43    "lyric.topPercent": number;
44    "lyric.leftPercent": number;
45    "lyric.align": number;
46    "lyric.color": string;
47    "lyric.backgroundColor": string;
48    "lyric.widthPercent": number;
49    "lyric.fontSize": number;
50    "lyric.detailFontSize": number;
51    "lyric.autoSearchLyric": boolean;
52
53    // Theme
54    "theme.background": string;
55    "theme.backgroundOpacity": number;
56    "theme.backgroundBlur": number;
57    "theme.colors": CustomizedColors;
58    "theme.customColors"?: CustomizedColors;
59    "theme.followSystem": boolean;
60    "theme.selectedTheme": string;
61
62    // Backup
63    "backup.resumeMode": ResumeMode;
64
65    // Plugin
66    "plugin.subscribeUrl": string;
67
68    // WebDAV
69    "webdav.url": string;
70    "webdav.username": string;
71    "webdav.password": string;
72
73    // Debug(保持嵌套结构)
74    "debug.errorLog": boolean;
75    "debug.traceLog": boolean;
76    "debug.devLog": boolean;
77}
78
79
80export type ConfigKey = keyof IConfig;
81
82// 迁移函数
83async function migrateConfig(): Promise<void> {
84    // 检查是否已经迁移
85    if (configStore.contains('$schema')) {
86        return;
87    }
88
89    // 获取旧配置
90    const oldConfig = await getStorage('local-config');
91
92    // 如果没有旧配置,直接初始化新配置
93    if (!oldConfig) {
94        configStore.set('$schema', '1');
95        return;
96    }
97
98    // 迁移每个字段
99    const mapping: [string, ConfigKey][] = [
100        // Basic
101        ['setting.basic.autoPlayWhenAppStart', 'basic.autoPlayWhenAppStart'],
102        ['setting.basic.useCelluarNetworkPlay', 'basic.useCelluarNetworkPlay'],
103        ['setting.basic.useCelluarNetworkDownload', 'basic.useCelluarNetworkDownload'],
104        ['setting.basic.maxDownload', 'basic.maxDownload'],
105        ['setting.basic.clickMusicInSearch', 'basic.clickMusicInSearch'],
106        ['setting.basic.clickMusicInAlbum', 'basic.clickMusicInAlbum'],
107        ['setting.basic.downloadPath', 'basic.downloadPath'],
108        ['setting.basic.notInterrupt', 'basic.notInterrupt'],
109        ['setting.basic.tempRemoteDuck', 'basic.tempRemoteDuck'],
110        ['setting.basic.autoStopWhenError', 'basic.autoStopWhenError'],
111        ['setting.basic.pluginCacheControl', 'basic.pluginCacheControl'],
112        ['setting.basic.maxCacheSize', 'basic.maxCacheSize'],
113        ['setting.basic.defaultPlayQuality', 'basic.defaultPlayQuality'],
114        ['setting.basic.playQualityOrder', 'basic.playQualityOrder'],
115        ['setting.basic.defaultDownloadQuality', 'basic.defaultDownloadQuality'],
116        ['setting.basic.downloadQualityOrder', 'basic.downloadQualityOrder'],
117        ['setting.basic.musicDetailDefault', 'basic.musicDetailDefault'],
118        ['setting.basic.musicDetailAwake', 'basic.musicDetailAwake'],
119        ['setting.basic.debug.errorLog', 'debug.errorLog'],
120        ['setting.basic.debug.traceLog', 'debug.traceLog'],
121        ['setting.basic.debug.devLog', 'debug.devLog'],
122        ['setting.basic.maxHistoryLen', 'basic.maxHistoryLen'],
123        ['setting.basic.autoUpdatePlugin', 'basic.autoUpdatePlugin'],
124        ['setting.basic.notCheckPluginVersion', 'basic.notCheckPluginVersion'],
125        ['setting.basic.associateLyricType', 'basic.associateLyricType'],
126        ['setting.basic.showExitOnNotification', 'basic.showExitOnNotification'],
127        ['setting.basic.musicOrderInLocalSheet', 'basic.musicOrderInLocalSheet'],
128        ['setting.basic.tryChangeSourceWhenPlayFail', 'basic.tryChangeSourceWhenPlayFail'],
129
130        // Lyric
131        ['setting.lyric.showStatusBarLyric', 'lyric.showStatusBarLyric'],
132        ['setting.lyric.topPercent', 'lyric.topPercent'],
133        ['setting.lyric.leftPercent', 'lyric.leftPercent'],
134        ['setting.lyric.align', 'lyric.align'],
135        ['setting.lyric.color', 'lyric.color'],
136        ['setting.lyric.backgroundColor', 'lyric.backgroundColor'],
137        ['setting.lyric.widthPercent', 'lyric.widthPercent'],
138        ['setting.lyric.fontSize', 'lyric.fontSize'],
139        ['setting.lyric.detailFontSize', 'lyric.detailFontSize'],
140        ['setting.lyric.autoSearchLyric', 'lyric.autoSearchLyric'],
141
142        // Theme
143        ['setting.theme.background', 'theme.background'],
144        ['setting.theme.backgroundOpacity', 'theme.backgroundOpacity'],
145        ['setting.theme.backgroundBlur', 'theme.backgroundBlur'],
146        ['setting.theme.colors', 'theme.colors'],
147        ['setting.theme.customColors', 'theme.customColors'],
148        ['setting.theme.followSystem', 'theme.followSystem'],
149        ['setting.theme.selectedTheme', 'theme.selectedTheme'],
150
151        // Backup
152        ['setting.backup.resumeMode', 'backup.resumeMode'],
153
154        // Plugin
155        ['setting.plugin.subscribeUrl', 'plugin.subscribeUrl'],
156
157        // WebDAV
158        ['setting.webdav.url', 'webdav.url'],
159        ['setting.webdav.username', 'webdav.username'],
160        ['setting.webdav.password', 'webdav.password'],
161    ];
162
163    // 执行迁移
164    function getPathValue(obj: Record<string, any>, path: string) {
165        const keys = path.split('.');
166        let tmp = obj;
167        for (let i = 0; i < keys.length; ++i) {
168            tmp = tmp?.[keys[i]];
169        }
170        return tmp;
171    }
172
173    mapping.forEach(([oldPath, newKey]) => {
174        const value = getPathValue(oldConfig, oldPath);
175        if (value !== undefined) {
176            configStore.set(newKey, safeStringify(value));
177        }
178    });
179
180    // 设置版本标识
181    configStore.set('$schema', '1');
182
183    // 清理旧配置
184    await removeStorage('local-config'); // 根据需求决定是否删除旧配置
185}
186
187/** 初始化config */
188async function setup() {
189    await migrateConfig();
190}
191
192function setConfig<K extends ConfigKey>(key: K, value?: IConfig[K]) {
193    if (value === undefined) {
194        configStore.delete(key);
195    } else {
196        configStore.set(key, safeStringify(value));
197    }
198}
199
200function getConfig<K extends ConfigKey>(key: K): IConfig[K] | undefined {
201    const value = configStore.getString(key);
202    if (value === undefined) {
203        return undefined;
204    }
205    return JSON.parse(value);
206}
207
208
209function useConfig<K extends ConfigKey>(key: K) {
210    return useMMKVObject<IConfig[K]>(key, configStore);
211}
212
213function useConfigValue<K extends ConfigKey>(key: K) {
214    return useMMKVObject<IConfig[K]>(key, configStore)[0];
215}
216
217const Config = {
218    setup,
219    setConfig,
220    getConfig,
221    useConfig,
222    useConfigValue
223}
224
225export default Config;
226