xref: /MusicFree/src/pages/setting/settingTypes/basicSetting.tsx (revision 41ddce918e1138d8f16e522cc7c19ac86ceca698)
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