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

Support for sending voice messages as replies and in threads #9097

Merged
merged 2 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 62 additions & 18 deletions cypress/e2e/14-timeline/timeline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,27 @@ describe("Timeline", () => {
let oldAvatarUrl: string;
let newAvatarUrl: string;

beforeEach(() => {
cy.startSynapse("default").then(data => {
synapse = data;
cy.initTestUser(synapse, OLD_NAME).then(() =>
cy.window({ log: false }).then(() => {
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
roomId = _room1Id;
});
}),
);
});
});

describe("useOnlyCurrentProfiles", () => {
beforeEach(() => {
cy.startSynapse("default").then(data => {
synapse = data;
cy.initTestUser(synapse, OLD_NAME).then(() =>
cy.window({ log: false }).then(() => {
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
roomId = _room1Id;
});
}),
).then(() => {
cy.uploadContent(OLD_AVATAR).then((url) => {
oldAvatarUrl = url;
cy.setAvatarUrl(url);
});
}).then(() => {
cy.uploadContent(NEW_AVATAR).then((url) => {
newAvatarUrl = url;
});
});
cy.uploadContent(OLD_AVATAR).then((url) => {
oldAvatarUrl = url;
cy.setAvatarUrl(url);
});
cy.uploadContent(NEW_AVATAR).then((url) => {
newAvatarUrl = url;
});
});

Expand Down Expand Up @@ -142,7 +143,9 @@ describe("Timeline", () => {
expectAvatar(e, newAvatarUrl);
});
});
});

describe("message displaying", () => {
it("should create and configure a room on IRC layout", () => {
cy.visit("/#/room/" + roomId);
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
Expand Down Expand Up @@ -190,4 +193,45 @@ describe("Timeline", () => {
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]");
});
});

describe("message sending", () => {
const MESSAGE = "Hello world";
const viewRoomSendMessageAndSetupReply = () => {
// View room
cy.visit("/#/room/" + roomId);

// Send a message
cy.getComposer().type(`${MESSAGE}{enter}`);

// Reply to the message
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile_line", "Hello world").within(() => {
cy.get('[aria-label="Reply"]').click({ force: true }); // Cypress has no ability to hover
});
};

it("can reply with a text message", () => {
const reply = "Reply";
viewRoomSendMessageAndSetupReply();

cy.getComposer().type(`${reply}{enter}`);

cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line").find(".mx_ReplyTile .mx_MTextBody")
.should("contain", MESSAGE);
cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MTextBody").contains(reply)
.should("have.length", 1);
});

it("can reply with a voice message", () => {
viewRoomSendMessageAndSetupReply();

cy.openMessageComposerOptions().find(`[aria-label="Voice Message"]`).click();
cy.wait(3000);
cy.getComposer().find(".mx_MessageComposer_sendMessage").click();

cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line").find(".mx_ReplyTile .mx_MTextBody")
.should("contain", MESSAGE);
cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MVoiceMessageBody")
.should("have.length", 1);
});
});
});
28 changes: 28 additions & 0 deletions cypress/e2e/5-threads/threads.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,34 @@ describe("Threads", () => {
.should("contain", "I'm very good thanks :)");
});

it("can send voice messages", () => {
// Increase viewport size and right-panel size, so that voice messages fit
cy.viewport(1280, 720);
cy.window().then((window) => {
window.localStorage.setItem("mx_rhs_size", "600");
});

let roomId: string;
cy.createRoom({}).then(_roomId => {
roomId = _roomId;
cy.visit("/#/room/" + roomId);
});

// Send message
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");

// Create thread
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
.realHover().find(".mx_MessageActionBar_threadButton").click();
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);

cy.openMessageComposerOptions(true).find(`[aria-label="Voice Message"]`).click();
cy.wait(3000);
cy.getComposer(true).find(".mx_MessageComposer_sendMessage").click();

cy.get(".mx_ThreadView .mx_MVoiceMessageBody").should("have.length", 1);
});

it("right panel behaves correctly", () => {
// Create room
let roomId: string;
Expand Down
5 changes: 4 additions & 1 deletion src/components/views/rooms/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
controls.push(<VoiceRecordComposerTile
key="controls_voice_record"
ref={this.voiceRecordingButton}
room={this.props.room} />);
room={this.props.room}
permalinkCreator={this.props.permalinkCreator}
relation={this.props.relation}
replyToEvent={this.props.replyToEvent} />);
} else if (this.context.tombstone) {
const replacementRoomId = this.context.tombstone.getContent()['replacement_room'];

Expand Down
31 changes: 30 additions & 1 deletion src/components/views/rooms/VoiceRecordComposerTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { MsgType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk";
import { IEventRelation, MatrixEvent } from "matrix-js-sdk/src/models/event";

import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { _t } from "../../../languageHandler";
Expand All @@ -38,9 +39,17 @@ import { NotificationColor } from "../../../stores/notifications/NotificationCol
import InlineSpinner from "../elements/InlineSpinner";
import { PlaybackManager } from "../../../audio/PlaybackManager";
import { doMaybeLocalRoomAction } from "../../../utils/local-room";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { attachRelation } from "./SendMessageComposer";
import { addReplyToMessageContent } from "../../../utils/Reply";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import RoomContext from "../../../contexts/RoomContext";

interface IProps {
room: Room;
permalinkCreator?: RoomPermalinkCreator;
relation?: IEventRelation;
replyToEvent?: MatrixEvent;
}

interface IState {
Expand All @@ -53,7 +62,10 @@ interface IState {
* Container tile for rendering the voice message recorder in the composer.
*/
export default class VoiceRecordComposerTile extends React.PureComponent<IProps, IState> {
public constructor(props) {
static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;

public constructor(props: IProps) {
super(props);

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

const { replyToEvent, relation, permalinkCreator } = this.props;

await this.state.recorder.stop();

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

attachRelation(content, relation);
if (replyToEvent) {
addReplyToMessageContent(content, replyToEvent, {
permalinkCreator,
includeLegacyFallback: true,
});
// Clear reply_to_event as we put the message into the queue
// if the send fails, retry will handle resending.
defaultDispatcher.dispatch({
action: 'reply_to_event',
event: null,
context: this.context.timelineRenderingType,
});
}

doMaybeLocalRoomAction(
this.props.room.roomId,
(actualRoomId: string) => MatrixClientPeg.get().sendMessage(actualRoomId, content),
Expand Down