diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 0c1b41ca38..5aaaa292d1 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -14,126 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_CallEvent { +.mx_CallEvent_wrapper { display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; + justify-content: center; + width: 100%; - background-color: $dark-panel-bg-color; - border-radius: 8px; - margin: 10px auto; - width: 75%; - box-sizing: border-box; - height: 60px; - - &.mx_CallEvent_voice { - .mx_CallEvent_type_icon::before, - .mx_CallEvent_content_button_callBack span::before, - .mx_CallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); - } - } - - &.mx_CallEvent_video { - .mx_CallEvent_type_icon::before, - .mx_CallEvent_content_button_callBack span::before, - .mx_CallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); - } - } - - &.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 { + .mx_CallEvent { + position: relative; display: flex; flex-direction: row; align-items: center; - margin-left: 12px; + justify-content: space-between; - .mx_CallEvent_info_basic { - display: flex; - flex-direction: column; - margin-left: 10px; // To match mx_CallEvent - - .mx_CallEvent_sender { - font-weight: 600; - font-size: 1.5rem; - line-height: 1.8rem; - margin-bottom: 3px; - } - - .mx_CallEvent_type { - font-weight: 400; - color: $secondary-fg-color; - font-size: 1.2rem; - line-height: $font-13px; - display: flex; - align-items: center; - - .mx_CallEvent_type_icon { - height: 13px; - width: 13px; - margin-right: 5px; - - &::before { - content: ''; - position: absolute; - height: 13px; - width: 13px; - background-color: $tertiary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - } - } - } - } - } - - .mx_CallEvent_content { - display: flex; - flex-direction: row; - align-items: center; - color: $secondary-fg-color; - margin-right: 16px; - - .mx_CallEvent_content_button { - height: 24px; - padding: 0px 12px; - margin-left: 8px; - - span { - padding: 8px 0; - display: flex; - align-items: center; - - &::before { - content: ''; - display: inline-block; - background-color: $button-fg-color; - mask-position: center; - mask-repeat: no-repeat; - mask-size: 16px; - width: 16px; - height: 16px; - margin-right: 8px; - } - } - } - - .mx_CallEvent_content_button_reject span::before { - mask-image: url('$(res)/img/element-icons/call/hangup.svg'); - } - - .mx_CallEvent_content_tooltip { - margin-right: 5px; - } + background-color: $dark-panel-bg-color; + border-radius: 8px; + width: 75%; + box-sizing: border-box; + height: 60px; .mx_CallEvent_iconButton { display: inline-flex; @@ -158,5 +55,146 @@ limitations under the License. .mx_CallEvent_unSilence::before { mask-image: url('$(res)/img/voip/un-silence.svg'); } + + &.mx_CallEvent_voice { + .mx_CallEvent_type_icon::before, + .mx_CallEvent_content_button_callBack span::before, + .mx_CallEvent_content_button_answer span::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } + } + + &.mx_CallEvent_video { + .mx_CallEvent_type_icon::before, + .mx_CallEvent_content_button_callBack span::before, + .mx_CallEvent_content_button_answer span::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } + } + + &.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; + align-items: center; + margin-left: 12px; + + .mx_CallEvent_info_basic { + display: flex; + flex-direction: column; + margin-left: 10px; // To match mx_CallEvent + + .mx_CallEvent_sender { + font-weight: 600; + font-size: 1.5rem; + line-height: 1.8rem; + margin-bottom: 3px; + } + + .mx_CallEvent_type { + font-weight: 400; + color: $secondary-fg-color; + font-size: 1.2rem; + line-height: $font-13px; + display: flex; + align-items: center; + + .mx_CallEvent_type_icon { + height: 13px; + width: 13px; + margin-right: 5px; + + &::before { + content: ''; + position: absolute; + height: 13px; + width: 13px; + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + } + } + } + } + } + + .mx_CallEvent_content { + display: flex; + flex-direction: row; + align-items: center; + color: $secondary-fg-color; + margin-right: 16px; + + .mx_CallEvent_content_button { + height: 24px; + padding: 0px 12px; + margin-left: 8px; + + span { + padding: 8px 0; + display: flex; + align-items: center; + + &::before { + content: ''; + display: inline-block; + background-color: $button-fg-color; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 16px; + width: 16px; + height: 16px; + margin-right: 8px; + } + } + } + + .mx_CallEvent_content_button_reject span::before { + mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + } + + .mx_CallEvent_content_tooltip { + margin-right: 5px; + } + } + + &.mx_CallEvent_narrow { + height: unset; + width: 290px; + flex-direction: column; + align-items: unset; + gap: 16px; + + .mx_CallEvent_iconButton { + position: absolute; + margin-right: 0; + top: 12px; + right: 12px; + height: 16px; + width: 16px; + display: flex; + } + + .mx_CallEvent_info { + margin-top: 12px; + margin-right: 12px; + + .mx_CallEvent_sender { + margin-bottom: 8px; + } + } + + .mx_CallEvent_content { + margin-left: 54px; // mx_CallEvent margin (12px) + avatar (32px) + mx_CallEvent_info_basic margin (10px) + margin-bottom: 16px; + } + } } } diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index a204907caa..822d99d5a6 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { createRef } from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t, _td } from '../../../languageHandler'; @@ -26,6 +26,8 @@ import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip'; import classNames from 'classnames'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; +const MAX_NON_NARROW_WIDTH = 400 / 70 * 100; + interface IProps { mxEvent: MatrixEvent; callEventGrouper: CallEventGrouper; @@ -34,6 +36,7 @@ interface IProps { interface IState { callState: CallState | CustomCallState; silenced: boolean; + narrow: boolean; } const TEXTUAL_STATES: Map = new Map([ @@ -41,26 +44,42 @@ const TEXTUAL_STATES: Map = new Map([ [CallState.Connecting, _td("Connecting")], ]); -export default class CallEvent extends React.Component { +export default class CallEvent extends React.PureComponent { + private wrapperElement = createRef(); + private resizeObserver: ResizeObserver; + constructor(props: IProps) { super(props); this.state = { callState: this.props.callEventGrouper.state, silenced: false, + narrow: false, }; } componentDidMount() { this.props.callEventGrouper.addListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.addListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + + this.resizeObserver = new ResizeObserver(this.resizeObserverCallback); + this.resizeObserver.observe(this.wrapperElement.current); } componentWillUnmount() { this.props.callEventGrouper.removeListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.removeListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + + this.resizeObserver.disconnect(); } + private resizeObserverCallback = (entries: ResizeObserverEntry[]): void => { + const wrapperElementEntry = entries.find((entry) => entry.target === this.wrapperElement.current); + if (!wrapperElementEntry) return; + + this.setState({ narrow: wrapperElementEntry.contentRect.width < MAX_NON_NARROW_WIDTH }); + }; + private onSilencedChanged = (newState) => { this.setState({ silenced: newState }); }; @@ -81,21 +100,32 @@ export default class CallEvent extends React.Component { ); } + private renderSilenceIcon(): JSX.Element { + const silenceClass = classNames({ + "mx_CallEvent_iconButton": true, + "mx_CallEvent_unSilence": this.state.silenced, + "mx_CallEvent_silence": !this.state.silenced, + }); + + return ( + + ); + } + private renderContent(state: CallState | CustomCallState): JSX.Element { if (state === CallState.Ringing) { - const silenceClass = classNames({ - "mx_CallEvent_iconButton": true, - "mx_CallEvent_unSilence": this.state.silenced, - "mx_CallEvent_silence": !this.state.silenced, - }); + let silenceIcon; + if (!this.state.narrow) { + silenceIcon = this.renderSilenceIcon(); + } return (
- + { silenceIcon } { const callState = this.state.callState; const hangupReason = this.props.callEventGrouper.hangupReason; const content = this.renderContent(callState); - const className = classNames({ - mx_CallEvent: true, + const className = classNames("mx_CallEvent", { mx_CallEvent_voice: isVoice, mx_CallEvent_video: !isVoice, + mx_CallEvent_narrow: this.state.narrow, mx_CallEvent_missed: ( callState === CustomCallState.Missed || (callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout) ), }); + let silenceIcon; + if (this.state.narrow && this.state.callState === CallState.Ringing) { + silenceIcon = this.renderSilenceIcon(); + } return ( -
-
- -
-
- { sender } -
-
-
- { callType } +
+
+ { silenceIcon } +
+ +
+
+ { sender } +
+
+
+ { callType } +
+ { content }
- { content }
); }