From 2c372a5d34c09b61b025d09b04c60c840b1c9a2e Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:56:07 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20three=20mobile=20bugs=20=E2=80=94=20scro?= =?UTF-8?q?ll,=20GeneralPanel=20loading,=20activity=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Chat scroll: add flex flex-col to MobileChatScreen messages wrapper so MessageList's flex-1 actually works (parent must be flex container) 2. Instance General panel: add fetchInstanceSettings/fetchStreamingLimits to MobileInstancePanel mount (mirrors desktop InstancePanel useEffect) 3. Friends activity: add Activity tab to mobile FriendsPage showing friends grouped by activity status (active, online, offline) --- .../web/src/components/chat/FriendsPage.tsx | 124 +++++++++++++++++- .../components/layout/MobileChatScreen.tsx | 2 +- .../components/layout/MobileInstancePanel.tsx | 12 +- 3 files changed, 133 insertions(+), 5 deletions(-) diff --git a/packages/web/src/components/chat/FriendsPage.tsx b/packages/web/src/components/chat/FriendsPage.tsx index 5ce7a8a8..df9cc55f 100644 --- a/packages/web/src/components/chat/FriendsPage.tsx +++ b/packages/web/src/components/chat/FriendsPage.tsx @@ -11,8 +11,13 @@ import { LoadingSpinner } from '../ui/LoadingSpinner'; import { getAvatarGradient } from '../../utils/gradients'; import { api } from '../../api/client'; import { Mascot } from '../ui/Mascot'; +import { useActivityStore } from '../../stores/activityStore'; +import { ActivityCard, hasRichActivity, getActivityAccentClass } from '../ui/ActivityCard'; +import { getPrimaryActivity } from '@backspace/shared/src/activities.js'; +import { parseFederatedUsername } from '../../utils/identity'; +import { Username } from '../ui/Username'; -type Tab = 'online' | 'all' | 'pending' | 'add'; +type Tab = 'online' | 'all' | 'pending' | 'add' | 'activity'; interface FriendsPageProps { mobile?: boolean; @@ -42,6 +47,9 @@ export function FriendsPage({ mobile }: FriendsPageProps) { loadRequests(); }, [loadFriends, loadRequests]); + const userActivities = useActivityStore((s) => s.userActivities); + const pushMobileScreen = useUIStore((s) => s.pushMobileScreen); + const onlineFriends = friends.filter(f => f.status !== 'offline'); const pendingIncoming = requests.filter(r => r.status === 'pending' && r.user?.id === r.fromId); const pendingOutgoing = requests.filter(r => r.status === 'pending' && r.user?.id === r.toId); @@ -172,6 +180,116 @@ export function FriendsPage({ mobile }: FriendsPageProps) { onOpenDm={handleOpenDm} /> ); + case 'activity': { + const activeFriends: TaggedFriend[] = []; + const idleFriends: TaggedFriend[] = []; + const offlineActivityFriends: TaggedFriend[] = []; + for (const f of friends) { + if (f.status === 'offline') { + offlineActivityFriends.push(f); + continue; + } + const acts = userActivities.get(f.homeUserId ?? f.id) ?? []; + const primary = getPrimaryActivity(acts); + if (primary && primary.type !== 'custom') { + activeFriends.push(f); + } else { + idleFriends.push(f); + } + } + + const renderActivityFriend = (friend: TaggedFriend, isOffline = false) => { + const { baseName, domain } = parseFederatedUsername(friend.username); + const friendDisplayName = friend.displayName ?? baseName; + const activities = userActivities.get(friend.homeUserId ?? friend.id) ?? []; + const isRichActivity = !isOffline && hasRichActivity(activities); + const primary = getPrimaryActivity(activities); + const accentClass = primary ? getActivityAccentClass(primary.type) : ''; + + const rowClass = isRichActivity + ? `flex items-center gap-3 px-4 py-2.5 rounded-[10px] mb-1 cursor-pointer transition-colors glass-pill border-l-2 ${accentClass}` + : 'flex items-center gap-3 px-4 py-2.5 rounded-[4px] hover:bg-interactive-hover cursor-pointer transition-colors active:bg-interactive-hover'; + + return ( +
{ + if (mobile) { + pushMobileScreen('user-profile', { userId: friend.id }); + } else { + handleOpenDm(friend.id, friend._instanceOrigin ?? '', friend.homeUserId ?? undefined); + } + }} + className={rowClass} + > + +
+ + {domain && !isOffline && ( +
@{domain}
+ )} + {!isOffline && ( + + )} +
+
+ ); + }; + + return ( +
+ {activeFriends.length === 0 && idleFriends.length === 0 && offlineActivityFriends.length === 0 ? ( +
+ +
+ It's quiet for now... When friends start an activity, we'll show it here! +
+
+ ) : ( + <> + {activeFriends.length > 0 && ( +
+

+ Active — {activeFriends.length} +

+ {activeFriends.map(f => renderActivityFriend(f))} +
+ )} + {idleFriends.length > 0 && ( +
+

+ Online — {idleFriends.length} +

+ {idleFriends.map(f => renderActivityFriend(f))} +
+ )} + {offlineActivityFriends.length > 0 && ( +
+

+ Offline — {offlineActivityFriends.length} +

+ {offlineActivityFriends.map(f => renderActivityFriend(f, true))} +
+ )} + + )} +
+ ); + } } }; @@ -227,7 +345,7 @@ export function FriendsPage({ mobile }: FriendsPageProps) { {/* Mobile tab bar */} {mobile && (
- {(['online', 'all', 'pending', 'add'] as Tab[]).map((tab) => ( + {(['online', 'all', 'pending', 'add', 'activity'] as Tab[]).map((tab) => (