import { Image } from 'expo-image'; 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 { colors, 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?.(); }; return ( {/* The card glows with its album art; crossfades when the song changes. */} {np.track.artworkUrl && ( )} {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 + 2, gap: space.sm + 2, overflow: 'hidden', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, }, details: { gap: space.md }, pinned: { borderColor: 'rgba(251,191,36,0.55)' }, backdrop: { position: 'absolute', top: '-40%', left: '-40%', width: '180%', height: '180%', opacity: 0.55 }, 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, fontWeight: '700', fontSize: 15, flexShrink: 1 }, statusRow: { flexDirection: 'row', alignItems: 'center', gap: space.xs + 2, flexShrink: 1 }, status: { color: colors.muted, fontSize: 13, flexShrink: 1 }, live: { color: colors.live, fontWeight: '600' }, via: { color: colors.muted, fontWeight: '400' }, viaLink: { textDecorationLine: 'underline' }, pin: { color: colors.muted, fontSize: 21, marginLeft: space.xs }, pinOn: { color: colors.pin, textShadowColor: 'rgba(251,191,36,0.6)', textShadowRadius: 10 }, });