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