@@ -17,10 +17,7 @@ limitations under the License.
17
17
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton" ;
18
18
import { _t } from "../../../languageHandler" ;
19
19
import React , { ReactNode } from "react" ;
20
- import {
21
- RecordingState ,
22
- VoiceRecording ,
23
- } from "../../../audio/VoiceRecording" ;
20
+ import { IUpload , RecordingState , VoiceRecording } from "../../../audio/VoiceRecording" ;
24
21
import { Room } from "matrix-js-sdk/src/models/room" ;
25
22
import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
26
23
import classNames from "classnames" ;
@@ -34,6 +31,10 @@ import { MsgType } from "matrix-js-sdk/src/@types/event";
34
31
import Modal from "../../../Modal" ;
35
32
import ErrorDialog from "../dialogs/ErrorDialog" ;
36
33
import MediaDeviceHandler , { MediaDeviceKindEnum } from "../../../MediaDeviceHandler" ;
34
+ import NotificationBadge from "./NotificationBadge" ;
35
+ import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState" ;
36
+ import { NotificationColor } from "../../../stores/notifications/NotificationColor" ;
37
+ import InlineSpinner from "../elements/InlineSpinner" ;
37
38
38
39
interface IProps {
39
40
room : Room ;
@@ -42,6 +43,7 @@ interface IProps {
42
43
interface IState {
43
44
recorder ?: VoiceRecording ;
44
45
recordingPhase ?: RecordingState ;
46
+ didUploadFail ?: boolean ;
45
47
}
46
48
47
49
/**
@@ -69,9 +71,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
69
71
70
72
await this . state . recorder . stop ( ) ;
71
73
74
+ let upload : IUpload ;
72
75
try {
73
- const upload = await this . state . recorder . upload ( this . props . room . roomId ) ;
76
+ upload = await this . state . recorder . upload ( this . props . room . roomId ) ;
77
+ } catch ( e ) {
78
+ console . error ( "Error uploading voice message:" , e ) ;
79
+
80
+ // Flag error and move on. The recording phase will be reset by the upload function.
81
+ this . setState ( { didUploadFail : true } ) ;
74
82
83
+ return ; // don't dispose the recording: the user has a chance to re-upload
84
+ }
85
+
86
+ try {
75
87
// noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
76
88
MatrixClientPeg . get ( ) . sendMessage ( this . props . room . roomId , {
77
89
"body" : "Voice message" ,
@@ -104,12 +116,11 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
104
116
"org.matrix.msc3245.voice" : { } , // No content, this is a rendering hint
105
117
} ) ;
106
118
} catch ( e ) {
107
- console . error ( "Error sending/uploading voice message:" , e ) ;
108
- Modal . createTrackedDialog ( 'Upload failed' , '' , ErrorDialog , {
109
- title : _t ( 'Upload Failed' ) ,
110
- description : _t ( "The voice message failed to upload." ) ,
111
- } ) ;
112
- return ; // don't dispose the recording so the user can retry, maybe
119
+ console . error ( "Error sending voice message:" , e ) ;
120
+
121
+ // Voice message should be in the timeline at this point, so let other things take care
122
+ // of error handling. We also shouldn't need the recording anymore, so fall through to
123
+ // disposal.
113
124
}
114
125
await this . disposeRecording ( ) ;
115
126
}
@@ -118,7 +129,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
118
129
await VoiceRecordingStore . instance . disposeRecording ( ) ;
119
130
120
131
// Reset back to no recording, which means no phase (ie: restart component entirely)
121
- this . setState ( { recorder : null , recordingPhase : null } ) ;
132
+ this . setState ( { recorder : null , recordingPhase : null , didUploadFail : false } ) ;
122
133
}
123
134
124
135
private onCancel = async ( ) => {
@@ -234,7 +245,25 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
234
245
/> ;
235
246
}
236
247
248
+ let uploadIndicator ;
249
+ if ( this . state . recordingPhase === RecordingState . Uploading ) {
250
+ uploadIndicator = < span className = 'mx_VoiceRecordComposerTile_uploadingState' >
251
+ < InlineSpinner w = { 16 } h = { 16 } />
252
+ </ span > ;
253
+ } else if ( this . state . didUploadFail && this . state . recordingPhase === RecordingState . Ended ) {
254
+ uploadIndicator = < span className = 'mx_VoiceRecordComposerTile_failedState' >
255
+ < span className = 'mx_VoiceRecordComposerTile_uploadState_badge' >
256
+ { /* Need to stick the badge in a span to ensure it doesn't create a block component */ }
257
+ < NotificationBadge
258
+ notification = { StaticNotificationState . forSymbol ( "!" , NotificationColor . Red ) }
259
+ />
260
+ </ span >
261
+ < span className = 'text-warning' > { _t ( "Failed to send" ) } </ span >
262
+ </ span > ;
263
+ }
264
+
237
265
return ( < >
266
+ { uploadIndicator }
238
267
{ deleteButton }
239
268
{ this . renderWaveformArea ( ) }
240
269
{ recordingInfo }
0 commit comments