diff --git a/playwright/e2e/voip/element-call.spec.ts b/playwright/e2e/voip/element-call.spec.ts index e52d781ac7..fe73e0bff4 100644 --- a/playwright/e2e/voip/element-call.spec.ts +++ b/playwright/e2e/voip/element-call.spec.ts @@ -12,7 +12,7 @@ import type { EventType, Preset } from "matrix-js-sdk/src/matrix"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import { test, expect } from "../../element-web-test"; import type { Credentials } from "../../plugins/homeserver"; -import { Bot } from "../../pages/bot"; +import type { Bot } from "../../pages/bot"; // Load a copy of our fake Element Call app, and the latest widget API. // The fake call app does *just* enough to convince Element Web that a call is ongoing @@ -37,28 +37,27 @@ function assertCommonCallParameters( expect(hash.get("preload")).toEqual("false"); } -async function sendRTCState(bot: Bot, roomId: string, notification?: "ring" | "notification", intent?: string) { +async function sendRTCState(bot: Bot, roomId: string, notification?: "ring" | "notification") { const resp = await bot.sendStateEvent( roomId, "org.matrix.msc3401.call.member", { - "application": "m.call", - "call_id": "", - "m.call.intent": intent, - "device_id": "OiDFxsZrjz", - "expires": 180000000, - "foci_preferred": [ + application: "m.call", + call_id: "", + device_id: "OiDFxsZrjz", + expires: 180000000, + foci_preferred: [ { livekit_alias: roomId, livekit_service_url: "https://example.org", type: "livekit", }, ], - "focus_active": { + focus_active: { focus_selection: "oldest_membership", type: "livekit", }, - "scope": "m.room", + scope: "m.room", }, `_@${bot.credentials.userId}_OiDFxsZrjz_m.call`, ); @@ -75,7 +74,6 @@ async function sendRTCState(bot: Bot, roomId: string, notification?: "ring" | "n event_id: resp.event_id, rel_type: "org.matrix.msc4075.rtc.notification.parent", }, - "m.call.intent": intent, "notification_type": notification, "sender_ts": 1758611895996, }); @@ -117,21 +115,15 @@ test.describe("Element Call", () => { }); test.describe("Group Chat", () => { - let charlie: Bot; test.use({ - room: async ({ page, app, user, homeserver, bot }, use) => { - charlie = new Bot(page, homeserver, { displayName: "Charlie" }); - await charlie.prepareClient(); - const roomId = await app.client.createRoom({ - name: "TestRoom", - invite: [bot.credentials.userId, charlie.credentials.userId], - }); + room: async ({ page, app, user, bot }, use) => { + const roomId = await app.client.createRoom({ name: "TestRoom", invite: [bot.credentials.userId] }); await use({ roomId }); }, }); test("should be able to start a video call", async ({ page, user, room, app }) => { await app.viewRoomById(room.roomId); - await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); + await expect(page.getByText("Bob joined the room")).toBeVisible(); await page.getByRole("button", { name: "Video call" }).click(); await page.getByRole("menuitem", { name: "Element Call" }).click(); @@ -146,16 +138,9 @@ test.describe("Element Call", () => { expect(hash.get("skipLobby")).toEqual(null); }); - test("should NOT be able to start a voice call", async ({ page, user, room, app }) => { - // Voice calls do not exist in group rooms - await app.viewRoomById(room.roomId); - await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); - await expect(page.getByRole("button", { name: "Voice call" })).not.toBeVisible(); - }); - test("should be able to skip lobby by holding down shift", async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); - await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); + await expect(page.getByText("Bob joined the room")).toBeVisible(); await page.getByRole("button", { name: "Video call" }).click(); await page.keyboard.down("Shift"); @@ -174,8 +159,8 @@ test.describe("Element Call", () => { test("should be able to join a call in progress", async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); // Allow bob to create a call - await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50); + await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call await sendRTCState(bot, room.roomId); const button = page.getByTestId("join-call-button"); @@ -183,6 +168,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); + console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -194,29 +180,29 @@ test.describe("Element Call", () => { [true, false].forEach((skipLobbyToggle) => { test( - `should be able to join a call via incoming video call toast (skipLobby=${skipLobbyToggle})`, + `should be able to join a call via incoming call toast (skipLobby=${skipLobbyToggle})`, { tag: ["@screenshot"] }, async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); // Allow bob to create a call - await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50); + await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call - await sendRTCState(bot, room.roomId, "notification", "video"); + await sendRTCState(bot, room.roomId, "notification"); const toast = page.locator(".mx_Toast_toast"); const button = toast.getByRole("button", { name: "Join" }); - if (skipLobbyToggle) { await toast.getByRole("switch").check(); - await expect(toast).toMatchScreenshot(`incoming-call-group-video-toast-checked.png`); + await expect(toast).toMatchScreenshot("incoming-call-group-video-toast-checked.png"); } else { await toast.getByRole("switch").uncheck(); - await expect(toast).toMatchScreenshot(`incoming-call-group-video-toast-unchecked.png`); + await expect(toast).toMatchScreenshot("incoming-call-group-video-toast-unchecked.png"); } // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); + console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -227,34 +213,6 @@ test.describe("Element Call", () => { }, ); }); - - test( - `should be able to join a call via incoming voice call toast`, - { tag: ["@screenshot"] }, - async ({ page, user, bot, room, app }) => { - await app.viewRoomById(room.roomId); - // Allow bob to create a call - await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); - await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50); - // Fake a start of a call - await sendRTCState(bot, room.roomId, "notification", "audio"); - const toast = page.locator(".mx_Toast_toast"); - const button = toast.getByRole("button", { name: "Join" }); - - await expect(toast).toMatchScreenshot(`incoming-call-group-voice-toast.png`); - - // And test joining - await button.click(); - const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); - const url = new URL(frameUrlStr); - const hash = new URLSearchParams(url.hash.slice(1)); - assertCommonCallParameters(url.searchParams, hash, user, room); - - expect(hash.get("intent")).toEqual("join_existing"); - expect(hash.get("skipLobby")).toEqual("true"); - }, - ); }); test.describe("DMs", () => { @@ -307,6 +265,7 @@ test.describe("Element Call", () => { test("should be able to join a call in progress", async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); + // Allow bob to create a call await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call await sendRTCState(bot, room.roomId); @@ -315,6 +274,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); + console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -330,31 +290,24 @@ test.describe("Element Call", () => { { tag: ["@screenshot"] }, async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); + // Allow bob to create a call await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call - await sendRTCState(bot, room.roomId, "ring", "video"); + await sendRTCState(bot, room.roomId, "ring"); const toast = page.locator(".mx_Toast_toast"); - const button = toast.getByRole("button", { name: "Accept" }); + const button = toast.getByRole("button", { name: "Join" }); if (skipLobbyToggle) { await toast.getByRole("switch").check(); + await expect(toast).toMatchScreenshot("incoming-call-dm-video-toast-checked.png"); } else { await toast.getByRole("switch").uncheck(); + await expect(toast).toMatchScreenshot("incoming-call-dm-video-toast-unchecked.png"); } - await expect(toast).toMatchScreenshot( - `incoming-call-dm-video-toast-${skipLobbyToggle ? "checked" : "unchecked"}.png`, - { - // Hide UserId - css: ` - .mx_IncomingCallToast_AvatarWithDetails span:nth-child(2) { - opacity: 0; - } - `, - }, - ); // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); + console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -365,39 +318,6 @@ test.describe("Element Call", () => { }, ); }); - - test( - `should be able to join a call via incoming voice call toast`, - { tag: ["@screenshot"] }, - async ({ page, user, bot, room, app }) => { - await app.viewRoomById(room.roomId); - await expect(page.getByText("Bob joined the room")).toBeVisible(); - // Fake a start of a call - await sendRTCState(bot, room.roomId, "ring", "audio"); - const toast = page.locator(".mx_Toast_toast"); - const button = toast.getByRole("button", { name: "Accept" }); - - await expect(toast).toMatchScreenshot(`incoming-call-dm-voice-toast.png`, { - // Hide UserId - css: ` - .mx_IncomingCallToast_AvatarWithDetails span:nth-child(2) { - opacity: 0; - } - `, - }); - - // And test joining - await button.click(); - const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); - const url = new URL(frameUrlStr); - const hash = new URLSearchParams(url.hash.slice(1)); - assertCommonCallParameters(url.searchParams, hash, user, room); - - expect(hash.get("intent")).toEqual("join_existing_dm_voice"); - expect(hash.get("skipLobby")).toEqual("true"); - }, - ); }); test.describe("Video Rooms", () => { diff --git a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png b/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png index b0fd216c56..f309c37f3a 100644 Binary files a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png and b/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png differ diff --git a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png b/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png index a7b7aea8a9..6d75547fcd 100644 Binary files a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png and b/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-unchecked-linux.png differ diff --git a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png b/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png deleted file mode 100644 index 34ddc1b02f..0000000000 Binary files a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png and /dev/null differ diff --git a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png b/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png deleted file mode 100644 index 29cf843d7b..0000000000 Binary files a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png and /dev/null differ diff --git a/res/css/views/rooms/_LiveContentSummary.pcss b/res/css/views/rooms/_LiveContentSummary.pcss index 04704c6a1a..e7e8bdb19e 100644 --- a/res/css/views/rooms/_LiveContentSummary.pcss +++ b/res/css/views/rooms/_LiveContentSummary.pcss @@ -25,10 +25,6 @@ Please see LICENSE files in the repository root for full details. mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg"); } - &.mx_LiveContentSummary_text_voice::before { - mask-image: url("@vector-im/compound-design-tokens/icons/voice-call-solid.svg"); - } - &.mx_LiveContentSummary_text_active { color: $accent; diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx index 30576e2dc2..107d1a4c60 100644 --- a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx @@ -7,7 +7,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix"; -import { CallType } from "matrix-js-sdk/src/webrtc/call"; import dispatcher from "../../../dispatcher/dispatcher"; import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; @@ -20,7 +19,7 @@ import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useEventEmitter, useEventEmitterState, useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import { DefaultTagID } from "../../../stores/room-list/models"; import { useCall, useConnectionState, useParticipantCount } from "../../../hooks/useCall"; -import { CallEvent, type ConnectionState } from "../../../models/Call"; +import { type ConnectionState } from "../../../models/Call"; import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; import DMRoomMap from "../../../utils/DMRoomMap"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; @@ -68,10 +67,6 @@ export interface RoomListItemViewState { * Whether there are participants in the call. */ hasParticipantInCall: boolean; - /** - * Whether the call is a voice or video call. - */ - callType: CallType | undefined; /** * Pre-rendered and translated preview for the latest message in the room, or undefined * if no preview should be shown. @@ -128,10 +123,10 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { // EC video call or video room const call = useCall(room.roomId); const connectionState = useConnectionState(call); - const participantCount = useParticipantCount(call); + const hasParticipantInCall = useParticipantCount(call) > 0; const callConnectionState = call ? connectionState : null; - const showNotificationDecoration = hasVisibleNotification || participantCount > 0; + const showNotificationDecoration = hasVisibleNotification || hasParticipantInCall; // Actions @@ -143,9 +138,6 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { }); }, [room]); - const [callType, setCallType] = useState(CallType.Video); - useTypedEventEmitter(call ?? undefined, CallEvent.CallTypeChanged, setCallType); - return { name, notificationState, @@ -156,10 +148,9 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { isBold, isVideoRoom, callConnectionState, - hasParticipantInCall: participantCount > 0, + hasParticipantInCall, messagePreview, showNotificationDecoration, - callType: call ? callType : undefined, }; } diff --git a/src/components/views/rooms/LiveContentSummary.tsx b/src/components/views/rooms/LiveContentSummary.tsx index ada3209f07..1f5d36e8d0 100644 --- a/src/components/views/rooms/LiveContentSummary.tsx +++ b/src/components/views/rooms/LiveContentSummary.tsx @@ -10,10 +10,12 @@ import React, { type FC } from "react"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; +import { type Call } from "../../../models/Call"; +import { useParticipantCount } from "../../../hooks/useCall"; export enum LiveContentType { Video, - Voice, + // More coming soon } interface Props { @@ -31,7 +33,6 @@ export const LiveContentSummary: FC = ({ type, text, active, participantC @@ -50,3 +51,16 @@ export const LiveContentSummary: FC = ({ type, text, active, participantC )} ); + +interface LiveContentSummaryWithCallProps { + call: Call; +} + +export const LiveContentSummaryWithCall: FC = ({ call }) => ( + +); diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx index 321625adb1..5dda3b0453 100644 --- a/src/components/views/rooms/NotificationDecoration.tsx +++ b/src/components/views/rooms/NotificationDecoration.tsx @@ -12,8 +12,6 @@ import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/ic import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid"; import { UnreadCounter, Unread } from "@vector-im/compound-web"; -import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call-solid"; -import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { Flex } from "@element-hq/web-shared-components"; import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState"; @@ -26,9 +24,9 @@ interface NotificationDecorationProps extends HTMLProps { */ notificationState: RoomNotificationState; /** - * Whether the room has a voice or video call. + * Whether the room has a video call. */ - callType?: CallType; + hasVideoCall: boolean; } /** @@ -36,7 +34,7 @@ interface NotificationDecorationProps extends HTMLProps { */ export function NotificationDecoration({ notificationState, - callType, + hasVideoCall, ...props }: NotificationDecorationProps): JSX.Element | null { // Listen to the notification state and update the component when it changes @@ -60,7 +58,7 @@ export function NotificationDecoration({ muted: notificationState.muted, })); - if (!hasAnyNotificationOrActivity && !muted && !callType) return null; + if (!hasAnyNotificationOrActivity && !muted && !hasVideoCall) return null; return ( {isUnsentMessage && } - {callType === CallType.Video && ( - - )} - {callType === CallType.Voice && ( - - )} + {hasVideoCall && } {invited && } {isMention && } {(isMention || isNotification) && } diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index 5db97397dc..ad25856d13 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -131,7 +131,7 @@ export const RoomListItemView = memo(function RoomListItemView({ )} diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 50f9c3497a..b37ef1b347 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -121,7 +121,7 @@ export enum Action { UpdateSystemFont = "update_system_font", /** - * Changes room based on payload parameters. Should be used with ViewRoomPayload. + * Changes room based on payload parameters. Should be used with JoinRoomPayload. */ ViewRoom = "view_room", diff --git a/src/dispatcher/payloads/ViewRoomPayload.ts b/src/dispatcher/payloads/ViewRoomPayload.ts index c1dba33feb..525dd50d62 100644 --- a/src/dispatcher/payloads/ViewRoomPayload.ts +++ b/src/dispatcher/payloads/ViewRoomPayload.ts @@ -39,7 +39,6 @@ interface BaseViewRoomPayload extends Pick { clear_search?: boolean; // Whether to clear the room list search view_call?: boolean; // Whether to view the call or call lobby for the room skipLobby?: boolean; // Whether to skip the call lobby when showing the call (only supported for element calls) - voiceOnly?: boolean; // Whether the call is voice only (only supported for element calls) opts?: JoinRoomPayload["opts"]; deferred_action?: ActionPayload; // Action to fire after MatrixChat handles this ViewRoom action diff --git a/src/hooks/room/useRoomCall.tsx b/src/hooks/room/useRoomCall.tsx index 97b30dea0d..9f72430492 100644 --- a/src/hooks/room/useRoomCall.tsx +++ b/src/hooks/room/useRoomCall.tsx @@ -142,6 +142,11 @@ export const useRoomCall = ( // If there are multiple options, the user will be prompted to choose. const callOptions = useMemo((): PlatformCallType[] => { const options: PlatformCallType[] = []; + if (memberCount <= 2) { + options.push(PlatformCallType.LegacyCall); + } else if (mayEditWidgets || hasJitsiWidget) { + options.push(PlatformCallType.JitsiCall); + } if (groupCallsEnabled) { if (hasGroupCall || mayCreateElementCalls) { options.push(PlatformCallType.ElementCall); @@ -150,11 +155,6 @@ export const useRoomCall = ( return [PlatformCallType.ElementCall]; } } - if (memberCount <= 2) { - options.push(PlatformCallType.LegacyCall); - } else if (mayEditWidgets || hasJitsiWidget) { - options.push(PlatformCallType.JitsiCall); - } if (hasGroupCall && WidgetType.CALL.matches(groupCall.widget.type)) { // only allow joining the ongoing Element call if there is one. return [PlatformCallType.ElementCall]; @@ -231,7 +231,7 @@ export const useRoomCall = ( if (widget && promptPinWidget) { WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top); } else { - placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined, true); + placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined); } }, [promptPinWidget, room, widget], @@ -244,7 +244,7 @@ export const useRoomCall = ( } else { // If we have pressed shift then always skip the lobby, otherwise `undefined` will defer // to the defaults of the call implementation. - placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined, false); + placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined); } }, [widget, promptPinWidget, room], @@ -279,13 +279,7 @@ export const useRoomCall = ( const roomDoesNotExist = room instanceof LocalRoom && room.state !== LocalRoomState.CREATED; // We hide the voice call button if it'd have the same effect as the video call button - let hideVoiceCallButton = - isManagedHybridWidgetEnabled(room) || - // Disable voice calls if Legacy calls are disabled - (!callOptions.includes(PlatformCallType.LegacyCall) && - // Disable voice calls in ECall if the room is a group (we only present video calls for groups of users) - (!callOptions.includes(PlatformCallType.ElementCall) || memberCount > 2)); - + let hideVoiceCallButton = isManagedHybridWidgetEnabled(room) || !callOptions.includes(PlatformCallType.LegacyCall); let hideVideoCallButton = false; // We hide both buttons if: // - they require widgets but widgets are disabled diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f1f4f4dc7c..f62c00a76d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -604,7 +604,6 @@ "video": "Video", "video_room": "Video room", "view_message": "View message", - "voice": "Voice", "warning": "Warning" }, "composer": { @@ -4098,11 +4097,9 @@ "user_busy_description": "The user you called is busy.", "user_is_presenting": "%(sharerName)s is presenting", "video_call": "Video call", - "video_call_incoming": "Incoming video call", "video_call_started": "Video call started", "video_call_using": "Video call using:", "voice_call": "Voice call", - "voice_call_incoming": "Incoming voice call", "you_are_presenting": "You are presenting" }, "web_default_device_name": "%(appName)s: %(browserName)s on %(osName)s", diff --git a/src/models/Call.ts b/src/models/Call.ts index 1882eec964..a5fbc8d0bd 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -84,7 +84,6 @@ export enum CallEvent { Participants = "participants", Close = "close", Destroy = "destroy", - CallTypeChanged = "call_type_changed", } interface CallEventHandlerMap { @@ -95,7 +94,6 @@ interface CallEventHandlerMap { ) => void; [CallEvent.Close]: () => void; [CallEvent.Destroy]: () => void; - [CallEvent.CallTypeChanged]: (callType: CallType) => void; } /** @@ -105,18 +103,6 @@ export abstract class Call extends TypedEventEmitter params.append("font", font)); } this.appendAnalyticsParams(params, client); - this.appendRoomParams(params, client, roomId, opts); + this.appendRoomParams(params, client, roomId); const replacedUrl = params.toString().replace(/%24/g, "$"); url.hash = `#?${replacedUrl}`; @@ -791,43 +751,11 @@ export class ElementCall extends Call { ); } - /** - * Get the correct intent for a widget, so that Element Call presents the correct - * default config. - * @param client The matrix client. - * @param roomId - * @param voiceOnly Should the call be voice-only, or video (default). - */ - public static getWidgetIntent(client: MatrixClient, roomId: string, voiceOnly?: boolean): ElementCallIntent { - const room = client.getRoom(roomId); - if (room !== null && !isVideoRoom(room)) { - const isDM = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId); - const oldestCallMember = client.matrixRTC.getRoomSession(room).getOldestMembership(); - const hasCallStarted = !!oldestCallMember && oldestCallMember.sender !== client.getSafeUserId(); - if (isDM) { - if (hasCallStarted) { - return voiceOnly ? ElementCallIntent.JoinExistingDMVoice : ElementCallIntent.JoinExistingDM; - } else { - return voiceOnly ? ElementCallIntent.StartCallDMVoice : ElementCallIntent.StartCallDM; - } - } else { - if (hasCallStarted) { - return ElementCallIntent.JoinExisting; - } else { - return ElementCallIntent.StartCall; - } - } - } - // If unknown, default to joining an existing call. - return ElementCallIntent.JoinExisting; - } - private static getWidgetData( client: MatrixClient, roomId: string, currentData: IWidgetData, overwriteData: IWidgetData, - voiceOnly?: boolean, ): IWidgetData { let perParticipantE2EE = false; if ( @@ -835,13 +763,9 @@ export class ElementCall extends Call { !SettingsStore.getValue("feature_disable_call_per_sender_encryption") ) perParticipantE2EE = true; - - const intent = ElementCall.getWidgetIntent(client, roomId, voiceOnly); - return { ...currentData, ...overwriteData, - intent, perParticipantE2EE, }; } @@ -867,7 +791,7 @@ export class ElementCall extends Call { this.updateParticipants(); } - public static get(room: Room, voiceOnly?: boolean): ElementCall | null { + public static get(room: Room): ElementCall | null { const apps = WidgetStore.instance.getApps(room.roomId); const hasEcWidget = apps.some((app) => WidgetType.CALL.matches(app.type)); const session = room.client.matrixRTC.getRoomSession(room); @@ -950,10 +874,7 @@ export class ElementCall extends Call { if (this.session.memberships.length === 0 && !this.presented && !this.room.isCallRoom()) this.destroy(); }; - private readonly onMembershipChanged = (): void => { - this.updateParticipants(); - this.callType = this.session.getConsensusCallIntent() === "audio" ? CallType.Voice : CallType.Video; - }; + private readonly onMembershipChanged = (): void => this.updateParticipants(); private updateParticipants(): void { const participants = new Map>(); diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 489dd775f8..b9fbe16328 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -381,9 +381,7 @@ export class RoomViewStore extends EventEmitter { call.presented = true; // Immediately start the call. This will connect to all required widget events // and allow the widget to show the lobby. - if (call.connectionState === ConnectionState.Disconnected) { - call.start({ skipLobby: payload.skipLobby, voiceOnly: payload.voiceOnly }); - } + if (call.connectionState === ConnectionState.Disconnected) call.start({ skipLobby: payload.skipLobby }); } // If we switch to a different room from the call, we are no longer presenting it const prevRoomCall = this.state.roomId ? CallStore.instance.getCall(this.state.roomId) : null; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 523788e8f5..f2de9aedc1 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -14,7 +14,6 @@ import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check" import CrossIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"; import { logger } from "matrix-js-sdk/src/logger"; import { type IRTCNotificationContent } from "matrix-js-sdk/src/matrixrtc"; -import { VoiceCallIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { AvatarWithDetails } from "@element-hq/web-shared-components"; import { _t } from "../languageHandler"; @@ -24,8 +23,12 @@ import defaultDispatcher from "../dispatcher/dispatcher"; import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; import { Action } from "../dispatcher/actions"; import ToastStore from "../stores/ToastStore"; -import { LiveContentSummary, LiveContentType } from "../components/views/rooms/LiveContentSummary"; -import { useCall, useJoinCallButtonDisabledTooltip, useParticipantCount } from "../hooks/useCall"; +import { + LiveContentSummary, + LiveContentSummaryWithCall, + LiveContentType, +} from "../components/views/rooms/LiveContentSummary"; +import { useCall, useJoinCallButtonDisabledTooltip } from "../hooks/useCall"; import AccessibleButton, { type ButtonEvent } from "../components/views/elements/AccessibleButton"; import { useDispatcher } from "../hooks/useDispatcher"; import { type ActionPayload } from "../dispatcher/payloads"; @@ -33,7 +36,6 @@ import { type Call, CallEvent } from "../models/Call"; import LegacyCallHandler, { AudioID } from "../LegacyCallHandler"; import { useEventEmitter } from "../hooks/useEventEmitter"; import { CallStore, CallStoreEvent } from "../stores/CallStore"; -import DMRoomMap from "../utils/DMRoomMap"; /** * Get the key for the incoming call toast. A combination of the event ID and room ID. @@ -69,15 +71,9 @@ interface JoinCallButtonWithCallProps { onClick: (e: ButtonEvent) => void; call: Call | null; disabledTooltip: string | undefined; - isRinging: boolean; } -function JoinCallButtonWithCall({ - onClick, - call, - disabledTooltip, - isRinging, -}: JoinCallButtonWithCallProps): JSX.Element { +function JoinCallButtonWithCall({ onClick, call, disabledTooltip }: JoinCallButtonWithCallProps): JSX.Element { let disTooltip = disabledTooltip; const disabledBecauseFullTooltip = useJoinCallButtonDisabledTooltip(call); disTooltip = disabledTooltip ?? disabledBecauseFullTooltip ?? undefined; @@ -92,7 +88,7 @@ function JoinCallButtonWithCall({ Icon={CheckIcon} size="sm" > - {isRinging ? _t("action|accept") : _t("action|join")} + {_t("action|join")} ); @@ -156,7 +152,7 @@ export function IncomingCallToast({ notificationEvent }: Props): JSX.Element { // This section can race, so we use a ref to keep track of whether we have started trying to play. // This is because `LegacyCallHandler.play` tries to load the sound and then play it asynchonously // and `LegacyCallHandler.isPlaying` will not be `true` until the sound starts playing. - const isRingToast = notificationContent.notification_type === "ring"; + const isRingToast = notificationContent.notification_type == "ring"; if (isRingToast && !soundHasStarted.current && !LegacyCallHandler.instance.isPlaying(AudioID.Ring)) { // Start ringing if not already. soundHasStarted.current = true; @@ -247,11 +243,10 @@ export function IncomingCallToast({ notificationEvent }: Props): JSX.Element { room_id: room?.roomId, view_call: true, skipLobby: ("shiftKey" in e && e.shiftKey) || skipLobbyToggle, - voiceOnly: notificationContent["m.call.intent"] === "audio", metricsTrigger: undefined, }); }, - [room, skipLobbyToggle, notificationContent], + [room, skipLobbyToggle], ); // Dismiss on closing toast. @@ -267,53 +262,34 @@ export function IncomingCallToast({ notificationEvent }: Props): JSX.Element { useEventEmitter(CallStore.instance, CallStoreEvent.Call, onCall); useEventEmitter(call ?? undefined, CallEvent.Participants, onParticipantChange); useEventEmitter(room, RoomEvent.Timeline, onTimelineChange); - const isVoice = notificationContent["m.call.intent"] === "audio"; - const otherUserId = DMRoomMap.shared().getUserIdForRoomId(roomId); - const participantCount = useParticipantCount(call); - const detailsInformation = - notificationContent.notification_type === "ring" ? ( - {otherUserId} - ) : ( - - ); + const callLiveContentSummary = call ? ( + + ) : ( + + ); return ( <>
- {isVoice ? ( -
- {" "} - {_t("voip|voice_call_incoming")} -
- ) : ( -
- {" "} - {notificationContent.notification_type === "ring" - ? _t("voip|video_call_incoming") - : _t("voip|video_call_started")} -
- )} +
+ {" "} + {_t("voip|video_call_started")} +
} - details={detailsInformation} + details={callLiveContentSummary} title={room ? room.name : _t("voip|call_toast_unknown_room")} - className="mx_IncomingCallToast_AvatarWithDetails" /> - {!isVoice && ( -
- {_t("voip|skip_lobby_toggle_option")} - setSkipLobbyToggle(e.target.checked)} - checked={skipLobbyToggle} - /> -
- )} +
+ {_t("voip|skip_lobby_toggle_option")} + setSkipLobbyToggle(e.target.checked)} checked={skipLobbyToggle} /> +
diff --git a/src/utils/room/placeCall.ts b/src/utils/room/placeCall.ts index 6c046116ce..590ded7a80 100644 --- a/src/utils/room/placeCall.ts +++ b/src/utils/room/placeCall.ts @@ -27,8 +27,7 @@ export const placeCall = async ( room: Room, callType: CallType, platformCallType: PlatformCallType, - skipLobby: boolean | undefined, - voiceOnly: boolean, + skipLobby?: boolean, ): Promise => { const { analyticsName } = getPlatformCallTypeProps(platformCallType); PosthogTrackers.trackInteraction(analyticsName); @@ -40,7 +39,6 @@ export const placeCall = async ( action: Action.ViewRoom, room_id: room.roomId, view_call: true, - voiceOnly, skipLobby, metricsTrigger: undefined, }); diff --git a/test/test-utils/call.ts b/test/test-utils/call.ts index 90ef5d1ea2..c122d1d756 100644 --- a/test/test-utils/call.ts +++ b/test/test-utils/call.ts @@ -181,7 +181,6 @@ export function setUpClientRoomAndStores(): { const roomSession = new MockEventEmitter({ memberships: [], getOldestMembership: jest.fn().mockReturnValue(undefined), - getConsensusCallIntent: jest.fn().mockReturnValue(undefined), room, }) as Mocked; diff --git a/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx index deeab46bc5..c54e16215e 100644 --- a/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx +++ b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx @@ -7,7 +7,6 @@ import React from "react"; import { render, screen } from "jest-matrix-react"; -import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState"; import { NotificationDecoration } from "../../../../../src/components/views/rooms/NotificationDecoration"; @@ -23,7 +22,7 @@ describe("", () => { it("should not render if RoomNotificationState.hasAnyNotificationOrActivity=true", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false); - render(); + render(); expect(screen.queryByTestId("notification-decoration")).toBeNull(); }); @@ -31,7 +30,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "isUnsentMessage", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -40,7 +39,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "invited", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -50,7 +49,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "isMention", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(1); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -60,7 +59,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "isNotification", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(1); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -70,7 +69,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "isNotification", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(0); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -79,7 +78,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "isActivityNotification", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -88,21 +87,14 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "muted", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); - it("should render the video call decoration", () => { + it("should render the video decoration", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false); const { asFragment } = render( - , - ); - expect(asFragment()).toMatchSnapshot(); - }); - it("should render the audio call decoration", () => { - jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false); - const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx index 499573f8a7..b5916402f3 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx @@ -10,7 +10,6 @@ import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; import { render, screen, waitFor } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; import { mocked } from "jest-mock"; -import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { mkRoom, stubClient, withClientContextRenderOptions } from "../../../../../test-utils"; import { RoomListItemView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemView"; @@ -65,7 +64,6 @@ describe("", () => { isBold: false, isVideoRoom: false, callConnectionState: null, - callType: CallType.Video, hasParticipantInCall: false, name: room.name, showNotificationDecoration: false, diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap index 9e84fc2d40..140af01613 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap @@ -103,17 +103,6 @@ exports[` should display notification decoration 1`] = ` data-testid="notification-decoration" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;" > - - - diff --git a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap index 9cbc1a6464..61842e94c1 100644 --- a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap @@ -16,28 +16,6 @@ exports[` should render the activity decoration 1`] = `; -exports[` should render the audio call decoration 1`] = ` - -
- - - -
-
-`; - exports[` should render the invitation decoration 1`] = `
should render the unset message decoration 1 `; -exports[` should render the video call decoration 1`] = ` +exports[` should render the video decoration 1`] = `
{ stubClient(); client = mocked(MatrixClientPeg.safeGet()); - DMRoomMap.makeShared(client); room = new Room("!1:example.org", client, "@alice:example.org", { pendingEventOrdering: PendingEventOrdering.Detached, diff --git a/test/unit-tests/toasts/IncomingCallToast-test.tsx b/test/unit-tests/toasts/IncomingCallToast-test.tsx index fd6508c228..3ac1d89548 100644 --- a/test/unit-tests/toasts/IncomingCallToast-test.tsx +++ b/test/unit-tests/toasts/IncomingCallToast-test.tsx @@ -188,7 +188,6 @@ describe("IncomingCallToast", () => { room_id: room.roomId, skipLobby: true, view_call: true, - voiceOnly: false, }), ); await waitFor(() => @@ -216,7 +215,6 @@ describe("IncomingCallToast", () => { room_id: room.roomId, skipLobby: false, view_call: true, - voiceOnly: false, }), ); await waitFor(() => @@ -241,7 +239,6 @@ describe("IncomingCallToast", () => { room_id: room.roomId, skipLobby: true, view_call: true, - voiceOnly: false, }), ); await waitFor(() =>