1import React, { useCallback, useEffect, useRef, useState } from "react"; 2import { SectionList, StyleSheet, TouchableOpacity, View } from "react-native"; 3import rpx from "@/utils/rpx"; 4import Config, { ConfigKey } from "@/core/config.ts"; 5import ListItem from "@/components/base/listItem"; 6import ThemeText from "@/components/base/themeText"; 7import ThemeSwitch from "@/components/base/switch"; 8import { clearCache, getCacheSize, sizeFormatter } from "@/utils/fileUtils"; 9 10import Toast from "@/utils/toast"; 11import toast from "@/utils/toast"; 12import pathConst from "@/constants/pathConst"; 13import { ROUTE_PATH, useNavigate } from "@/core/router"; 14import { readdir } from "react-native-fs"; 15import { qualityKeys, qualityText } from "@/utils/qualities"; 16import { clearLog, getErrorLogContent } from "@/utils/log"; 17import { FlatList, ScrollView } from "react-native-gesture-handler"; 18import { showDialog } from "@/components/dialogs/useDialog"; 19import { showPanel } from "@/components/panels/usePanel"; 20import Paragraph from "@/components/base/paragraph"; 21import LyricUtil, { NativeTextAlignment } from "@/native/lyricUtil"; 22import Slider from "@react-native-community/slider"; 23import useColors from "@/hooks/useColors"; 24import ColorBlock from "@/components/base/colorBlock"; 25import { SortType } from "@/constants/commonConst.ts"; 26import Clipboard from "@react-native-clipboard/clipboard"; 27 28function createSwitch( 29 title: string, 30 changeKey: ConfigKey, 31 value: boolean, 32 callback?: (newValue: boolean) => void, 33) { 34 const onPress = () => { 35 if (callback) { 36 callback(!value); 37 } else { 38 Config.setConfig(changeKey, !value); 39 } 40 }; 41 return { 42 title, 43 onPress, 44 right: <ThemeSwitch value={value} onValueChange={onPress} />, 45 }; 46} 47 48const createRadio = function ( 49 title: string, 50 changeKey: ConfigKey, 51 candidates: Array<string | number>, 52 value: string | number, 53 valueMap?: Record<string | number, string | number>, 54 onChange?: (value: string | number) => void, 55) { 56 const onPress = () => { 57 showDialog('RadioDialog', { 58 title, 59 content: valueMap 60 ? candidates.map(_ => ({ 61 label: valueMap[_] as string, 62 value: _, 63 })) 64 : candidates, 65 onOk(val) { 66 Config.setConfig(changeKey, val); 67 onChange?.(val); 68 }, 69 }); 70 }; 71 return { 72 title, 73 right: ( 74 <ThemeText style={styles.centerText}> 75 {valueMap ? valueMap[value] : value} 76 </ThemeText> 77 ), 78 onPress, 79 }; 80}; 81 82function useCacheSize() { 83 const [cacheSize, setCacheSize] = useState({ 84 music: 0, 85 lyric: 0, 86 image: 0, 87 }); 88 89 const refreshCacheSize = useCallback(async () => { 90 const [musicCache, lyricCache, imageCache] = await Promise.all([ 91 getCacheSize('music'), 92 getCacheSize('lyric'), 93 getCacheSize('image'), 94 ]); 95 setCacheSize({ 96 music: musicCache, 97 lyric: lyricCache, 98 image: imageCache, 99 }); 100 }, []); 101 102 return [cacheSize, refreshCacheSize] as const; 103} 104 105export default function BasicSetting() { 106 107 const autoPlayWhenAppStart = Config.useConfigValue('basic.autoPlayWhenAppStart'); 108 const useCelluarNetworkPlay = Config.useConfigValue('basic.useCelluarNetworkPlay'); 109 const useCelluarNetworkDownload = Config.useConfigValue('basic.useCelluarNetworkDownload'); 110 const maxDownload = Config.useConfigValue('basic.maxDownload'); 111 const clickMusicInSearch = Config.useConfigValue('basic.clickMusicInSearch'); 112 const clickMusicInAlbum = Config.useConfigValue('basic.clickMusicInAlbum'); 113 const downloadPath = Config.useConfigValue('basic.downloadPath'); 114 const notInterrupt = Config.useConfigValue('basic.notInterrupt'); 115 const tempRemoteDuck = Config.useConfigValue('basic.tempRemoteDuck'); 116 const autoStopWhenError = Config.useConfigValue('basic.autoStopWhenError'); 117 const maxCacheSize = Config.useConfigValue('basic.maxCacheSize'); 118 const defaultPlayQuality = Config.useConfigValue('basic.defaultPlayQuality'); 119 const playQualityOrder = Config.useConfigValue('basic.playQualityOrder'); 120 const defaultDownloadQuality = Config.useConfigValue('basic.defaultDownloadQuality'); 121 const downloadQualityOrder = Config.useConfigValue('basic.downloadQualityOrder'); 122 const musicDetailDefault = Config.useConfigValue('basic.musicDetailDefault'); 123 const musicDetailAwake = Config.useConfigValue('basic.musicDetailAwake'); 124 const maxHistoryLen = Config.useConfigValue('basic.maxHistoryLen'); 125 const autoUpdatePlugin = Config.useConfigValue('basic.autoUpdatePlugin'); 126 const notCheckPluginVersion = Config.useConfigValue('basic.notCheckPluginVersion'); 127 const associateLyricType = Config.useConfigValue('basic.associateLyricType'); 128 const showExitOnNotification = Config.useConfigValue('basic.showExitOnNotification'); 129 const musicOrderInLocalSheet = Config.useConfigValue('basic.musicOrderInLocalSheet'); 130 const tryChangeSourceWhenPlayFail = Config.useConfigValue('basic.tryChangeSourceWhenPlayFail'); 131 132 133 const debugEnableErrorLog = Config.useConfigValue('debug.errorLog'); 134 const debugEnableTraceLog = Config.useConfigValue('debug.traceLog'); 135 const debugEnableDevLog = Config.useConfigValue('debug.devLog'); 136 137 const navigate = useNavigate(); 138 139 const [cacheSize, refreshCacheSize] = useCacheSize(); 140 141 const sectionListRef = useRef<SectionList | null>(null); 142 // const titleListRef = useRef<FlatList | null>(null); 143 144 useEffect(() => { 145 refreshCacheSize(); 146 }, []); 147 148 const basicOptions = [ 149 { 150 title: '常规', 151 data: [ 152 createRadio( 153 '历史记录最多保存条数', 154 'basic.maxHistoryLen', 155 [20, 50, 100, 200, 500], 156 maxHistoryLen ?? 50, 157 ), 158 createRadio( 159 '打开歌曲详情页时', 160 'basic.musicDetailDefault', 161 ['album', 'lyric'], 162 musicDetailDefault ?? 'album', 163 { 164 album: '默认展示歌曲封面', 165 lyric: '默认展示歌词页', 166 }, 167 ), 168 createSwitch( 169 '处于歌曲详情页时常亮', 170 'basic.musicDetailAwake', 171 musicDetailAwake ?? false, 172 ), 173 createRadio( 174 '关联歌词方式', 175 'basic.associateLyricType', 176 ['input', 'search'], 177 associateLyricType ?? 'search', 178 { 179 input: '输入歌曲ID', 180 search: '搜索歌词', 181 }, 182 ), 183 createSwitch( 184 '通知栏显示关闭按钮 (重启后生效)', 185 'basic.showExitOnNotification', 186 showExitOnNotification ?? false, 187 ), 188 ], 189 }, 190 { 191 title: '歌单&专辑', 192 data: [ 193 createRadio( 194 '点击搜索结果内单曲时', 195 'basic.clickMusicInSearch', 196 ['播放歌曲', '播放歌曲并替换播放列表'], 197 clickMusicInSearch ?? '播放歌曲', 198 ), 199 createRadio( 200 '点击专辑内单曲时', 201 'basic.clickMusicInAlbum', 202 ['播放单曲', '播放专辑'], 203 clickMusicInAlbum ?? '播放专辑', 204 ), 205 createRadio( 206 '打开歌曲详情页时', 207 'basic.musicDetailDefault', 208 ['album', 'lyric'], 209 musicDetailDefault ?? 'album', 210 { 211 album: '默认展示歌曲封面', 212 lyric: '默认展示歌词页', 213 }, 214 ), 215 createRadio( 216 '新建歌单时默认歌曲排序', 217 'basic.musicOrderInLocalSheet', 218 [ 219 SortType.Title, 220 SortType.Artist, 221 SortType.Album, 222 SortType.Newest, 223 SortType.Oldest, 224 ], 225 musicOrderInLocalSheet ?? 'end', 226 { 227 [SortType.Title]: '按歌曲名排序', 228 [SortType.Artist]: '按作者名排序', 229 [SortType.Album]: '按专辑名排序', 230 [SortType.Newest]: '按收藏时间从新到旧排序', 231 [SortType.Oldest]: '按收藏时间从旧到新排序', 232 }, 233 ), 234 ], 235 }, 236 { 237 title: '插件', 238 data: [ 239 createSwitch( 240 '软件启动时自动更新插件', 241 'basic.autoUpdatePlugin', 242 autoUpdatePlugin ?? false, 243 ), 244 createSwitch( 245 '安装插件时不校验版本', 246 'basic.notCheckPluginVersion', 247 notCheckPluginVersion ?? false, 248 ), 249 ], 250 }, 251 { 252 title: '播放', 253 data: [ 254 createSwitch( 255 '允许与其他应用同时播放', 256 'basic.notInterrupt', 257 notInterrupt ?? false, 258 ), 259 createSwitch( 260 '软件启动时自动播放歌曲', 261 'basic.autoPlayWhenAppStart', 262 autoPlayWhenAppStart ?? false, 263 ), 264 createSwitch( 265 '播放失败时尝试更换音源', 266 'basic.tryChangeSourceWhenPlayFail', 267 tryChangeSourceWhenPlayFail ?? false, 268 ), 269 createSwitch( 270 '播放失败时自动暂停', 271 'basic.autoStopWhenError', 272 autoStopWhenError ?? false, 273 ), 274 createRadio( 275 '播放被暂时打断时', 276 'basic.tempRemoteDuck', 277 ['暂停', '降低音量'], 278 tempRemoteDuck ?? '暂停', 279 ), 280 createRadio( 281 '默认播放音质', 282 'basic.defaultPlayQuality', 283 qualityKeys, 284 defaultPlayQuality ?? 'standard', 285 qualityText, 286 ), 287 createRadio( 288 '默认播放音质缺失时', 289 'basic.playQualityOrder', 290 ['asc', 'desc'], 291 playQualityOrder ?? 'asc', 292 { 293 asc: '播放更高音质', 294 desc: '播放更低音质', 295 }, 296 ), 297 ], 298 }, 299 { 300 title: '下载', 301 data: [ 302 { 303 title: '下载路径', 304 right: ( 305 <ThemeText 306 fontSize="subTitle" 307 style={styles.centerText} 308 numberOfLines={3}> 309 {downloadPath ?? 310 pathConst.downloadMusicPath} 311 </ThemeText> 312 ), 313 onPress() { 314 navigate<'file-selector'>(ROUTE_PATH.FILE_SELECTOR, { 315 fileType: 'folder', 316 multi: false, 317 actionText: '选择文件夹', 318 async onAction(selectedFiles) { 319 try { 320 const targetDir = selectedFiles[0]; 321 await readdir(targetDir.path); 322 Config.setConfig( 323 'basic.downloadPath', 324 targetDir.path, 325 ); 326 return true; 327 } catch { 328 Toast.warn('文件夹不存在或无权限'); 329 return false; 330 } 331 }, 332 }); 333 }, 334 }, 335 createRadio( 336 '最大同时下载数目', 337 'basic.maxDownload', 338 [1, 3, 5, 7], 339 maxDownload ?? 3, 340 ), 341 createRadio( 342 '默认下载音质', 343 'basic.defaultDownloadQuality', 344 qualityKeys, 345 defaultDownloadQuality ?? 'standard', 346 qualityText, 347 ), 348 createRadio( 349 '默认下载音质缺失时', 350 'basic.downloadQualityOrder', 351 ['asc', 'desc'], 352 downloadQualityOrder ?? 'asc', 353 { 354 asc: '下载更高音质', 355 desc: '下载更低音质', 356 }, 357 ), 358 ], 359 }, 360 { 361 title: '网络', 362 data: [ 363 createSwitch( 364 '使用移动网络播放', 365 'basic.useCelluarNetworkPlay', 366 useCelluarNetworkPlay ?? false, 367 ), 368 createSwitch( 369 '使用移动网络下载', 370 'basic.useCelluarNetworkDownload', 371 useCelluarNetworkDownload ?? false, 372 ), 373 ], 374 }, 375 { 376 title: '歌词', 377 data: [], 378 footer: <LyricSetting />, 379 }, 380 { 381 title: '缓存', 382 data: [ 383 { 384 title: '音乐缓存上限', 385 right: ( 386 <ThemeText style={styles.centerText}> 387 {maxCacheSize 388 ? sizeFormatter(maxCacheSize) 389 : '512M'} 390 </ThemeText> 391 ), 392 onPress() { 393 showPanel('SimpleInput', { 394 title: '设置缓存', 395 placeholder: '输入缓存占用上限,100M-8192M,单位M', 396 onOk(text, closePanel) { 397 let val = parseInt(text); 398 if (val < 100) { 399 val = 100; 400 } else if (val > 8192) { 401 val = 8192; 402 } 403 if (val >= 100 && val <= 8192) { 404 Config.setConfig( 405 'basic.maxCacheSize', 406 val * 1024 * 1024, 407 ); 408 closePanel(); 409 Toast.success('设置成功'); 410 } 411 }, 412 }); 413 }, 414 }, 415 416 { 417 title: '清除音乐缓存', 418 right: ( 419 <ThemeText style={styles.centerText}> 420 {sizeFormatter(cacheSize.music)} 421 </ThemeText> 422 ), 423 onPress() { 424 showDialog('SimpleDialog', { 425 title: '清除音乐缓存', 426 content: '确定清除音乐缓存吗?', 427 async onOk() { 428 await clearCache('music'); 429 Toast.success('已清除音乐缓存'); 430 refreshCacheSize(); 431 }, 432 }); 433 }, 434 }, 435 { 436 title: '清除歌词缓存', 437 right: ( 438 <ThemeText style={styles.centerText}> 439 {sizeFormatter(cacheSize.lyric)} 440 </ThemeText> 441 ), 442 onPress() { 443 showDialog('SimpleDialog', { 444 title: '清除歌词缓存', 445 content: '确定清除歌词缓存吗?', 446 async onOk() { 447 await clearCache('lyric'); 448 Toast.success('已清除歌词缓存'); 449 refreshCacheSize(); 450 }, 451 }); 452 }, 453 }, 454 { 455 title: '清除图片缓存', 456 right: ( 457 <ThemeText style={styles.centerText}> 458 {sizeFormatter(cacheSize.image)} 459 </ThemeText> 460 ), 461 onPress() { 462 showDialog('SimpleDialog', { 463 title: '清除图片缓存', 464 content: '确定清除图片缓存吗?', 465 async onOk() { 466 await clearCache('image'); 467 Toast.success('已清除图片缓存'); 468 refreshCacheSize(); 469 }, 470 }); 471 }, 472 }, 473 ], 474 }, 475 { 476 title: '开发选项', 477 data: [ 478 createSwitch( 479 '记录错误日志', 480 'debug.errorLog', 481 debugEnableErrorLog ?? false, 482 ), 483 createSwitch( 484 '记录详细日志', 485 'debug.traceLog', 486 debugEnableTraceLog ?? false, 487 ), 488 createSwitch( 489 '调试面板', 490 'debug.devLog', 491 debugEnableDevLog ?? false, 492 ), 493 { 494 title: '查看错误日志', 495 right: undefined, 496 async onPress() { 497 // 获取日志文件夹 498 const errorLogContent = await getErrorLogContent(); 499 showDialog('SimpleDialog', { 500 title: '错误日志', 501 content: ( 502 <ScrollView> 503 <Paragraph> 504 {errorLogContent || '暂无记录'} 505 </Paragraph> 506 </ScrollView> 507 ), 508 cancelText: '我知道了', 509 okText: '复制日志', 510 onOk() { 511 if (errorLogContent) { 512 Clipboard.setString(errorLogContent); 513 toast.success('复制成功'); 514 } 515 }, 516 }); 517 }, 518 }, 519 { 520 title: '清空日志', 521 right: undefined, 522 async onPress() { 523 try { 524 await clearLog(); 525 Toast.success('日志已清空'); 526 } catch {} 527 }, 528 }, 529 ], 530 }, 531 ]; 532 533 return ( 534 <View style={styles.wrapper}> 535 <FlatList 536 style={styles.headerContainer} 537 showsHorizontalScrollIndicator={false} 538 contentContainerStyle={styles.headerContentContainer} 539 horizontal 540 data={basicOptions.map(it => it.title)} 541 renderItem={({item, index}) => ( 542 <TouchableOpacity 543 onPress={() => { 544 sectionListRef.current?.scrollToLocation({ 545 sectionIndex: index, 546 itemIndex: 0, 547 }); 548 }} 549 activeOpacity={0.7} 550 style={styles.headerItemStyle}> 551 <ThemeText fontWeight="bold">{item}</ThemeText> 552 </TouchableOpacity> 553 )} 554 /> 555 <SectionList 556 sections={basicOptions} 557 renderSectionHeader={({section}) => ( 558 <View style={styles.sectionHeader}> 559 <ThemeText 560 fontSize="subTitle" 561 fontColor="textSecondary" 562 fontWeight="bold"> 563 {section.title} 564 </ThemeText> 565 </View> 566 )} 567 ref={sectionListRef} 568 renderSectionFooter={({section}) => { 569 return section.footer ?? null; 570 }} 571 renderItem={({item}) => { 572 const Right = item.right; 573 574 return ( 575 <ListItem 576 withHorizontalPadding 577 heightType="small" 578 onPress={item.onPress}> 579 <ListItem.Content title={item.title} /> 580 {Right} 581 </ListItem> 582 ); 583 }} 584 /> 585 </View> 586 ); 587} 588 589const styles = StyleSheet.create({ 590 wrapper: { 591 width: '100%', 592 paddingBottom: rpx(24), 593 flex: 1, 594 }, 595 centerText: { 596 textAlignVertical: 'center', 597 maxWidth: rpx(400), 598 }, 599 sectionHeader: { 600 paddingHorizontal: rpx(24), 601 height: rpx(72), 602 flexDirection: 'row', 603 alignItems: 'center', 604 marginTop: rpx(20), 605 }, 606 headerContainer: { 607 height: rpx(80), 608 }, 609 headerContentContainer: { 610 height: rpx(80), 611 alignItems: 'center', 612 paddingHorizontal: rpx(24), 613 }, 614 headerItemStyle: { 615 paddingHorizontal: rpx(36), 616 height: rpx(80), 617 justifyContent: 'center', 618 alignItems: 'center', 619 }, 620}); 621 622function LyricSetting() { 623 /** 624 * // Lyric 625 * "lyric.showStatusBarLyric": boolean; 626 * "lyric.topPercent": number; 627 * "lyric.leftPercent": number; 628 * "lyric.align": number; 629 * "lyric.color": string; 630 * "lyric.backgroundColor": string; 631 * "lyric.widthPercent": number; 632 * "lyric.fontSize": number; 633 * "lyric.detailFontSize": number; 634 * "lyric.autoSearchLyric": boolean; 635 */ 636 const showStatusBarLyric = Config.useConfigValue('lyric.showStatusBarLyric'); 637 const topPercent = Config.useConfigValue('lyric.topPercent'); 638 const leftPercent = Config.useConfigValue('lyric.leftPercent'); 639 const align = Config.useConfigValue('lyric.align'); 640 const color = Config.useConfigValue('lyric.color'); 641 const backgroundColor = Config.useConfigValue('lyric.backgroundColor'); 642 const widthPercent = Config.useConfigValue('lyric.widthPercent'); 643 const fontSize = Config.useConfigValue('lyric.fontSize'); 644 const enableAutoSearchLyric = Config.useConfigValue('lyric.autoSearchLyric'); 645 646 647 648 const colors = useColors(); 649 650 const autoSearchLyric = createSwitch( 651 '歌词缺失时自动搜索歌词', 652 'lyric.autoSearchLyric', 653 enableAutoSearchLyric ?? false, 654 ); 655 656 const openStatusBarLyric = createSwitch( 657 '开启桌面歌词', 658 'lyric.showStatusBarLyric', 659 showStatusBarLyric ?? false, 660 async newValue => { 661 try { 662 if (newValue) { 663 const hasPermission = 664 await LyricUtil.checkSystemAlertPermission(); 665 666 if (hasPermission) { 667 const statusBarLyricConfig = { 668 topPercent: Config.getConfig("lyric.topPercent"), 669 leftPercent: Config.getConfig("lyric.leftPercent"), 670 align: Config.getConfig("lyric.align"), 671 color: Config.getConfig("lyric.color"), 672 backgroundColor: Config.getConfig("lyric.backgroundColor"), 673 widthPercent: Config.getConfig("lyric.widthPercent"), 674 fontSize: Config.getConfig("lyric.fontSize") 675 }; 676 LyricUtil.showStatusBarLyric( 677 "MusicFree", 678 statusBarLyricConfig ?? {} 679 ); 680 Config.setConfig('lyric.showStatusBarLyric', true); 681 } else { 682 LyricUtil.requestSystemAlertPermission().finally(() => { 683 Toast.warn('无悬浮窗权限'); 684 }); 685 } 686 } else { 687 LyricUtil.hideStatusBarLyric(); 688 Config.setConfig('lyric.showStatusBarLyric', false); 689 } 690 } catch {} 691 }, 692 ); 693 694 const alignStatusBarLyric = createRadio( 695 '对齐方式', 696 'lyric.align', 697 [ 698 NativeTextAlignment.LEFT, 699 NativeTextAlignment.CENTER, 700 NativeTextAlignment.RIGHT, 701 ], 702 align ?? NativeTextAlignment.CENTER, 703 { 704 [NativeTextAlignment.LEFT]: '左对齐', 705 [NativeTextAlignment.CENTER]: '居中对齐', 706 [NativeTextAlignment.RIGHT]: '右对齐', 707 }, 708 newVal => { 709 if (showStatusBarLyric) { 710 LyricUtil.setStatusBarLyricAlign(newVal as any); 711 } 712 }, 713 ); 714 715 return ( 716 <View> 717 <ListItem 718 withHorizontalPadding 719 heightType="small" 720 onPress={autoSearchLyric.onPress}> 721 <ListItem.Content title={autoSearchLyric.title} /> 722 {autoSearchLyric.right} 723 </ListItem> 724 <ListItem 725 withHorizontalPadding 726 heightType="small" 727 onPress={openStatusBarLyric.onPress}> 728 <ListItem.Content title={openStatusBarLyric.title} /> 729 {openStatusBarLyric.right} 730 </ListItem> 731 <View style={lyricStyles.sliderContainer}> 732 <ThemeText>左右距离</ThemeText> 733 <Slider 734 style={lyricStyles.slider} 735 minimumTrackTintColor={colors.primary} 736 maximumTrackTintColor={colors.text ?? '#999999'} 737 thumbTintColor={colors.primary} 738 minimumValue={0} 739 step={0.01} 740 value={leftPercent ?? 0.5} 741 maximumValue={1} 742 onValueChange={val => { 743 if (showStatusBarLyric) { 744 LyricUtil.setStatusBarLyricLeft(val); 745 } 746 }} 747 onSlidingComplete={val => { 748 Config.setConfig('lyric.leftPercent', val); 749 }} 750 /> 751 </View> 752 <View style={lyricStyles.sliderContainer}> 753 <ThemeText>上下距离</ThemeText> 754 <Slider 755 style={lyricStyles.slider} 756 minimumTrackTintColor={colors.primary} 757 maximumTrackTintColor={colors.text ?? '#999999'} 758 thumbTintColor={colors.primary} 759 minimumValue={0} 760 value={topPercent ?? 0} 761 step={0.01} 762 maximumValue={1} 763 onValueChange={val => { 764 if (showStatusBarLyric) { 765 LyricUtil.setStatusBarLyricTop(val); 766 } 767 }} 768 onSlidingComplete={val => { 769 Config.setConfig('lyric.topPercent', val); 770 }} 771 /> 772 </View> 773 <View style={lyricStyles.sliderContainer}> 774 <ThemeText>歌词宽度</ThemeText> 775 <Slider 776 style={lyricStyles.slider} 777 minimumTrackTintColor={colors.primary} 778 maximumTrackTintColor={colors.text ?? '#999999'} 779 thumbTintColor={colors.primary} 780 minimumValue={0} 781 step={0.01} 782 value={widthPercent ?? 0.5} 783 maximumValue={1} 784 onValueChange={val => { 785 if (showStatusBarLyric) { 786 LyricUtil.setStatusBarLyricWidth(val); 787 } 788 }} 789 onSlidingComplete={val => { 790 Config.setConfig('lyric.widthPercent', val); 791 }} 792 /> 793 </View> 794 <View style={lyricStyles.sliderContainer}> 795 <ThemeText>字体大小</ThemeText> 796 <Slider 797 style={lyricStyles.slider} 798 minimumTrackTintColor={colors.primary} 799 maximumTrackTintColor={colors.text ?? '#999999'} 800 thumbTintColor={colors.primary} 801 minimumValue={Math.round(rpx(18))} 802 step={0.5} 803 maximumValue={Math.round(rpx(56))} 804 value={fontSize ?? Math.round(rpx(24))} 805 onValueChange={val => { 806 if (showStatusBarLyric) { 807 LyricUtil.setStatusBarLyricFontSize(val); 808 } 809 }} 810 onSlidingComplete={val => { 811 Config.setConfig('lyric.fontSize', val); 812 }} 813 /> 814 </View> 815 <ListItem 816 withHorizontalPadding 817 heightType="small" 818 onPress={alignStatusBarLyric.onPress}> 819 <ListItem.Content title={alignStatusBarLyric.title} /> 820 {alignStatusBarLyric.right} 821 </ListItem> 822 <ListItem 823 withHorizontalPadding 824 heightType="small" 825 onPress={() => { 826 showPanel('ColorPicker', { 827 closePanelWhenSelected: true, 828 defaultColor: color ?? 'transparent', 829 onSelected(color) { 830 if (showStatusBarLyric) { 831 const colorStr = color.hexa(); 832 LyricUtil.setStatusBarColors(colorStr, null); 833 Config.setConfig('lyric.color', colorStr); 834 } 835 }, 836 }); 837 }}> 838 <ListItem.Content title="文本颜色" /> 839 <ColorBlock color={color ?? '#FFE9D2FF'} /> 840 </ListItem> 841 <ListItem 842 withHorizontalPadding 843 heightType="small" 844 onPress={() => { 845 showPanel('ColorPicker', { 846 closePanelWhenSelected: true, 847 defaultColor: 848 backgroundColor ?? 'transparent', 849 onSelected(color) { 850 if (showStatusBarLyric) { 851 const colorStr = color.hexa(); 852 LyricUtil.setStatusBarColors(null, colorStr); 853 Config.setConfig( 854 'lyric.backgroundColor', 855 colorStr, 856 ); 857 } 858 }, 859 }); 860 }}> 861 <ListItem.Content title="文本背景色" /> 862 <ColorBlock 863 color={backgroundColor ?? '#84888153'} 864 /> 865 </ListItem> 866 </View> 867 ); 868} 869 870const lyricStyles = StyleSheet.create({ 871 slider: { 872 flex: 1, 873 marginLeft: rpx(24), 874 }, 875 sliderContainer: { 876 height: rpx(96), 877 width: '100%', 878 flexDirection: 'row', 879 alignItems: 'center', 880 paddingHorizontal: rpx(24), 881 }, 882}); 883