import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Alert, Switch, ActivityIndicator, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Path } from 'react-native-svg';
import { theme } from '../theme';
import * as Cache from '../utils/cache';
const TrashIcon = () => (
);
const InfoIcon = () => (
);
const ChevronRight = () => (
);
interface SettingItemProps {
title: string;
subtitle?: string;
rightElement?: React.ReactNode;
onPress?: () => void;
showChevron?: boolean;
isDanger?: boolean;
}
const SettingItem: React.FC = ({
title,
subtitle,
rightElement,
onPress,
showChevron = false,
isDanger = false,
}) => (
{title}
{subtitle && (
{subtitle}
)}
{rightElement}
{showChevron && }
);
interface SettingsScreenProps {
cacheOnly: boolean;
resumePlaybackEnabled: boolean;
playlistFiles: string[];
currentPlaylistFile: string;
cachedTrackCount: number;
trackCount: number;
loggedInUser: string | null;
onCacheOnlyChange: (value: boolean) => Promise;
onResumePlaybackChange: (value: boolean) => Promise;
onCacheChanged: () => Promise | void;
onPlaylistChange: (fileName: string) => Promise;
onLoginPress: () => void;
onLogout: () => void;
}
export const SettingsScreen: React.FC = ({
cacheOnly,
resumePlaybackEnabled,
playlistFiles,
currentPlaylistFile,
cachedTrackCount,
trackCount,
loggedInUser,
onCacheOnlyChange,
onResumePlaybackChange,
onCacheChanged,
onPlaylistChange,
onLoginPress,
onLogout,
}) => {
const [cacheSize, setCacheSize] = useState('0 B');
const [isLoading, setIsLoading] = useState(false);
const [playlistExpanded, setPlaylistExpanded] = useState(false);
const [isSwitchingPlaylist, setIsSwitchingPlaylist] = useState(false);
const loadCacheSize = async () => {
try {
const size = await Cache.getCacheSize();
setCacheSize(Cache.formatSize(size));
} catch (error) {
console.error('Failed to get cache size:', error);
}
};
const toggleCacheOnly = async (value: boolean) => {
try {
await onCacheOnlyChange(value);
} catch (error) {
console.error('Failed to save setting:', error);
Alert.alert('错误', '保存设置失败');
}
};
const toggleResumePlayback = async (value: boolean) => {
try {
await onResumePlaybackChange(value);
} catch (error) {
console.error('Failed to save resume playback setting:', error);
Alert.alert('错误', '保存设置失败');
}
};
useEffect(() => {
loadCacheSize();
}, []);
const handlePlaylistPress = async (fileName: string) => {
if (fileName === currentPlaylistFile || isSwitchingPlaylist) {
setPlaylistExpanded(false);
return;
}
setIsSwitchingPlaylist(true);
try {
await onPlaylistChange(fileName);
setPlaylistExpanded(false);
} catch (error) {
console.error('切换歌单失败:', error);
} finally {
setIsSwitchingPlaylist(false);
}
};
const handleClearCache = () => {
Alert.alert(
'清除缓存',
'确定要清除所有缓存的音频文件吗?这不会删除歌曲列表。',
[
{ text: '取消', style: 'cancel' },
{
text: '清除',
style: 'destructive',
onPress: async () => {
setIsLoading(true);
try {
await Cache.clearAllCache();
await onCacheChanged();
await loadCacheSize();
Alert.alert('完成', '缓存已清除');
} catch (error) {
Alert.alert('错误', '清除缓存失败');
} finally {
setIsLoading(false);
}
},
},
]
);
};
const handleLogout = () => {
Alert.alert(
'退出登录',
'确定要退出登录吗?退出后云端配置将不再同步。',
[
{ text: '取消', style: 'cancel' },
{
text: '退出',
style: 'destructive',
onPress: () => {
onLogout();
Alert.alert('已退出', '已退出登录');
},
},
]
);
};
return (
设置
播放设置
setPlaylistExpanded((prev) => !prev)}
rightElement={
isSwitchingPlaylist
?
: {cachedTrackCount}/{trackCount} 已缓存
}
showChevron={!isSwitchingPlaylist}
/>
{playlistExpanded && (
{playlistFiles.map((fileName) => {
const isActive = fileName === currentPlaylistFile;
return (
{ void handlePlaylistPress(fileName); }}
disabled={isSwitchingPlaylist}
>
{fileName}
{isActive && 当前}
);
})}
自动读取所有以 playlist.json 结尾的歌单,并记住你上次选择。
)}
记住上次位置继续听
默认开启,下次打开时从上次暂停的位置继续播放
只用缓存听歌曲
节约流量,只播放已缓存的歌曲
缓存管理
}
/>
}
/>
账号
{loggedInUser ? (
<>
退出
}
/>
歌单选择、播放进度等配置已同步到云端,跨设备可用。
>
) : (
)}
关于
观空禅音
ZenVoid
这不是一款为了喧闹和效率而设计的播放器。它更像一处留白,让人愿意慢下来,听见一点安静。
“ZenVoid”和“观空”都不是标准答案,只是我们尽力靠近这种感受的一种表达。希望它陪你在日常里,留一点空,听一点静。
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
scrollContent: {
flex: 1,
},
scrollContentInner: {
paddingBottom: theme.spacing.xl,
},
header: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
},
headerTitle: {
fontSize: 18,
fontWeight: '600',
color: theme.colors.text,
},
section: {
marginTop: theme.spacing.md,
},
sectionTitle: {
fontSize: 13,
fontWeight: '600',
color: theme.colors.textSecondary,
textTransform: 'uppercase',
letterSpacing: 0.5,
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.xs,
},
settingItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: theme.spacing.md,
paddingHorizontal: theme.spacing.md,
backgroundColor: theme.colors.surface,
borderTopWidth: 1,
borderBottomWidth: 1,
borderColor: theme.colors.border,
},
settingItemLeft: {
flex: 1,
},
settingTitle: {
fontSize: 16,
color: theme.colors.text,
},
settingTitleDanger: {
color: theme.colors.error,
},
settingSubtitle: {
fontSize: 14,
color: theme.colors.textSecondary,
marginTop: 2,
},
settingMeta: {
fontSize: 12,
color: theme.colors.primary,
marginRight: theme.spacing.xs,
},
playlistOptions: {
marginTop: 1,
paddingHorizontal: theme.spacing.md,
paddingTop: theme.spacing.sm,
paddingBottom: theme.spacing.md,
backgroundColor: theme.colors.surface,
borderTopWidth: 1,
borderBottomWidth: 1,
borderColor: theme.colors.border,
gap: theme.spacing.xs,
},
playlistOption: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: theme.spacing.sm,
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.md,
backgroundColor: 'rgba(255,255,255,0.03)',
},
playlistOptionActive: {
borderWidth: 1,
borderColor: theme.colors.primary,
backgroundColor: theme.colors.primarySoft,
},
playlistOptionText: {
flex: 1,
fontSize: 14,
color: theme.colors.text,
},
playlistOptionTextActive: {
color: theme.colors.primary,
fontWeight: '600',
},
playlistOptionBadge: {
fontSize: 12,
color: theme.colors.primary,
},
playlistHint: {
marginTop: theme.spacing.xs,
fontSize: 12,
lineHeight: 18,
color: theme.colors.textSecondary,
},
aboutCard: {
marginHorizontal: theme.spacing.md,
marginBottom: theme.spacing.xs,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
backgroundColor: theme.colors.surface,
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.lg,
},
aboutTitle: {
fontSize: 18,
fontWeight: '600',
color: theme.colors.text,
},
aboutEnglish: {
marginTop: 4,
fontSize: 13,
letterSpacing: 0.8,
color: theme.colors.primary,
},
aboutBody: {
marginTop: theme.spacing.sm,
fontSize: 14,
lineHeight: 22,
color: theme.colors.textSecondary,
},
logoutBtn: {
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.sm,
borderWidth: 1,
borderColor: theme.colors.error,
},
logoutText: {
fontSize: 13,
color: theme.colors.error,
},
syncHint: {
fontSize: 12,
lineHeight: 18,
color: theme.colors.textSecondary,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.xs,
},
});