Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Implement broadcast message preview (#9762)
Browse files Browse the repository at this point in the history
  • Loading branch information
weeman1337 authored Dec 16, 2022
1 parent 6205c70 commit 5155439
Show file tree
Hide file tree
Showing 15 changed files with 353 additions and 32 deletions.
5 changes: 1 addition & 4 deletions src/TextForEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a>voice broadcast</a>": "You ended a <a>voice broadcast</a>",
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s ended a <a>voice broadcast</a>",
"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",
Expand Down
6 changes: 6 additions & 0 deletions src/stores/room-list/MessagePreviewStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion src/stores/room-list/previews/MessageEventPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand Down
31 changes: 31 additions & 0 deletions src/stores/room-list/previews/VoiceBroadcastPreview.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
23 changes: 23 additions & 0 deletions src/utils/event/getSenderName.ts
Original file line number Diff line number Diff line change
@@ -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");
}
27 changes: 2 additions & 25 deletions src/voice-broadcast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
};
}
40 changes: 40 additions & 0 deletions src/voice-broadcast/types.ts
Original file line number Diff line number Diff line change
@@ -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;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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) });
};
3 changes: 2 additions & 1 deletion test/TextForEvent-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => ({
Expand Down
96 changes: 96 additions & 0 deletions test/stores/room-list/previews/MessageEventPreview-test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
Loading

0 comments on commit 5155439

Please sign in to comment.