Merge pull request #6530 from matrix-org/travis/voice-messages/uploading

Improve voice messages uploading state
This commit is contained in:
Travis Ralston 2021-08-05 08:36:53 -06:00 committed by GitHub
commit 280c017238
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 13 deletions

View file

@ -46,6 +46,21 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/trashcan.svg'); mask-image: url('$(res)/img/element-icons/trashcan.svg');
} }
.mx_VoiceRecordComposerTile_uploadingState {
margin-right: 10px;
color: $secondary-fg-color;
}
.mx_VoiceRecordComposerTile_failedState {
margin-right: 21px;
.mx_VoiceRecordComposerTile_uploadState_badge {
display: inline-block;
margin-right: 4px;
vertical-align: middle;
}
}
.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer { .mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
// Note: remaining class properties are in the PlayerContainer CSS. // Note: remaining class properties are in the PlayerContainer CSS.

View file

@ -17,10 +17,7 @@ limitations under the License.
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording";
RecordingState,
VoiceRecording,
} from "../../../audio/VoiceRecording";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import classNames from "classnames"; import classNames from "classnames";
@ -34,6 +31,10 @@ import { MsgType } from "matrix-js-sdk/src/@types/event";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog"; import ErrorDialog from "../dialogs/ErrorDialog";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler"; import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
import NotificationBadge from "./NotificationBadge";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import InlineSpinner from "../elements/InlineSpinner";
interface IProps { interface IProps {
room: Room; room: Room;
@ -42,6 +43,7 @@ interface IProps {
interface IState { interface IState {
recorder?: VoiceRecording; recorder?: VoiceRecording;
recordingPhase?: RecordingState; recordingPhase?: RecordingState;
didUploadFail?: boolean;
} }
/** /**
@ -69,9 +71,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
await this.state.recorder.stop(); await this.state.recorder.stop();
let upload: IUpload;
try { try {
const upload = await this.state.recorder.upload(this.props.room.roomId); upload = await this.state.recorder.upload(this.props.room.roomId);
} catch (e) {
console.error("Error uploading voice message:", e);
// Flag error and move on. The recording phase will be reset by the upload function.
this.setState({ didUploadFail: true });
return; // don't dispose the recording: the user has a chance to re-upload
}
try {
// noinspection ES6MissingAwait - we don't care if it fails, it'll get queued. // noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
MatrixClientPeg.get().sendMessage(this.props.room.roomId, { MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
"body": "Voice message", "body": "Voice message",
@ -104,12 +116,11 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
"org.matrix.msc3245.voice": {}, // No content, this is a rendering hint "org.matrix.msc3245.voice": {}, // No content, this is a rendering hint
}); });
} catch (e) { } catch (e) {
console.error("Error sending/uploading voice message:", e); console.error("Error sending voice message:", e);
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
title: _t('Upload Failed'), // Voice message should be in the timeline at this point, so let other things take care
description: _t("The voice message failed to upload."), // of error handling. We also shouldn't need the recording anymore, so fall through to
}); // disposal.
return; // don't dispose the recording so the user can retry, maybe
} }
await this.disposeRecording(); await this.disposeRecording();
} }
@ -118,7 +129,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
await VoiceRecordingStore.instance.disposeRecording(); await VoiceRecordingStore.instance.disposeRecording();
// Reset back to no recording, which means no phase (ie: restart component entirely) // Reset back to no recording, which means no phase (ie: restart component entirely)
this.setState({ recorder: null, recordingPhase: null }); this.setState({ recorder: null, recordingPhase: null, didUploadFail: false });
} }
private onCancel = async () => { private onCancel = async () => {
@ -234,7 +245,25 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
/>; />;
} }
let uploadIndicator;
if (this.state.recordingPhase === RecordingState.Uploading) {
uploadIndicator = <span className='mx_VoiceRecordComposerTile_uploadingState'>
<InlineSpinner w={16} h={16} />
</span>;
} else if (this.state.didUploadFail && this.state.recordingPhase === RecordingState.Ended) {
uploadIndicator = <span className='mx_VoiceRecordComposerTile_failedState'>
<span className='mx_VoiceRecordComposerTile_uploadState_badge'>
{ /* Need to stick the badge in a span to ensure it doesn't create a block component */ }
<NotificationBadge
notification={StaticNotificationState.forSymbol("!", NotificationColor.Red)}
/>
</span>
<span className='text-warning'>{ _t("Failed to send") }</span>
</span>;
}
return (<> return (<>
{ uploadIndicator }
{ deleteButton } { deleteButton }
{ this.renderWaveformArea() } { this.renderWaveformArea() }
{ recordingInfo } { recordingInfo }

View file

@ -1697,7 +1697,6 @@
"Invited by %(sender)s": "Invited by %(sender)s", "Invited by %(sender)s": "Invited by %(sender)s",
"Jump to first unread message.": "Jump to first unread message.", "Jump to first unread message.": "Jump to first unread message.",
"Mark all as read": "Mark all as read", "Mark all as read": "Mark all as read",
"The voice message failed to upload.": "The voice message failed to upload.",
"Unable to access your microphone": "Unable to access your microphone", "Unable to access your microphone": "Unable to access your microphone",
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.", "We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
"No microphone found": "No microphone found", "No microphone found": "No microphone found",