Improve UI around Voice Message limits

This commit is contained in:
Josh Perez 2020-02-20 14:51:25 -08:00 committed by Scott Nonnenberg
parent 148aedeeb4
commit 2138395bcb
4 changed files with 48 additions and 2 deletions

View file

@ -777,6 +777,18 @@
"message": "Original message found, but not loaded. Scroll up to load it.", "message": "Original message found, but not loaded. Scroll up to load it.",
"description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database" "description": "Shown in toast if user clicks on quote references messages not loaded in view, but in database"
}, },
"voiceRecordingInterruptedMax": {
"message": "Voice message recording stopped because the maximum time limit was reached.",
"description": "Confirmation dialog message for when the voice recording is interrupted due to max time limit"
},
"voiceRecordingInterruptedBlur": {
"message": "Voice message recording stopped because you switched to another app.",
"description": "Confirmation dialog message for when the voice recording is interrupted due to app losing focus"
},
"voiceNoteLimit": {
"message": "Voice messages are limited to five minutes. Recording will stop if you switch to another app.",
"description": "Shown in toast to warn user about limited time and that window must be in focus"
},
"voiceNoteMustBeOnlyAttachment": { "voiceNoteMustBeOnlyAttachment": {
"message": "A voice message must have only one attachment.", "message": "A voice message must have only one attachment.",
"description": "Shown in toast if tries to record a voice note with any staged attachments" "description": "Shown in toast if tries to record a voice note with any staged attachments"
@ -863,6 +875,9 @@
"cancel": { "cancel": {
"message": "Cancel" "message": "Cancel"
}, },
"discard": {
"message": "Discard"
},
"failedToSend": { "failedToSend": {
"message": "Failed to send to some recipients. Check your network connection." "message": "Failed to send to some recipients. Check your network connection."
}, },

View file

@ -75,6 +75,11 @@
return { toastMessage: i18n('messageFoundButNotLoaded') }; return { toastMessage: i18n('messageFoundButNotLoaded') };
}, },
}); });
Whisper.VoiceNoteLimit = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('voiceNoteLimit') };
},
});
Whisper.VoiceNoteMustBeOnlyAttachmentToast = Whisper.ToastView.extend({ Whisper.VoiceNoteMustBeOnlyAttachmentToast = Whisper.ToastView.extend({
render_attributes() { render_attributes() {
return { toastMessage: i18n('voiceNoteMustBeOnlyAttachment') }; return { toastMessage: i18n('voiceNoteMustBeOnlyAttachment') };
@ -1650,6 +1655,8 @@
return; return;
} }
this.showToast(Whisper.VoiceNoteLimit);
// Note - clicking anywhere will close the audio capture panel, due to // Note - clicking anywhere will close the audio capture panel, due to
// the onClick handler in InboxView, which calls its closeRecording method. // the onClick handler in InboxView, which calls its closeRecording method.
@ -1663,6 +1670,7 @@
const view = this.captureAudioView; const view = this.captureAudioView;
view.render(); view.render();
view.on('send', this.handleAudioCapture.bind(this)); view.on('send', this.handleAudioCapture.bind(this));
view.on('confirm', this.handleAudioConfirm.bind(this));
view.on('closed', this.endCaptureAudio.bind(this)); view.on('closed', this.endCaptureAudio.bind(this));
view.$el.appendTo(this.$('.capture-audio')); view.$el.appendTo(this.$('.capture-audio'));
view.$('.finish').focus(); view.$('.finish').focus();
@ -1671,6 +1679,19 @@
this.disableMessageField(); this.disableMessageField();
this.$('.microphone').hide(); this.$('.microphone').hide();
}, },
handleAudioConfirm(blob, lostFocus) {
const dialog = new Whisper.ConfirmationDialogView({
cancelText: i18n('discard'),
message: lostFocus ? i18n('voiceRecordingInterruptedBlur') : i18n('voiceRecordingInterruptedMax'),
okText: i18n('sendAnyway'),
resolve: async () => {
await this.handleAudioCapture(blob);
},
});
this.$el.prepend(dialog.el);
dialog.focusCancel();
},
async handleAudioCapture(blob) { async handleAudioCapture(blob) {
if (this.hasFiles()) { if (this.hasFiles()) {
throw new Error('A voice note cannot be sent with other attachments'); throw new Error('A voice note cannot be sent with other attachments');

View file

@ -29,6 +29,8 @@
close: 'close', close: 'close',
}, },
onSwitchAway() { onSwitchAway() {
this.lostFocus = true;
this.recorder.finishRecording();
this.close(); this.close();
}, },
handleKeyDown(event) { handleKeyDown(event) {
@ -89,11 +91,14 @@
handleBlob(recorder, blob) { handleBlob(recorder, blob) {
if (blob && this.clickedFinish) { if (blob && this.clickedFinish) {
this.trigger('send', blob); this.trigger('send', blob);
} else if (blob) {
this.trigger('confirm', blob, this.lostFocus);
} else { } else {
this.close(); this.close();
} }
}, },
start() { start() {
this.lostFocus = false;
this.clickedFinish = false; this.clickedFinish = false;
this.context = new AudioContext(); this.context = new AudioContext();
this.input = this.context.createGain(); this.input = this.context.createGain();
@ -103,6 +108,7 @@
}); });
this.recorder.onComplete = this.handleBlob.bind(this); this.recorder.onComplete = this.handleBlob.bind(this);
this.recorder.onError = this.onError.bind(this); this.recorder.onError = this.onError.bind(this);
this.recorder.onTimeout = this.onTimeout.bind(this);
navigator.webkitGetUserMedia( navigator.webkitGetUserMedia(
{ audio: true }, { audio: true },
stream => { stream => {
@ -113,6 +119,10 @@
); );
this.recorder.startRecording(); this.recorder.startRecording();
}, },
onTimeout() {
this.recorder.finishRecording();
this.close();
},
onError(error) { onError(error) {
// Protect against out-of-band errors, which can happen if the user revokes media // Protect against out-of-band errors, which can happen if the user revokes media
// permissions after successfully accessing the microphone. // permissions after successfully accessing the microphone.

View file

@ -793,7 +793,7 @@
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "js/views/recorder_view.js", "path": "js/views/recorder_view.js",
"line": " this.$('.time').text(`${minutes}:${seconds}`);", "line": " this.$('.time').text(`${minutes}:${seconds}`);",
"lineNumber": 49, "lineNumber": 50,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z", "updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input" "reasonDetail": "Protected from arbitrary input"
@ -802,7 +802,7 @@
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "js/views/recorder_view.js", "path": "js/views/recorder_view.js",
"line": " $(window).off('blur', this.onSwitchAwayBound);", "line": " $(window).off('blur', this.onSwitchAwayBound);",
"lineNumber": 80, "lineNumber": 81,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2018-10-11T19:22:47.331Z", "updated": "2018-10-11T19:22:47.331Z",
"reasonDetail": "Operating on already-existing DOM elements" "reasonDetail": "Operating on already-existing DOM elements"