import React, { useMemo } from 'react'; import { useUIStore } from '../../stores/uiStore'; import { useSpaceStore } from '../../stores/spaceStore'; import { useChatStore } from '../../stores/chatStore'; import { useAuthStore } from '../../stores/authStore'; import { useSocialStore } from '../../stores/socialStore'; import { useContextMenuStore } from '../../stores/contextMenuStore'; import { Avatar } from '../ui/Avatar'; import { Mascot } from '../ui/Mascot'; import { resolveAssetUrl } from '../../utils/assetUrls'; import { useNavigate } from 'react-router-dom'; import { parseFederatedUsername } from '../../utils/identity'; export function MobileDmsScreen() { const pushMobileScreen = useUIStore((s) => s.pushMobileScreen); const openModal = useUIStore((s) => s.openModal); const dmChannels = useSpaceStore((s) => s.dmChannels); const readStates = useChatStore((s) => s.readStates); const authUser = useAuthStore((s) => s.user); const friends = useSocialStore((s) => s.friends); const navigate = useNavigate(); const openContextMenu = useContextMenuStore((s) => s.open); const setCurrentChannel = useChatStore((s) => s.setCurrentChannel); // Online friends for the activity row const onlineFriends = useMemo(() => friends.filter(f => f.status === 'online' || f.status === 'idle' || f.status === 'dnd'), [friends] ); // Sort DMs by last message time (newest first) const sortedDms = useMemo(() => [...dmChannels].sort((a, b) => { const aTime = a.lastMessage?.createdAt ?? a.createdAt; const bTime = b.lastMessage?.createdAt ?? b.createdAt; return bTime - aTime; }), [dmChannels] ); const handleDmTap = (dmId: string) => { navigate(`/channels/@me/${dmId}`); pushMobileScreen('channel-chat', { channelId: dmId, spaceId: '@me' }); }; const handleDmContextMenu = (e: React.MouseEvent, dmId: string, isGroup: boolean) => { if (!isGroup) return; e.preventDefault(); e.stopPropagation(); openContextMenu({ x: e.clientX, y: e.clientY }, [ { key: 'leave-group', type: 'action', label: 'Leave Group', danger: true, icon: , onClick: () => { const currentChId = useChatStore.getState().currentChannelId; if (currentChId === dmId) { navigate('/channels/@me'); setCurrentChannel(null); } useSpaceStore.getState().leaveDm(dmId); }, }, ]); }; const formatTimestamp = (ts: number): string => { const now = Date.now(); const diff = now - ts; if (diff < 60_000) return 'now'; if (diff < 3600_000) return `${Math.floor(diff / 60_000)}m`; if (diff < 86400_000) return `${Math.floor(diff / 3600_000)}h`; if (diff < 604800_000) return `${Math.floor(diff / 86400_000)}d`; return new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); }; return (
{/* Header */}

Messages

{/* Activity row — online friends */} {onlineFriends.length > 0 && (
{onlineFriends.map(friend => { const avatarUrl = friend.avatar ? resolveAssetUrl(friend.avatar, friend._instanceOrigin) ?? `/api/uploads/${friend.avatar}` : null; return ( ); })}
)} {/* DM list */}
{sortedDms.map(dm => { const otherMembers = dm.members.filter(m => m.id !== authUser?.id); const isGroup = dm.members.length > 2; const name = isGroup ? otherMembers.map(m => m.displayName ?? parseFederatedUsername(m.username).baseName).join(', ') : otherMembers[0]?.displayName ?? (parseFederatedUsername(otherMembers[0]?.username ?? '').baseName || 'Unknown'); const lastMsgId = dm.lastMessage?.id; const readState = readStates.get(dm.id); const isUnread = lastMsgId && (!readState || readState < lastMsgId); const preview = dm.lastMessage?.content; const previewTime = dm.lastMessage?.createdAt; const previewSender = dm.lastMessage ? dm.members.find(m => m.id === dm.lastMessage!.userId) : null; const mainUser = otherMembers[0]; const avatarUrl = mainUser?.avatar ? `/api/uploads/${mainUser.avatar}` : null; return ( ); })} {sortedDms.length === 0 && (

No conversations yet.

)}
{/* FAB — New DM */}
); }