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