diff --git a/ts/components/CallManager.stories.tsx b/ts/components/CallManager.stories.tsx index 6741fd83f..8e69580b5 100644 --- a/ts/components/CallManager.stories.tsx +++ b/ts/components/CallManager.stories.tsx @@ -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 => ({ 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'), diff --git a/ts/components/CallManager.tsx b/ts/components/CallManager.tsx index a512c6132..c9a35a7d1 100644 --- a/ts/components/CallManager.tsx +++ b/ts/components/CallManager.tsx @@ -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 = ({ setRendererCanvas, setOutgoingRing, startCall, + switchToPresentationView, + switchFromPresentationView, theme, toggleParticipants, togglePip, @@ -270,8 +274,9 @@ const ActiveCallManager: React.FC = ({ setGroupCallVideoRequest={setGroupCallVideoRequestForConversation} setLocalPreview={setLocalPreview} setRendererCanvas={setRendererCanvas} + switchToPresentationView={switchToPresentationView} + switchFromPresentationView={switchFromPresentationView} togglePip={togglePip} - toggleSpeakerView={toggleSpeakerView} /> ); } @@ -313,6 +318,8 @@ const ActiveCallManager: React.FC = ({ setLocalVideo={setLocalVideo} setPresenting={setPresenting} stickyControls={showParticipantsList} + switchToPresentationView={switchToPresentationView} + switchFromPresentationView={switchFromPresentationView} toggleScreenRecordingPermissionsDialog={ toggleScreenRecordingPermissionsDialog } diff --git a/ts/components/CallScreen.stories.tsx b/ts/components/CallScreen.stories.tsx index 01b9a69cf..b98e60d56 100644 --- a/ts/components/CallScreen.stories.tsx +++ b/ts/components/CallScreen.stories.tsx @@ -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( diff --git a/ts/components/CallScreen.tsx b/ts/components/CallScreen.tsx index 3a03bf7b0..bb70cb7c1 100644 --- a/ts/components/CallScreen.tsx +++ b/ts/components/CallScreen.tsx @@ -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 = ({ setPresenting, setRendererCanvas, stickyControls, + switchToPresentationView, + switchFromPresentationView, toggleParticipants, togglePip, toggleScreenRecordingPermissionsDialog, @@ -131,18 +136,17 @@ export const CallScreen: React.FC = ({ 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 = ({ = ({ > ({ 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 => ({ 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); diff --git a/ts/components/CallingPip.tsx b/ts/components/CallingPip.tsx index 976280f1e..bac3c77d2 100644 --- a/ts/components/CallingPip.tsx +++ b/ts/components/CallingPip.tsx @@ -57,8 +57,9 @@ export type PropsType = { setGroupCallVideoRequest: (_: Array) => 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); 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 }); diff --git a/ts/hooks/useActivateSpeakerViewOnPresenting.ts b/ts/hooks/useActivateSpeakerViewOnPresenting.ts index 0d215c1d2..d3781e022 100644 --- a/ts/hooks/useActivateSpeakerViewOnPresenting.ts +++ b/ts/hooks/useActivateSpeakerViewOnPresenting.ts @@ -11,19 +11,30 @@ type RemoteParticipant = { uuid?: string; }; -export function useActivateSpeakerViewOnPresenting( - remoteParticipants: ReadonlyArray, - isInSpeakerView: boolean, - toggleSpeakerView: () => void -): void { +export function useActivateSpeakerViewOnPresenting({ + remoteParticipants, + switchToPresentationView, + switchFromPresentationView, +}: { + remoteParticipants: ReadonlyArray; + 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, + ]); } diff --git a/ts/state/ducks/calling.ts b/ts/state/ducks/calling.ts index 8b314dae9..a63f6c0b0 100644 --- a/ts/state/ducks/calling.ts +++ b/ts/state/ducks/calling.ts @@ -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, }, }; } diff --git a/ts/state/selectors/calling.ts b/ts/state/selectors/calling.ts index d1a8a8605..dcad8920e 100644 --- a/ts/state/selectors/calling.ts +++ b/ts/state/selectors/calling.ts @@ -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 | undefined +): boolean => { + return Boolean( + call?.viewMode === CallViewMode.Presentation || + call?.viewMode === CallViewMode.Speaker + ); +}; diff --git a/ts/state/smart/CallManager.tsx b/ts/state/smart/CallManager.tsx index dcdd78aa1..c5eed54ea 100644 --- a/ts/state/smart/CallManager.tsx +++ b/ts/state/smart/CallManager.tsx @@ -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, diff --git a/ts/test-electron/state/ducks/calling_test.ts b/ts/test-electron/state/ducks/calling_test.ts index 80d0a4af7..036e85623 100644 --- a/ts/test-electron/state/ducks/calling_test.ts +++ b/ts/test-electron/state/ducks/calling_test.ts @@ -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 + ); }); }); }); diff --git a/ts/test-electron/state/selectors/calling_test.ts b/ts/test-electron/state/selectors/calling_test.ts index 4c29d71b2..556a61d7b 100644 --- a/ts/test-electron/state/selectors/calling_test.ts +++ b/ts/test-electron/state/selectors/calling_test.ts @@ -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, diff --git a/ts/types/Calling.ts b/ts/types/Calling.ts index 2239274bd..b6ed3b327 100644 --- a/ts/types/Calling.ts +++ b/ts/types/Calling.ts @@ -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;