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 (
);
};
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',
},
});