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

Commit 60696d7

Browse files
Support for sending voice messages as replies and in threads (#9097)
* Support for sending voice messages as replies and in threads Signed-off-by: Šimon Brandner <[email protected]> * Add tests Signed-off-by: Šimon Brandner <[email protected]>
1 parent b4b146f commit 60696d7

File tree

4 files changed

+124
-20
lines changed

4 files changed

+124
-20
lines changed

cypress/e2e/14-timeline/timeline.spec.ts

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,27 @@ describe("Timeline", () => {
7171
let oldAvatarUrl: string;
7272
let newAvatarUrl: string;
7373

74+
beforeEach(() => {
75+
cy.startSynapse("default").then(data => {
76+
synapse = data;
77+
cy.initTestUser(synapse, OLD_NAME).then(() =>
78+
cy.window({ log: false }).then(() => {
79+
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
80+
roomId = _room1Id;
81+
});
82+
}),
83+
);
84+
});
85+
});
86+
7487
describe("useOnlyCurrentProfiles", () => {
7588
beforeEach(() => {
76-
cy.startSynapse("default").then(data => {
77-
synapse = data;
78-
cy.initTestUser(synapse, OLD_NAME).then(() =>
79-
cy.window({ log: false }).then(() => {
80-
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
81-
roomId = _room1Id;
82-
});
83-
}),
84-
).then(() => {
85-
cy.uploadContent(OLD_AVATAR).then((url) => {
86-
oldAvatarUrl = url;
87-
cy.setAvatarUrl(url);
88-
});
89-
}).then(() => {
90-
cy.uploadContent(NEW_AVATAR).then((url) => {
91-
newAvatarUrl = url;
92-
});
93-
});
89+
cy.uploadContent(OLD_AVATAR).then((url) => {
90+
oldAvatarUrl = url;
91+
cy.setAvatarUrl(url);
92+
});
93+
cy.uploadContent(NEW_AVATAR).then((url) => {
94+
newAvatarUrl = url;
9495
});
9596
});
9697

@@ -142,7 +143,9 @@ describe("Timeline", () => {
142143
expectAvatar(e, newAvatarUrl);
143144
});
144145
});
146+
});
145147

148+
describe("message displaying", () => {
146149
it("should create and configure a room on IRC layout", () => {
147150
cy.visit("/#/room/" + roomId);
148151
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
@@ -216,4 +219,45 @@ describe("Timeline", () => {
216219
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]");
217220
});
218221
});
222+
223+
describe("message sending", () => {
224+
const MESSAGE = "Hello world";
225+
const viewRoomSendMessageAndSetupReply = () => {
226+
// View room
227+
cy.visit("/#/room/" + roomId);
228+
229+
// Send a message
230+
cy.getComposer().type(`${MESSAGE}{enter}`);
231+
232+
// Reply to the message
233+
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile_line", "Hello world").within(() => {
234+
cy.get('[aria-label="Reply"]').click({ force: true }); // Cypress has no ability to hover
235+
});
236+
};
237+
238+
it("can reply with a text message", () => {
239+
const reply = "Reply";
240+
viewRoomSendMessageAndSetupReply();
241+
242+
cy.getComposer().type(`${reply}{enter}`);
243+
244+
cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line").find(".mx_ReplyTile .mx_MTextBody")
245+
.should("contain", MESSAGE);
246+
cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MTextBody").contains(reply)
247+
.should("have.length", 1);
248+
});
249+
250+
it("can reply with a voice message", () => {
251+
viewRoomSendMessageAndSetupReply();
252+
253+
cy.openMessageComposerOptions().find(`[aria-label="Voice Message"]`).click();
254+
cy.wait(3000);
255+
cy.getComposer().find(".mx_MessageComposer_sendMessage").click();
256+
257+
cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line").find(".mx_ReplyTile .mx_MTextBody")
258+
.should("contain", MESSAGE);
259+
cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MVoiceMessageBody")
260+
.should("have.length", 1);
261+
});
262+
});
219263
});

cypress/e2e/5-threads/threads.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,34 @@ describe("Threads", () => {
213213
.should("contain", "I'm very good thanks :)");
214214
});
215215

