From 4aa8d8ff21d8ac7a084c161b73fe175c9b8de3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 08:36:41 +0200 Subject: [PATCH 01/10] Add missed call icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/img/voip/missed-video.svg | 3 +++ res/img/voip/missed-voice.svg | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 res/img/voip/missed-video.svg create mode 100644 res/img/voip/missed-voice.svg diff --git a/res/img/voip/missed-video.svg b/res/img/voip/missed-video.svg new file mode 100644 index 00000000000..a2f3bc73ac5 --- /dev/null +++ b/res/img/voip/missed-video.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/voip/missed-voice.svg b/res/img/voip/missed-voice.svg new file mode 100644 index 00000000000..5e3993584e0 --- /dev/null +++ b/res/img/voip/missed-voice.svg @@ -0,0 +1,4 @@ + + + + From aedd535c9253869fe4d882a1bf5962e5d1409a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 08:37:25 +0200 Subject: [PATCH 02/10] Correct iconography for missed calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_CallEvent.scss | 8 ++++++++ src/components/views/messages/CallEvent.tsx | 1 + 2 files changed, 9 insertions(+) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 54c7df3e0bc..f2ceb086c4f 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -43,6 +43,14 @@ limitations under the License. } } + &.mx_CallEvent_voice.mx_CallEvent_missed .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/missed-voice.svg'); + } + + &.mx_CallEvent_video.mx_CallEvent_missed .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/missed-video.svg'); + } + .mx_CallEvent_info { display: flex; flex-direction: row; diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index c81055bfb7a..0831b9b21df 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -191,6 +191,7 @@ export default class CallEvent extends React.Component { mx_CallEvent: true, mx_CallEvent_voice: isVoice, mx_CallEvent_video: !isVoice, + mx_CallEvent_missed: this.state.callState === CustomCallState.Missed, }); return ( From 4784ddc1755dc1363be1ca812320ec813adf19e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 08:57:29 +0200 Subject: [PATCH 03/10] Add state for when the remote doesn't pick up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 24 +++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 0831b9b21df..356963d5a4d 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -116,6 +116,19 @@ export default class CallEvent extends React.Component { { _t("This call has ended") } ); + } else if (hangupReason === CallErrorCode.InviteTimeout) { + return ( +
+ { _t("The remote side didn't pick up") } + + { _t("Call again") } + +
+ ); } let reason; @@ -133,8 +146,6 @@ export default class CallEvent extends React.Component { // (as opposed to an error code they gave but we don't know about, // in which case we show the error code) reason = _t("An unknown error occurred"); - } else if (hangupReason === CallErrorCode.InviteTimeout) { - reason = _t("No answer"); } else if (hangupReason === CallErrorCode.UserBusy) { reason = _t("The user you called is busy."); } else { @@ -186,12 +197,17 @@ export default class CallEvent extends React.Component { const sender = event.sender ? event.sender.name : event.getSender(); const isVoice = this.props.callEventGrouper.isVoice; const callType = isVoice ? _t("Voice call") : _t("Video call"); - const content = this.renderContent(this.state.callState); + const callState = this.state.callState; + const hangupReason = this.props.callEventGrouper.hangupReason; + const content = this.renderContent(callState); const className = classNames({ mx_CallEvent: true, mx_CallEvent_voice: isVoice, mx_CallEvent_video: !isVoice, - mx_CallEvent_missed: this.state.callState === CustomCallState.Missed, + mx_CallEvent_missed: ( + callState === CustomCallState.Missed || + (callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout) + ), }); return ( From f63b6fc0a93f41c259a398077e4f18a9e7201f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 10:28:27 +0200 Subject: [PATCH 04/10] Support for getting reject info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/CallEventGrouper.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/structures/CallEventGrouper.ts b/src/components/structures/CallEventGrouper.ts index 384f20cd4e9..ce3b5308585 100644 --- a/src/components/structures/CallEventGrouper.ts +++ b/src/components/structures/CallEventGrouper.ts @@ -74,6 +74,14 @@ export default class CallEventGrouper extends EventEmitter { return this.hangup?.getContent()?.reason; } + public get rejectParty(): string { + return this.reject?.getSender(); + } + + public get gotRejected(): boolean { + return Boolean(this.reject); + } + /** * Returns true if there are only events from the other side - we missed the call */ From f7087d5148fc7ab7e88f6295f115d32a9e54082f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 10:30:53 +0200 Subject: [PATCH 05/10] Add declined state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 41 +++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 356963d5a4d..b093d184367 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -25,6 +25,7 @@ import { CallErrorCode, CallState } from 'matrix-js-sdk/src/webrtc/call'; import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip'; import classNames from 'classnames'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; interface IProps { mxEvent: MatrixEvent; @@ -69,6 +70,18 @@ export default class CallEvent extends React.Component { this.setState({ callState: newState }); }; + private renderCallBackButton(text: string): JSX.Element { + return ( + + { text } + + ); + } + private renderContent(state: CallState | CustomCallState): JSX.Element { if (state === CallState.Ringing) { const silenceClass = classNames({ @@ -103,8 +116,18 @@ export default class CallEvent extends React.Component { } if (state === CallState.Ended) { const hangupReason = this.props.callEventGrouper.hangupReason; + const gotRejected = this.props.callEventGrouper.gotRejected; + const rejectParty = this.props.callEventGrouper.rejectParty; - if ([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason) { + if (gotRejected) { + const weDeclinedCall = MatrixClientPeg.get().getUserId() === rejectParty; + return ( +
+ { weDeclinedCall ? _t("You declined this call") : _t("The other party declined this call") } + { this.renderCallBackButton(weDeclinedCall ? _t("Call back") : _t("Call again")) } +
+ ); + } else if (([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason)) { // workaround for https://github.com/vector-im/element-web/issues/5178 // it seems Android randomly sets a reason of "user hangup" which is // interpreted as an error code :( @@ -120,13 +143,7 @@ export default class CallEvent extends React.Component { return (
{ _t("The remote side didn't pick up") } - - { _t("Call again") } - + { this.renderCallBackButton(_t("Call again")) }
); } @@ -174,13 +191,7 @@ export default class CallEvent extends React.Component { return (
{ _t("You missed this call") } - - { _t("Call back") } - + { this.renderCallBackButton(_t("Call back")) }
); } From 7c805501bc7327ba7463619c2ab6a588809842d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 10:56:04 +0200 Subject: [PATCH 06/10] Cleanup hangup dialogs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e7ba1aa9fb2..cfc4de2721e 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -491,28 +491,17 @@ export default class CallHandler extends EventEmitter { break; case CallState.Ended: { - Analytics.trackEvent('voip', 'callEnded', 'hangupReason', call.hangupReason); + const hangupReason = call.hangupReason; + Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason); this.removeCallForRoom(mappedRoomId); - if (oldState === CallState.InviteSent && ( - call.hangupParty === CallParty.Remote || - (call.hangupParty === CallParty.Local && call.hangupReason === CallErrorCode.InviteTimeout) - )) { + if (oldState === CallState.InviteSent && call.hangupParty === CallParty.Remote) { this.play(AudioID.Busy); let title; let description; - if (call.hangupReason === CallErrorCode.UserHangup) { - title = _t("Call Declined"); - description = _t("The other party declined the call."); - } else if (call.hangupReason === CallErrorCode.UserBusy) { + if (call.hangupReason === CallErrorCode.UserBusy) { title = _t("User Busy"); description = _t("The user you called is busy."); - } else if (call.hangupReason === CallErrorCode.InviteTimeout) { - title = _t("Call Failed"); - // XXX: full stop appended as some relic here, but these - // strings need proper input from design anyway, so let's - // not change this string until we have a proper one. - description = _t('The remote side failed to pick up') + '.'; - } else { + } else if (hangupReason && ![CallErrorCode.UserHangup, "user hangup"].includes(hangupReason)) { title = _t("Call Failed"); description = _t("The call could not be established"); } @@ -521,7 +510,7 @@ export default class CallHandler extends EventEmitter { title, description, }); } else if ( - call.hangupReason === CallErrorCode.AnsweredElsewhere && oldState === CallState.Connecting + hangupReason === CallErrorCode.AnsweredElsewhere && oldState === CallState.Connecting ) { Modal.createTrackedDialog('Call Handler', 'Call Failed', ErrorDialog, { title: _t("Answered Elsewhere"), From a2c703516321717b2c429cd932cc8a21b8640967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 10:57:46 +0200 Subject: [PATCH 07/10] Tweak copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index b093d184367..2c9448fcfe2 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -123,7 +123,7 @@ export default class CallEvent extends React.Component { const weDeclinedCall = MatrixClientPeg.get().getUserId() === rejectParty; return (
- { weDeclinedCall ? _t("You declined this call") : _t("The other party declined this call") } + { weDeclinedCall ? _t("You declined this call") : _t("The remote side declined this call") } { this.renderCallBackButton(weDeclinedCall ? _t("Call back") : _t("Call again")) }
); From 456800af21884d72a68189ee6f1a43cf074602a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 10:57:51 +0200 Subject: [PATCH 08/10] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b36910b41bc..510e2c12e5c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -35,11 +35,8 @@ "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", "Dismiss": "Dismiss", "Call Failed": "Call Failed", - "Call Declined": "Call Declined", - "The other party declined the call.": "The other party declined the call.", "User Busy": "User Busy", "The user you called is busy.": "The user you called is busy.", - "The remote side failed to pick up": "The remote side failed to pick up", "The call could not be established": "The call could not be established", "Answered Elsewhere": "Answered Elsewhere", "The call was answered on another device.": "The call was answered on another device.", @@ -1862,16 +1859,19 @@ "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", "Connected": "Connected", + "You declined this call": "You declined this call", + "The remote side declined this call": "The remote side declined this call", + "Call back": "Call back", + "Call again": "Call again", "This call has ended": "This call has ended", + "The remote side didn't pick up": "The remote side didn't pick up", "Could not connect media": "Could not connect media", "Connection failed": "Connection failed", "Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone", "An unknown error occurred": "An unknown error occurred", - "No answer": "No answer", "Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)", "This call has failed": "This call has failed", "You missed this call": "You missed this call", - "Call back": "Call back", "The call is in an unknown state!": "The call is in an unknown state!", "Sunday": "Sunday", "Monday": "Monday", From 8c635884d906e54ee344463d22e4191a839e910f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 11:05:26 +0200 Subject: [PATCH 09/10] Add comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index cfc4de2721e..72be3f8e67c 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -498,6 +498,7 @@ export default class CallHandler extends EventEmitter { this.play(AudioID.Busy); let title; let description; + // TODO: We should either do away with these or figure out a copy for each code (expect user_hangup...) if (call.hangupReason === CallErrorCode.UserBusy) { title = _t("User Busy"); description = _t("The user you called is busy."); From 29b0d03fe4837f13bce751b4d8dd5719eac963d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 21:27:22 +0200 Subject: [PATCH 10/10] Update copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 4 ++-- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 2c9448fcfe2..2de66f897a7 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -123,7 +123,7 @@ export default class CallEvent extends React.Component { const weDeclinedCall = MatrixClientPeg.get().getUserId() === rejectParty; return (
- { weDeclinedCall ? _t("You declined this call") : _t("The remote side declined this call") } + { weDeclinedCall ? _t("You declined this call") : _t("They declined this call") } { this.renderCallBackButton(weDeclinedCall ? _t("Call back") : _t("Call again")) }
); @@ -142,7 +142,7 @@ export default class CallEvent extends React.Component { } else if (hangupReason === CallErrorCode.InviteTimeout) { return (
- { _t("The remote side didn't pick up") } + { _t("They didn't pick up") } { this.renderCallBackButton(_t("Call again")) }
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 510e2c12e5c..102a481f520 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1860,11 +1860,11 @@ "Compare emoji": "Compare emoji", "Connected": "Connected", "You declined this call": "You declined this call", - "The remote side declined this call": "The remote side declined this call", + "They declined this call": "They declined this call", "Call back": "Call back", "Call again": "Call again", "This call has ended": "This call has ended", - "The remote side didn't pick up": "The remote side didn't pick up", + "They didn't pick up": "They didn't pick up", "Could not connect media": "Could not connect media", "Connection failed": "Connection failed", "Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone",