import React from 'react'; import { View, Text, TouchableOpacity, StyleSheet, PanResponder } from 'react-native'; import Svg, { Circle, Path, Polyline, Text as SvgText } from 'react-native-svg'; import { theme } from '../theme'; import { RepeatMode } from '../types'; interface PlayerControlsProps { isPlaying: boolean; isShuffle: boolean; repeatMode: RepeatMode; position: number; duration: number; onPlayPause: () => void; onNext: () => void; onPrev: () => void; onShuffle: () => void; onRepeat: () => void; onSeek: (position: number) => void; } const formatTime = (seconds: number): string => { if (isNaN(seconds) || seconds < 0) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; const PlayIcon = ({ size = 24 }: { size?: number }) => ( ); const PauseIcon = ({ size = 24 }: { size?: number }) => ( ); const NextIcon = ({ size = 22 }: { size?: number }) => ( ); const PrevIcon = ({ size = 22 }: { size?: number }) => ( ); const ShuffleIcon = ({ active = false }: { active?: boolean }) => ( ); const RepeatIcon = ({ mode }: { mode: RepeatMode }) => { const active = mode !== 'none'; const color = active ? theme.colors.primary : theme.colors.textSecondary; const badgeFill = mode === 'none' ? 'transparent' : theme.colors.primary; const badgeStroke = mode === 'none' ? 'transparent' : theme.colors.background; return ( {mode !== 'none' ? : null} {mode === 'one' ? ( 1 ) : null} {mode === 'all' ? ( <> ) : null} ); }; export const PlayerControls: React.FC = ({ isPlaying, isShuffle, repeatMode, position, duration, onPlayPause, onNext, onPrev, onShuffle, onRepeat, onSeek, }) => { const progress = duration > 0 ? position / duration : 0; const progressBarRef = React.useRef(null); const [isDragging, setIsDragging] = React.useState(false); const [dragProgress, setDragProgress] = React.useState(progress); const handleSeekTo = (newProgress: number) => { if (duration > 0) { onSeek(newProgress * duration); } }; const panResponder = React.useMemo( () => PanResponder.create({ onStartShouldSetPanResponder: () => true, onStartShouldSetPanResponderCapture: () => true, onMoveShouldSetPanResponder: () => true, onMoveShouldSetPanResponderCapture: () => true, onPanResponderGrant: () => { setIsDragging(true); }, onPanResponderMove: (evt) => { // 测量进度条宽度 progressBarRef.current?.measure((x, y, width) => { if (width > 0) { const newProgress = Math.max(0, Math.min(1, evt.nativeEvent.locationX / width)); setDragProgress(newProgress); } }); }, onPanResponderRelease: (evt) => { setIsDragging(false); progressBarRef.current?.measure((x, y, width) => { if (width > 0) { const newProgress = Math.max(0, Math.min(1, evt.nativeEvent.locationX / width)); handleSeekTo(newProgress); } }); }, onPanResponderTerminate: (evt) => { setIsDragging(false); progressBarRef.current?.measure((x, y, width) => { if (width > 0) { const newProgress = Math.max(0, Math.min(1, evt.nativeEvent.locationX / width)); handleSeekTo(newProgress); } }); }, }), [duration] ); const currentProgress = isDragging ? dragProgress : progress; const thumbPosition = currentProgress * 100; return ( {/* Progress bar */} {formatTime(position)} {formatTime(duration)} {/* Main controls */} onPrev()}> onPlayPause()}> {isPlaying ? : } onNext()}> onRepeat()}> ); }; const styles = StyleSheet.create({ container: { paddingHorizontal: theme.spacing.md, paddingBottom: theme.spacing.md, }, glassBar: { backgroundColor: 'rgba(10,10,30,0.65)', borderRadius: theme.borderRadius.xl, borderWidth: 1, borderColor: theme.colors.border, padding: theme.spacing.md, }, progressWrap: { marginBottom: theme.spacing.md, }, progressTrack: { height: 4, backgroundColor: 'rgba(255,255,255,0.10)', borderRadius: 2, position: 'relative', }, progressFill: { height: '100%', backgroundColor: theme.colors.primary, borderRadius: 2, }, progressThumb: { position: 'absolute', top: -5, width: 14, height: 14, borderRadius: 7, backgroundColor: theme.colors.primary, shadowColor: theme.colors.primary, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.5, shadowRadius: 4, elevation: 5, }, timeRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: theme.spacing.xs, }, timeText: { fontSize: 11, color: theme.colors.textSecondary, fontVariant: ['tabular-nums'], }, controls: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: theme.spacing.lg, }, ctrlBtn: { padding: theme.spacing.sm, }, playBtn: { width: 52, height: 52, borderRadius: 26, backgroundColor: theme.colors.primary, justifyContent: 'center', alignItems: 'center', }, });