diff --git a/src/app/components/CallBanner.svelte b/src/app/components/CallBanner.svelte index b732f801..856b7ac8 100644 --- a/src/app/components/CallBanner.svelte +++ b/src/app/components/CallBanner.svelte @@ -17,6 +17,7 @@ callState, callTargetRoom, callMicMuted, + cancelJoinVoiceRoom, leaveVoiceRoom, toggleMute, } from "@app/call" @@ -24,7 +25,7 @@ const {relay, h} = $derived($page.params) const routeUrl = $derived(relay ? decodeRelay(relay) : undefined) - // The call's own room page already shows full controls (VoiceWidget), so + // The call's own room page already shows full controls (CallControlBar), so // the banner would just be redundant clutter there. const isViewingCurrentVoiceRoom = $derived( $callTargetRoom !== undefined && @@ -49,6 +50,16 @@ if (!$callTargetRoom) return void goto(makeRoomPath($callTargetRoom.url, $callTargetRoom.h)) } + + // leaveVoiceRoom no-ops during Joining (no session exists yet to leave) — cancel + // the in-flight join instead, otherwise this button silently does nothing. + const endCall = () => { + if ($callState === CallState.Joining) { + cancelJoinVoiceRoom() + } else { + leaveVoiceRoom() + } + } {#if visible} @@ -87,9 +98,11 @@ {/if} diff --git a/src/app/components/CallControlBar.svelte b/src/app/components/CallControlBar.svelte new file mode 100644 index 00000000..df654cb2 --- /dev/null +++ b/src/app/components/CallControlBar.svelte @@ -0,0 +1,199 @@ + + +{#if joiningHere || connectedHere || showJoin} + +
+ {#if joiningHere} +
+ + +
+ {/if} + {#if connectedHere} +
+ + {#if $currentCallSession} + + {#if !Capacitor.isNativePlatform()} + + {/if} + {/if} + + + + +
+ {/if} + {#if showJoin} +
+ +
+ {/if} +
+{/if} diff --git a/src/app/components/RoomChat.svelte b/src/app/components/RoomChat.svelte index 86e2c9f9..68895549 100644 --- a/src/app/components/RoomChat.svelte +++ b/src/app/components/RoomChat.svelte @@ -14,7 +14,6 @@ import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl" import ClockCircle from "@assets/icons/clock-circle.svg?dataurl" import Login2 from "@assets/icons/login-3.svg?dataurl" - import ChatRound from "@assets/icons/chat-round.svg?dataurl" import Close from "@assets/icons/close.svg?dataurl" import {fade, fly} from "@lib/transition" import {documentActive} from "@lib/html" @@ -30,7 +29,7 @@ import RoomPinnedMessages from "@app/components/RoomPinnedMessages.svelte" import ThunkToast from "@app/components/ThunkToast.svelte" import VideoCallContent from "@app/components/VideoCallContent.svelte" - import VoiceWidget from "@app/components/VoiceWidget.svelte" + import CallControlBar from "@app/components/CallControlBar.svelte" import {deletes, relays, rooms, thunks, user} from "@app/core" import {publishRoomJoinRequest} from "@app/access" import {CallState, callTargetRoom, callState, VideoCallLayout, videoCallLayout} from "@app/call" @@ -44,7 +43,7 @@ } from "@app/rooms" import {userSettingsValues} from "@app/settings" import {makeFeed} from "@app/feeds" - import {checked, deferredRoomPath, notifications, setChecked} from "@app/notifications" + import {checked, deferredRoomPath, setChecked} from "@app/notifications" import {makeRoomPath} from "@app/routes" import {pendingShare, type Share} from "@app/share" import {pushToast} from "@app/toast" @@ -60,15 +59,17 @@ const addMemberKind = h ? ROOM_ADD_MEMBER : RELAY_ADD_MEMBER const isVoiceRoom = $derived($room && getRoomType($room) === RoomType.Voice) + const isCallTargetingThisRoom = $derived($callTargetRoom?.url === url && $callTargetRoom?.h === h) + const voiceConnectedHere = $derived( - isVoiceRoom && - $callState === CallState.Connected && - $callTargetRoom?.url === url && - $callTargetRoom?.h === h, + isVoiceRoom && $callState === CallState.Connected && isCallTargetingThisRoom, ) + // Reuses voiceConnectedHere (rather than re-deriving isVoiceRoom/callState) so it + // can't diverge and stay true for a different voice room the call isn't targeting + // — that previously hid this room's messages/compose row while connected elsewhere. const showMobileVideoPanel = $derived( - isVoiceRoom && $callState === CallState.Connected && $videoCallLayout === VideoCallLayout.Video, + voiceConnectedHere && $videoCallLayout === VideoCallLayout.Video, ) // Basic pass at issue #121's "chat overlaid on video" idea: float the chat panel over @@ -81,9 +82,7 @@ ) const roomPath = h ? makeRoomPath(url, h) : undefined - const chatUnread = $derived(roomPath !== undefined && $notifications.has(roomPath)) - const openChat = () => videoCallLayout.set(VideoCallLayout.Split) const closeChat = () => videoCallLayout.set(VideoCallLayout.Video) $effect(() => { @@ -503,7 +502,7 @@ )} /> {/if} - {#if h && isVoiceRoom && $callState === CallState.Connected} + {#if h && voiceConnectedHere}
{#if $room?.meta?.isPrivate() && $membershipStatus !== MembershipStatus.Granted} @@ -660,31 +663,13 @@ {/key} {/if}
- {#if isVoiceRoom || $callState === CallState.Joining || $callState === CallState.Connected} -
- + {#if h} +
+
{/if}
- - {#if videoCallChatHidden} - - {/if} {#if showScrollButton} diff --git a/src/app/components/RoomCompose.svelte b/src/app/components/RoomCompose.svelte index 385b6b3d..cfaf8587 100644 --- a/src/app/components/RoomCompose.svelte +++ b/src/app/components/RoomCompose.svelte @@ -107,7 +107,7 @@ }) -
+
- @@ -50,7 +48,6 @@
- diff --git a/src/app/components/VideoCallContent.svelte b/src/app/components/VideoCallContent.svelte index 310d2d0b..eb1ddf1b 100644 --- a/src/app/components/VideoCallContent.svelte +++ b/src/app/components/VideoCallContent.svelte @@ -7,7 +7,7 @@ import Icon from "@lib/components/Icon.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte" import VideoCallTile from "@app/components/VideoCallTile.svelte" - import VoiceWidget from "@app/components/VoiceWidget.svelte" + import CallControlBar from "@app/components/CallControlBar.svelte" import VoiceParticipantMediaBadges from "@app/components/VoiceParticipantMediaBadges.svelte" import { VideoCallLayout, @@ -150,10 +150,12 @@ }) const useSpotlightLayout = $derived(primaryTile !== undefined) - const useMultiGrid = $derived(!useSpotlightLayout && videoTiles.length > 2) - const multiGridClass = $derived( - layout === VideoCallLayout.Split ? "grid-cols-1" : "grid-cols-1 sm:grid-cols-2", - ) + // 2 tiles stacked full-width felt like they were "overlapping" each other rather + // than tiling — put 2+ side by side in a grid, same as everyone else's call UI. + // Chat (Split) floats over the video as an overlay card rather than shrinking + // this panel's width, so the tile grid doesn't need to collapse for it either. + const useMultiGrid = $derived(!useSpotlightLayout && videoTiles.length > 1) + const multiGridClass = "grid-cols-1 sm:grid-cols-2" $effect(() => { const k = $videoPrimaryTileKey @@ -198,11 +200,13 @@ {@const label = labelFor(tile.liveKitIdentity, tile.source)}
{#if tile.track} + class="flex min-h-[12rem] flex-1 flex-col items-center justify-center gap-2 rounded-2xl border border-line bg-surface-more p-4 text-center text-sm opacity-80">

Waiting for participants…

{/if} @@ -283,17 +287,24 @@ {#if showVideoContent}
- {#if mobile} -
-
- {@render videoPanelBody()} -
-
- -
-
- {:else} - {@render videoPanelBody()} - {/if} + {@render videoPanelBody()} +
+ +
+
{/if} diff --git a/src/app/components/VoiceWidget.svelte b/src/app/components/VoiceWidget.svelte deleted file mode 100644 index dfc81dfb..00000000 --- a/src/app/components/VoiceWidget.svelte +++ /dev/null @@ -1,230 +0,0 @@ - - -{#if targetRoom} -
- -
- {#if $callState === CallState.Joining} - - {:else if $callState === CallState.Connected && $currentCallSession} - - - {#if !Capacitor.isNativePlatform()} - - {/if} - - - {#if showChatButton} - - {/if} - {:else} - - {/if} -
-
-{/if}