Calling: Lobby

This commit is contained in:
Josh Perez 2020-10-07 21:25:33 -04:00 committed by Josh Perez
parent 358ee4ab72
commit 59a181bd30
21 changed files with 1146 additions and 388 deletions

View File

@ -1150,6 +1150,34 @@
"message": "Calling",
"description": "Header for calling options on the settings screen"
},
"calling__start": {
"message": "Start Call",
"description": "Button label in the call lobby for starting a call"
},
"calling__join": {
"message": "Join Call",
"description": "Button label in the call lobby for joining a call"
},
"calling__button--video-off": {
"message": "Turn off camera",
"description": "Button tooltip label for turning off the camera"
},
"calling__button--video-on": {
"message": "Turn on camera",
"description": "Button tooltip label for turning on the camera"
},
"calling__button--audio-off": {
"message": "Turn off microphone",
"description": "Button tooltip label for turning off the microphone"
},
"calling__button--audio-on": {
"message": "Turn on microphone",
"description": "Button tooltip label for turning on the microphone"
},
"calling__your-video-is-off": {
"message": "Your video is off",
"description": "Label in the calling lobby indicating that your camera is off"
},
"alwaysRelayCallsDescription": {
"message": "Always relay calls",
"description": "Description of the always relay calls setting"
@ -2889,6 +2917,10 @@
"message": "Settings",
"description": "Title for device selection settings"
},
"calling__participants": {
"message": "Participants",
"description": "Title for participants list toggle"
},
"calling__pip": {
"message": "Picture-in-picture",
"description": "Title for picture-in-picture toggle"

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>group-solid-24</title><path d="M10.25,16.25m-2.5-8.5a3.5,3.5,0,1,0,3.5,3.5A3.5,3.5,0,0,0,7.75,7.75Zm3.539,7.027a4.988,4.988,0,0,1-7.078,0A4.735,4.735,0,0,0,1,19.25V20a1,1,0,0,0,1,1H13.5a1,1,0,0,0,1-1v-.75A4.735,4.735,0,0,0,11.289,14.777ZM16.25,2.75a3.5,3.5,0,1,0,3.5,3.5A3.5,3.5,0,0,0,16.25,2.75Zm3.539,7.027a4.989,4.989,0,0,1-7.074.005c-.063.022-.131.033-.193.057a4.675,4.675,0,0,1-.333,3.663A6.294,6.294,0,0,1,15.078,16H22a1,1,0,0,0,1-1v-.75A4.735,4.735,0,0,0,19.789,9.777Z"/></svg>

After

Width:  |  Height:  |  Size: 574 B

View File

@ -5815,6 +5815,65 @@ button.module-image__border-overlay:focus {
}
// Module: Calling
.module-calling {
&__container {
align-items: center;
background-color: $color-gray-95;
display: flex;
flex-direction: column;
height: 100vh;
position: relative;
width: 100%;
}
&__header {
color: #ffffff;
font-style: normal;
padding-bottom: 24px;
padding-top: 24px;
text-align: center;
text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
top: 0;
width: 100%;
&--header-name {
font-weight: 600;
font-size: 15px;
line-height: 21px;
letter-spacing: -0.009em;
}
}
&__buttons {
bottom: 0;
display: flex;
justify-content: center;
padding-bottom: 32px;
padding-top: 32px;
position: absolute;
text-align: center;
width: 100%;
}
&__background {
background-repeat: no-repeat;
background-size: cover;
border-radius: 4px 4px 0 0;
height: 100%;
position: absolute;
width: 100%;
&--blur {
backdrop-filter: blur(7px);
backface-visibility: hidden;
background-color: $color-black-alpha-40;
border-radius: 4px 4px 0 0;
height: 100%;
position: absolute;
width: 100%;
}
}
}
.module-incoming-call {
align-items: center;
@ -5862,114 +5921,140 @@ button.module-image__border-overlay:focus {
margin-right: 16px;
}
.module-incoming-call__button--accept-video-as-audio {
background-color: $color-gray-45;
.module-incoming-call__button {
&--accept-video-as-audio {
background-color: $color-gray-45;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
div {
@include color-svg(
'../images/icons/v2/video-off-solid-24.svg',
$color-white
);
height: 24px;
width: 24px;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
&--accept-video {
background-color: $color-accent-green;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
div {
@include color-svg('../images/icons/v2/video-solid-24.svg', $color-white);
height: 24px;
width: 24px;
}
}
div {
@include color-svg(
'../images/icons/v2/video-off-solid-24.svg',
$color-white
);
height: 24px;
width: 24px;
&--accept-audio {
background-color: $color-accent-green;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
div {
@include color-svg(
'../images/icons/v2/phone-right-solid-24.svg',
$color-white
);
height: 24px;
width: 24px;
}
}
&--decline {
background-color: $color-accent-red;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
div {
@include color-svg('../images/icons/v2/phone-down-24.svg', $color-white);
height: 24px;
width: 24px;
}
}
}
.module-incoming-call__button--accept-video {
background-color: $color-accent-green;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
div {
@include color-svg('../images/icons/v2/video-solid-24.svg', $color-white);
height: 24px;
width: 24px;
}
}
.module-incoming-call__button--accept-audio {
background-color: $color-accent-green;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
div {
@include color-svg(
'../images/icons/v2/phone-right-solid-24.svg',
$color-white
);
height: 24px;
width: 24px;
}
}
.module-incoming-call__button--decline {
background-color: $color-accent-red;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
}
}
@include mouse-mode {
&:hover {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
div {
@include color-svg('../images/icons/v2/phone-down-24.svg', $color-white);
height: 24px;
width: 24px;
}
}
.module-incoming-call__icon,
.module-ongoing-call__icon {
.module-incoming-call__button,
.module-calling-button__icon {
align-items: center;
border-radius: 40px;
border: none;
display: flex;
height: 40px;
justify-content: center;
margin-left: 24px;
margin-left: 12px;
margin-right: 12px;
outline: none;
width: 40px;
}
.module-ongoing-call__icon {
.module-calling-button {
&__participants {
@include color-svg('../images/icons/v2/group-solid-24.svg', $color-white);
height: 22px;
width: 22px;
}
&__settings {
@include color-svg(
'../images/icons/v2/settings-solid-16.svg',
$color-white
);
height: 22px;
width: 22px;
}
&__pip {
@include color-svg('../images/icons/v2/collapse-24.svg', $color-white);
height: 22px;
width: 22px;
}
}
.module-calling-button__icon {
border-radius: 56px;
height: 56px;
width: 56px;
@ -6036,79 +6121,6 @@ button.module-image__border-overlay:focus {
}
}
.module-ongoing-call,
.module-call-need-permission-screen {
background-color: $color-gray-95;
height: 100vh;
width: 100%;
position: relative;
}
.module-ongoing-call__remote-video-enabled {
background-color: $color-gray-95;
height: 100vh;
width: 100%;
}
.module-ongoing-call__remote-video-disabled {
background-color: $color-gray-95;
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.module-ongoing-call__local-video {
transform: rotateY(180deg);
background-color: transparent;
bottom: 160px;
height: 152px;
position: absolute;
right: 32px;
width: 210px;
}
.module-ongoing-call__header {
background: linear-gradient($color-black-alpha-40, transparent);
padding-bottom: 24px;
padding-top: 24px;
position: absolute;
text-align: center;
top: 0;
width: 100%;
font-style: normal;
color: #ffffff;
text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
}
.module-ongoing-call__header-name {
font-weight: 600;
font-size: 15px;
line-height: 21px;
letter-spacing: -0.009em;
}
.module-ongoing-call__header-message {
font-weight: normal;
font-size: 13px;
line-height: 18px;
letter-spacing: -0.0025em;
}
.module-ongoing-call__actions {
background: linear-gradient(transparent, $color-black-alpha-40);
bottom: 0;
display: flex;
justify-content: center;
padding-bottom: 32px;
padding-top: 32px;
position: absolute;
text-align: center;
width: 100%;
}
@keyframes module-ongoing-call__controls--fade-in {
from {
opacity: 0;
@ -6127,48 +6139,129 @@ button.module-image__border-overlay:focus {
}
}
.module-ongoing-call__controls--fadeIn {
animation: {
name: module-ongoing-call__controls--fade-in;
duration: 400ms;
timing-function: $ease-out-expo;
fill-mode: forwards;
.module-ongoing-call {
&__remote-video-enabled {
background-color: $color-gray-95;
height: 100vh;
width: 100%;
}
&__remote-video-disabled {
background-color: $color-gray-95;
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
&__local-video {
background-color: transparent;
bottom: 160px;
height: 152px;
position: absolute;
right: 32px;
transform: rotateY(180deg);
width: 210px;
}
&__header {
position: absolute;
}
&__header-message {
font-weight: normal;
font-size: 13px;
line-height: 18px;
letter-spacing: -0.0025em;
}
&__actions {
background: linear-gradient(transparent, $color-black-alpha-40);
bottom: 0;
display: flex;
justify-content: center;
padding-bottom: 32px;
padding-top: 32px;
position: absolute;
text-align: center;
width: 100%;
}
&__controls--fadeIn {
animation: {
name: module-ongoing-call__controls--fade-in;
duration: 400ms;
timing-function: $ease-out-expo;
fill-mode: forwards;
}
}
&__controls--fadeOut {
animation: {
name: module-ongoing-call__controls--fade-out;
duration: 1200ms;
timing-function: $ease-out-expo;
fill-mode: forwards;
}
}
}
.module-ongoing-call__controls--fadeOut {
animation: {
name: module-ongoing-call__controls--fade-out;
duration: 1200ms;
timing-function: $ease-out-expo;
fill-mode: forwards;
}
}
.module-ongoing-call__settings {
.module-calling-tools {
display: flex;
justify-content: flex-end;
position: absolute;
top: 25px;
right: 65px;
top: 24px;
width: 100%;
&--button {
@include color-svg(
'../images/icons/v2/settings-solid-16.svg',
$color-white
);
height: 22px;
width: 22px;
&__button {
margin-right: 25px;
}
}
.module-ongoing-call__pip {
position: absolute;
top: 25px;
right: 25px;
.module-calling-lobby {
&__actions {
flex: 0 0 100px;
}
&--button {
@include color-svg('../images/icons/v2/collapse-24.svg', $color-white);
height: 22px;
width: 22px;
&__button {
margin-left: 8px;
margin-right: 8px;
width: 160px;
}
&__video {
@include font-body-2;
align-items: center;
background-color: $color-gray-80;
border-radius: 8px;
color: $color-white;
display: flex;
flex-direction: column;
flex: 1 1 auto;
justify-content: center;
margin-bottom: 24px;
margin-top: 24px;
max-width: 640px;
overflow: hidden;
position: relative;
width: 100%;
}
&__video-off {
&--icon {
@include color-svg(
'../images/icons/v2/video-off-solid-24.svg',
$color-white
);
height: 24px;
margin-bottom: 8px;
width: 24px;
}
&--text {
z-index: 1;
}
}
}
@ -6203,25 +6296,6 @@ button.module-image__border-overlay:focus {
width: 32px;
}
&--background {
background-repeat: no-repeat;
background-size: cover;
border-radius: 4px 4px 0 0;
height: 100%;
position: absolute;
width: 100%;
}
&--blur {
backdrop-filter: blur(7px);
backface-visibility: hidden;
background-color: $color-black-alpha-40;
border-radius: 4px 4px 0 0;
height: 100%;
position: absolute;
width: 100%;
}
&--avatar img {
-webkit-user-drag: none;
-webkit-user-select: none;
@ -6270,11 +6344,15 @@ button.module-image__border-overlay:focus {
}
.module-call-need-permission-screen {
align-items: center;
background-color: $color-gray-95;
color: $color-gray-05;
display: flex;
flex-direction: column;
height: 100vh;
justify-content: center;
align-items: center;
color: $color-gray-05;
position: relative;
width: 100%;
&__text {
margin: 2em 1em;
@ -9342,6 +9420,52 @@ button.module-image__border-overlay:focus {
justify-content: flex-end;
}
.module-button {
&__gray {
@include font-body-1-bold;
background-color: $color-gray-45;
border-radius: 4px;
border: none;
color: $color-white;
outline: none;
padding: 7px 14px;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
}
&__green {
@include font-body-1-bold;
background-color: $color-accent-green;
border-radius: 4px;
border: none;
color: $color-white;
outline: none;
padding: 7px 14px;
@include keyboard-mode {
&:focus {
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
}
}
}
}
.module-background-color {
&__default {
background-color: $color-black-alpha-40;
}
@each $color, $value in $conversation-colors {
&__#{$color} {
background-color: $value;
}
}
}
/* Third-party module: react-tooltip-lite */
.react-tooltip-lite {

View File

@ -0,0 +1,36 @@
import React from 'react';
import classNames from 'classnames';
import { ColorType } from '../types/Colors';
export type PropsType = {
avatarPath?: string;
children: React.ReactNode;
color?: ColorType;
};
export const CallBackgroundBlur = ({
avatarPath,
children,
color,
}: PropsType): JSX.Element => {
const backgroundProps = avatarPath
? {
style: {
backgroundImage: `url("${avatarPath}")`,
},
}
: {
className: classNames(
'module-calling__background',
`module-background-color__${color || 'default'}`
),
};
return (
<>
<div className="module-calling__background" {...backgroundProps} />
<div className="module-calling__background--blur" />
{children}
</>
);
};

View File

@ -15,6 +15,7 @@ const callDetails = {
isIncoming: true,
isVideoCall: true,
id: '3051234567',
avatarPath: undefined,
color: 'ultramarine' as ColorType,
title: 'Rick Sanchez',
@ -27,6 +28,7 @@ const defaultProps = {
acceptCall: action('accept-call'),
callDetails,
callState: CallState.Accepted,
cancelCall: action('cancel-call'),
closeNeedPermissionScreen: action('close-need-permission-screen'),
declineCall: action('decline-call'),
hangUp: action('hang-up'),
@ -41,6 +43,8 @@ const defaultProps = {
setLocalVideo: action('set-local-video'),
setRendererCanvas: action('set-renderer-canvas'),
settingsDialogOpen: false,
startCall: action('start-call'),
toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'),
toggleSettings: action('toggle-settings'),
};

View File

@ -1,22 +1,26 @@
import React from 'react';
import { CallingPip } from './CallingPip';
import { CallNeedPermissionScreen } from './CallNeedPermissionScreen';
import { CallingLobby } from './CallingLobby';
import { CallScreen, PropsType as CallScreenPropsType } from './CallScreen';
import {
IncomingCallBar,
PropsType as IncomingCallBarPropsType,
} from './IncomingCallBar';
import { CallState, CallEndedReason } from '../types/Calling';
import { CallDetailsType } from '../state/ducks/calling';
import { CallDetailsType, OutgoingCallType } from '../state/ducks/calling';
type CallManagerPropsType = {
callDetails?: CallDetailsType;
callState?: CallState;
callEndedReason?: CallEndedReason;
callState?: CallState;
cancelCall: () => void;
pip: boolean;
closeNeedPermissionScreen: () => void;
renderDeviceSelection: () => JSX.Element;
settingsDialogOpen: boolean;
startCall: (payload: OutgoingCallType) => void;
toggleParticipants: () => void;
};
type PropsType = IncomingCallBarPropsType &
@ -28,6 +32,7 @@ export const CallManager = ({
callDetails,
callState,
callEndedReason,
cancelCall,
closeNeedPermissionScreen,
declineCall,
hangUp,
@ -42,10 +47,12 @@ export const CallManager = ({
setLocalVideo,
setRendererCanvas,
settingsDialogOpen,
startCall,
toggleParticipants,
togglePip,
toggleSettings,
}: PropsType): JSX.Element | null => {
if (!callDetails || !callState) {
if (!callDetails) {
return null;
}
const incoming = callDetails.isIncoming;
@ -68,6 +75,31 @@ export const CallManager = ({
return null;
}
if (!callState) {
return (
<>
<CallingLobby
callDetails={callDetails}
callState={callState}
hasLocalAudio={hasLocalAudio}
hasLocalVideo={hasLocalVideo}
i18n={i18n}
isGroupCall={false}
onCallCanceled={cancelCall}
onJoinCall={() => {
startCall({ callDetails });
}}
setLocalPreview={setLocalPreview}
setLocalAudio={setLocalAudio}
setLocalVideo={setLocalVideo}
toggleParticipants={toggleParticipants}
toggleSettings={toggleSettings}
/>
{settingsDialogOpen && renderDeviceSelection()}
</>
);
}
if (outgoing || ongoing) {
if (pip) {
return (

View File

@ -5,7 +5,7 @@ import { action } from '@storybook/addon-actions';
import { CallState } from '../types/Calling';
import { ColorType } from '../types/Colors';
import { CallScreen } from './CallScreen';
import { CallScreen, PropsType } from './CallScreen';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
@ -16,6 +16,7 @@ const callDetails = {
isIncoming: true,
isVideoCall: true,
id: '3051234567',
avatarPath: undefined,
color: 'ultramarine' as ColorType,
title: 'Rick Sanchez',
@ -24,13 +25,20 @@ const callDetails = {
profileName: 'Rick Sanchez',
};
const defaultProps = {
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
callDetails,
callState: CallState.Accepted,
callState: select(
'callState',
CallState,
overrideProps.callState || CallState.Accepted
),
hangUp: action('hang-up'),
hasLocalAudio: true,
hasLocalVideo: true,
hasRemoteVideo: true,
hasLocalAudio: boolean('hasLocalAudio', overrideProps.hasLocalAudio || false),
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
hasRemoteVideo: boolean(
'hasRemoteVideo',
overrideProps.hasRemoteVideo || false
),
i18n,
setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'),
@ -38,82 +46,38 @@ const defaultProps = {
setRendererCanvas: action('set-renderer-canvas'),
togglePip: action('toggle-pip'),
toggleSettings: action('toggle-settings'),
};
});
const permutations = [
{
title: 'Call Screen',
props: {},
},
{
title: 'Call Screen (Pre-ring)',
props: {
callState: CallState.Prering,
},
},
{
title: 'Call Screen (Ringing)',
props: {
callState: CallState.Ringing,
},
},
{
title: 'Call Screen (Reconnecting)',
props: {
callState: CallState.Reconnecting,
},
},
{
title: 'Call Screen (Ended)',
props: {
callState: CallState.Ended,
},
},
{
title: 'Calling (no local audio)',
props: {
...defaultProps,
hasLocalAudio: false,
},
},
{
title: 'Calling (no local video)',
props: {
...defaultProps,
hasLocalVideo: false,
},
},
{
title: 'Calling (no remote video)',
props: {
...defaultProps,
hasRemoteVideo: false,
},
},
];
const story = storiesOf('Components/CallScreen', module);
storiesOf('Components/CallScreen', module)
.add('Knobs Playground', () => {
const callState = select('callState', CallState, CallState.Accepted);
const hasLocalAudio = boolean('hasLocalAudio', true);
const hasLocalVideo = boolean('hasLocalVideo', true);
const hasRemoteVideo = boolean('hasRemoteVideo', true);
story.add('Default', () => {
return <CallScreen {...createProps()} />;
});
return (
<CallScreen
{...defaultProps}
callState={callState}
hasLocalAudio={hasLocalAudio}
hasLocalVideo={hasLocalVideo}
hasRemoteVideo={hasRemoteVideo}
/>
);
})
.add('Iterations', () => {
return permutations.map(({ props, title }) => (
<>
<h3>{title}</h3>
<CallScreen {...defaultProps} {...props} />
</>
));
});
story.add('Pre-Ring', () => {
return <CallScreen {...createProps({ callState: CallState.Prering })} />;
});
story.add('Ringing', () => {
return <CallScreen {...createProps({ callState: CallState.Ringing })} />;
});
story.add('Reconnecting', () => {
return <CallScreen {...createProps({ callState: CallState.Reconnecting })} />;
});
story.add('Ended', () => {
return <CallScreen {...createProps({ callState: CallState.Ended })} />;
});
story.add('hasLocalAudio', () => {
return <CallScreen {...createProps({ hasLocalAudio: true })} />;
});
story.add('hasLocalVideo', () => {
return <CallScreen {...createProps({ hasLocalVideo: true })} />;
});
story.add('hasRemoteVideo', () => {
return <CallScreen {...createProps({ hasRemoteVideo: true })} />;
});

View File

@ -9,30 +9,10 @@ import {
SetRendererCanvasType,
} from '../state/ducks/calling';
import { Avatar } from './Avatar';
import { CallingButton, CallingButtonType } from './CallingButton';
import { CallState } from '../types/Calling';
import { LocalizerType } from '../types/Util';
type CallingButtonProps = {
classNameSuffix: string;
onClick: () => void;
};
const CallingButton = ({
classNameSuffix,
onClick,
}: CallingButtonProps): JSX.Element => {
const className = classNames(
'module-ongoing-call__icon',
`module-ongoing-call__icon${classNameSuffix}`
);
return (
<button type="button" className={className} onClick={onClick}>
<div />
</button>
);
};
export type PropsType = {
callDetails?: CallDetailsType;
callState?: CallState;
@ -137,10 +117,10 @@ export class CallScreen extends React.Component<PropsType, StateType> {
let eventHandled = false;
if (event.key === 'V') {
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
this.toggleVideo();
eventHandled = true;
} else if (event.key === 'M') {
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
this.toggleAudio();
eventHandled = true;
}
@ -225,42 +205,41 @@ export class CallScreen extends React.Component<PropsType, StateType> {
!showControls && !isAudioOnly && callState === CallState.Accepted,
});
const toggleAudioSuffix = hasLocalAudio
? '--audio--enabled'
: '--audio--disabled';
const toggleVideoSuffix = hasLocalVideo
? '--video--enabled'
: '--video--disabled';
const videoButtonType = hasLocalVideo
? CallingButtonType.VIDEO_ON
: CallingButtonType.VIDEO_OFF;
const audioButtonType = hasLocalAudio
? CallingButtonType.AUDIO_ON
: CallingButtonType.AUDIO_OFF;
return (
<div
className="module-ongoing-call"
className="module-calling__container"
onMouseMove={this.showControls}
role="group"
>
<div
className={classNames(
'module-calling__header',
'module-ongoing-call__header',
controlsFadeClass
)}
>
<div className="module-ongoing-call__header-name">
<div className="module-calling__header--header-name">
{callDetails.title}
</div>
{this.renderMessage(callState)}
<div className="module-ongoing-call__settings">
<div className="module-calling-tools">
<button
type="button"
aria-label={i18n('callingDeviceSelection__settings')}
className="module-ongoing-call__settings--button"
className="module-calling-tools__button module-calling-button__settings"
onClick={toggleSettings}
/>
</div>
<div className="module-ongoing-call__pip">
<button
type="button"
aria-label={i18n('calling__pip')}
className="module-ongoing-call__pip--button"
className="module-calling-tools__button module-calling-button__pip"
onClick={togglePip}
/>
</div>
@ -276,18 +255,24 @@ export class CallScreen extends React.Component<PropsType, StateType> {
)}
>
<CallingButton
classNameSuffix={toggleVideoSuffix}
buttonType={videoButtonType}
i18n={i18n}
onClick={this.toggleVideo}
tooltipDistance={24}
/>
<CallingButton
classNameSuffix={toggleAudioSuffix}
buttonType={audioButtonType}
i18n={i18n}
onClick={this.toggleAudio}
tooltipDistance={24}
/>
<CallingButton
classNameSuffix="--hangup"
buttonType={CallingButtonType.HANG_UP}
i18n={i18n}
onClick={() => {
hangUp({ callId: callDetails.callId });
}}
tooltipDistance={24}
/>
</div>
</div>

View File

@ -0,0 +1,76 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { number, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import {
CallingButton,
CallingButtonType,
PropsType,
TooltipDirection,
} from './CallingButton';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
buttonType: select(
'buttonType',
CallingButtonType,
overrideProps.buttonType || CallingButtonType.HANG_UP
),
i18n,
onClick: action('on-click'),
tooltipDirection: select(
'tooltipDirection',
TooltipDirection,
overrideProps.tooltipDirection || TooltipDirection.DOWN
),
tooltipDistance: number(
'tooltipDistance',
overrideProps.tooltipDistance || 16
),
});
const story = storiesOf('Components/CallingButton', module);
story.add('Default', () => {
const props = createProps();
return <CallingButton {...props} />;
});
story.add('Audio On', () => {
const props = createProps({
buttonType: CallingButtonType.AUDIO_ON,
});
return <CallingButton {...props} />;
});
story.add('Audio Off', () => {
const props = createProps({
buttonType: CallingButtonType.AUDIO_OFF,
});
return <CallingButton {...props} />;
});
story.add('Video On', () => {
const props = createProps({
buttonType: CallingButtonType.VIDEO_ON,
});
return <CallingButton {...props} />;
});
story.add('Video Off', () => {
const props = createProps({
buttonType: CallingButtonType.VIDEO_OFF,
});
return <CallingButton {...props} />;
});
story.add('Tooltip right', () => {
const props = createProps({
tooltipDirection: TooltipDirection.RIGHT,
});
return <CallingButton {...props} />;
});

View File

@ -0,0 +1,76 @@
import React from 'react';
import classNames from 'classnames';
import Tooltip from 'react-tooltip-lite';
import { LocalizerType } from '../types/Util';
export enum TooltipDirection {
UP = 'up',
RIGHT = 'right',
DOWN = 'down',
LEFT = 'left',
}
export enum CallingButtonType {
AUDIO_OFF = 'AUDIO_OFF',
AUDIO_ON = 'AUDIO_ON',
HANG_UP = 'HANG_UP',
VIDEO_OFF = 'VIDEO_OFF',
VIDEO_ON = 'VIDEO_ON',
}
export type PropsType = {
buttonType: CallingButtonType;
i18n: LocalizerType;
onClick: () => void;
tooltipDirection?: TooltipDirection;
tooltipDistance?: number;
};
export const CallingButton = ({
buttonType,
i18n,
onClick,
tooltipDirection = TooltipDirection.DOWN,
tooltipDistance = 16,
}: PropsType): JSX.Element => {
let classNameSuffix = '';
let tooltipContent = '';
if (buttonType === CallingButtonType.AUDIO_OFF) {
classNameSuffix = 'audio--disabled';
tooltipContent = i18n('calling__button--audio-on');
} else if (buttonType === CallingButtonType.AUDIO_ON) {
classNameSuffix = 'audio--enabled';
tooltipContent = i18n('calling__button--audio-off');
} else if (buttonType === CallingButtonType.VIDEO_OFF) {
classNameSuffix = 'video--disabled';
tooltipContent = i18n('calling__button--video-on');
} else if (buttonType === CallingButtonType.VIDEO_ON) {
classNameSuffix = 'video--enabled';
tooltipContent = i18n('calling__button--video-off');
} else if (buttonType === CallingButtonType.HANG_UP) {
classNameSuffix = 'hangup';
tooltipContent = i18n('calling__hangup');
}
const className = classNames(
'module-calling-button__icon',
`module-calling-button__icon--${classNameSuffix}`
);
return (
<button
aria-label={tooltipContent}
type="button"
className={className}
onClick={onClick}
>
<Tooltip
arrowSize={6}
content={tooltipContent}
direction={tooltipDirection}
distance={tooltipDistance}
hoverDelay={0}
/>
</button>
);
};

View File

@ -0,0 +1,59 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { ColorType } from '../types/Colors';
import { CallingLobby, PropsType } from './CallingLobby';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const callDetails = {
callId: 0,
isIncoming: true,
isVideoCall: true,
id: '3051234567',
avatarPath: undefined,
color: 'ultramarine' as ColorType,
title: 'Rick Sanchez',
name: 'Rick Sanchez',
phoneNumber: '3051234567',
profileName: 'Rick Sanchez',
};
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
callDetails,
hasLocalAudio: boolean('hasLocalAudio', overrideProps.hasLocalAudio || false),
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
i18n,
isGroupCall: boolean('isGroupCall', overrideProps.isGroupCall || false),
onCallCanceled: action('on-call-canceled'),
onJoinCall: action('on-join-call'),
setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'),
setLocalVideo: action('set-local-video'),
toggleParticipants: action('toggle-participants'),
toggleSettings: action('toggle-settings'),
});
const story = storiesOf('Components/CallingLobby', module);
story.add('Default', () => {
const props = createProps();
return <CallingLobby {...props} />;
});
story.add('Local Video', () => {
const props = createProps({
hasLocalVideo: true,
});
return <CallingLobby {...props} />;
});
story.add('Group Call', () => {
const props = createProps({ isGroupCall: true });
return <CallingLobby {...props} />;
});

View File

@ -0,0 +1,181 @@
import React from 'react';
import {
CallDetailsType,
SetLocalAudioType,
SetLocalPreviewType,
SetLocalVideoType,
} from '../state/ducks/calling';
import { CallState } from '../types/Calling';
import {
CallingButton,
CallingButtonType,
TooltipDirection,
} from './CallingButton';
import { CallBackgroundBlur } from './CallBackgroundBlur';
import { LocalizerType } from '../types/Util';
export type PropsType = {
callDetails: CallDetailsType;
callState?: CallState;
hasLocalAudio: boolean;
hasLocalVideo: boolean;
i18n: LocalizerType;
isGroupCall: boolean;
onCallCanceled: () => void;
onJoinCall: () => void;
setLocalAudio: (_: SetLocalAudioType) => void;
setLocalVideo: (_: SetLocalVideoType) => void;
setLocalPreview: (_: SetLocalPreviewType) => void;
toggleParticipants: () => void;
toggleSettings: () => void;
};
export const CallingLobby = ({
callDetails,
hasLocalAudio,
hasLocalVideo,
i18n,
isGroupCall = false,
onCallCanceled,
onJoinCall,
setLocalAudio,
setLocalPreview,
setLocalVideo,
toggleParticipants,
toggleSettings,
}: PropsType): JSX.Element => {
const localVideoRef = React.useRef(null);
const toggleAudio = React.useCallback((): void => {
if (!callDetails) {
return;
}
setLocalAudio({ enabled: !hasLocalAudio });
}, [callDetails, hasLocalAudio, setLocalAudio]);
const toggleVideo = React.useCallback((): void => {
if (!callDetails) {
return;
}
setLocalVideo({ enabled: !hasLocalVideo });
}, [callDetails, hasLocalVideo, setLocalVideo]);
React.useEffect(() => {
setLocalPreview({ element: localVideoRef });
return () => {
setLocalPreview({ element: undefined });
};
}, [setLocalPreview]);
React.useEffect(() => {
function handleKeyDown(event: KeyboardEvent): void {
let eventHandled = false;
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
toggleVideo();
eventHandled = true;
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
toggleAudio();
eventHandled = true;
}
if (eventHandled) {
event.preventDefault();
event.stopPropagation();
}
}
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [toggleVideo, toggleAudio]);
const videoButtonType = hasLocalVideo
? CallingButtonType.VIDEO_ON
: CallingButtonType.VIDEO_OFF;
const audioButtonType = hasLocalAudio
? CallingButtonType.AUDIO_ON
: CallingButtonType.AUDIO_OFF;
return (
<div className="module-calling__container">
<div className="module-calling__header">
<div className="module-calling__header--header-name">
{callDetails.title}
</div>
<div className="module-calling-tools">
{isGroupCall ? (
<button
type="button"
aria-label={i18n('calling__participants')}
className="module-calling-tools__button module-calling-button__participants"
onClick={toggleParticipants}
/>
) : null}
<button
type="button"
aria-label={i18n('callingDeviceSelection__settings')}
className="module-calling-tools__button module-calling-button__settings"
onClick={toggleSettings}
/>
</div>
</div>
<div className="module-calling-lobby__video">
{hasLocalVideo ? (
<video ref={localVideoRef} autoPlay />
) : (
<CallBackgroundBlur
avatarPath={callDetails.avatarPath}
color={callDetails.color}
>
<div className="module-calling-lobby__video-off--icon" />
<span className="module-calling-lobby__video-off--text">
{i18n('calling__your-video-is-off')}
</span>
</CallBackgroundBlur>
)}
<div className="module-calling__buttons">
<CallingButton
buttonType={videoButtonType}
i18n={i18n}
onClick={toggleVideo}
tooltipDirection={TooltipDirection.UP}
tooltipDistance={24}
/>
<CallingButton
buttonType={audioButtonType}
i18n={i18n}
onClick={toggleAudio}
tooltipDirection={TooltipDirection.UP}
tooltipDistance={24}
/>
</div>
</div>
<div className="module-calling-lobby__actions">
<button
className="module-button__gray module-calling-lobby__button"
onClick={onCallCanceled}
tabIndex={0}
type="button"
>
{i18n('cancel')}
</button>
<button
className="module-button__green module-calling-lobby__button"
onClick={onJoinCall}
tabIndex={0}
type="button"
>
{isGroupCall ? i18n('calling__join') : i18n('calling__start')}
</button>
</div>
</div>
);
};

View File

@ -15,6 +15,7 @@ const callDetails = {
isIncoming: true,
isVideoCall: true,
id: '3051234567',
avatarPath: undefined,
color: 'ultramarine' as ColorType,
title: 'Rick Sanchez',
@ -24,7 +25,7 @@ const callDetails = {
};
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
callDetails,
callDetails: overrideProps.callDetails || callDetails,
hangUp: action('hang-up'),
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
hasRemoteVideo: boolean(
@ -43,3 +44,23 @@ story.add('Default', () => {
const props = createProps();
return <CallingPip {...props} />;
});
story.add('Contact (with avatar)', () => {
const props = createProps({
callDetails: {
...callDetails,
avatarPath: 'https://www.fillmurray.com/64/64',
},
});
return <CallingPip {...props} />;
});
story.add('Contact (no color)', () => {
const props = createProps({
callDetails: {
...callDetails,
color: undefined,
},
});
return <CallingPip {...props} />;
});

View File

@ -6,6 +6,7 @@ import {
SetRendererCanvasType,
} from '../state/ducks/calling';
import { Avatar } from './Avatar';
import { CallBackgroundBlur } from './CallBackgroundBlur';
import { LocalizerType } from '../types/Util';
function renderAvatar(
@ -21,35 +22,24 @@ function renderAvatar(
title,
} = callDetails;
const backgroundStyle = avatarPath
? {
backgroundImage: `url("${avatarPath}")`,
}
: {
backgroundColor: color,
};
return (
<div className="module-calling-pip__video--remote">
<div
className="module-calling-pip__video--background"
style={backgroundStyle}
/>
<div className="module-calling-pip__video--blur" />
<div className="module-calling-pip__video--avatar">
<Avatar
avatarPath={avatarPath}
color={color || 'ultramarine'}
noteToSelf={false}
conversationType="direct"
i18n={i18n}
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
size={52}
/>
</div>
<CallBackgroundBlur avatarPath={avatarPath} color={color}>
<div className="module-calling-pip__video--avatar">
<Avatar
avatarPath={avatarPath}
color={color || 'ultramarine'}
noteToSelf={false}
conversationType="direct"
i18n={i18n}
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
size={52}
/>
</div>
</CallBackgroundBlur>
</div>
);
}

View File

@ -17,6 +17,7 @@ const defaultProps = {
isIncoming: true,
isVideoCall: true,
id: '3051234567',
avatarPath: undefined,
contactColor: 'ultramarine' as ColorType,
name: 'Rick Sanchez',

View File

@ -31,7 +31,7 @@ const CallButton = ({
}: CallButtonProps): JSX.Element => {
return (
<button
className={`module-incoming-call__icon module-incoming-call__button--${classSuffix}`}
className={`module-incoming-call__button module-incoming-call__button--${classSuffix}`}
onClick={onClick}
tabIndex={tabIndex}
type="button"

View File

@ -90,11 +90,11 @@ export class CallingClass {
RingRTC.handleLogMessage = this.handleLogMessage.bind(this);
}
async startOutgoingCall(
async startCallingLobby(
conversation: ConversationModel,
isVideoCall: boolean
): Promise<void> {
window.log.info('CallingClass.startOutgoingCall()');
window.log.info('CallingClass.startCallingLobby()');
if (!this.uxActions) {
window.log.error('Missing uxActions, new call not allowed.');
@ -113,6 +113,75 @@ export class CallingClass {
return;
}
window.log.info('CallingClass.startCallingLobby(): Getting call settings');
// Check state after awaiting to debounce call button.
if (RingRTC.call && RingRTC.call.state !== CallState.Ended) {
window.log.info('Call already in progress, new call not allowed.');
return;
}
const conversationProps = conversation.cachedProps;
if (!conversationProps) {
window.log.error(
'CallingClass.startCallingLobby(): No conversation props?'
);
return;
}
window.log.info('CallingClass.startCallingLobby(): Starting lobby');
this.uxActions.showCallLobby({
callDetails: {
...conversationProps,
callId: undefined,
isIncoming: false,
isVideoCall,
},
});
await this.startDeviceReselectionTimer();
this.enableLocalCamera();
}
stopCallingLobby(): void {
this.disableLocalCamera();
this.stopDeviceReselectionTimer();
this.lastMediaDeviceSettings = undefined;
}
async startOutgoingCall(
conversationId: string,
isVideoCall: boolean
): Promise<void> {
window.log.info('CallingClass.startCallingLobby()');
if (!this.uxActions) {
throw new Error('Redux actions not available');
}
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
window.log.error('Could not find conversation, cannot start call');
this.stopCallingLobby();
return;
}
const remoteUserId = this.getRemoteUserIdFromConversation(conversation);
if (!remoteUserId || !this.localDeviceId) {
window.log.error('Missing identifier, new call not allowed.');
this.stopCallingLobby();
return;
}
const haveMediaPermissions = await this.requestPermissions(isVideoCall);
if (!haveMediaPermissions) {
window.log.info('Permissions were denied, new call not allowed.');
this.stopCallingLobby();
return;
}
window.log.info('CallingClass.startOutgoingCall(): Getting call settings');
const callSettings = await this.getCallSettings(conversation);
@ -120,6 +189,7 @@ export class CallingClass {
// Check state after awaiting to debounce call button.
if (RingRTC.call && RingRTC.call.state !== CallState.Ended) {
window.log.info('Call already in progress, new call not allowed.');
this.stopCallingLobby();
return;
}
@ -394,6 +464,14 @@ export class CallingClass {
RingRTC.setAudioOutput(device.index);
}
enableLocalCamera(): void {
this.videoCapturer.enableCapture();
}
disableLocalCamera(): void {
this.videoCapturer.disable();
}
async setPreferredCamera(device: string): Promise<void> {
window.log.info('MediaDevice: setPreferredCamera', device);
window.storage.put('preferred-video-input-device', device);
@ -755,12 +833,13 @@ export class CallingClass {
conversation: ConversationModel,
call: Call
): CallDetailsType {
// Does not meet CallDetailsType interface requirements
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return {
...conversation.cachedProps,
const conversationProps = conversation.cachedProps;
if (!conversationProps) {
throw new Error('getUxCallDetails: No conversation props?');
}
return {
...conversationProps,
callId: call.callId,
isIncoming: call.isIncoming,
isVideoCall: call.isVideoCall,

View File

@ -25,6 +25,7 @@ export type CallDetailsType = {
isIncoming: boolean;
isVideoCall: boolean;
id: string;
avatarPath?: string;
color?: ColorType;
name?: string;
@ -40,6 +41,7 @@ export type CallingStateType = MediaDeviceSettings & {
hasLocalAudio: boolean;
hasLocalVideo: boolean;
hasRemoteVideo: boolean;
participantsList: boolean;
pip: boolean;
settingsDialogOpen: boolean;
};
@ -76,12 +78,12 @@ export type RemoteVideoChangeType = {
};
export type SetLocalAudioType = {
callId: CallId;
callId?: CallId;
enabled: boolean;
};
export type SetLocalVideoType = {
callId: CallId;
callId?: CallId;
enabled: boolean;
};
@ -96,6 +98,8 @@ export type SetRendererCanvasType = {
// Actions
const ACCEPT_CALL = 'calling/ACCEPT_CALL';
const CANCEL_CALL = 'calling/CANCEL_CALL';
const SHOW_CALL_LOBBY = 'calling/SHOW_CALL_LOBBY';
const CALL_STATE_CHANGE = 'calling/CALL_STATE_CHANGE';
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
const CHANGE_IO_DEVICE = 'calling/CHANGE_IO_DEVICE';
@ -110,6 +114,8 @@ const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE';
const SET_LOCAL_AUDIO = 'calling/SET_LOCAL_AUDIO';
const SET_LOCAL_VIDEO = 'calling/SET_LOCAL_VIDEO';
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
const START_CALL = 'calling/START_CALL';
const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
@ -118,6 +124,15 @@ type AcceptCallActionType = {
payload: AcceptCallType;
};
type CancelCallActionType = {
type: 'calling/CANCEL_CALL';
};
type CallLobbyActionType = {
type: 'calling/SHOW_CALL_LOBBY';
payload: OutgoingCallType;
};
type CallStateChangeActionType = {
type: 'calling/CALL_STATE_CHANGE';
payload: Promise<CallStateChangeType>;
@ -188,6 +203,14 @@ type SetLocalVideoFulfilledActionType = {
payload: SetLocalVideoType;
};
type StartCallActionType = {
type: 'calling/START_CALL';
};
type ToggleParticipantsActionType = {
type: 'calling/TOGGLE_PARTICIPANTS';
};
type TogglePipActionType = {
type: 'calling/TOGGLE_PIP';
};
@ -198,6 +221,8 @@ type ToggleSettingsActionType = {
export type CallingActionType =
| AcceptCallActionType
| CancelCallActionType
| CallLobbyActionType
| CallStateChangeActionType
| CallStateChangeFulfilledActionType
| ChangeIODeviceActionType
@ -212,6 +237,8 @@ export type CallingActionType =
| SetLocalAudioActionType
| SetLocalVideoActionType
| SetLocalVideoFulfilledActionType
| StartCallActionType
| ToggleParticipantsActionType
| TogglePipActionType
| ToggleSettingsActionType;
@ -314,6 +341,14 @@ function closeNeedPermissionScreen(): CloseNeedPermissionScreenActionType {
};
}
function cancelCall(): CancelCallActionType {
window.Signal.Services.calling.stopCallingLobby();
return {
type: CANCEL_CALL,
};
}
function declineCall(payload: DeclineCallType): DeclineCallActionType {
calling.decline(payload.callId);
@ -385,7 +420,9 @@ function setRendererCanvas(payload: SetRendererCanvasType): NoopActionType {
}
function setLocalAudio(payload: SetLocalAudioType): SetLocalAudioActionType {
calling.setOutgoingAudio(payload.callId, payload.enabled);
if (payload.callId) {
calling.setOutgoingAudio(payload.callId, payload.enabled);
}
return {
type: SET_LOCAL_AUDIO,
@ -400,6 +437,31 @@ function setLocalVideo(payload: SetLocalVideoType): SetLocalVideoActionType {
};
}
function showCallLobby(payload: OutgoingCallType): CallLobbyActionType {
return {
type: SHOW_CALL_LOBBY,
payload,
};
}
function startCall(payload: OutgoingCallType): StartCallActionType {
const { callDetails } = payload;
window.Signal.Services.calling.startOutgoingCall(
callDetails.id,
callDetails.isVideoCall
);
return {
type: START_CALL,
};
}
function toggleParticipants(): ToggleParticipantsActionType {
return {
type: TOGGLE_PARTICIPANTS,
};
}
function togglePip(): TogglePipActionType {
return {
type: TOGGLE_PIP,
@ -416,7 +478,13 @@ async function doSetLocalVideo(
payload: SetLocalVideoType
): Promise<SetLocalVideoType> {
if (await requestCameraPermissions()) {
calling.setOutgoingVideo(payload.callId, payload.enabled);
if (payload.callId) {
calling.setOutgoingVideo(payload.callId, payload.enabled);
} else if (payload.enabled) {
calling.enableLocalCamera();
} else {
calling.disableLocalCamera();
}
return payload;
}
@ -428,6 +496,7 @@ async function doSetLocalVideo(
export const actions = {
acceptCall,
cancelCall,
callStateChange,
changeIODevice,
closeNeedPermissionScreen,
@ -441,6 +510,9 @@ export const actions = {
setRendererCanvas,
setLocalAudio,
setLocalVideo,
showCallLobby,
startCall,
toggleParticipants,
togglePip,
toggleSettings,
};
@ -460,6 +532,7 @@ function getEmptyState(): CallingStateType {
hasLocalAudio: false,
hasLocalVideo: false,
hasRemoteVideo: false,
participantsList: false,
pip: false,
selectedCamera: undefined,
selectedMicrophone: undefined,
@ -472,6 +545,23 @@ export function reducer(
state: CallingStateType = getEmptyState(),
action: CallingActionType
): CallingStateType {
if (action.type === SHOW_CALL_LOBBY) {
return {
...state,
callDetails: action.payload.callDetails,
callState: undefined,
hasLocalAudio: true,
hasLocalVideo: action.payload.callDetails.isVideoCall,
};
}
if (action.type === START_CALL) {
return {
...state,
callState: CallState.Prering,
};
}
if (action.type === ACCEPT_CALL) {
return {
...state,
@ -481,6 +571,7 @@ export function reducer(
}
if (
action.type === CANCEL_CALL ||
action.type === DECLINE_CALL ||
action.type === HANG_UP ||
action.type === CLOSE_NEED_PERMISSION_SCREEN
@ -501,8 +592,6 @@ export function reducer(
...state,
callDetails: action.payload.callDetails,
callState: CallState.Prering,
hasLocalAudio: true,
hasLocalVideo: action.payload.callDetails.isVideoCall,
};
}
@ -590,6 +679,13 @@ export function reducer(
};
}
if (action.type === TOGGLE_PARTICIPANTS) {
return {
...state,
participantsList: !state.participantsList,
};
}
if (action.type === TOGGLE_PIP) {
return {
...state,

View File

@ -19,8 +19,9 @@ function renderDeviceSelection(): JSX.Element {
}
const mapStateToProps = (state: StateType) => {
const { calling } = state;
return {
...state.calling,
...calling,
i18n: getIntl(state),
renderDeviceSelection,
};

View File

@ -12847,7 +12847,7 @@
"rule": "React-createRef",
"path": "ts/components/CallScreen.js",
"line": " this.localVideoRef = react_1.default.createRef();",
"lineNumber": 98,
"lineNumber": 94,
"reasonCategory": "usageTrusted",
"updated": "2020-09-14T23:03:44.863Z"
},
@ -12855,7 +12855,7 @@
"rule": "React-createRef",
"path": "ts/components/CallScreen.js",
"line": " this.remoteVideoRef = react_1.default.createRef();",
"lineNumber": 99,
"lineNumber": 95,
"reasonCategory": "usageTrusted",
"updated": "2020-09-14T23:03:44.863Z"
},
@ -12863,7 +12863,7 @@
"rule": "React-createRef",
"path": "ts/components/CallScreen.tsx",
"line": " this.localVideoRef = React.createRef();",
"lineNumber": 78,
"lineNumber": 58,
"reasonCategory": "usageTrusted",
"updated": "2020-06-02T21:51:34.813Z",
"reasonDetail": "Used to render local preview video"
@ -12872,7 +12872,7 @@
"rule": "React-createRef",
"path": "ts/components/CallScreen.tsx",
"line": " this.remoteVideoRef = React.createRef();",
"lineNumber": 79,
"lineNumber": 59,
"reasonCategory": "usageTrusted",
"updated": "2020-09-14T23:03:44.863Z"
},

View File

@ -479,7 +479,7 @@ Whisper.ConversationView = Whisper.View.extend({
window.log.info(
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
);
await window.Signal.Services.calling.startOutgoingCall(
await window.Signal.Services.calling.startCallingLobby(
conversation,
isVideoCall
);
@ -504,7 +504,7 @@ Whisper.ConversationView = Whisper.View.extend({
window.log.info(
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
);
await window.Signal.Services.calling.startOutgoingCall(
await window.Signal.Services.calling.startCallingLobby(
conversation,
isVideoCall
);