WIP: the new call views work now

just need to add the buttons and then get rid of the status bar
pull/21833/head
David Baker 2020-11-12 18:09:56 +00:00
parent b3a80d5a50
commit c921567831
9 changed files with 257 additions and 142 deletions

View File

@ -41,7 +41,7 @@ limitations under the License.
.mx_BaseAvatar_image { .mx_BaseAvatar_image {
object-fit: cover; object-fit: cover;
border-radius: 40px; border-radius: 125px;
vertical-align: top; vertical-align: top;
background-color: $avatar-bg-color; background-color: $avatar-bg-color;
} }

View File

@ -15,47 +15,46 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { .mx_CallView_voice {
background-color: $accent-color;
color: $accent-fg-color;
cursor: pointer;
padding: 6px; padding: 6px;
font-weight: bold; font-weight: bold;
border-radius: 8px;
min-width: 200px; min-width: 200px;
text-align: center;
display: flex; vertical-align: middle;
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;
}
} }
.mx_CallView_hangup { .mx_CallView_hangup {
@ -92,6 +91,7 @@ limitations under the License.
background-color: $primary-fg-color; background-color: $primary-fg-color;
} }
} }
*/
.mx_CallView_video { .mx_CallView_video {
width: 100%; width: 100%;
@ -99,3 +99,76 @@ limitations under the License.
z-index: 30; 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');
}
}

