import { router } from 'expo-router'; import { useEffect, useState } from 'react'; import { Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native'; import Animated, { FadeInDown, LinearTransition } from 'react-native-reanimated'; import { SafeAreaView } from 'react-native-safe-area-context'; import useSWR from 'swr'; import { sortFeed, type ConnectionDTO, type FeedItemDTO, type FollowListDTO, type ProfileDTO } from '@musicshare/shared'; import { AmbientBackground } from '@/components/AmbientBackground'; import { Avatar } from '@/components/Avatar'; import { NowPlayingCard } from '@/components/NowPlayingCard'; import { RelinkReminder } from '@/components/RelinkReminder'; import { SharingControls } from '@/components/SharingControls'; import { Body, Button, Card, Title } from '@/components/ui'; import { api } from '@/lib/api'; import { showError } from '@/lib/errors'; import { appleMusicAvailable } from '@/lib/appleMusic'; import { useReader } from '@/lib/nowPlayingReader'; import { shouldOfferSetup } from '@/lib/setup'; import { useRealtime } from '@/lib/useRealtime'; import { useSession } from '@/lib/session'; import { colors, space } from '@/lib/theme'; export default function FeedScreen() { const { me } = useSession(); const { data: feed, mutate, isLoading } = useSWR('/feed'); // Your own profile, for the "You" card. Loading it also marks you as watched, so your updates arrive quickly. const { data: mine, mutate: mutateMine } = useSWR(me ? `/users/${me.username}` : null); // Everyone you follow; the feed only has the ones who've played something. const { data: following, mutate: mutateFollowing } = useSWR(me ? `/users/${me.username}/following` : null); const inFeed = new Set(feed?.map((n) => n.user.id)); const quiet = following?.users.filter((u) => !inFeed.has(u.id)) ?? []; const [refreshing, setRefreshing] = useState(false); const reader = useReader(); const { data: connections } = useSWR(appleMusicAvailable ? '/connections' : null); const offerAppleMusic = appleMusicAvailable && connections !== undefined && connections.length === 0; // First run on Android: open the one-tap setup once (deferred so navigation has mounted). useEffect(() => { const timer = setTimeout(() => { shouldOfferSetup().then((offer) => offer && router.push('/share-setup')); }, 400); return () => clearTimeout(timer); }, []); useRealtime(true, (msg) => { if (msg.type !== 'now_playing') return; if (msg.data.user.id === me?.id) { if (mine) mutateMine({ ...mine, nowPlaying: msg.data }, { revalidate: false }); return; } mutate( (current = []) => { const existing = current.find((n) => n.user.id === msg.data.user.id); return sortFeed([{ ...msg.data, pinnedAt: existing?.pinnedAt ?? null }, ...current.filter((n) => n !== existing)]); }, { revalidate: false }, ); }); const togglePin = async (item: FeedItemDTO) => { const pinnedAt = item.pinnedAt ? null : new Date().toISOString(); const previous = feed; mutate(sortFeed((feed ?? []).map((n) => (n.user.id === item.user.id ? { ...n, pinnedAt } : n))), { revalidate: false }); try { await (pinnedAt ? api.pin(item.user.username) : api.unpin(item.user.username)); } catch (err) { mutate(previous, { revalidate: false }); showError(err, "Couldn't pin"); } }; const refresh = async () => { setRefreshing(true); await Promise.all([mutate(), mutateMine(), mutateFollowing()]).catch(() => {}); setRefreshing(false); }; return ( {/* Cards rise in on load and glide to new positions when someone pins or starts playing. */} item.user.id} contentContainerStyle={styles.list} refreshControl={} ListHeaderComponent={ {offerAppleMusic && ( Connect Apple Music so friends see what you play. One tap.