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 * as Haptics from 'expo-haptics'; import Svg, { Path } from 'react-native-svg'; import { Colors, Font, MINI_PLAYER_HEIGHT, TAB_BAR_HEIGHT } from '../constants/theme'; import { usePlayer, useLikes } from '../store'; import { togglePlay, seekTo } from '../hooks/useAudio'; import { coverUrl } from '../services/api'; export default function MiniPlayer() { const { queue, index, playing, expanded, setExpanded, progress, duration } = usePlayer(); const { toggleLike, isLiked } = useLikes(); const track = queue[index]; 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 onNext = useCallback(() => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); const ni = usePlayer.getState().nextTrack(); usePlayer.getState().setIndex(ni); }, []); const onPrev = useCallback(() => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); const pi = usePlayer.getState().prevTrack(); usePlayer.getState().setIndex(pi); }, []); if (!track || expanded) return null; const cover = coverUrl(track.cover_url); const liked = isLiked(track.id); const pct = duration > 0 ? (progress / duration) * 100 : 0; return ( {/* Progress line */} {track.title} {track.artist} { e.stopPropagation(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); toggleLike(track.id); }} hitSlop={10} style={s.ctlBtn}> { e.stopPropagation(); onPrev(); }} hitSlop={10} style={s.ctlBtn}> { e.stopPropagation(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); togglePlay(); }} hitSlop={10} style={s.ctlBtn}> {playing ? : } { e.stopPropagation(); onNext(); }} hitSlop={10} style={s.ctlBtn}> ); } const s = StyleSheet.create({ wrap: { position: 'absolute', left: 0, right: 0, height: MINI_PLAYER_HEIGHT, overflow: 'hidden', borderTopLeftRadius: 14, borderTopRightRadius: 14 }, bgTint: { backgroundColor: 'rgba(10,10,10,0.4)' }, progressBg: { position: 'absolute', top: 0, left: 0, right: 0, height: 2, backgroundColor: 'rgba(255,255,255,0.07)', zIndex: 2 }, progressFill:{ height: 2, backgroundColor: Colors.accent }, row: { flex: 1, flexDirection: 'row', alignItems: 'center', paddingLeft: 14, paddingRight: 4, gap: 12 }, cover: { width: 44, height: 44, borderRadius: 9, backgroundColor: Colors.elevated, flexShrink: 0 }, info: { flex: 1, minWidth: 0 }, title: { color: Colors.text, fontSize: Font.md, fontWeight: '600' }, artist: { color: Colors.text2, fontSize: Font.xs, marginTop: 2 }, controls: { flexDirection: 'row', alignItems: 'center' }, ctlBtn: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' }, });