@@ -457,22 +496,18 @@ function getCallModeClassSuffix(
}
}
-function renderHeaderMessage(
+function renderDirectCallHeaderMessage(
i18n: LocalizerType,
callState: CallState,
acceptedDuration: null | number
): string | undefined {
- let message;
- if (callState === CallState.Prering) {
- message = i18n('outgoingCallPrering');
- } else if (callState === CallState.Ringing) {
- message = i18n('outgoingCallRinging');
- } else if (callState === CallState.Reconnecting) {
- message = i18n('callReconnecting');
- } else if (callState === CallState.Accepted && acceptedDuration) {
- message = i18n('callDuration', [renderDuration(acceptedDuration)]);
+ if (callState === CallState.Reconnecting) {
+ return i18n('callReconnecting');
}
- return message;
+ if (callState === CallState.Accepted && acceptedDuration) {
+ return i18n('callDuration', [renderDuration(acceptedDuration)]);
+ }
+ return undefined;
}
function renderDuration(ms: number): string {
diff --git a/ts/components/CallingButton.tsx b/ts/components/CallingButton.tsx
index 80ea1c80a..3516b6e3d 100644
--- a/ts/components/CallingButton.tsx
+++ b/ts/components/CallingButton.tsx
@@ -16,6 +16,9 @@ export enum CallingButtonType {
PRESENTING_DISABLED = 'PRESENTING_DISABLED',
PRESENTING_OFF = 'PRESENTING_OFF',
PRESENTING_ON = 'PRESENTING_ON',
+ RING_DISABLED = 'RING_DISABLED',
+ RING_OFF = 'RING_OFF',
+ RING_ON = 'RING_ON',
VIDEO_DISABLED = 'VIDEO_DISABLED',
VIDEO_OFF = 'VIDEO_OFF',
VIDEO_ON = 'VIDEO_ON',
@@ -24,6 +27,7 @@ export enum CallingButtonType {
export type PropsType = {
buttonType: CallingButtonType;
i18n: LocalizerType;
+ isVisible?: boolean;
onClick: () => void;
tooltipDirection?: TooltipPlacement;
};
@@ -31,6 +35,7 @@ export type PropsType = {
export const CallingButton = ({
buttonType,
i18n,
+ isVisible = true,
onClick,
tooltipDirection,
}: PropsType): JSX.Element => {
@@ -70,6 +75,21 @@ export const CallingButton = ({
classNameSuffix = 'hangup';
tooltipContent = i18n('calling__hangup');
label = i18n('calling__hangup');
+ } else if (buttonType === CallingButtonType.RING_DISABLED) {
+ classNameSuffix = 'ring--disabled';
+ disabled = true;
+ tooltipContent = i18n(
+ 'calling__button--ring__disabled-because-group-is-too-large'
+ );
+ label = i18n('calling__button--ring__label');
+ } else if (buttonType === CallingButtonType.RING_OFF) {
+ classNameSuffix = 'ring--off';
+ tooltipContent = i18n('calling__button--ring__on');
+ label = i18n('calling__button--ring__label');
+ } else if (buttonType === CallingButtonType.RING_ON) {
+ classNameSuffix = 'ring--on';
+ tooltipContent = i18n('calling__button--ring__off');
+ label = i18n('calling__button--ring__label');
} else if (buttonType === CallingButtonType.PRESENTING_DISABLED) {
classNameSuffix = 'presenting--disabled';
tooltipContent = i18n('calling__button--presenting-disabled');
@@ -96,7 +116,12 @@ export const CallingButton = ({
direction={tooltipDirection}
theme={Theme.Dark}
>
-
+
({
hasLocalVideo: boolean('hasLocalVideo', false),
isInSpeakerView: boolean('isInSpeakerView', false),
joinedAt: Date.now(),
+ outgoingRing: true,
pip: true,
settingsDialogOpen: false,
showParticipantsList: false,
diff --git a/ts/components/CallingPreCallInfo.stories.tsx b/ts/components/CallingPreCallInfo.stories.tsx
index 8db785d3c..3896a5eb7 100644
--- a/ts/components/CallingPreCallInfo.stories.tsx
+++ b/ts/components/CallingPreCallInfo.stories.tsx
@@ -8,7 +8,7 @@ import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
-import { CallingPreCallInfo } from './CallingPreCallInfo';
+import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
const i18n = setupI18n('en', enMessages);
const getDefaultGroupConversation = () =>
@@ -28,34 +28,28 @@ story.add('Direct conversation', () => (
conversation={getDefaultConversation()}
i18n={i18n}
me={getDefaultConversation()}
- />
-));
-
-story.add('Group conversation, empty group', () => (
-
));
times(5, numberOfOtherPeople => {
- story.add(
- `Group conversation, group has ${numberOfOtherPeople} other member${
- numberOfOtherPeople === 1 ? '' : 's'
- }`,
- () => (
-
- )
- );
+ [true, false].forEach(willRing => {
+ story.add(
+ `Group conversation, group has ${numberOfOtherPeople} other member${
+ numberOfOtherPeople === 1 ? '' : 's'
+ }, will ${willRing ? 'ring' : 'notify'}`,
+ () => (
+
+ )
+ );
+ });
});
range(1, 5).forEach(numberOfOtherPeople => {
@@ -70,6 +64,7 @@ range(1, 5).forEach(numberOfOtherPeople => {
i18n={i18n}
me={getDefaultConversation()}
peekedParticipants={otherMembers.slice(0, numberOfOtherPeople)}
+ ringMode={RingMode.WillRing}
/>
)
);
@@ -84,6 +79,7 @@ story.add('Group conversation, you on an other device', () => {
i18n={i18n}
me={me}
peekedParticipants={[me]}
+ ringMode={RingMode.WillRing}
/>
);
});
@@ -96,5 +92,6 @@ story.add('Group conversation, call is full', () => (
isCallFull
me={getDefaultConversation()}
peekedParticipants={otherMembers}
+ ringMode={RingMode.WillRing}
/>
));
diff --git a/ts/components/CallingPreCallInfo.tsx b/ts/components/CallingPreCallInfo.tsx
index 8a2d8c128..d30e8d3ff 100644
--- a/ts/components/CallingPreCallInfo.tsx
+++ b/ts/components/CallingPreCallInfo.tsx
@@ -9,6 +9,12 @@ import { Emojify } from './conversation/Emojify';
import { getParticipantName } from '../util/callingGetParticipantName';
import { missingCaseError } from '../util/missingCaseError';
+export enum RingMode {
+ WillNotRing,
+ WillRing,
+ IsRinging,
+}
+
type PropsType = {
conversation: Pick<
ConversationType,
@@ -25,10 +31,11 @@ type PropsType = {
| 'unblurredAvatarPath'
>;
i18n: LocalizerType;
- me: Pick;
+ me: Pick;
+ ringMode: RingMode;
// The following should only be set for group conversations.
- groupMembers?: Array>;
+ groupMembers?: Array>;
isCallFull?: boolean;
peekedParticipants?: Array<
Pick
@@ -42,9 +49,12 @@ export const CallingPreCallInfo: FunctionComponent = ({
isCallFull = false,
me,
peekedParticipants = [],
+ ringMode,
}) => {
let subtitle: string;
- if (isCallFull) {
+ if (ringMode === RingMode.IsRinging) {
+ subtitle = i18n('outgoingCallRinging');
+ } else if (isCallFull) {
subtitle = i18n('calling__call-is-full');
} else if (peekedParticipants.length) {
// It should be rare to see yourself in this list, but it's possible if (1) you rejoin
@@ -86,45 +96,67 @@ export const CallingPreCallInfo: FunctionComponent = ({
});
break;
}
- } else if (conversation.type === 'direct') {
- subtitle = i18n('calling__pre-call-info--will-ring-1', [
- getParticipantName(conversation),
- ]);
- } else if (conversation.type === 'group') {
- const memberNames = groupMembers.map(getParticipantName);
+ } else {
+ let memberNames: Array;
+ switch (conversation.type) {
+ case 'direct':
+ memberNames = [getParticipantName(conversation)];
+ break;
+ case 'group':
+ memberNames = groupMembers
+ .filter(member => member.id !== me.id)
+ .map(getParticipantName);
+ break;
+ default:
+ throw missingCaseError(conversation.type);
+ }
+
+ const ring = ringMode === RingMode.WillRing;
switch (memberNames.length) {
case 0:
subtitle = i18n('calling__pre-call-info--empty-group');
break;
- case 1:
- subtitle = i18n('calling__pre-call-info--will-notify-1', [
- memberNames[0],
- ]);
+ case 1: {
+ const i18nValues = [memberNames[0]];
+ subtitle = ring
+ ? i18n('calling__pre-call-info--will-ring-1', i18nValues)
+ : i18n('calling__pre-call-info--will-notify-1', i18nValues);
break;
- case 2:
- subtitle = i18n('calling__pre-call-info--will-notify-2', {
+ }
+ case 2: {
+ const i18nValues = {
first: memberNames[0],
second: memberNames[1],
- });
+ };
+ subtitle = ring
+ ? i18n('calling__pre-call-info--will-ring-2', i18nValues)
+ : i18n('calling__pre-call-info--will-notify-2', i18nValues);
break;
- case 3:
- subtitle = i18n('calling__pre-call-info--will-notify-3', {
+ }
+ case 3: {
+ const i18nValues = {
first: memberNames[0],
second: memberNames[1],
third: memberNames[2],
- });
+ };
+ subtitle = ring
+ ? i18n('calling__pre-call-info--will-ring-3', i18nValues)
+ : i18n('calling__pre-call-info--will-notify-3', i18nValues);
break;
- default:
- subtitle = i18n('calling__pre-call-info--will-notify-many', {
+ }
+ default: {
+ const i18nValues = {
first: memberNames[0],
second: memberNames[1],
others: String(memberNames.length - 2),
- });
+ };
+ subtitle = ring
+ ? i18n('calling__pre-call-info--will-ring-many', i18nValues)
+ : i18n('calling__pre-call-info--will-notify-many', i18nValues);
break;
+ }
}
- } else {
- throw missingCaseError(conversation.type);
}
return (
diff --git a/ts/services/calling.ts b/ts/services/calling.ts
index 9c0cbb9ca..6de4855ee 100644
--- a/ts/services/calling.ts
+++ b/ts/services/calling.ts
@@ -684,7 +684,8 @@ export class CallingClass {
public joinGroupCall(
conversationId: string,
hasLocalAudio: boolean,
- hasLocalVideo: boolean
+ hasLocalVideo: boolean,
+ shouldRing: boolean
): void {
this.attemptToGiveOurUuidToRingRtc();
@@ -717,10 +718,7 @@ export class CallingClass {
groupCall.setOutgoingVideoMuted(!hasLocalVideo);
this.videoCapturer.enableCaptureAndSend(groupCall);
- // This is a temporary flag to help all client teams (Desktop, iOS, and Android)
- // debug. Soon, this will be exposed in the UI (see DESKTOP-2113).
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- if (window.RING_WHEN_JOINING_GROUP_CALLS) {
+ if (shouldRing) {
groupCall.ringAll();
}
diff --git a/ts/state/ducks/calling.ts b/ts/state/ducks/calling.ts
index 3a5a70bbf..515038472 100644
--- a/ts/state/ducks/calling.ts
+++ b/ts/state/ducks/calling.ts
@@ -88,6 +88,7 @@ export type ActiveCallStateType = {
hasLocalVideo: boolean;
isInSpeakerView: boolean;
joinedAt?: number;
+ outgoingRing: boolean;
pip: boolean;
presentingSource?: PresentedSource;
presentingSourcesAvailable?: Array;
@@ -286,6 +287,7 @@ const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE';
const RETURN_TO_ACTIVE_CALL = 'calling/RETURN_TO_ACTIVE_CALL';
const SET_LOCAL_AUDIO_FULFILLED = 'calling/SET_LOCAL_AUDIO_FULFILLED';
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
+const SET_OUTGOING_RING = 'calling/SET_OUTGOING_RING';
const SET_PRESENTING = 'calling/SET_PRESENTING';
const SET_PRESENTING_SOURCES = 'calling/SET_PRESENTING_SOURCES';
const TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS =
@@ -420,6 +422,11 @@ type SetPresentingSourcesActionType = {
payload: Array;
};
+type SetOutgoingRingActionType = {
+ type: 'calling/SET_OUTGOING_RING';
+ payload: boolean;
+};
+
type ShowCallLobbyActionType = {
type: 'calling/SHOW_CALL_LOBBY';
payload: ShowCallLobbyType;
@@ -474,6 +481,7 @@ export type CallingActionType =
| SetLocalAudioActionType
| SetLocalVideoFulfilledActionType
| SetPresentingSourcesActionType
+ | SetOutgoingRingActionType
| ShowCallLobbyActionType
| StartDirectCallActionType
| ToggleNeedsScreenRecordingPermissionsActionType
@@ -502,7 +510,7 @@ function acceptCall(
await calling.acceptDirectCall(conversationId, asVideoCall);
break;
case CallMode.Group:
- calling.joinGroupCall(conversationId, true, asVideoCall);
+ calling.joinGroupCall(conversationId, true, asVideoCall, false);
break;
default:
throw missingCaseError(call);
@@ -1020,6 +1028,13 @@ function setPresenting(
};
}
+function setOutgoingRing(payload: boolean): SetOutgoingRingActionType {
+ return {
+ type: SET_OUTGOING_RING,
+ payload,
+ };
+}
+
function startCallingLobby(
payload: StartCallingLobbyType
): ThunkAction {
@@ -1040,7 +1055,7 @@ function showCallLobby(payload: ShowCallLobbyType): CallLobbyActionType {
function startCall(
payload: StartCallType
): ThunkAction {
- return dispatch => {
+ return (dispatch, getState) => {
switch (payload.callMode) {
case CallMode.Direct:
calling.startOutgoingDirectCall(
@@ -1053,15 +1068,20 @@ function startCall(
payload,
});
break;
- case CallMode.Group:
+ case CallMode.Group: {
+ const outgoingRing = Boolean(
+ getState().calling.activeCallState?.outgoingRing
+ );
calling.joinGroupCall(
payload.conversationId,
payload.hasLocalAudio,
- payload.hasLocalVideo
+ payload.hasLocalVideo,
+ outgoingRing
);
// The calling service should already be wired up to Redux so we don't need to
// dispatch anything here.
break;
+ }
default:
throw missingCaseError(payload.callMode);
}
@@ -1126,6 +1146,7 @@ export const actions = {
setLocalVideo,
setPresenting,
setRendererCanvas,
+ setOutgoingRing,
showCallLobby,
startCall,
startCallingLobby,
@@ -1184,6 +1205,7 @@ export function reducer(
const { conversationId } = action.payload;
let call: DirectCallStateType | GroupCallStateType;
+ let outgoingRing: boolean;
switch (action.payload.callMode) {
case CallMode.Direct:
call = {
@@ -1192,6 +1214,7 @@ export function reducer(
isIncoming: false,
isVideoCall: action.payload.hasLocalVideo,
};
+ outgoingRing = true;
break;
case CallMode.Group: {
// We expect to be in this state briefly. The Calling service should update the
@@ -1211,6 +1234,8 @@ export function reducer(
remoteParticipants: action.payload.remoteParticipants,
...getGroupCallRingState(existingCall),
};
+ outgoingRing =
+ !call.peekInfo.uuids.length && !call.remoteParticipants.length;
break;
}
default:
@@ -1232,6 +1257,7 @@ export function reducer(
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
showParticipantsList: false,
+ outgoingRing,
},
};
}
@@ -1258,6 +1284,7 @@ export function reducer(
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
showParticipantsList: false,
+ outgoingRing: true,
},
};
}
@@ -1279,6 +1306,7 @@ export function reducer(
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
showParticipantsList: false,
+ outgoingRing: false,
},
};
}
@@ -1412,6 +1440,7 @@ export function reducer(
safetyNumberChangedUuids: [],
settingsDialogOpen: false,
showParticipantsList: false,
+ outgoingRing: true,
},
};
}
@@ -1513,6 +1542,18 @@ export function reducer(
: state.activeCallState;
}
+ if (
+ newActiveCallState &&
+ newActiveCallState.outgoingRing &&
+ newActiveCallState.conversationId === conversationId &&
+ isAnybodyElseInGroupCall(newPeekInfo, ourUuid)
+ ) {
+ newActiveCallState = {
+ ...newActiveCallState,
+ outgoingRing: false,
+ };
+ }
+
let newRingState: GroupCallRingStateType;
if (joinState === GroupCallJoinState.NotJoined) {
newRingState = existingRingState;
@@ -1801,6 +1842,22 @@ export function reducer(
};
}
+ if (action.type === SET_OUTGOING_RING) {
+ const { activeCallState } = state;
+ if (!activeCallState) {
+ window.log.warn('Cannot set outgoing ring when there is no active call');
+ return state;
+ }
+
+ return {
+ ...state,
+ activeCallState: {
+ ...activeCallState,
+ outgoingRing: action.payload,
+ },
+ };
+ }
+
if (action.type === TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS) {
const { activeCallState } = state;
if (!activeCallState) {
diff --git a/ts/state/smart/CallManager.tsx b/ts/state/smart/CallManager.tsx
index 9daad679b..d61feed86 100644
--- a/ts/state/smart/CallManager.tsx
+++ b/ts/state/smart/CallManager.tsx
@@ -12,6 +12,8 @@ import { getMe, getConversationSelector } from '../selectors/conversations';
import { getActiveCall } from '../ducks/calling';
import { ConversationType } from '../ducks/conversations';
import { getIncomingCall } from '../selectors/calling';
+import { getMaxGroupCallRingSize } from '../../groups/limits';
+import { isGroupCallOutboundRingEnabled } from '../../util/isGroupCallOutboundRingEnabled';
import {
ActiveCallType,
CallMode,
@@ -111,6 +113,7 @@ const mapStateToActiveCallProp = (
hasLocalVideo: activeCallState.hasLocalVideo,
isInSpeakerView: activeCallState.isInSpeakerView,
joinedAt: activeCallState.joinedAt,
+ outgoingRing: activeCallState.outgoingRing,
pip: activeCallState.pip,
presentingSource: activeCallState.presentingSource,
presentingSourcesAvailable: activeCallState.presentingSourcesAvailable,
@@ -292,7 +295,9 @@ const mapStateToProps = (state: StateType) => ({
availableCameras: state.calling.availableCameras,
getGroupCallVideoFrameSource,
i18n: getIntl(state),
+ isGroupCallOutboundRingEnabled: isGroupCallOutboundRingEnabled(),
incomingCall: mapStateToIncomingCallProp(state),
+ maxGroupCallRingSize: getMaxGroupCallRingSize(),
me: {
...getMe(state),
// `getMe` returns a `ConversationType` which might not have a UUID, at least
diff --git a/ts/test-electron/state/ducks/calling_test.ts b/ts/test-electron/state/ducks/calling_test.ts
index f9ceaf8ca..d218263e6 100644
--- a/ts/test-electron/state/ducks/calling_test.ts
+++ b/ts/test-electron/state/ducks/calling_test.ts
@@ -46,6 +46,7 @@ describe('calling duck', () => {
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
+ outgoingRing: true,
pip: false,
settingsDialogOpen: false,
},
@@ -118,6 +119,7 @@ describe('calling duck', () => {
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
+ outgoingRing: false,
pip: false,
settingsDialogOpen: false,
},
@@ -423,6 +425,7 @@ describe('calling duck', () => {
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
+ outgoingRing: false,
pip: false,
settingsDialogOpen: false,
});
@@ -514,6 +517,7 @@ describe('calling duck', () => {
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
+ outgoingRing: false,
pip: false,
settingsDialogOpen: false,
});
@@ -1224,6 +1228,7 @@ describe('calling duck', () => {
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
+ outgoingRing: false,
pip: false,
settingsDialogOpen: false,
});
@@ -1264,6 +1269,62 @@ describe('calling duck', () => {
assert.isTrue(result.activeCallState?.hasLocalAudio);
assert.isTrue(result.activeCallState?.hasLocalVideo);
});
+
+ it("doesn't stop ringing if nobody is in the call", () => {
+ const state = {
+ ...stateWithActiveGroupCall,
+ activeCallState: {
+ ...stateWithActiveGroupCall.activeCallState,
+ outgoingRing: true,
+ },
+ };
+ const result = reducer(
+ state,
+ getAction({
+ conversationId: 'fake-group-call-conversation-id',
+ connectionState: GroupCallConnectionState.Connected,
+ joinState: GroupCallJoinState.Joined,
+ hasLocalAudio: true,
+ hasLocalVideo: true,
+ peekInfo: {
+ uuids: [],
+ maxDevices: 16,
+ deviceCount: 0,
+ },
+ remoteParticipants: [],
+ })
+ );
+
+ assert.isTrue(result.activeCallState?.outgoingRing);
+ });
+
+ it('stops ringing if someone enters the call', () => {
+ const state = {
+ ...stateWithActiveGroupCall,
+ activeCallState: {
+ ...stateWithActiveGroupCall.activeCallState,
+ outgoingRing: true,
+ },
+ };
+ const result = reducer(
+ state,
+ getAction({
+ conversationId: 'fake-group-call-conversation-id',
+ connectionState: GroupCallConnectionState.Connected,
+ joinState: GroupCallJoinState.Joined,
+ hasLocalAudio: true,
+ hasLocalVideo: true,
+ peekInfo: {
+ uuids: ['1b9e4d42-1f56-45c5-b6f4-d1be5a54fefa'],
+ maxDevices: 16,
+ deviceCount: 1,
+ },
+ remoteParticipants: [],
+ })
+ );
+
+ assert.isFalse(result.activeCallState?.outgoingRing);
+ });
});
describe('peekNotConnectedGroupCall', () => {
@@ -1519,6 +1580,24 @@ describe('calling duck', () => {
});
});
+ describe('setOutgoingRing', () => {
+ const { setOutgoingRing } = actions;
+
+ it('enables a desire to ring', () => {
+ const action = setOutgoingRing(true);
+ const result = reducer(stateWithActiveGroupCall, action);
+
+ assert.isTrue(result.activeCallState?.outgoingRing);
+ });
+
+ it('disables a desire to ring', () => {
+ const action = setOutgoingRing(false);
+ const result = reducer(stateWithActiveDirectCall, action);
+
+ assert.isFalse(result.activeCallState?.outgoingRing);
+ });
+ });
+
describe('showCallLobby', () => {
const { showCallLobby } = actions;
@@ -1548,6 +1627,7 @@ describe('calling duck', () => {
safetyNumberChangedUuids: [],
pip: false,
settingsDialogOpen: false,
+ outgoingRing: true,
});
});
@@ -1610,6 +1690,7 @@ describe('calling duck', () => {
result.activeCallState?.conversationId,
'fake-conversation-id'
);
+ assert.isFalse(result.activeCallState?.outgoingRing);
});
it('chooses fallback peek info if none is sent and there is no existing call', () => {
@@ -1841,6 +1922,7 @@ describe('calling duck', () => {
safetyNumberChangedUuids: [],
pip: false,
settingsDialogOpen: false,
+ outgoingRing: true,
});
});
diff --git a/ts/test-electron/state/selectors/calling_test.ts b/ts/test-electron/state/selectors/calling_test.ts
index e397f1e95..257f9ffb5 100644
--- a/ts/test-electron/state/selectors/calling_test.ts
+++ b/ts/test-electron/state/selectors/calling_test.ts
@@ -54,6 +54,7 @@ describe('state/selectors/calling', () => {
isInSpeakerView: false,
showParticipantsList: false,
safetyNumberChangedUuids: [],
+ outgoingRing: true,
pip: false,
settingsDialogOpen: false,
},
diff --git a/ts/types/Calling.ts b/ts/types/Calling.ts
index 325b2fb60..04abd1e0d 100644
--- a/ts/types/Calling.ts
+++ b/ts/types/Calling.ts
@@ -30,6 +30,7 @@ type ActiveCallBaseType = {
isInSpeakerView: boolean;
isSharingScreen?: boolean;
joinedAt?: number;
+ outgoingRing: boolean;
pip: boolean;
presentingSource?: PresentedSource;
presentingSourcesAvailable?: Array;
@@ -60,7 +61,7 @@ type ActiveGroupCallType = ActiveCallBaseType & {
joinState: GroupCallJoinState;
maxDevices: number;
deviceCount: number;
- groupMembers: Array>;
+ groupMembers: Array>;
peekedParticipants: Array;
remoteParticipants: Array;
};
diff --git a/ts/util/isGroupCallOutboundRingEnabled.ts b/ts/util/isGroupCallOutboundRingEnabled.ts
new file mode 100644
index 000000000..57bdbb1cb
--- /dev/null
+++ b/ts/util/isGroupCallOutboundRingEnabled.ts
@@ -0,0 +1,11 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import * as RemoteConfig from '../RemoteConfig';
+
+export function isGroupCallOutboundRingEnabled(): boolean {
+ return Boolean(
+ RemoteConfig.isEnabled('desktop.internalUser') ||
+ RemoteConfig.isEnabled('desktop.groupCallOutboundRing')
+ );
+}
diff --git a/ts/window.d.ts b/ts/window.d.ts
index cabf1b796..eca2fbfbd 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -511,7 +511,6 @@ declare global {
GV2_ENABLE_STATE_PROCESSING: boolean;
GV2_MIGRATION_DISABLE_ADD: boolean;
GV2_MIGRATION_DISABLE_INVITE: boolean;
- RING_WHEN_JOINING_GROUP_CALLS: boolean;
RETRY_DELAY: boolean;
}