import { LinearGradient } from 'expo-linear-gradient'; import { router } from 'expo-router'; import { useEffect, useRef, useState } from 'react'; import { Linking, Pressable, StyleSheet, Text, View } from 'react-native'; import Animated, { FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withSequence, withSpring, } from 'react-native-reanimated'; import { SERVICE_LABELS, timeAgo, type NowPlayingDTO, type StreamingService } from '@musicshare/shared'; import { successHaptic, tapHaptic } from '@/lib/haptics'; import { OnHero, colors, fonts, heroGradient, panelShadow, radius, space } from '@/lib/theme'; import { Avatar } from './Avatar'; import { Equalizer } from './Equalizer'; import { Lyrics } from './Lyrics'; import { ProgressBar } from './ProgressBar'; import { Reactions } from './Reactions'; import { ServiceLinks, TrackRow } from './TrackCard'; export function NowPlayingCard({ np, preferred, showUser = true, pinned, onTogglePin, }: { np: NowPlayingDTO; preferred: StreamingService | null | undefined; showUser?: boolean; pinned?: boolean; onTogglePin?: () => void; }) { // Star "pop" when pinned. const star = useSharedValue(1); const wasPinned = useRef(pinned); useEffect(() => { if (pinned && !wasPinned.current) star.set(withSequence(withSpring(1.6, { damping: 6, stiffness: 400 }), withSpring(1))); wasPinned.current = pinned; }, [pinned, star]); const starStyle = useAnimatedStyle(() => ({ transform: [{ scale: star.value }] })); const [expanded, setExpanded] = useState(false); const toggle = () => { tapHaptic(); setExpanded((v) => !v); }; const togglePin = () => { if (pinned) tapHaptic(); else successHaptic(); onTogglePin?.(); }; // Your own song gets the gradient "hero" card; friends' songs sit on plain panels. const hero = !showUser; return ( {hero && } {showUser && ( router.push(`/u/${np.user.username}`)} accessibilityRole="link"> {np.user.displayName ?? np.user.username} )} {np.isPlaying && } {np.isPlaying ? 'Listening now' : `Played ${timeAgo(np.updatedAt)}`} Linking.openURL(np.sourceUrl!) : undefined}> {' · '} {np.sourceApp ?? SERVICE_LABELS[np.source]} {onTogglePin && ( {pinned ? '★' : '☆'} )} {expanded && ( )} ); } const styles = StyleSheet.create({ card: { borderRadius: radius.lg, padding: space.md + 4, gap: space.sm + 2, backgroundColor: colors.surface, borderWidth: 1, borderColor: 'transparent', ...panelShadow, }, heroCard: { overflow: 'hidden', shadowColor: colors.accent, shadowOpacity: 0.4, shadowRadius: 22 }, heroText: { color: 'rgba(255,255,255,0.85)' }, heroLive: { color: '#ffffff', fontFamily: fonts.bodyMedium }, details: { gap: space.md }, pinned: { borderColor: colors.accentLine }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: space.sm }, user: { flexDirection: 'row', alignItems: 'center', gap: space.sm, flexShrink: 1 }, name: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 14.5, flexShrink: 1 }, statusRow: { flexDirection: 'row', alignItems: 'center', gap: space.xs + 2, flexShrink: 1 }, status: { color: colors.muted, fontSize: 12.5, fontFamily: fonts.body, flexShrink: 1 }, live: { color: colors.live, fontFamily: fonts.bodyMedium }, via: { color: colors.muted, fontFamily: fonts.body }, viaLink: { textDecorationLine: 'underline' }, pin: { color: colors.muted, fontSize: 21, marginLeft: space.xs }, pinOn: { color: colors.accent, textShadowColor: 'rgba(255,79,139,0.6)', textShadowRadius: 10 }, });