216+
it("can send voice messages", () => {
217+
// Increase viewport size and right-panel size, so that voice messages fit
218+
cy.viewport(1280, 720);
219+
cy.window().then((window) => {
220+
window.localStorage.setItem("mx_rhs_size", "600");
221+
});
222+
223+
let roomId: string;
224+
cy.createRoom({}).then(_roomId => {
225+
roomId = _roomId;
226+
cy.visit("/#/room/" + roomId);
227+
});
228+
229+
// Send message
230+
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
231+
232+
// Create thread
233+
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
234+
.realHover().find(".mx_MessageActionBar_threadButton").click();
235+
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
236+
237+
cy.openMessageComposerOptions(true).find(`[aria-label="Voice Message"]`).click();
238+
cy.wait(3000);
239+
cy.getComposer(true).find(".mx_MessageComposer_sendMessage").click();
240+
241+
cy.get(".mx_ThreadView .mx_MVoiceMessageBody").should("have.length", 1);
242+
});
243+
216244
it("right panel behaves correctly", () => {
217245
// Create room
218246
let roomId: string;

src/components/views/rooms/MessageComposer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
383383
controls.push(<VoiceRecordComposerTile
384384
key="controls_voice_record"
385385
ref={this.voiceRecordingButton}
386-
room={this.props.room} />);
386+
room={this.props.room}
387+
permalinkCreator={this.props.permalinkCreator}
388+
relation={this.props.relation}
389+
replyToEvent={this.props.replyToEvent} />);
387390
} else if (this.context.tombstone) {
388391
const replacementRoomId = this.context.tombstone.getContent()['replacement_room'];
389392

src/components/views/rooms/VoiceRecordComposerTile.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
1919
import { MsgType } from "matrix-js-sdk/src/@types/event";
2020
import { logger } from "matrix-js-sdk/src/logger";
2121
import { Optional } from "matrix-events-sdk";
22+
import { IEventRelation, MatrixEvent } from "matrix-js-sdk/src/models/event";
2223

2324
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
2425
import { _t } from "../../../languageHandler";
@@ -38,9 +39,17 @@ import { NotificationColor } from "../../../stores/notifications/NotificationCol
3839
import InlineSpinner from "../elements/InlineSpinner";
3940
import { PlaybackManager } from "../../../audio/PlaybackManager";
4041
import { doMaybeLocalRoomAction } from "../../../utils/local-room";
42+
import defaultDispatcher from "../../../dispatcher/dispatcher";
43+
import { attachRelation } from "./SendMessageComposer";
44+
import { addReplyToMessageContent } from "../../../utils/Reply";
45+
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
46+
import RoomContext from "../../../contexts/RoomContext";
4147

4248
interface IProps {
4349
room: Room;
50+
permalinkCreator?: RoomPermalinkCreator;
51+
relation?: IEventRelation;
52+
replyToEvent?: MatrixEvent;
4453
}
4554

4655
interface IState {
@@ -53,7 +62,10 @@ interface IState {
5362
* Container tile for rendering the voice message recorder in the composer.
5463
*/
5564
export default class VoiceRecordComposerTile extends React.PureComponent<IProps, IState> {
56-
public constructor(props) {
65+
static contextType = RoomContext;
66+
public context!: React.ContextType<typeof RoomContext>;
67+
68+
public constructor(props: IProps) {
5769
super(props);
5870

5971
this.state = {
@@ -88,6 +100,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
88100
throw new Error("No recording started - cannot send anything");
89101
}
90102

103+
const { replyToEvent, relation, permalinkCreator } = this.props;
104+
91105
await this.state.recorder.stop();
92106

93107
let upload: IUpload;
@@ -135,6 +149,21 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
135149
"org.matrix.msc3245.voice": {}, // No content, this is a rendering hint
136150
};
137151

152+
attachRelation(content, relation);
153+
if (replyToEvent) {
154+
addReplyToMessageContent(content, replyToEvent, {
155+
permalinkCreator,
156+
includeLegacyFallback: true,
157+
});
158+
// Clear reply_to_event as we put the message into the queue
159+
// if the send fails, retry will handle resending.
160+
defaultDispatcher.dispatch({
161+
action: 'reply_to_event',
162+
event: null,
163+
context: this.context.timelineRenderingType,
164+
});
165+
}
166+
138167
doMaybeLocalRoomAction(
139168
this.props.room.roomId,
140169
(actualRoomId: string) => MatrixClientPeg.get().sendMessage(actualRoomId, content),

0 commit comments

Comments
 (0)