From acf6f6fe99c465bd266a4eb4b9e78e543251fb4a Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:55:51 +0100 Subject: [PATCH] refactor: align MobileVoiceFullScreen voice state resolution Brings mute/deafen state resolution in line with VoiceChannel.tsx: - Use LiveKit participant state (then WS fallback) for mute/deafen instead of only voiceUserStates - Use local isMuted/isDeafened store values for the current user (self) - Add spaceMutedUserIds, spaceDeafenedUserIds, permissionMutedUserIds checks - Render deafened indicator (headphones-slash) when user is deafened vs muted - Add LiveKit participant username and userId as terminal fallbacks in display name chain --- .../layout/MobileVoiceFullScreen.tsx | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/web/src/components/layout/MobileVoiceFullScreen.tsx b/packages/web/src/components/layout/MobileVoiceFullScreen.tsx index a5f4f950..898afcc3 100644 --- a/packages/web/src/components/layout/MobileVoiceFullScreen.tsx +++ b/packages/web/src/components/layout/MobileVoiceFullScreen.tsx @@ -23,6 +23,10 @@ export function MobileVoiceFullScreen() { const leaveVoice = useVoiceStore((s) => s.leaveVoice); const voiceUsers = useVoiceStore((s) => s.voiceUsers); const voiceUserStates = useVoiceStore((s) => s.voiceUserStates); + const participants = useVoiceStore((s) => s.participants); + const spaceMutedUserIds = useVoiceStore((s) => s.spaceMutedUserIds); + const spaceDeafenedUserIds = useVoiceStore((s) => s.spaceDeafenedUserIds); + const permissionMutedUserIds = useVoiceStore((s) => s.permissionMutedUserIds); const channels = useSpaceStore((s) => s.channels); const dmChannels = useSpaceStore((s) => s.dmChannels); @@ -67,7 +71,7 @@ export function MobileVoiceFullScreen() { const member = members.find(m => m.userId === userId); if (member) { return { - name: member.nickname ?? member.user?.displayName ?? member.user?.username ?? 'User', + name: member.nickname ?? member.user?.displayName ?? member.user?.username ?? userId, avatar: member.user?.avatar ? `/api/uploads/${member.user.avatar}` : null, avatarColor: member.user?.avatarColor ?? null, }; @@ -83,7 +87,12 @@ export function MobileVoiceFullScreen() { }; } } - return { name: 'User', avatar: null, avatarColor: null }; + // Fall back to LiveKit participant metadata + const participant = participants.find(p => p.userId === userId); + if (participant?.username) { + return { name: participant.username, avatar: null, avatarColor: null }; + } + return { name: userId, avatar: null, avatarColor: null }; }; const handleDisconnect = () => { @@ -159,9 +168,31 @@ export function MobileVoiceFullScreen() { }`}> {participantIds.map(userId => { const info = getParticipantInfo(userId); - const state = voiceUserStates.get(userId); + const wsStatus = voiceUserStates.get(userId); + const participant = participants.find(p => p.userId === userId); const isMe = userId === authUser?.id; + // Resolve mute/deafen: local state for self, LiveKit participant then WS fallback for others + const isUserMuted = isMe + ? isMuted + : (participant?.isMuted ?? wsStatus?.isMuted ?? false); + const isUserDeafened = isMe + ? isDeafened + : (participant?.isDeafened ?? wsStatus?.isDeafened ?? false); + + // Server-enforced states + const spaceId = !isDmCall && currentVoiceChannelId + ? channelToSpaceMap.get(currentVoiceChannelId) + : undefined; + const isSpaceMuted = spaceId ? spaceMutedUserIds.has(`${spaceId}:${userId}`) : false; + const isSpaceDeafened = spaceId ? spaceDeafenedUserIds.has(`${spaceId}:${userId}`) : false; + const isPermissionMuted = spaceId ? permissionMutedUserIds.has(`${spaceId}:${userId}`) : false; + + // Any muted indicator: self-mute, server mute, or permission mute + const showMuted = isUserMuted || isSpaceMuted || isPermissionMuted; + // Any deafened indicator: self-deafen or server deafen + const showDeafened = isUserDeafened || isSpaceDeafened; + return (
- {state?.isMuted && ( + {(showMuted || showDeafened) && (