View File

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_VideoFeed video { /*.mx_VideoFeed video {
width: 100%; width: 100%;
} }*/
.mx_VideoFeed_remote { .mx_VideoFeed_remote {
width: 100%; width: 100%;
@ -28,16 +28,17 @@ limitations under the License.
width: 25%; width: 25%;
height: 25%; height: 25%;
position: absolute; position: absolute;
left: 10px; right: 10px;
bottom: 10px; top: 10px;
z-index: 100; z-index: 100;
border-radius: 4px;
} }
.mx_VideoFeed_local video { /*.mx_VideoFeed_local video {
width: auto; width: auto;
height: 100%; height: 100%;
} }*/
.mx_VideoFeed_mirror video { .mx_VideoFeed_mirror {
transform: scale(-1, 1); transform: scale(-1, 1);
} }

View File

@ -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<IProps> = (props) => {
return <div className="mx_PulsedAvatar">
{props.children}
</div>;
};
export default PulsedAvatar;

View File

@ -26,6 +26,15 @@ import PersistentApp from "../elements/PersistentApp";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; 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 { interface IProps {
} }
@ -94,14 +103,13 @@ export default class CallPreview extends React.Component<IProps, IState> {
const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId); const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId);
const showCall = ( const showCall = (
this.state.activeCall && this.state.activeCall &&
this.state.activeCall.state === CallState.Connected && SHOW_CALL_IN_STATES.includes(this.state.activeCall.state) &&
!callForRoom !callForRoom
); );
if (showCall) { if (showCall) {
return ( return (
<CallView <CallView
className="mx_CallPreview"
onClick={this.onCallViewClick} onClick={this.onCallViewClick}
showHangup={true} showHangup={true}
/> />

View File

@ -21,10 +21,8 @@ import dis from '../../../dispatcher/dispatcher';
import CallHandler from '../../../CallHandler'; import CallHandler from '../../../CallHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import VideoFeed, { VideoFeedType } from "./VideoFeed"; import VideoFeed, { VideoFeedType } from "./VideoFeed";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import PulsedAvatar from '../avatars/PulsedAvatar';
import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { CallEvent } 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. // in a way that is likely to cause a resize.
onResize?: any; onResize?: any;
// classname applied to view,
className?: string;
// Whether to show the hang up icon:W // Whether to show the hang up icon:W
showHangup?: boolean; showHangup?: boolean;
} }
@ -85,7 +80,7 @@ function exitFullscreen() {
export default class CallView extends React.Component<IProps, IState> { export default class CallView extends React.Component<IProps, IState> {
private dispatcherRef: string; private dispatcherRef: string;
private container = createRef<HTMLDivElement>(); private contentRef = createRef<HTMLDivElement>();
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -111,11 +106,11 @@ export default class CallView extends React.Component<IProps, IState> {
private onAction = (payload) => { private onAction = (payload) => {
switch (payload.action) { switch (payload.action) {
case 'video_fullscreen': { case 'video_fullscreen': {
if (!this.container.current) { if (!this.contentRef.current) {
return; return;
} }
if (payload.fullscreen) { if (payload.fullscreen) {
requestFullscreen(this.container.current); requestFullscreen(this.contentRef.current);
} else if (getFullScreenElement()) { } else if (getFullScreenElement()) {
exitFullscreen(); exitFullscreen();
} }
@ -144,11 +139,6 @@ export default class CallView extends React.Component<IProps, IState> {
if (this.props.room) { if (this.props.room) {
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
call = CallHandler.sharedInstance().getCallForRoom(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 { } else {
call = CallHandler.sharedInstance().getAnyActiveCall(); call = CallHandler.sharedInstance().getAnyActiveCall();
// Ignore calls if we can't get the room associated with them. // Ignore calls if we can't get the room associated with them.
@ -160,7 +150,7 @@ export default class CallView extends React.Component<IProps, IState> {
} }
} }
if (call && call.state == CallState.Ended) return null; if (call && [CallState.Ended, CallState.Ringing].includes(call.state)) return null;
return call; return call;
} }
@ -177,51 +167,91 @@ export default class CallView extends React.Component<IProps, IState> {
}); });
}; };
private onFullscreenClick = () => {
dis.dispatch({
action: 'video_fullscreen',
fullscreen: true,
});
};
public render() { public render() {
let view: React.ReactNode; if (!this.state.call) return null;
if (this.state.call) { const client = MatrixClientPeg.get();
if (this.state.call.type === "voice") { 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"); //const callControls = <div className="mx_CallView_callControls">
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 = <AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}> //</div>;
<PulsedAvatar>
<RoomAvatar
room={callRoom}
height={35}
width={35}
/>
</PulsedAvatar>
<div>
<h1>{callRoom.name}</h1>
<p>{ caption }</p>
</div>
</AccessibleButton>;
} 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. // The 'content' for the call, ie. the videos for a video call and profile picture
const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight; // for voice calls (fills the bg)
view = <div className="mx_CallView_video" onClick={this.props.onClick}> let contentView: React.ReactNode;
<VideoFeed type={VideoFeedType.Remote} call={this.state.call} onResize={this.props.onResize}
maxHeight={maxVideoHeight} if (this.state.call.type === CallType.Video) {
/> // if we're fullscreen, we don't want to set a maxHeight on the video element.
<VideoFeed type={VideoFeedType.Local} call={this.state.call} /> const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight;
</div>; contentView = <div className="mx_CallView_video" ref={this.contentRef}>
} <VideoFeed type={VideoFeedType.Remote} call={this.state.call} onResize={this.props.onResize}
maxHeight={maxVideoHeight}
/>
<VideoFeed type={VideoFeedType.Local} call={this.state.call} />
</div>;
} else {
const avatarSize = this.props.room ? 200 : 75;
contentView = <div className="mx_CallView_voice">
<RoomAvatar
room={callRoom}
height={avatarSize}
width={avatarSize}
/>
</div>;
} }
/*
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 = <AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}>
<PulsedAvatar>
<RoomAvatar
room={callRoom}
height={35}
width={35}
/>
</PulsedAvatar>
<div>
<h1>{callRoom.name}</h1>
<p>{ caption }</p>
</div>
</AccessibleButton>;
} 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 = <div className="mx_CallView_video" onClick={this.props.onClick}>
<VideoFeed type={VideoFeedType.Remote} call={this.state.call} onResize={this.props.onResize}
maxHeight={maxVideoHeight}
/>
<VideoFeed type={VideoFeedType.Local} call={this.state.call} />
</div>;
}
*/
/*
let hangup: React.ReactNode; let hangup: React.ReactNode;
if (this.props.showHangup) { if (this.props.showHangup) {
hangup = <div hangup = <div
@ -234,10 +264,45 @@ export default class CallView extends React.Component<IProps, IState> {
}} }}
/>; />;
} }
*/
return <div className={this.props.className} ref={this.container}> const callTypeText = this.state.call.type === CallType.Video ? _t("Video Call") : _t("Voice Call");
{view} let myClassName;
{hangup}
let fullScreenButton;
if (this.state.call.type === CallType.Video) {
fullScreenButton = <div className="mx_CallView_header_control_fullscreen"
onClick={this.onFullscreenClick} title={_t("Fill screen")}
/>;
}
const headerControls = <div className="mx_CallView_header_controls">
{fullScreenButton}
</div>;
let header: React.ReactNode;
if (this.props.room) {
header = <div className="mx_CallView_header">
<div className="mx_CallView_header_phoneIcon"></div>
<span className="mx_CallView_header_callType">{callTypeText}</span>
{headerControls}
</div>;
myClassName = 'mx_CallView_large';
} else {
header = <div className="mx_CallView_header">
<RoomAvatar room={callRoom} height={32} width={32} />
<div>
<div className="mx_CallView_header_roomName">{callRoom.name}</div>
<div className="mx_CallView_header_callTypeSmall">{callTypeText}</div>
</div>
{headerControls}
</div>;
myClassName = 'mx_CallView_pip';
}
return <div className={"mx_CallView " + myClassName}>
{header}
{contentView}
</div>; </div>;
} }
} }

View File

@ -22,7 +22,6 @@ import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads'; import { ActionPayload } from '../../../dispatcher/payloads';
import CallHandler from '../../../CallHandler'; import CallHandler from '../../../CallHandler';
import PulsedAvatar from '../avatars/PulsedAvatar';
import RoomAvatar from '../avatars/RoomAvatar'; import RoomAvatar from '../avatars/RoomAvatar';
import FormButton from '../elements/FormButton'; import FormButton from '../elements/FormButton';
import { CallState } from 'matrix-js-sdk/lib/webrtc/call'; import { CallState } from 'matrix-js-sdk/lib/webrtc/call';
@ -108,13 +107,11 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
return <div className="mx_IncomingCallBox"> return <div className="mx_IncomingCallBox">
<div className="mx_IncomingCallBox_CallerInfo"> <div className="mx_IncomingCallBox_CallerInfo">
<PulsedAvatar> <RoomAvatar
<RoomAvatar room={room}
room={room} height={32}
height={32} width={32}
width={32} />
/>
</PulsedAvatar>
<div> <div>
<h1>{caller}</h1> <h1>{caller}</h1>
<p>{incomingCallText}</p> <p>{incomingCallText}</p>

View File

@ -73,8 +73,6 @@ export default class VideoFeed extends React.Component<IProps> {
let videoStyle = {}; let videoStyle = {};
if (this.props.maxHeight) videoStyle = { maxHeight: this.props.maxHeight }; if (this.props.maxHeight) videoStyle = { maxHeight: this.props.maxHeight };
return <div className={classnames(videoClasses)}> return <video className={classnames(videoClasses)} ref={this.vid} style={videoStyle} />;
<video ref={this.vid} style={videoStyle}></video>
</div>;
} }
} }

View File

@ -529,8 +529,8 @@
"When rooms are upgraded": "When rooms are upgraded", "When rooms are upgraded": "When rooms are upgraded",
"My Ban List": "My Ban List", "My Ban List": "My Ban List",
"This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!", "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!",
"Active call": "Active call", "Video Call": "Video Call",
"Call Paused": "Call Paused", "Voice Call": "Voice Call",
"Unknown caller": "Unknown caller", "Unknown caller": "Unknown caller",
"Incoming voice call": "Incoming voice call", "Incoming voice call": "Incoming voice call",
"Incoming video call": "Incoming video call", "Incoming video call": "Incoming video call",
@ -2123,6 +2123,7 @@
"%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|one": "<resendText>Resend message</resendText> or <cancelText>cancel message</cancelText> now.", "%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|one": "<resendText>Resend message</resendText> or <cancelText>cancel message</cancelText> now.",
"Calling...": "Calling...", "Calling...": "Calling...",
"Call connecting...": "Call connecting...", "Call connecting...": "Call connecting...",
"Active call": "Active call",
"Starting camera...": "Starting camera...", "Starting camera...": "Starting camera...",
"Starting microphone...": "Starting microphone...", "Starting microphone...": "Starting microphone...",
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.",