diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index e82e8f18fe6c..1cb589923f6d 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -650,6 +650,8 @@
"Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.",
"You ended a voice broadcast": "You ended a voice broadcast",
"%(senderName)s ended a voice broadcast": "%(senderName)s ended a voice broadcast",
+ "You ended a voice broadcast": "You ended a voice broadcast",
+ "%(senderName)s ended a voice broadcast": "%(senderName)s ended a voice broadcast",
"Stop live broadcasting?": "Stop live broadcasting?",
"Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.",
"Yes, stop broadcast": "Yes, stop broadcast",
diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts
index 955298a83739..035e34274712 100644
--- a/src/stores/room-list/MessagePreviewStore.ts
+++ b/src/stores/room-list/MessagePreviewStore.ts
@@ -32,6 +32,8 @@ import { StickerEventPreview } from "./previews/StickerEventPreview";
import { ReactionEventPreview } from "./previews/ReactionEventPreview";
import { UPDATE_EVENT } from "../AsyncStore";
import { IPreview } from "./previews/IPreview";
+import { VoiceBroadcastInfoEventType } from "../../voice-broadcast";
+import { VoiceBroadcastPreview } from "./previews/VoiceBroadcastPreview";
// Emitted event for when a room's preview has changed. First argument will the room for which
// the change happened.
@@ -76,6 +78,10 @@ const PREVIEWS: Record<
isState: false,
previewer: new PollStartEventPreview(),
},
+ [VoiceBroadcastInfoEventType]: {
+ isState: true,
+ previewer: new VoiceBroadcastPreview(),
+ },
};
// The maximum number of events we're willing to look back on to get a preview.
diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts
index e33560e73e2b..ea81543cd6b7 100644
--- a/src/stores/room-list/previews/MessageEventPreview.ts
+++ b/src/stores/room-list/previews/MessageEventPreview.ts
@@ -28,6 +28,9 @@ export class MessageEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
let eventContent = event.getContent();
+ // no preview for broadcast chunks
+ if (eventContent["io.element.voice_broadcast_chunk"]) return null;
+
if (event.isRelation(RelationType.Replace)) {
// It's an edit, generate the preview on the new text
eventContent = event.getContent()["m.new_content"];
diff --git a/src/stores/room-list/previews/VoiceBroadcastPreview.ts b/src/stores/room-list/previews/VoiceBroadcastPreview.ts
new file mode 100644
index 000000000000..9f2ad034ae74
--- /dev/null
+++ b/src/stores/room-list/previews/VoiceBroadcastPreview.ts
@@ -0,0 +1,30 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+
+import { textForVoiceBroadcastStoppedEventWithoutLink, VoiceBroadcastInfoState } from "../../../voice-broadcast";
+import { IPreview } from "./IPreview";
+
+export class VoiceBroadcastPreview implements IPreview {
+ getTextFor(event: MatrixEvent, tagId?: string, isThread?: boolean): string | null {
+ if (!event.isRedacted() && event.getContent()?.state === VoiceBroadcastInfoState.Stopped) {
+ return textForVoiceBroadcastStoppedEventWithoutLink(event);
+ }
+
+ return null;
+ }
+}
diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts
index f71ce077ad8d..d917ff991140 100644
--- a/src/voice-broadcast/index.ts
+++ b/src/voice-broadcast/index.ts
@@ -53,6 +53,7 @@ export * from "./utils/shouldDisplayAsVoiceBroadcastTile";
export * from "./utils/shouldDisplayAsVoiceBroadcastStoppedText";
export * from "./utils/startNewVoiceBroadcastRecording";
export * from "./utils/textForVoiceBroadcastStoppedEvent";
+export * from "./utils/textForVoiceBroadcastStoppedEventWithoutLink";
export * from "./utils/VoiceBroadcastResumer";
export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info";
diff --git a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts b/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts
new file mode 100644
index 000000000000..3664cbcd0b0c
--- /dev/null
+++ b/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts
@@ -0,0 +1,31 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+
+import { _t } from "../../languageHandler";
+import { MatrixClientPeg } from "../../MatrixClientPeg";
+import { getSenderName } from "../../TextForEvent";
+
+export const textForVoiceBroadcastStoppedEventWithoutLink = (event: MatrixEvent): string => {
+ const ownUserId = MatrixClientPeg.get()?.getUserId();
+
+ if (ownUserId && ownUserId === event.getSender()) {
+ return _t("You ended a voice broadcast", {});
+ }
+
+ return _t("%(senderName)s ended a voice broadcast", { senderName: getSenderName(event) });
+};
diff --git a/test/stores/room-list/previews/MessageEventPreview-test.ts b/test/stores/room-list/previews/MessageEventPreview-test.ts
new file mode 100644
index 000000000000..48f9c030365e
--- /dev/null
+++ b/test/stores/room-list/previews/MessageEventPreview-test.ts
@@ -0,0 +1,96 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { RelationType } from "matrix-js-sdk/src/matrix";
+
+import { MessageEventPreview } from "../../../../src/stores/room-list/previews/MessageEventPreview";
+import { mkEvent, stubClient } from "../../../test-utils";
+
+describe("MessageEventPreview", () => {
+ const preview = new MessageEventPreview();
+ const userId = "@user:example.com";
+
+ beforeAll(() => {
+ stubClient();
+ });
+
+ describe("getTextFor", () => {
+ it("when called with an event with empty content should return null", () => {
+ const event = mkEvent({
+ event: true,
+ content: {},
+ user: userId,
+ type: "m.room.message",
+ });
+ expect(preview.getTextFor(event)).toBeNull();
+ });
+
+ it("when called with an event with empty body should return null", () => {
+ const event = mkEvent({
+ event: true,
+ content: {
+ body: "",
+ },
+ user: userId,
+ type: "m.room.message",
+ });
+ expect(preview.getTextFor(event)).toBeNull();
+ });
+
+ it("when called with an event with body should return »user: body«", () => {
+ const event = mkEvent({
+ event: true,
+ content: {
+ body: "test body",
+ },
+ user: userId,
+ type: "m.room.message",
+ });
+ expect(preview.getTextFor(event)).toBe(`${userId}: test body`);
+ });
+
+ it("when called for a replaced event with new content should return the new content body", () => {
+ const event = mkEvent({
+ event: true,
+ content: {
+ ["m.new_content"]: {
+ body: "test new content body",
+ },
+ ["m.relates_to"]: {
+ rel_type: RelationType.Replace,
+ event_id: "$asd123",
+ },
+ },
+ user: userId,
+ type: "m.room.message",
+ });
+ expect(preview.getTextFor(event)).toBe(`${userId}: test new content body`);
+ });
+
+ it("when called with a broadcast chunk event it should return null", () => {
+ const event = mkEvent({
+ event: true,
+ content: {
+ body: "test body",
+ ["io.element.voice_broadcast_chunk"]: {},
+ },
+ user: userId,
+ type: "m.room.message",
+ });
+ expect(preview.getTextFor(event)).toBeNull();
+ });
+ });
+});
diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts
index d0c7d34b4522..21de3983fb98 100644
--- a/test/test-utils/test-utils.ts
+++ b/test/test-utils/test-utils.ts
@@ -88,6 +88,7 @@ export function createTestClient(): MatrixClient {
getIdentityServerUrl: jest.fn(),
getDomain: jest.fn().mockReturnValue("matrix.org"),
getUserId: jest.fn().mockReturnValue("@userId:matrix.org"),
+ getSafeUserId: jest.fn().mockReturnValue("@userId:matrix.org"),
getUserIdLocalpart: jest.fn().mockResolvedValue("userId"),
getUser: jest.fn().mockReturnValue({ on: jest.fn() }),
getDeviceId: jest.fn().mockReturnValue("ABCDEFGHI"),
diff --git a/test/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink-test.ts b/test/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink-test.ts
new file mode 100644
index 000000000000..8f17e3c43a22
--- /dev/null
+++ b/test/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink-test.ts
@@ -0,0 +1,55 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { mocked } from "jest-mock";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+
+import { textForVoiceBroadcastStoppedEventWithoutLink, VoiceBroadcastInfoState } from "../../../src/voice-broadcast";
+import { stubClient } from "../../test-utils";
+import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
+
+describe("textForVoiceBroadcastStoppedEventWithoutLink", () => {
+ const otherUserId = "@other:example.com";
+ const roomId = "!room:example.com";
+ let client: MatrixClient;
+
+ beforeAll(() => {
+ client = stubClient();
+ });
+
+ const getText = (senderId: string, startEventId?: string) => {
+ const event = mkVoiceBroadcastInfoStateEvent(
+ roomId,
+ VoiceBroadcastInfoState.Stopped,
+ senderId,
+ client.deviceId!,
+ );
+ return textForVoiceBroadcastStoppedEventWithoutLink(event);
+ };
+
+ it("when called for an own broadcast it should return the expected text", () => {
+ expect(getText(client.getUserId()!)).toBe("You ended a voice broadcast");
+ });
+
+ it("when called for other ones broadcast it should return the expected text", () => {
+ expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`);
+ });
+
+ it("when not logged in it should return the exptected text", () => {
+ mocked(client.getUserId).mockReturnValue(null);
+ expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`);
+ });
+});