diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx
index 7d4ad5eb4f..01b7fe4eaf 100644
--- a/src/TextForEvent.tsx
+++ b/src/TextForEvent.tsx
@@ -45,10 +45,7 @@ import RightPanelStore from "./stores/right-panel/RightPanelStore";
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
import { ElementCall } from "./models/Call";
import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast";
-
-export function getSenderName(event: MatrixEvent): string {
- return event.sender?.name ?? event.getSender() ?? _t("Someone");
-}
+import { getSenderName } from "./utils/event/getSenderName";
function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()): string {
const client = MatrixClientPeg.get();
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index abee5e48db..888910a517 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 955298a837..035e342747 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 e33560e73e..af572ed0b0 100644
--- a/src/stores/room-list/previews/MessageEventPreview.ts
+++ b/src/stores/room-list/previews/MessageEventPreview.ts
@@ -23,11 +23,15 @@ import { _t, sanitizeForTranslation } from "../../../languageHandler";
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
import { getHtmlText } from "../../../HtmlUtils";
import { stripHTMLReply, stripPlainReply } from "../../../utils/Reply";
+import { VoiceBroadcastChunkEventType } from "../../../voice-broadcast/types";
export class MessageEventPreview implements IPreview {
- public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
+ public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
let eventContent = event.getContent();
+ // no preview for broadcast chunks
+ if (eventContent[VoiceBroadcastChunkEventType]) 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 0000000000..b05ef4473d
--- /dev/null
+++ b/src/stores/room-list/previews/VoiceBroadcastPreview.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 { VoiceBroadcastInfoState } from "../../../voice-broadcast/types";
+import { textForVoiceBroadcastStoppedEventWithoutLink } from "../../../voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink";
+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/utils/event/getSenderName.ts b/src/utils/event/getSenderName.ts
new file mode 100644
index 0000000000..18bfc8d927
--- /dev/null
+++ b/src/utils/event/getSenderName.ts
@@ -0,0 +1,23 @@
+/*
+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";
+
+export function getSenderName(event: MatrixEvent): string {
+ return event.sender?.name ?? event.getSender() ?? _t("Someone");
+}
diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts
index 952a3969af..bba5bf02b2 100644
--- a/src/voice-broadcast/index.ts
+++ b/src/voice-broadcast/index.ts
@@ -19,8 +19,7 @@ limitations under the License.
* {@link https://github.com/vector-im/element-meta/discussions/632}
*/
-import { RelationType } from "matrix-js-sdk/src/matrix";
-
+export * from "./types";
export * from "./models/VoiceBroadcastPlayback";
export * from "./models/VoiceBroadcastPreRecording";
export * from "./models/VoiceBroadcastRecording";
@@ -55,27 +54,5 @@ 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";
-export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk";
-
-export type VoiceBroadcastLiveness = "live" | "not-live" | "grey";
-
-export enum VoiceBroadcastInfoState {
- Started = "started",
- Paused = "paused",
- Resumed = "resumed",
- Stopped = "stopped",
-}
-
-export interface VoiceBroadcastInfoEventContent {
- device_id: string;
- state: VoiceBroadcastInfoState;
- chunk_length?: number;
- last_chunk_sequence?: number;
- ["m.relates_to"]?: {
- rel_type: RelationType;
- event_id: string;
- };
-}
diff --git a/src/voice-broadcast/types.ts b/src/voice-broadcast/types.ts
new file mode 100644
index 0000000000..a54e5513c9
--- /dev/null
+++ b/src/voice-broadcast/types.ts
@@ -0,0 +1,40 @@
+/*
+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";
+
+export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info";
+export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk";
+
+export type VoiceBroadcastLiveness = "live" | "not-live" | "grey";
+
+export enum VoiceBroadcastInfoState {
+ Started = "started",
+ Paused = "paused",
+ Resumed = "resumed",
+ Stopped = "stopped",
+}
+
+export interface VoiceBroadcastInfoEventContent {
+ device_id: string;
+ state: VoiceBroadcastInfoState;
+ chunk_length?: number;
+ last_chunk_sequence?: number;
+ ["m.relates_to"]?: {
+ rel_type: RelationType;
+ event_id: string;
+ };
+}
diff --git a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx b/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx
index 611908b750..4564c73d13 100644
--- a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx
+++ b/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx
@@ -20,8 +20,8 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { highlightEvent } from "../../utils/EventUtils";
-import { getSenderName } from "../../TextForEvent";
import { _t } from "../../languageHandler";
+import { getSenderName } from "../../utils/event/getSenderName";
export const textForVoiceBroadcastStoppedEvent = (event: MatrixEvent): (() => ReactNode) => {
return (): ReactNode => {
diff --git a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts b/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts
new file mode 100644
index 0000000000..f0ecbc4e83
--- /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 "../../utils/event/getSenderName";
+
+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/TextForEvent-test.ts b/test/TextForEvent-test.ts
index 53455ba5b0..e05ae0b339 100644
--- a/test/TextForEvent-test.ts
+++ b/test/TextForEvent-test.ts
@@ -19,12 +19,13 @@ import TestRenderer from "react-test-renderer";
import { ReactElement } from "react";
import { mocked } from "jest-mock";
-import { getSenderName, textForEvent } from "../src/TextForEvent";
+import { textForEvent } from "../src/TextForEvent";
import SettingsStore from "../src/settings/SettingsStore";
import { createTestClient, stubClient } from "./test-utils";
import { MatrixClientPeg } from "../src/MatrixClientPeg";
import UserIdentifierCustomisations from "../src/customisations/UserIdentifier";
import { ElementCall } from "../src/models/Call";
+import { getSenderName } from "../src/utils/event/getSenderName";
jest.mock("../src/settings/SettingsStore");
jest.mock("../src/customisations/UserIdentifier", () => ({
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 0000000000..48f9c03036
--- /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/stores/room-list/previews/VoiceBroadcastPreview-test.ts b/test/stores/room-list/previews/VoiceBroadcastPreview-test.ts
new file mode 100644
index 0000000000..ccffdeaa76
--- /dev/null
+++ b/test/stores/room-list/previews/VoiceBroadcastPreview-test.ts
@@ -0,0 +1,57 @@
+/*
+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 { VoiceBroadcastPreview } from "../../../../src/stores/room-list/previews/VoiceBroadcastPreview";
+import { VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
+import { mkEvent } from "../../../test-utils";
+import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
+
+describe("VoiceBroadcastPreview.getTextFor", () => {
+ const roomId = "!room:example.com";
+ const userId = "@user:example.com";
+ const deviceId = "d42";
+ let preview: VoiceBroadcastPreview;
+
+ beforeAll(() => {
+ preview = new VoiceBroadcastPreview();
+ });
+
+ it("when passing an event with empty content, it should return null", () => {
+ const event = mkEvent({
+ event: true,
+ content: {},
+ user: userId,
+ type: "m.room.message",
+ });
+ expect(preview.getTextFor(event)).toBeNull();
+ });
+
+ it("when passing a broadcast started event, it should return null", () => {
+ const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId);
+ expect(preview.getTextFor(event)).toBeNull();
+ });
+
+ it("when passing a broadcast stopped event, it should return the expected text", () => {
+ const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId);
+ expect(preview.getTextFor(event)).toBe("@user:example.com ended a voice broadcast");
+ });
+
+ it("when passing a redacted broadcast stopped event, it should return null", () => {
+ const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId);
+ event.makeRedacted(mkEvent({ event: true, content: {}, user: userId, type: "m.room.redaction" }));
+ expect(preview.getTextFor(event)).toBeNull();
+ });
+});
diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts
index d0c7d34b45..21de3983fb 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 0000000000..8f17e3c43a
--- /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`);
+ });
+});