Restore call view mode after presentation end
This commit is contained in:
parent
9e1528fa24
commit
80c90540f6
13 changed files with 289 additions and 51 deletions
|
@ -12,6 +12,7 @@ import {
|
|||
CallEndedReason,
|
||||
CallMode,
|
||||
CallState,
|
||||
CallViewMode,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
} from '../types/Calling';
|
||||
|
@ -51,7 +52,11 @@ const getCommonActiveCallData = () => ({
|
|||
hasLocalAudio: boolean('hasLocalAudio', true),
|
||||
hasLocalVideo: boolean('hasLocalVideo', false),
|
||||
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
|
||||
isInSpeakerView: boolean('isInSpeakerView', false),
|
||||
viewMode: select(
|
||||
'viewMode',
|
||||
[CallViewMode.Grid, CallViewMode.Presentation, CallViewMode.Speaker],
|
||||
CallViewMode.Grid
|
||||
),
|
||||
outgoingRing: boolean('outgoingRing', true),
|
||||
pip: boolean('pip', false),
|
||||
settingsDialogOpen: boolean('settingsDialogOpen', false),
|
||||
|
@ -101,6 +106,8 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
|||
setOutgoingRing: action('set-outgoing-ring'),
|
||||
startCall: action('start-call'),
|
||||
stopRingtone: action('stop-ringtone'),
|
||||
switchToPresentationView: action('switch-to-presentation-view'),
|
||||
switchFromPresentationView: action('switch-from-presentation-view'),
|
||||
theme: ThemeType.light,
|
||||
toggleParticipants: action('toggle-participants'),
|
||||
togglePip: action('toggle-pip'),
|
||||
|
|
|
@ -91,6 +91,8 @@ export type PropsType = {
|
|||
setPresenting: (_?: PresentedSource) => void;
|
||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||
stopRingtone: () => unknown;
|
||||
switchToPresentationView: () => void;
|
||||
switchFromPresentationView: () => void;
|
||||
hangUpActiveCall: () => void;
|
||||
theme: ThemeType;
|
||||
togglePip: () => void;
|
||||
|
@ -127,6 +129,8 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
|||
setRendererCanvas,
|
||||
setOutgoingRing,
|
||||
startCall,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
theme,
|
||||
toggleParticipants,
|
||||
togglePip,
|
||||
|
@ -270,8 +274,9 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
|||
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
|
||||
setLocalPreview={setLocalPreview}
|
||||
setRendererCanvas={setRendererCanvas}
|
||||
switchToPresentationView={switchToPresentationView}
|
||||
switchFromPresentationView={switchFromPresentationView}
|
||||
togglePip={togglePip}
|
||||
toggleSpeakerView={toggleSpeakerView}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -313,6 +318,8 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
|||
setLocalVideo={setLocalVideo}
|
||||
setPresenting={setPresenting}
|
||||
stickyControls={showParticipantsList}
|
||||
switchToPresentationView={switchToPresentationView}
|
||||
switchFromPresentationView={switchFromPresentationView}
|
||||
toggleScreenRecordingPermissionsDialog={
|
||||
toggleScreenRecordingPermissionsDialog
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { action } from '@storybook/addon-actions';
|
|||
import type { GroupCallRemoteParticipantType } from '../types/Calling';
|
||||
import {
|
||||
CallMode,
|
||||
CallViewMode,
|
||||
CallState,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
|
@ -45,7 +46,7 @@ type OverridePropsBase = {
|
|||
hasLocalAudio?: boolean;
|
||||
hasLocalVideo?: boolean;
|
||||
localAudioLevel?: number;
|
||||
isInSpeakerView?: boolean;
|
||||
viewMode?: CallViewMode;
|
||||
};
|
||||
|
||||
type DirectCallOverrideProps = OverridePropsBase & {
|
||||
|
@ -126,9 +127,10 @@ const createActiveCallProp = (
|
|||
[0, 0.5, 1],
|
||||
overrideProps.localAudioLevel || 0
|
||||
),
|
||||
isInSpeakerView: boolean(
|
||||
'isInSpeakerView',
|
||||
overrideProps.isInSpeakerView || false
|
||||
viewMode: select(
|
||||
'viewMode',
|
||||
[CallViewMode.Grid, CallViewMode.Speaker, CallViewMode.Presentation],
|
||||
overrideProps.viewMode || CallViewMode.Grid
|
||||
),
|
||||
outgoingRing: true,
|
||||
pip: false,
|
||||
|
@ -172,6 +174,8 @@ const createProps = (
|
|||
setPresenting: action('toggle-presenting'),
|
||||
setRendererCanvas: action('set-renderer-canvas'),
|
||||
stickyControls: boolean('stickyControls', false),
|
||||
switchToPresentationView: action('switch-to-presentation-view'),
|
||||
switchFromPresentationView: action('switch-from-presentation-view'),
|
||||
toggleParticipants: action('toggle-participants'),
|
||||
togglePip: action('toggle-pip'),
|
||||
toggleScreenRecordingPermissionsDialog: action(
|
||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
|||
SetLocalVideoType,
|
||||
SetRendererCanvasType,
|
||||
} from '../state/ducks/calling';
|
||||
import { isInSpeakerView } from '../state/selectors/calling';
|
||||
import { Avatar } from './Avatar';
|
||||
import { CallingHeader } from './CallingHeader';
|
||||
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
||||
|
@ -61,6 +62,8 @@ export type PropsType = {
|
|||
setPresenting: (_?: PresentedSource) => void;
|
||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||
stickyControls: boolean;
|
||||
switchToPresentationView: () => void;
|
||||
switchFromPresentationView: () => void;
|
||||
toggleParticipants: () => void;
|
||||
togglePip: () => void;
|
||||
toggleScreenRecordingPermissionsDialog: () => unknown;
|
||||
|
@ -120,6 +123,8 @@ export const CallScreen: React.FC<PropsType> = ({
|
|||
setPresenting,
|
||||
setRendererCanvas,
|
||||
stickyControls,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
toggleParticipants,
|
||||
togglePip,
|
||||
toggleScreenRecordingPermissionsDialog,
|
||||
|
@ -131,18 +136,17 @@ export const CallScreen: React.FC<PropsType> = ({
|
|||
hasLocalAudio,
|
||||
hasLocalVideo,
|
||||
localAudioLevel,
|
||||
isInSpeakerView,
|
||||
presentingSource,
|
||||
remoteParticipants,
|
||||
showNeedsScreenRecordingPermissionsWarning,
|
||||
showParticipantsList,
|
||||
} = activeCall;
|
||||
|
||||
useActivateSpeakerViewOnPresenting(
|
||||
useActivateSpeakerViewOnPresenting({
|
||||
remoteParticipants,
|
||||
isInSpeakerView,
|
||||
toggleSpeakerView
|
||||
);
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
});
|
||||
|
||||
const activeCallShortcuts = useActiveCallShortcuts(hangUpActiveCall);
|
||||
useKeyboardShortcuts(activeCallShortcuts);
|
||||
|
@ -293,7 +297,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
|||
<GroupCallRemoteParticipants
|
||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||
i18n={i18n}
|
||||
isInSpeakerView={isInSpeakerView}
|
||||
isInSpeakerView={isInSpeakerView(activeCall)}
|
||||
remoteParticipants={activeCall.remoteParticipants}
|
||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||
remoteAudioLevels={activeCall.remoteAudioLevels}
|
||||
|
@ -448,7 +452,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
|||
>
|
||||
<CallingHeader
|
||||
i18n={i18n}
|
||||
isInSpeakerView={isInSpeakerView}
|
||||
isInSpeakerView={isInSpeakerView(activeCall)}
|
||||
isGroupCall={isGroupCall}
|
||||
message={headerMessage}
|
||||
participantCount={participantCount}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { CallingPip } from './CallingPip';
|
|||
import type { ActiveCallType } from '../types/Calling';
|
||||
import {
|
||||
CallMode,
|
||||
CallViewMode,
|
||||
CallState,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
|
@ -40,7 +41,11 @@ const getCommonActiveCallData = () => ({
|
|||
hasLocalAudio: boolean('hasLocalAudio', true),
|
||||
hasLocalVideo: boolean('hasLocalVideo', false),
|
||||
localAudioLevel: select('localAudioLevel', [0, 0.5, 1], 0),
|
||||
isInSpeakerView: boolean('isInSpeakerView', false),
|
||||
viewMode: select(
|
||||
'viewMode',
|
||||
[CallViewMode.Grid, CallViewMode.Speaker, CallViewMode.Presentation],
|
||||
CallViewMode.Grid
|
||||
),
|
||||
joinedAt: Date.now(),
|
||||
outgoingRing: true,
|
||||
pip: true,
|
||||
|
@ -67,8 +72,9 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
||||
setLocalPreview: action('set-local-preview'),
|
||||
setRendererCanvas: action('set-renderer-canvas'),
|
||||
switchFromPresentationView: action('switch-to-presentation-view'),
|
||||
switchToPresentationView: action('switch-to-presentation-view'),
|
||||
togglePip: action('toggle-pip'),
|
||||
toggleSpeakerView: action('toggleSpeakerView'),
|
||||
});
|
||||
|
||||
const story = storiesOf('Components/CallingPip', module);
|
||||
|
|
|
@ -57,8 +57,9 @@ export type PropsType = {
|
|||
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
||||
setLocalPreview: (_: SetLocalPreviewType) => void;
|
||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||
switchToPresentationView: () => void;
|
||||
switchFromPresentationView: () => void;
|
||||
togglePip: () => void;
|
||||
toggleSpeakerView: () => void;
|
||||
};
|
||||
|
||||
const PIP_HEIGHT = 156;
|
||||
|
@ -75,8 +76,9 @@ export const CallingPip = ({
|
|||
setGroupCallVideoRequest,
|
||||
setLocalPreview,
|
||||
setRendererCanvas,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
togglePip,
|
||||
toggleSpeakerView,
|
||||
}: PropsType): JSX.Element | null => {
|
||||
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
|
||||
const localVideoRef = React.useRef(null);
|
||||
|
@ -88,11 +90,11 @@ export const CallingPip = ({
|
|||
offsetY: PIP_TOP_MARGIN,
|
||||
});
|
||||
|
||||
useActivateSpeakerViewOnPresenting(
|
||||
activeCall.remoteParticipants,
|
||||
activeCall.isInSpeakerView,
|
||||
toggleSpeakerView
|
||||
);
|
||||
useActivateSpeakerViewOnPresenting({
|
||||
remoteParticipants: activeCall.remoteParticipants,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
setLocalPreview({ element: localVideoRef });
|
||||
|
|
|
@ -11,19 +11,30 @@ type RemoteParticipant = {
|
|||
uuid?: string;
|
||||
};
|
||||
|
||||
export function useActivateSpeakerViewOnPresenting(
|
||||
remoteParticipants: ReadonlyArray<RemoteParticipant>,
|
||||
isInSpeakerView: boolean,
|
||||
toggleSpeakerView: () => void
|
||||
): void {
|
||||
export function useActivateSpeakerViewOnPresenting({
|
||||
remoteParticipants,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
}: {
|
||||
remoteParticipants: ReadonlyArray<RemoteParticipant>;
|
||||
switchToPresentationView: () => void;
|
||||
switchFromPresentationView: () => void;
|
||||
}): void {
|
||||
const presenterUuid = remoteParticipants.find(
|
||||
participant => participant.presenting
|
||||
)?.uuid;
|
||||
const prevPresenterUuid = usePrevious(presenterUuid, presenterUuid);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevPresenterUuid !== presenterUuid && !isInSpeakerView) {
|
||||
toggleSpeakerView();
|
||||
if (prevPresenterUuid !== presenterUuid && presenterUuid) {
|
||||
switchToPresentationView();
|
||||
} else if (prevPresenterUuid && !presenterUuid) {
|
||||
switchFromPresentationView();
|
||||
}
|
||||
}, [isInSpeakerView, presenterUuid, prevPresenterUuid, toggleSpeakerView]);
|
||||
}, [
|
||||
presenterUuid,
|
||||
prevPresenterUuid,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import type {
|
|||
import {
|
||||
CallingDeviceType,
|
||||
CallMode,
|
||||
CallViewMode,
|
||||
CallState,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
|
@ -104,7 +105,7 @@ export type ActiveCallStateType = {
|
|||
hasLocalAudio: boolean;
|
||||
hasLocalVideo: boolean;
|
||||
localAudioLevel: number;
|
||||
isInSpeakerView: boolean;
|
||||
viewMode: CallViewMode;
|
||||
joinedAt?: number;
|
||||
outgoingRing: boolean;
|
||||
pip: boolean;
|
||||
|
@ -427,6 +428,8 @@ const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
|
|||
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
||||
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
|
||||
const TOGGLE_SPEAKER_VIEW = 'calling/TOGGLE_SPEAKER_VIEW';
|
||||
const SWITCH_TO_PRESENTATION_VIEW = 'calling/SWITCH_TO_PRESENTATION_VIEW';
|
||||
const SWITCH_FROM_PRESENTATION_VIEW = 'calling/SWITCH_FROM_PRESENTATION_VIEW';
|
||||
|
||||
type AcceptCallPendingActionType = {
|
||||
type: 'calling/ACCEPT_CALL_PENDING';
|
||||
|
@ -597,6 +600,14 @@ type ToggleSpeakerViewActionType = {
|
|||
type: 'calling/TOGGLE_SPEAKER_VIEW';
|
||||
};
|
||||
|
||||
type SwitchToPresentationViewActionType = {
|
||||
type: 'calling/SWITCH_TO_PRESENTATION_VIEW';
|
||||
};
|
||||
|
||||
type SwitchFromPresentationViewActionType = {
|
||||
type: 'calling/SWITCH_FROM_PRESENTATION_VIEW';
|
||||
};
|
||||
|
||||
export type CallingActionType =
|
||||
| AcceptCallPendingActionType
|
||||
| CancelCallActionType
|
||||
|
@ -632,7 +643,9 @@ export type CallingActionType =
|
|||
| TogglePipActionType
|
||||
| SetPresentingFulfilledActionType
|
||||
| ToggleSettingsActionType
|
||||
| ToggleSpeakerViewActionType;
|
||||
| ToggleSpeakerViewActionType
|
||||
| SwitchToPresentationViewActionType
|
||||
| SwitchFromPresentationViewActionType;
|
||||
|
||||
// Action Creators
|
||||
|
||||
|
@ -1314,6 +1327,18 @@ function toggleSpeakerView(): ToggleSpeakerViewActionType {
|
|||
};
|
||||
}
|
||||
|
||||
function switchToPresentationView(): SwitchToPresentationViewActionType {
|
||||
return {
|
||||
type: SWITCH_TO_PRESENTATION_VIEW,
|
||||
};
|
||||
}
|
||||
|
||||
function switchFromPresentationView(): SwitchFromPresentationViewActionType {
|
||||
return {
|
||||
type: SWITCH_FROM_PRESENTATION_VIEW,
|
||||
};
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
acceptCall,
|
||||
callStateChange,
|
||||
|
@ -1349,6 +1374,8 @@ export const actions = {
|
|||
setOutgoingRing,
|
||||
startCall,
|
||||
startCallingLobby,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
toggleParticipants,
|
||||
togglePip,
|
||||
toggleScreenRecordingPermissionsDialog,
|
||||
|
@ -1457,7 +1484,7 @@ export function reducer(
|
|||
hasLocalAudio: action.payload.hasLocalAudio,
|
||||
hasLocalVideo: action.payload.hasLocalVideo,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
pip: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
settingsDialogOpen: false,
|
||||
|
@ -1485,7 +1512,7 @@ export function reducer(
|
|||
hasLocalAudio: action.payload.hasLocalAudio,
|
||||
hasLocalVideo: action.payload.hasLocalVideo,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
pip: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
settingsDialogOpen: false,
|
||||
|
@ -1508,7 +1535,7 @@ export function reducer(
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: action.payload.asVideoCall,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
pip: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
settingsDialogOpen: false,
|
||||
|
@ -1662,7 +1689,7 @@ export function reducer(
|
|||
hasLocalAudio: action.payload.hasLocalAudio,
|
||||
hasLocalVideo: action.payload.hasLocalVideo,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
pip: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
settingsDialogOpen: false,
|
||||
|
@ -2129,11 +2156,61 @@ export function reducer(
|
|||
return state;
|
||||
}
|
||||
|
||||
let newViewMode: CallViewMode;
|
||||
if (activeCallState.viewMode === CallViewMode.Grid) {
|
||||
newViewMode = CallViewMode.Speaker;
|
||||
} else {
|
||||
// This will switch presentation/speaker to grid
|
||||
newViewMode = CallViewMode.Grid;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeCallState: {
|
||||
...activeCallState,
|
||||
isInSpeakerView: !activeCallState.isInSpeakerView,
|
||||
viewMode: newViewMode,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === SWITCH_TO_PRESENTATION_VIEW) {
|
||||
const { activeCallState } = state;
|
||||
if (!activeCallState) {
|
||||
log.warn('Cannot switch to speaker view when there is no active call');
|
||||
return state;
|
||||
}
|
||||
|
||||
// "Presentation" mode reverts to "Grid" when the call is over so don't
|
||||
// switch it if it is in "Speaker" mode.
|
||||
if (activeCallState.viewMode === CallViewMode.Speaker) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeCallState: {
|
||||
...activeCallState,
|
||||
viewMode: CallViewMode.Presentation,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === SWITCH_FROM_PRESENTATION_VIEW) {
|
||||
const { activeCallState } = state;
|
||||
if (!activeCallState) {
|
||||
log.warn('Cannot switch to speaker view when there is no active call');
|
||||
return state;
|
||||
}
|
||||
|
||||
if (activeCallState.viewMode !== CallViewMode.Presentation) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeCallState: {
|
||||
...activeCallState,
|
||||
viewMode: CallViewMode.Grid,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
|
|||
|
||||
import type { StateType } from '../reducer';
|
||||
import type {
|
||||
ActiveCallStateType,
|
||||
CallingStateType,
|
||||
CallsByConversationType,
|
||||
DirectCallStateType,
|
||||
|
@ -13,6 +14,7 @@ import type {
|
|||
import { getIncomingCall as getIncomingCallHelper } from '../ducks/calling';
|
||||
import { getUserUuid } from './user';
|
||||
import { getOwn } from '../../util/getOwn';
|
||||
import { CallViewMode } from '../../types/Calling';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
export type CallStateType = DirectCallStateType | GroupCallStateType;
|
||||
|
@ -71,3 +73,12 @@ export const getIncomingCall = createSelector(
|
|||
return getIncomingCallHelper(callsByConversation, ourUuid);
|
||||
}
|
||||
);
|
||||
|
||||
export const isInSpeakerView = (
|
||||
call: Pick<ActiveCallStateType, 'viewMode'> | undefined
|
||||
): boolean => {
|
||||
return Boolean(
|
||||
call?.viewMode === CallViewMode.Presentation ||
|
||||
call?.viewMode === CallViewMode.Speaker
|
||||
);
|
||||
};
|
||||
|
|
|
@ -131,7 +131,7 @@ const mapStateToActiveCallProp = (
|
|||
hasLocalAudio: activeCallState.hasLocalAudio,
|
||||
hasLocalVideo: activeCallState.hasLocalVideo,
|
||||
localAudioLevel: activeCallState.localAudioLevel,
|
||||
isInSpeakerView: activeCallState.isInSpeakerView,
|
||||
viewMode: activeCallState.viewMode,
|
||||
joinedAt: activeCallState.joinedAt,
|
||||
outgoingRing: activeCallState.outgoingRing,
|
||||
pip: activeCallState.pip,
|
||||
|
|
|
@ -23,6 +23,7 @@ import { calling as callingService } from '../../../services/calling';
|
|||
import {
|
||||
CallMode,
|
||||
CallState,
|
||||
CallViewMode,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
} from '../../../types/Calling';
|
||||
|
@ -52,7 +53,7 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: false,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
outgoingRing: true,
|
||||
|
@ -131,7 +132,7 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: false,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
outgoingRing: false,
|
||||
|
@ -140,6 +141,22 @@ describe('calling duck', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const stateWithActivePresentationViewGroupCall = {
|
||||
...stateWithGroupCall,
|
||||
activeCallState: {
|
||||
...stateWithActiveGroupCall.activeCallState,
|
||||
viewMode: CallViewMode.Presentation,
|
||||
},
|
||||
};
|
||||
|
||||
const stateWithActiveSpeakerViewGroupCall = {
|
||||
...stateWithGroupCall,
|
||||
activeCallState: {
|
||||
...stateWithActiveGroupCall.activeCallState,
|
||||
viewMode: CallViewMode.Speaker,
|
||||
},
|
||||
};
|
||||
|
||||
const ourUuid = UUID.generate().toString();
|
||||
|
||||
const getEmptyRootState = () => {
|
||||
|
@ -437,7 +454,7 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: true,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
outgoingRing: false,
|
||||
|
@ -530,7 +547,7 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: true,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
outgoingRing: false,
|
||||
|
@ -1122,7 +1139,7 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: false,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
outgoingRing: false,
|
||||
|
@ -1651,7 +1668,7 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: true,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
pip: false,
|
||||
|
@ -1937,7 +1954,7 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: false,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
pip: false,
|
||||
|
@ -2013,7 +2030,7 @@ describe('calling duck', () => {
|
|||
describe('toggleSpeakerView', () => {
|
||||
const { toggleSpeakerView } = actions;
|
||||
|
||||
it('toggles speaker view', () => {
|
||||
it('toggles speaker view from grid view', () => {
|
||||
const afterOneToggle = reducer(
|
||||
stateWithActiveGroupCall,
|
||||
toggleSpeakerView()
|
||||
|
@ -2021,9 +2038,92 @@ describe('calling duck', () => {
|
|||
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);
|
||||
assert.strictEqual(
|
||||
afterOneToggle.activeCallState?.viewMode,
|
||||
CallViewMode.Speaker
|
||||
);
|
||||
assert.strictEqual(
|
||||
afterTwoToggles.activeCallState?.viewMode,
|
||||
CallViewMode.Grid
|
||||
);
|
||||
assert.strictEqual(
|
||||
afterThreeToggles.activeCallState?.viewMode,
|
||||
CallViewMode.Speaker
|
||||
);
|
||||
});
|
||||
|
||||
it('toggles speaker view from presentation view', () => {
|
||||
const afterOneToggle = reducer(
|
||||
stateWithActivePresentationViewGroupCall,
|
||||
toggleSpeakerView()
|
||||
);
|
||||
const afterTwoToggles = reducer(afterOneToggle, toggleSpeakerView());
|
||||
const afterThreeToggles = reducer(afterTwoToggles, toggleSpeakerView());
|
||||
|
||||
assert.strictEqual(
|
||||
afterOneToggle.activeCallState?.viewMode,
|
||||
CallViewMode.Grid
|
||||
);
|
||||
assert.strictEqual(
|
||||
afterTwoToggles.activeCallState?.viewMode,
|
||||
CallViewMode.Speaker
|
||||
);
|
||||
assert.strictEqual(
|
||||
afterThreeToggles.activeCallState?.viewMode,
|
||||
CallViewMode.Grid
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switchToPresentationView', () => {
|
||||
const { switchToPresentationView, switchFromPresentationView } = actions;
|
||||
|
||||
it('toggles presentation view from grid view', () => {
|
||||
const afterOneToggle = reducer(
|
||||
stateWithActiveGroupCall,
|
||||
switchToPresentationView()
|
||||
);
|
||||
const afterTwoToggles = reducer(
|
||||
afterOneToggle,
|
||||
switchToPresentationView()
|
||||
);
|
||||
const finalState = reducer(
|
||||
afterOneToggle,
|
||||
switchFromPresentationView()
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
afterOneToggle.activeCallState?.viewMode,
|
||||
CallViewMode.Presentation
|
||||
);
|
||||
assert.strictEqual(
|
||||
afterTwoToggles.activeCallState?.viewMode,
|
||||
CallViewMode.Presentation
|
||||
);
|
||||
assert.strictEqual(
|
||||
finalState.activeCallState?.viewMode,
|
||||
CallViewMode.Grid
|
||||
);
|
||||
});
|
||||
|
||||
it('does not toggle presentation view from speaker view', () => {
|
||||
const afterOneToggle = reducer(
|
||||
stateWithActiveSpeakerViewGroupCall,
|
||||
switchToPresentationView()
|
||||
);
|
||||
const finalState = reducer(
|
||||
afterOneToggle,
|
||||
switchFromPresentationView()
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
afterOneToggle.activeCallState?.viewMode,
|
||||
CallViewMode.Speaker
|
||||
);
|
||||
assert.strictEqual(
|
||||
finalState.activeCallState?.viewMode,
|
||||
CallViewMode.Speaker
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import { noopAction } from '../../../state/ducks/noop';
|
|||
import {
|
||||
CallMode,
|
||||
CallState,
|
||||
CallViewMode,
|
||||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
} from '../../../types/Calling';
|
||||
|
@ -52,7 +53,7 @@ describe('state/selectors/calling', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: false,
|
||||
localAudioLevel: 0,
|
||||
isInSpeakerView: false,
|
||||
viewMode: CallViewMode.Grid,
|
||||
showParticipantsList: false,
|
||||
safetyNumberChangedUuids: [],
|
||||
outgoingRing: true,
|
||||
|
|
|
@ -11,6 +11,14 @@ export enum CallMode {
|
|||
Group = 'Group',
|
||||
}
|
||||
|
||||
// Speaker and Presentation has the same UI, but Presentation mode will switch
|
||||
// to Grid mode when the presentation is over.
|
||||
export enum CallViewMode {
|
||||
Grid = 'Grid',
|
||||
Speaker = 'Speaker',
|
||||
Presentation = 'Presentation',
|
||||
}
|
||||
|
||||
export type PresentableSource = {
|
||||
appIcon?: string;
|
||||
id: string;
|
||||
|
@ -29,7 +37,7 @@ type ActiveCallBaseType = {
|
|||
hasLocalAudio: boolean;
|
||||
hasLocalVideo: boolean;
|
||||
localAudioLevel: number;
|
||||
isInSpeakerView: boolean;
|
||||
viewMode: CallViewMode;
|
||||
isSharingScreen?: boolean;
|
||||
joinedAt?: number;
|
||||
outgoingRing: boolean;
|
||||
|
|
Loading…
Add table
Reference in a new issue