Feed: show everyone you follow
CI / check (push) Canceled after 0s

The feed only listed people who had played something, so following
someone who hadn't connected a service yet looked like it did nothing.
Under the feed, a "Following" row now shows everyone else you follow,
linking to their profiles, and the empty message says why it's empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
2026-10-06 15:25:41 -04:00
co-authored by Claude Opus 5.5
parent 601398acd6
commit 773e6f0e3f
3 changed files with 74 additions and 8 deletions
+38 -4
View File
@@ -1,11 +1,12 @@
import { router } from 'expo-router';
import { useEffect, useState } from 'react';
import { RefreshControl, StyleSheet, Text, View } from 'react-native';
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 ProfileDTO } from '@musicshare/shared';
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';
@@ -24,6 +25,10 @@ export default function FeedScreen() {
const { data: feed, mutate, isLoading } = useSWR<FeedItemDTO[]>('/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<ProfileDTO>(me ? `/users/${me.username}` : null);
// Everyone you follow; the feed only has the ones who've played something.
const { data: following, mutate: mutateFollowing } = useSWR<FollowListDTO>(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();
@@ -67,7 +72,7 @@ export default function FeedScreen() {
const refresh = async () => {
setRefreshing(true);
await Promise.all([mutate(), mutateMine()]).catch(() => {});
await Promise.all([mutate(), mutateMine(), mutateFollowing()]).catch(() => {});
setRefreshing(false);
};
@@ -115,11 +120,37 @@ export default function FeedScreen() {
ListEmptyComponent={
isLoading ? null : (
<Card>
<Body muted>Nothing yet. Follow some friends and their music will show up here live.</Body>
<Body muted>
{following?.users.length
? 'Nobody you follow has played anything yet. Their music will show up here live.'
: 'Nothing yet. Follow some friends and their music will show up here live.'}
</Body>
<Button small variant="primary" title="Find friends" onPress={() => router.push('/search')} style={{ alignSelf: 'flex-start' }} />
</Card>
)
}
ListFooterComponent={
quiet.length > 0 ? (
<View style={{ gap: space.sm, marginTop: space.md }}>
<Text style={styles.sectionLabel}>FOLLOWING · NOTHING PLAYED YET</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.quiet}>
{quiet.map((u) => (
<Pressable key={u.id} onPress={() => router.push(`/u/${u.username}`)} style={styles.quietPerson} accessibilityLabel={u.displayName ?? u.username}>
<Avatar user={u} size={52} />
<Text style={styles.quietName} numberOfLines={1}>
{u.displayName ?? u.username}
</Text>
</Pressable>
))}
{following?.nextCursor && me && (
<Pressable onPress={() => router.push(`/u/${me.username}/following`)} style={[styles.quietPerson, { justifyContent: 'center' }]}>
<Text style={[styles.quietName, { color: colors.muted }]}>See all</Text>
</Pressable>
)}
</ScrollView>
</View>
) : null
}
renderItem={({ item, index }) => (
<Animated.View entering={FadeInDown.delay(Math.min(index, 8) * 60).springify().damping(16)}>
<NowPlayingCard
@@ -140,4 +171,7 @@ const styles = StyleSheet.create({
list: { padding: space.lg, gap: space.md, paddingBottom: space.xl * 3 },
header: { gap: space.md, marginBottom: space.xs },
sectionLabel: { color: colors.muted, fontSize: 12, fontWeight: '700', letterSpacing: 0.8 },
quiet: { gap: space.lg, paddingVertical: space.xs },
quietPerson: { width: 64, alignItems: 'center', gap: 4 },
quietName: { color: colors.text, fontSize: 12, textAlign: 'center' },
});