Group calling: add speaker view
This commit is contained in:
parent
fbfcdbf84e
commit
b281420a40
|
@ -3147,6 +3147,14 @@
|
||||||
"message": "Fullscreen call",
|
"message": "Fullscreen call",
|
||||||
"description": "Title for picture-in-picture toggle"
|
"description": "Title for picture-in-picture toggle"
|
||||||
},
|
},
|
||||||
|
"calling__switch-view--to-grid": {
|
||||||
|
"message": "Switch to grid view",
|
||||||
|
"description": "Title for grid/speaker view toggle when on a call"
|
||||||
|
},
|
||||||
|
"calling__switch-view--to-speaker": {
|
||||||
|
"message": "Switch to speaker view",
|
||||||
|
"description": "Title for grid/speaker view toggle when on a call"
|
||||||
|
},
|
||||||
"calling__hangup": {
|
"calling__hangup": {
|
||||||
"message": "Leave call",
|
"message": "Leave call",
|
||||||
"description": "Title for hang up button"
|
"description": "Title for hang up button"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m21.5 8.5h-4a1 1 0 0 1 -1-1v-2a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2a1 1 0 0 1 -1 1zm-6.5-1v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1zm-7.5 0v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1zm7.5 5.5v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1zm-7.5 0v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1zm7.5 5.5v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1zm-7.5 0v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1zm15-5.5v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1zm0 5.5v-2a1 1 0 0 0 -1-1h-4a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1z"/></svg>
|
After Width: | Height: | Size: 788 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m15 19.5h-12.5a1 1 0 0 1 -1-1v-13a1 1 0 0 1 1-1h12.5a1 1 0 0 1 1 1v13a1 1 0 0 1 -1 1zm7.5-12v-2a1 1 0 0 0 -1-1h-3a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1zm0 5.5v-2a1 1 0 0 0 -1-1h-3a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1zm0 5.5v-2a1 1 0 0 0 -1-1h-3a1 1 0 0 0 -1 1v2a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1z"/></svg>
|
After Width: | Height: | Size: 412 B |
|
@ -6090,11 +6090,13 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-calling-button {
|
.module-calling-button {
|
||||||
|
$size: 22px;
|
||||||
|
|
||||||
&__participants {
|
&__participants {
|
||||||
@include color-svg('../images/icons/v2/group-solid-24.svg', $color-white);
|
@include color-svg('../images/icons/v2/group-solid-24.svg', $color-white);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 22px;
|
height: $size;
|
||||||
width: 22px;
|
width: $size;
|
||||||
|
|
||||||
&--container {
|
&--container {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
|
@ -6123,14 +6125,32 @@ button.module-image__border-overlay:focus {
|
||||||
'../images/icons/v2/settings-solid-16.svg',
|
'../images/icons/v2/settings-solid-16.svg',
|
||||||
$color-white
|
$color-white
|
||||||
);
|
);
|
||||||
height: 22px;
|
height: $size;
|
||||||
width: 22px;
|
width: $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__grid-view {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/grid-view-solid-24.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
height: $size;
|
||||||
|
width: $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__speaker-view {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/speaker-view-solid-24.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
height: $size;
|
||||||
|
width: $size;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__pip {
|
&__pip {
|
||||||
@include color-svg('../images/icons/v2/pip-minimize-24.svg', $color-white);
|
@include color-svg('../images/icons/v2/pip-minimize-24.svg', $color-white);
|
||||||
height: 22px;
|
height: $size;
|
||||||
width: 22px;
|
width: $size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ const getCommonActiveCallData = () => ({
|
||||||
joinedAt: Date.now(),
|
joinedAt: Date.now(),
|
||||||
hasLocalAudio: boolean('hasLocalAudio', true),
|
hasLocalAudio: boolean('hasLocalAudio', true),
|
||||||
hasLocalVideo: boolean('hasLocalVideo', false),
|
hasLocalVideo: boolean('hasLocalVideo', false),
|
||||||
|
isInSpeakerView: boolean('isInSpeakerView', false),
|
||||||
pip: boolean('pip', false),
|
pip: boolean('pip', false),
|
||||||
settingsDialogOpen: boolean('settingsDialogOpen', false),
|
settingsDialogOpen: boolean('settingsDialogOpen', false),
|
||||||
showParticipantsList: boolean('showParticipantsList', false),
|
showParticipantsList: boolean('showParticipantsList', false),
|
||||||
|
@ -87,6 +88,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
toggleParticipants: action('toggle-participants'),
|
toggleParticipants: action('toggle-participants'),
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
toggleSettings: action('toggle-settings'),
|
toggleSettings: action('toggle-settings'),
|
||||||
|
toggleSpeakerView: action('toggle-speaker-view'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const story = storiesOf('Components/CallManager', module);
|
const story = storiesOf('Components/CallManager', module);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
@ -73,6 +73,7 @@ export interface PropsType {
|
||||||
hangUp: (_: HangUpType) => void;
|
hangUp: (_: HangUpType) => void;
|
||||||
togglePip: () => void;
|
togglePip: () => void;
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
|
toggleSpeakerView: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActiveCallManagerPropsType extends PropsType {
|
interface ActiveCallManagerPropsType extends PropsType {
|
||||||
|
@ -100,6 +101,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
|
toggleSpeakerView,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
conversation,
|
conversation,
|
||||||
|
@ -265,6 +267,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
toggleParticipants={toggleParticipants}
|
toggleParticipants={toggleParticipants}
|
||||||
togglePip={togglePip}
|
togglePip={togglePip}
|
||||||
toggleSettings={toggleSettings}
|
toggleSettings={toggleSettings}
|
||||||
|
toggleSpeakerView={toggleSpeakerView}
|
||||||
/>
|
/>
|
||||||
{settingsDialogOpen && renderDeviceSelection()}
|
{settingsDialogOpen && renderDeviceSelection()}
|
||||||
{showParticipantsList && activeCall.callMode === CallMode.Group ? (
|
{showParticipantsList && activeCall.callMode === CallMode.Group ? (
|
||||||
|
|
|
@ -44,6 +44,7 @@ const conversation = {
|
||||||
interface OverridePropsBase {
|
interface OverridePropsBase {
|
||||||
hasLocalAudio?: boolean;
|
hasLocalAudio?: boolean;
|
||||||
hasLocalVideo?: boolean;
|
hasLocalVideo?: boolean;
|
||||||
|
isInSpeakerView?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DirectCallOverrideProps extends OverridePropsBase {
|
interface DirectCallOverrideProps extends OverridePropsBase {
|
||||||
|
@ -113,6 +114,10 @@ const createActiveCallProp = (
|
||||||
'hasLocalVideo',
|
'hasLocalVideo',
|
||||||
overrideProps.hasLocalVideo || false
|
overrideProps.hasLocalVideo || false
|
||||||
),
|
),
|
||||||
|
isInSpeakerView: boolean(
|
||||||
|
'isInSpeakerView',
|
||||||
|
overrideProps.isInSpeakerView || false
|
||||||
|
),
|
||||||
pip: false,
|
pip: false,
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
|
@ -152,6 +157,7 @@ const createProps = (
|
||||||
toggleParticipants: action('toggle-participants'),
|
toggleParticipants: action('toggle-participants'),
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
toggleSettings: action('toggle-settings'),
|
toggleSettings: action('toggle-settings'),
|
||||||
|
toggleSpeakerView: action('toggle-speaker-view'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const story = storiesOf('Components/CallScreen', module);
|
const story = storiesOf('Components/CallScreen', module);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
|
@ -53,6 +53,7 @@ export type PropsType = {
|
||||||
toggleParticipants: () => void;
|
toggleParticipants: () => void;
|
||||||
togglePip: () => void;
|
togglePip: () => void;
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
|
toggleSpeakerView: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallScreen: React.FC<PropsType> = ({
|
export const CallScreen: React.FC<PropsType> = ({
|
||||||
|
@ -71,6 +72,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
|
toggleSpeakerView,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
conversation,
|
conversation,
|
||||||
|
@ -190,6 +192,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
<GroupCallRemoteParticipants
|
<GroupCallRemoteParticipants
|
||||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isInSpeakerView={activeCall.isInSpeakerView}
|
||||||
remoteParticipants={activeCall.remoteParticipants}
|
remoteParticipants={activeCall.remoteParticipants}
|
||||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||||
/>
|
/>
|
||||||
|
@ -244,6 +247,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
<CallingHeader
|
<CallingHeader
|
||||||
canPip
|
canPip
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isInSpeakerView={activeCall.isInSpeakerView}
|
||||||
isGroupCall={activeCall.callMode === CallMode.Group}
|
isGroupCall={activeCall.callMode === CallMode.Group}
|
||||||
message={headerMessage}
|
message={headerMessage}
|
||||||
participantCount={participantCount}
|
participantCount={participantCount}
|
||||||
|
@ -252,6 +256,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
toggleParticipants={toggleParticipants}
|
toggleParticipants={toggleParticipants}
|
||||||
togglePip={togglePip}
|
togglePip={togglePip}
|
||||||
toggleSettings={toggleSettings}
|
toggleSettings={toggleSettings}
|
||||||
|
toggleSpeakerView={toggleSpeakerView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{remoteParticipantsElement}
|
{remoteParticipantsElement}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -10,6 +10,7 @@ import { Theme } from '../util/theme';
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
canPip?: boolean;
|
canPip?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
isInSpeakerView?: boolean;
|
||||||
isGroupCall?: boolean;
|
isGroupCall?: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
participantCount: number;
|
participantCount: number;
|
||||||
|
@ -18,11 +19,13 @@ export type PropsType = {
|
||||||
toggleParticipants?: () => void;
|
toggleParticipants?: () => void;
|
||||||
togglePip?: () => void;
|
togglePip?: () => void;
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
|
toggleSpeakerView?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallingHeader = ({
|
export const CallingHeader = ({
|
||||||
canPip = false,
|
canPip = false,
|
||||||
i18n,
|
i18n,
|
||||||
|
isInSpeakerView,
|
||||||
isGroupCall = false,
|
isGroupCall = false,
|
||||||
message,
|
message,
|
||||||
participantCount,
|
participantCount,
|
||||||
|
@ -31,6 +34,7 @@ export const CallingHeader = ({
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
|
toggleSpeakerView,
|
||||||
}: PropsType): JSX.Element => (
|
}: PropsType): JSX.Element => (
|
||||||
<div className="module-calling__header">
|
<div className="module-calling__header">
|
||||||
{title ? (
|
{title ? (
|
||||||
|
@ -80,6 +84,33 @@ export const CallingHeader = ({
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
{isGroupCall && participantCount > 2 && toggleSpeakerView && (
|
||||||
|
<div className="module-calling-tools__button">
|
||||||
|
<Tooltip
|
||||||
|
content={i18n(
|
||||||
|
isInSpeakerView
|
||||||
|
? 'calling__switch-view--to-grid'
|
||||||
|
: 'calling__switch-view--to-speaker'
|
||||||
|
)}
|
||||||
|
theme={Theme.Dark}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label={i18n(
|
||||||
|
isInSpeakerView
|
||||||
|
? 'calling__switch-view--to-grid'
|
||||||
|
: 'calling__switch-view--to-speaker'
|
||||||
|
)}
|
||||||
|
className={
|
||||||
|
isInSpeakerView
|
||||||
|
? 'module-calling-button__grid-view'
|
||||||
|
: 'module-calling-button__speaker-view'
|
||||||
|
}
|
||||||
|
onClick={toggleSpeakerView}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{canPip && (
|
{canPip && (
|
||||||
<div className="module-calling-tools__button">
|
<div className="module-calling-tools__button">
|
||||||
<Tooltip content={i18n('calling__pip--on')} theme={Theme.Dark}>
|
<Tooltip content={i18n('calling__pip--on')} theme={Theme.Dark}>
|
||||||
|
|
|
@ -39,6 +39,7 @@ const getCommonActiveCallData = () => ({
|
||||||
conversation,
|
conversation,
|
||||||
hasLocalAudio: boolean('hasLocalAudio', true),
|
hasLocalAudio: boolean('hasLocalAudio', true),
|
||||||
hasLocalVideo: boolean('hasLocalVideo', false),
|
hasLocalVideo: boolean('hasLocalVideo', false),
|
||||||
|
isInSpeakerView: boolean('isInSpeakerView', false),
|
||||||
joinedAt: Date.now(),
|
joinedAt: Date.now(),
|
||||||
pip: true,
|
pip: true,
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
|
|
@ -38,6 +38,7 @@ interface GridArrangement {
|
||||||
interface PropsType {
|
interface PropsType {
|
||||||
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
|
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
isInSpeakerView: boolean;
|
||||||
remoteParticipants: ReadonlyArray<GroupCallRemoteParticipantType>;
|
remoteParticipants: ReadonlyArray<GroupCallRemoteParticipantType>;
|
||||||
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
||||||
}
|
}
|
||||||
|
@ -68,6 +69,7 @@ interface PropsType {
|
||||||
export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
|
export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
i18n,
|
i18n,
|
||||||
|
isInSpeakerView,
|
||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -122,6 +124,14 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
|
||||||
[remoteParticipants]
|
[remoteParticipants]
|
||||||
);
|
);
|
||||||
const gridParticipants: Array<GroupCallRemoteParticipantType> = useMemo(() => {
|
const gridParticipants: Array<GroupCallRemoteParticipantType> = useMemo(() => {
|
||||||
|
if (!sortedParticipants.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateParticipants = isInSpeakerView
|
||||||
|
? [sortedParticipants[0]]
|
||||||
|
: sortedParticipants;
|
||||||
|
|
||||||
// Imagine that we laid out all of the rows end-to-end. That's the maximum total
|
// Imagine that we laid out all of the rows end-to-end. That's the maximum total
|
||||||
// width. So if there were 5 rows and the container was 100px wide, then we can't
|
// width. So if there were 5 rows and the container was 100px wide, then we can't
|
||||||
// possibly fit more than 500px of participants.
|
// possibly fit more than 500px of participants.
|
||||||
|
@ -130,11 +140,16 @@ export const GroupCallRemoteParticipants: React.FC<PropsType> = ({
|
||||||
// We do the same thing for participants, "laying them out end-to-end" until they
|
// We do the same thing for participants, "laying them out end-to-end" until they
|
||||||
// exceed the maximum total width.
|
// exceed the maximum total width.
|
||||||
let totalWidth = 0;
|
let totalWidth = 0;
|
||||||
return takeWhile(sortedParticipants, remoteParticipant => {
|
return takeWhile(candidateParticipants, remoteParticipant => {
|
||||||
totalWidth += remoteParticipant.videoAspectRatio * MIN_RENDERED_HEIGHT;
|
totalWidth += remoteParticipant.videoAspectRatio * MIN_RENDERED_HEIGHT;
|
||||||
return totalWidth < maxTotalWidth;
|
return totalWidth < maxTotalWidth;
|
||||||
}).sort(stableParticipantComparator);
|
}).sort(stableParticipantComparator);
|
||||||
}, [maxRowCount, containerDimensions.width, sortedParticipants]);
|
}, [
|
||||||
|
containerDimensions.width,
|
||||||
|
isInSpeakerView,
|
||||||
|
maxRowCount,
|
||||||
|
sortedParticipants,
|
||||||
|
]);
|
||||||
const overflowedParticipants: Array<GroupCallRemoteParticipantType> = useMemo(
|
const overflowedParticipants: Array<GroupCallRemoteParticipantType> = useMemo(
|
||||||
() =>
|
() =>
|
||||||
sortedParticipants
|
sortedParticipants
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { ThunkAction } from 'redux-thunk';
|
import { ThunkAction } from 'redux-thunk';
|
||||||
|
@ -68,12 +68,13 @@ export interface GroupCallStateType {
|
||||||
|
|
||||||
export interface ActiveCallStateType {
|
export interface ActiveCallStateType {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
joinedAt?: number;
|
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
|
isInSpeakerView: boolean;
|
||||||
|
joinedAt?: number;
|
||||||
pip: boolean;
|
pip: boolean;
|
||||||
settingsDialogOpen: boolean;
|
|
||||||
safetyNumberChangedUuids: Array<string>;
|
safetyNumberChangedUuids: Array<string>;
|
||||||
|
settingsDialogOpen: boolean;
|
||||||
showParticipantsList: boolean;
|
showParticipantsList: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,6 +244,7 @@ const START_DIRECT_CALL = 'calling/START_DIRECT_CALL';
|
||||||
const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
|
const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
|
||||||
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
||||||
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
|
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
|
||||||
|
const TOGGLE_SPEAKER_VIEW = 'calling/TOGGLE_SPEAKER_VIEW';
|
||||||
|
|
||||||
type AcceptCallPendingActionType = {
|
type AcceptCallPendingActionType = {
|
||||||
type: 'calling/ACCEPT_CALL_PENDING';
|
type: 'calling/ACCEPT_CALL_PENDING';
|
||||||
|
@ -365,6 +367,10 @@ type ToggleSettingsActionType = {
|
||||||
type: 'calling/TOGGLE_SETTINGS';
|
type: 'calling/TOGGLE_SETTINGS';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ToggleSpeakerViewActionType = {
|
||||||
|
type: 'calling/TOGGLE_SPEAKER_VIEW';
|
||||||
|
};
|
||||||
|
|
||||||
export type CallingActionType =
|
export type CallingActionType =
|
||||||
| AcceptCallPendingActionType
|
| AcceptCallPendingActionType
|
||||||
| CancelCallActionType
|
| CancelCallActionType
|
||||||
|
@ -389,7 +395,8 @@ export type CallingActionType =
|
||||||
| StartDirectCallActionType
|
| StartDirectCallActionType
|
||||||
| ToggleParticipantsActionType
|
| ToggleParticipantsActionType
|
||||||
| TogglePipActionType
|
| TogglePipActionType
|
||||||
| ToggleSettingsActionType;
|
| ToggleSettingsActionType
|
||||||
|
| ToggleSpeakerViewActionType;
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
|
@ -856,6 +863,12 @@ function toggleSettings(): ToggleSettingsActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleSpeakerView(): ToggleSpeakerViewActionType {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_SPEAKER_VIEW,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
acceptCall,
|
acceptCall,
|
||||||
cancelCall,
|
cancelCall,
|
||||||
|
@ -884,6 +897,7 @@ export const actions = {
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
|
toggleSpeakerView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ActionsType = typeof actions;
|
export type ActionsType = typeof actions;
|
||||||
|
@ -974,6 +988,7 @@ export function reducer(
|
||||||
conversationId: action.payload.conversationId,
|
conversationId: action.payload.conversationId,
|
||||||
hasLocalAudio: action.payload.hasLocalAudio,
|
hasLocalAudio: action.payload.hasLocalAudio,
|
||||||
hasLocalVideo: action.payload.hasLocalVideo,
|
hasLocalVideo: action.payload.hasLocalVideo,
|
||||||
|
isInSpeakerView: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -999,6 +1014,7 @@ export function reducer(
|
||||||
conversationId: action.payload.conversationId,
|
conversationId: action.payload.conversationId,
|
||||||
hasLocalAudio: action.payload.hasLocalAudio,
|
hasLocalAudio: action.payload.hasLocalAudio,
|
||||||
hasLocalVideo: action.payload.hasLocalVideo,
|
hasLocalVideo: action.payload.hasLocalVideo,
|
||||||
|
isInSpeakerView: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -1019,6 +1035,7 @@ export function reducer(
|
||||||
conversationId: action.payload.conversationId,
|
conversationId: action.payload.conversationId,
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: action.payload.asVideoCall,
|
hasLocalVideo: action.payload.asVideoCall,
|
||||||
|
isInSpeakerView: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -1084,6 +1101,7 @@ export function reducer(
|
||||||
conversationId: action.payload.conversationId,
|
conversationId: action.payload.conversationId,
|
||||||
hasLocalAudio: action.payload.hasLocalAudio,
|
hasLocalAudio: action.payload.hasLocalAudio,
|
||||||
hasLocalVideo: action.payload.hasLocalVideo,
|
hasLocalVideo: action.payload.hasLocalVideo,
|
||||||
|
isInSpeakerView: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
@ -1409,6 +1427,24 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === TOGGLE_SPEAKER_VIEW) {
|
||||||
|
const { activeCallState } = state;
|
||||||
|
if (!activeCallState) {
|
||||||
|
window.log.warn(
|
||||||
|
'Cannot toggle speaker view when there is no active call'
|
||||||
|
);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeCallState: {
|
||||||
|
...activeCallState,
|
||||||
|
isInSpeakerView: !activeCallState.isInSpeakerView,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type === MARK_CALL_UNTRUSTED) {
|
if (action.type === MARK_CALL_UNTRUSTED) {
|
||||||
const { activeCallState } = state;
|
const { activeCallState } = state;
|
||||||
if (!activeCallState) {
|
if (!activeCallState) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -75,6 +75,7 @@ const mapStateToActiveCallProp = (
|
||||||
conversation,
|
conversation,
|
||||||
hasLocalAudio: activeCallState.hasLocalAudio,
|
hasLocalAudio: activeCallState.hasLocalAudio,
|
||||||
hasLocalVideo: activeCallState.hasLocalVideo,
|
hasLocalVideo: activeCallState.hasLocalVideo,
|
||||||
|
isInSpeakerView: activeCallState.isInSpeakerView,
|
||||||
joinedAt: activeCallState.joinedAt,
|
joinedAt: activeCallState.joinedAt,
|
||||||
pip: activeCallState.pip,
|
pip: activeCallState.pip,
|
||||||
settingsDialogOpen: activeCallState.settingsDialogOpen,
|
settingsDialogOpen: activeCallState.settingsDialogOpen,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
@ -43,6 +43,7 @@ describe('calling duck', () => {
|
||||||
conversationId: 'fake-direct-call-conversation-id',
|
conversationId: 'fake-direct-call-conversation-id',
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
|
isInSpeakerView: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -98,6 +99,7 @@ describe('calling duck', () => {
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
conversationId: 'fake-group-call-conversation-id',
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
|
isInSpeakerView: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -202,6 +204,7 @@ describe('calling duck', () => {
|
||||||
conversationId: 'fake-direct-call-conversation-id',
|
conversationId: 'fake-direct-call-conversation-id',
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
|
isInSpeakerView: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -578,6 +581,7 @@ describe('calling duck', () => {
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
conversationId: 'fake-group-call-conversation-id',
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
|
isInSpeakerView: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -815,6 +819,7 @@ describe('calling duck', () => {
|
||||||
conversationId: 'fake-conversation-id',
|
conversationId: 'fake-conversation-id',
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
|
isInSpeakerView: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -1050,6 +1055,7 @@ describe('calling duck', () => {
|
||||||
conversationId: 'fake-conversation-id',
|
conversationId: 'fake-conversation-id',
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
|
isInSpeakerView: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
@ -1120,6 +1126,23 @@ describe('calling duck', () => {
|
||||||
assert.isTrue(afterThreeToggles.activeCallState?.pip);
|
assert.isTrue(afterThreeToggles.activeCallState?.pip);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toggleSpeakerView', () => {
|
||||||
|
const { toggleSpeakerView } = actions;
|
||||||
|
|
||||||
|
it('toggles speaker view', () => {
|
||||||
|
const afterOneToggle = reducer(
|
||||||
|
stateWithActiveGroupCall,
|
||||||
|
toggleSpeakerView()
|
||||||
|
);
|
||||||
|
const afterTwoToggles = reducer(afterOneToggle, toggleSpeakerView());
|
||||||
|
const afterThreeToggles = reducer(afterTwoToggles, toggleSpeakerView());
|
||||||
|
|
||||||
|
assert.isTrue(afterOneToggle.activeCallState?.isInSpeakerView);
|
||||||
|
assert.isFalse(afterTwoToggles.activeCallState?.isInSpeakerView);
|
||||||
|
assert.isTrue(afterThreeToggles.activeCallState?.isInSpeakerView);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('helpers', () => {
|
describe('helpers', () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2019-2020 Signal Messenger, LLC
|
// Copyright 2019-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
@ -40,6 +40,7 @@ describe('state/selectors/calling', () => {
|
||||||
conversationId: 'fake-direct-call-conversation-id',
|
conversationId: 'fake-direct-call-conversation-id',
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: true,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
|
isInSpeakerView: false,
|
||||||
showParticipantsList: false,
|
showParticipantsList: false,
|
||||||
safetyNumberChangedUuids: [],
|
safetyNumberChangedUuids: [],
|
||||||
pip: false,
|
pip: false,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { ConversationType } from '../state/ducks/conversations';
|
import { ConversationType } from '../state/ducks/conversations';
|
||||||
|
@ -14,6 +14,7 @@ interface ActiveCallBaseType {
|
||||||
conversation: ConversationType;
|
conversation: ConversationType;
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
|
isInSpeakerView: boolean;
|
||||||
joinedAt?: number;
|
joinedAt?: number;
|
||||||
pip: boolean;
|
pip: boolean;
|
||||||
settingsDialogOpen: boolean;
|
settingsDialogOpen: boolean;
|
||||||
|
|
Loading…
Reference in New Issue