From 70a5715b3d79438588b048692f4e9ad5f0fa1e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Jun 2021 14:46:41 +0200 Subject: [PATCH] Support hangup reasons 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 | 4 + src/components/structures/CallEventGrouper.ts | 4 + src/components/views/messages/CallEvent.tsx | 104 +++++++++++++----- 3 files changed, 87 insertions(+), 25 deletions(-) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 9f61295a5a..2e36daccfa 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -53,5 +53,9 @@ limitations under the License. .mx_CallEvent_content_callBack { margin-left: 10px; // To match mx_callEvent } + + .mx_CallEvent_content_tooltip { + margin-right: 5px; + } } } diff --git a/src/components/structures/CallEventGrouper.ts b/src/components/structures/CallEventGrouper.ts index c53efadd7a..15de2dcaf7 100644 --- a/src/components/structures/CallEventGrouper.ts +++ b/src/components/structures/CallEventGrouper.ts @@ -82,6 +82,10 @@ export default class CallEventGrouper extends EventEmitter { return this.state; } + public getHangupReason(): string | null { + return this.events.find((event) => event.getType() === EventType.CallHangup)?.getContent()?.reason; + } + /** * Returns true if there are only events from the other side - we missed the call */ diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index fbc653a8ca..a4c0d02797 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -22,6 +22,7 @@ import MemberAvatar from '../avatars/MemberAvatar'; import CallEventGrouper, { CallEventGrouperEvent, CustomCallState } from '../../structures/CallEventGrouper'; import FormButton from '../elements/FormButton'; import { CallState } from 'matrix-js-sdk/src/webrtc/call'; +import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip'; interface IProps { mxEvent: MatrixEvent; @@ -35,7 +36,6 @@ interface IState { const TEXTUAL_STATES: Map = new Map([ [CallState.Connected, _td("Connected")], [CallState.Connecting, _td("Connecting")], - [CallState.Ended, _td("This call has ended")], ]); export default class CallEvent extends React.Component { @@ -59,53 +59,107 @@ export default class CallEvent extends React.Component { this.setState({callState: newState}); } - render() { - const event = this.props.mxEvent; - const sender = event.sender ? event.sender.name : event.getSender(); - - const state = this.state.callState; - let content; + private renderContent(state: CallState | CustomCallState): JSX.Element { if (state === CallState.Ringing) { - content = ( + return (
); - } else if (Array.from(TEXTUAL_STATES.keys()).includes(state)) { - content = ( + } + if (state === CallState.Ended) { + const hangupReason = this.props.callEventGrouper.getHangupReason(); + + if (["user_hangup", "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 :( + // https://github.com/vector-im/riot-android/issues/2623 + // Also the correct hangup code as of VoIP v1 (with underscore) + // Also, if we don't have a reason + return ( +
+ { _t("This call has ended") } +
+ ); + } + + let reason; + if (hangupReason === "ice_failed") { + // We couldn't establish a connection at all + reason = _t("Could not connect media"); + } else if (hangupReason === "ice_timeout") { + // We established a connection but it died + reason = _t("Connection failed"); + } else if (hangupReason === "user_media_failed") { + // The other side couldn't open capture devices + reason = _t("Their device couldn't start the camera or microphone"); + } else if (hangupReason === "unknown_error") { + // An error code the other side doesn't have a way to express + // (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 === "invite_timeout") { + reason = _t("No answer"); + } else { + reason = _t('Unknown failure: %(reason)s)', {reason: hangupReason}); + } + + return ( +
+ + { _t("This call has failed") } +
+ ); + } + if (Array.from(TEXTUAL_STATES.keys()).includes(state)) { + return (
{ TEXTUAL_STATES.get(state) }
); - } else if (state === CustomCallState.Missed) { - content = ( + } + if (state === CustomCallState.Missed) { + return (
{ _t("You missed this call") }
); - } else { - content = ( -
- { "The call is in an unknown state!" } -
- ); } + // XXX: Should we translate this? + return ( +
+ { "The call is in an unknown state!" } +
+ ); + } + + render() { + const event = this.props.mxEvent; + const sender = event.sender ? event.sender.name : event.getSender(); + const callType = this.props.callEventGrouper.isVoice() ? _t("Voice call") : _t("Video call"); + const content = this.renderContent(this.state.callState); + return (
@@ -119,7 +173,7 @@ export default class CallEvent extends React.Component { { sender }
- { this.props.callEventGrouper.isVoice() ? _t("Voice call") : _t("Video call") } + { callType }