import { useCallback } from 'react';
import { View, Text, StyleSheet, Pressable } from 'react-native';
import Animated, {
useSharedValue, useAnimatedStyle, withSpring, FadeInDown, FadeOutDown,
useAnimatedGestureHandler, runOnJS,
} from 'react-native-reanimated';
import { PanGestureHandler } from 'react-native-gesture-handler';
import { BlurView } from 'expo-blur';
import { Image } from 'expo-image';
import Svg, { Path } from 'react-native-svg';
import * as Haptics from 'expo-haptics';
import { Colors, Font, Radius, Spacing, TAB_BAR_HEIGHT, MINI_PLAYER_HEIGHT } from '../constants/theme';
import { usePlayer } from '../store';
import { useAudio } from '../hooks/useAudio';
import { coverUrl } from '../services/api';
export default function MiniPlayer() {
const { queue, index, playing, expanded, setExpanded } = usePlayer();
const { togglePlay } = useAudio();
const track = queue[index];
const insets = useSafeAreaInsets();
const translateY = useSharedValue(0);
const opacity = useSharedValue(1);
const gestureHandler = useAnimatedGestureHandler({
onActive: (e) => {
if (e.translationY < 0) {
translateY.value = Math.max(e.translationY, -80);
opacity.value = 1 + e.translationY / 80;
}
},
onEnd: (e) => {
if (e.translationY < -40 || e.velocityY < -400) {
runOnJS(openPlayer)();
}
translateY.value = withSpring(0);
opacity.value = withSpring(1);
},
});
const animStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
opacity: opacity.value,
}));
const openPlayer = useCallback(() => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setExpanded(true);
}, [setExpanded]);
const onPlayPress = useCallback(() => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
togglePlay();
}, [togglePlay]);
if (!track || expanded) return null;
const cover = coverUrl(track.cover_url);
return (
{/* Cover */}
{/* Info */}
{track.title}
{track.artist}
{/* Play/Pause */}
{playing ? : }
{/* Next */}
{
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
const next = usePlayer.getState().nextTrack();
usePlayer.getState().setIndex(next);
}}
hitSlop={12}
>
{/* Progress line */}
);
}
function ProgressLine() {
const { progress, duration } = usePlayer();
const pct = duration > 0 ? (progress / duration) * 100 : 0;
return (
);
}
function PlayIcon() { return ; }
function PauseIcon() { return ; }
function NextIcon() { return ; }
const styles = StyleSheet.create({
wrap: { position: 'absolute', left: 0, right: 0, height: MINI_PLAYER_HEIGHT, overflow: 'hidden' },
border: { position: 'absolute', top: 0, left: 0, right: 0, height: StyleSheet.hairlineWidth, backgroundColor: Colors.border },
row: { flex: 1, flexDirection: 'row', alignItems: 'center', paddingHorizontal: Spacing.md, gap: Spacing.md },
cover: { width: 42, height: 42, borderRadius: Radius.sm, backgroundColor: Colors.elevated },
info: { flex: 1 },
title: { color: Colors.text, fontSize: Font.sm, fontWeight: '600' },
artist: { color: Colors.text2, fontSize: Font.xs, marginTop: 2 },
btn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
progressBg: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, backgroundColor: Colors.border },
progressFill:{ height: 2, backgroundColor: Colors.green },
});