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 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 @@
})
-