From c9215678314f5b34e0272de7f84cb882988e8c76 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 12 Nov 2020 18:09:56 +0000 Subject: [PATCH 01/12] WIP: the new call views work now just need to add the buttons and then get rid of the status bar --- res/css/views/avatars/_BaseAvatar.scss | 2 +- res/css/views/voip/_CallView.scss | 145 +++++++++++---- res/css/views/voip/_VideoFeed.scss | 15 +- src/components/views/avatars/PulsedAvatar.tsx | 28 --- src/components/views/voip/CallPreview.tsx | 12 +- src/components/views/voip/CallView.tsx | 175 ++++++++++++------ src/components/views/voip/IncomingCallBox.tsx | 13 +- src/components/views/voip/VideoFeed.tsx | 4 +- src/i18n/strings/en_EN.json | 5 +- 9 files changed, 257 insertions(+), 142 deletions(-) delete mode 100644 src/components/views/avatars/PulsedAvatar.tsx diff --git a/res/css/views/avatars/_BaseAvatar.scss b/res/css/views/avatars/_BaseAvatar.scss index 1a1e14e7ac..cbddd97e18 100644 --- a/res/css/views/avatars/_BaseAvatar.scss +++ b/res/css/views/avatars/_BaseAvatar.scss @@ -41,7 +41,7 @@ limitations under the License. .mx_BaseAvatar_image { object-fit: cover; - border-radius: 40px; + border-radius: 125px; vertical-align: top; background-color: $avatar-bg-color; } diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 2aeaaa87dc..eadad831ab 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -15,47 +15,46 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_CallView { + border-radius: 10px; + background-color: $input-lighter-bg-color; + padding-left: 8px; + padding-right: 8px; + // XXX: CallContainer sets pointer-events: none - should probably be set back in a better place + pointer-events: initial; +} + +.mx_CallView_large { + padding-bottom: 10px; + + .mx_CallView_voice { + height: 360px; + } +} + +.mx_CallView_pip { + width: 320px; + + .mx_CallView_voice { + height: 180px + } +} + +.mx_CallView_voice { + display: flex; + align-items: center; + justify-content: center; + background-color: $inverted-bg-color; +} + +/* .mx_CallView_voice { - background-color: $accent-color; - color: $accent-fg-color; - cursor: pointer; padding: 6px; font-weight: bold; - border-radius: 8px; min-width: 200px; - - display: flex; - align-items: center; - - img { - margin: 4px; - margin-right: 10px; - } - - > div { - display: flex; - flex-direction: column; - // Hacky vertical align - padding-top: 3px; - } - - > div > p, - > div > h1 { - padding: 0; - margin: 0; - font-size: $font-13px; - line-height: $font-15px; - } - - > div > p { - font-weight: bold; - } - - > * { - flex-grow: 0; - flex-shrink: 0; - } + text-align: center; + vertical-align: middle; } .mx_CallView_hangup { @@ -92,6 +91,7 @@ limitations under the License. background-color: $primary-fg-color; } } +*/ .mx_CallView_video { width: 100%; @@ -99,3 +99,76 @@ limitations under the License. z-index: 30; } +.mx_CallView_header { + height: 44px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: left; + + .mx_BaseAvatar { + margin-right: 12px; + } +} + +.mx_CallView_header_callType { + font-weight: bold; + vertical-align: middle; +} + +.mx_CallView_header_controls { + margin-left: auto; +} + +.mx_CallView_header_control_fullscreen { + display: inline-block; + vertical-align: middle; + cursor: pointer; + + &::before { + content: ''; + display: inline-block; + height: 20px; + width: 20px; + vertical-align: middle; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + mask-image: url('$(res)/img/element-icons/call/fullscreen.svg'); + } +} + +.mx_CallView_header_roomName { + font-weight: bold; + font-size: 12px; + line-height: initial; +} + +.mx_CallView_header_callTypeSmall { + font-size: 12px; + color: $secondary-fg-color; + line-height: initial; +} + +.mx_CallView_header_phoneIcon { + display: inline-block; + margin-right: 6px; + height: 16px; + width: 16px; + vertical-align: middle; + + &::before { + content: ''; + display: inline-block; + vertical-align: top; + + height: 16px; + width: 16px; + background-color: $warning-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } +} diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index e5e3587dac..1368ead02f 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VideoFeed video { +/*.mx_VideoFeed video { width: 100%; -} +}*/ .mx_VideoFeed_remote { width: 100%; @@ -28,16 +28,17 @@ limitations under the License. width: 25%; height: 25%; position: absolute; - left: 10px; - bottom: 10px; + right: 10px; + top: 10px; z-index: 100; + border-radius: 4px; } -.mx_VideoFeed_local video { +/*.mx_VideoFeed_local video { width: auto; height: 100%; -} +}*/ -.mx_VideoFeed_mirror video { +.mx_VideoFeed_mirror { transform: scale(-1, 1); } diff --git a/src/components/views/avatars/PulsedAvatar.tsx b/src/components/views/avatars/PulsedAvatar.tsx deleted file mode 100644 index b4e876b9f6..0000000000 --- a/src/components/views/avatars/PulsedAvatar.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -interface IProps { -} - -const PulsedAvatar: React.FC = (props) => { - return
- {props.children} -
; -}; - -export default PulsedAvatar; diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 3d9235792b..8e1b0dd963 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -26,6 +26,15 @@ import PersistentApp from "../elements/PersistentApp"; import SettingsStore from "../../../settings/SettingsStore"; import { CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; +const SHOW_CALL_IN_STATES = [ + CallState.Connected, + CallState.InviteSent, + CallState.Connecting, + CallState.CreateAnswer, + CallState.CreateOffer, + CallState.WaitLocalMedia, +]; + interface IProps { } @@ -94,14 +103,13 @@ export default class CallPreview extends React.Component { const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId); const showCall = ( this.state.activeCall && - this.state.activeCall.state === CallState.Connected && + SHOW_CALL_IN_STATES.includes(this.state.activeCall.state) && !callForRoom ); if (showCall) { return ( diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 653a72cca0..7288cd1d5b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -21,10 +21,8 @@ import dis from '../../../dispatcher/dispatcher'; import CallHandler from '../../../CallHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import AccessibleButton from '../elements/AccessibleButton'; import VideoFeed, { VideoFeedType } from "./VideoFeed"; import RoomAvatar from "../avatars/RoomAvatar"; -import PulsedAvatar from '../avatars/PulsedAvatar'; import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { CallEvent } from 'matrix-js-sdk/src/webrtc/call'; @@ -43,9 +41,6 @@ interface IProps { // in a way that is likely to cause a resize. onResize?: any; - // classname applied to view, - className?: string; - // Whether to show the hang up icon:W showHangup?: boolean; } @@ -85,7 +80,7 @@ function exitFullscreen() { export default class CallView extends React.Component { private dispatcherRef: string; - private container = createRef(); + private contentRef = createRef(); constructor(props: IProps) { super(props); @@ -111,11 +106,11 @@ export default class CallView extends React.Component { private onAction = (payload) => { switch (payload.action) { case 'video_fullscreen': { - if (!this.container.current) { + if (!this.contentRef.current) { return; } if (payload.fullscreen) { - requestFullscreen(this.container.current); + requestFullscreen(this.contentRef.current); } else if (getFullScreenElement()) { exitFullscreen(); } @@ -144,11 +139,6 @@ export default class CallView extends React.Component { if (this.props.room) { const roomId = this.props.room.roomId; call = CallHandler.sharedInstance().getCallForRoom(roomId); - - // We don't currently show voice calls in this view when in the room: - // they're represented in the room status bar at the bottom instead - // (but this will all change with the new designs) - if (call && call.type == CallType.Voice) call = null; } else { call = CallHandler.sharedInstance().getAnyActiveCall(); // Ignore calls if we can't get the room associated with them. @@ -160,7 +150,7 @@ export default class CallView extends React.Component { } } - if (call && call.state == CallState.Ended) return null; + if (call && [CallState.Ended, CallState.Ringing].includes(call.state)) return null; return call; } @@ -177,51 +167,91 @@ export default class CallView extends React.Component { }); }; + private onFullscreenClick = () => { + dis.dispatch({ + action: 'video_fullscreen', + fullscreen: true, + }); + }; + public render() { - let view: React.ReactNode; + if (!this.state.call) return null; - if (this.state.call) { - if (this.state.call.type === "voice") { - const client = MatrixClientPeg.get(); - const callRoom = client.getRoom(this.state.call.roomId); + const client = MatrixClientPeg.get(); + const callRoom = client.getRoom(this.state.call.roomId); - let caption = _t("Active call"); - if (this.state.isLocalOnHold) { - // we currently have no UI for holding / unholding a call (apart from slash - // commands) so we don't disintguish between when we've put the call on hold - // (ie. we'd show an unhold button) and when the other side has put us on hold - // (where obviously we would not show such a button). - caption = _t("Call Paused"); - } + //const callControls =
- view = - - - -
-

{callRoom.name}

-

{ caption }

-
-
; - } else { - // For video calls, we currently ignore the call hold state altogether - // (the video will just go black) + //
; - // if we're fullscreen, we don't want to set a maxHeight on the video element. - const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight; - view =
- - -
; - } + // The 'content' for the call, ie. the videos for a video call and profile picture + // for voice calls (fills the bg) + let contentView: React.ReactNode; + + if (this.state.call.type === CallType.Video) { + // if we're fullscreen, we don't want to set a maxHeight on the video element. + const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight; + contentView =
+ + +
; + } else { + const avatarSize = this.props.room ? 200 : 75; + contentView =
+ +
; } + /* + if (!this.props.room) { + const client = MatrixClientPeg.get(); + const callRoom = client.getRoom(this.state.call.roomId); + + let caption = _t("Active call"); + if (this.state.isLocalOnHold) { + // we currently have no UI for holding / unholding a call (apart from slash + // commands) so we don't disintguish between when we've put the call on hold + // (ie. we'd show an unhold button) and when the other side has put us on hold + // (where obviously we would not show such a button). + caption = _t("Call Paused"); + } + + view = + + + +
+

{callRoom.name}

+

{ caption }

+
+
; + } else { + // For video calls, we currently ignore the call hold state altogether + // (the video will just go black) + + // if we're fullscreen, we don't want to set a maxHeight on the video element. + const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight; + view =
+ + +
; + } + */ + + + /* let hangup: React.ReactNode; if (this.props.showHangup) { hangup =
{ }} />; } + */ - return
- {view} - {hangup} + const callTypeText = this.state.call.type === CallType.Video ? _t("Video Call") : _t("Voice Call"); + let myClassName; + + let fullScreenButton; + if (this.state.call.type === CallType.Video) { + fullScreenButton =
; + } + + const headerControls =
+ {fullScreenButton} +
; + + let header: React.ReactNode; + if (this.props.room) { + header =
+
+ {callTypeText} + {headerControls} +
; + myClassName = 'mx_CallView_large'; + } else { + header =
+ +
+
{callRoom.name}
+
{callTypeText}
+
+ {headerControls} +
; + myClassName = 'mx_CallView_pip'; + } + + return
+ {header} + {contentView}
; } } diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 355dff9ff6..0403a9eb75 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -22,7 +22,6 @@ import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; import { ActionPayload } from '../../../dispatcher/payloads'; import CallHandler from '../../../CallHandler'; -import PulsedAvatar from '../avatars/PulsedAvatar'; import RoomAvatar from '../avatars/RoomAvatar'; import FormButton from '../elements/FormButton'; import { CallState } from 'matrix-js-sdk/lib/webrtc/call'; @@ -108,13 +107,11 @@ export default class IncomingCallBox extends React.Component { return
- - - +

{caller}

{incomingCallText}

diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 9dba9fa9c8..5fb71a6d69 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -73,8 +73,6 @@ export default class VideoFeed extends React.Component { let videoStyle = {}; if (this.props.maxHeight) videoStyle = { maxHeight: this.props.maxHeight }; - return
- -
; + return