Centralize logic for finding/fetching the ringing call
This commit is contained in:
parent
6888bb9cba
commit
1ce3988579
6 changed files with 131 additions and 173 deletions
|
@ -105,7 +105,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
hangUpActiveCall: action('hang-up-active-call'),
|
hangUpActiveCall: action('hang-up-active-call'),
|
||||||
hasInitialLoadCompleted: true,
|
hasInitialLoadCompleted: true,
|
||||||
i18n,
|
i18n,
|
||||||
incomingCall: null,
|
ringingCall: null,
|
||||||
callLink: storyProps.callLink ?? undefined,
|
callLink: storyProps.callLink ?? undefined,
|
||||||
me: {
|
me: {
|
||||||
...getDefaultConversation({
|
...getDefaultConversation({
|
||||||
|
@ -148,7 +148,6 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
'toggle-screen-recording-permissions-dialog'
|
'toggle-screen-recording-permissions-dialog'
|
||||||
),
|
),
|
||||||
toggleSettings: action('toggle-settings'),
|
toggleSettings: action('toggle-settings'),
|
||||||
isConversationTooBigToRing: false,
|
|
||||||
pauseVoiceNotePlayer: action('pause-audio-player'),
|
pauseVoiceNotePlayer: action('pause-audio-player'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -243,7 +242,7 @@ export function RingingDirectCall(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CallManager
|
<CallManager
|
||||||
{...createProps({
|
{...createProps({
|
||||||
incomingCall: {
|
ringingCall: {
|
||||||
callMode: CallMode.Direct as const,
|
callMode: CallMode.Direct as const,
|
||||||
conversation: getConversation(),
|
conversation: getConversation(),
|
||||||
isVideoCall: true,
|
isVideoCall: true,
|
||||||
|
@ -257,7 +256,7 @@ export function RingingGroupCall(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CallManager
|
<CallManager
|
||||||
{...createProps({
|
{...createProps({
|
||||||
incomingCall: {
|
ringingCall: {
|
||||||
callMode: CallMode.Group as const,
|
callMode: CallMode.Group as const,
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
connectionState: GroupCallConnectionState.NotConnected,
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
|
|
|
@ -13,14 +13,13 @@ import { CallingPip } from './CallingPip';
|
||||||
import { IncomingCallBar } from './IncomingCallBar';
|
import { IncomingCallBar } from './IncomingCallBar';
|
||||||
import type {
|
import type {
|
||||||
ActiveCallType,
|
ActiveCallType,
|
||||||
CallingConversationType,
|
|
||||||
CallViewMode,
|
CallViewMode,
|
||||||
|
GroupCallConnectionState,
|
||||||
GroupCallVideoRequest,
|
GroupCallVideoRequest,
|
||||||
} from '../types/Calling';
|
} from '../types/Calling';
|
||||||
import {
|
import {
|
||||||
CallEndedReason,
|
CallEndedReason,
|
||||||
CallState,
|
CallState,
|
||||||
GroupCallConnectionState,
|
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
} from '../types/Calling';
|
} from '../types/Calling';
|
||||||
import { CallMode } from '../types/CallDisposition';
|
import { CallMode } from '../types/CallDisposition';
|
||||||
|
@ -90,7 +89,7 @@ export type PropsType = {
|
||||||
) => VideoFrameSource;
|
) => VideoFrameSource;
|
||||||
getIsSharingPhoneNumberWithEverybody: () => boolean;
|
getIsSharingPhoneNumberWithEverybody: () => boolean;
|
||||||
getPresentingSources: () => void;
|
getPresentingSources: () => void;
|
||||||
incomingCall: DirectIncomingCall | GroupIncomingCall | null;
|
ringingCall: DirectIncomingCall | GroupIncomingCall | null;
|
||||||
renderDeviceSelection: () => JSX.Element;
|
renderDeviceSelection: () => JSX.Element;
|
||||||
renderReactionPicker: (
|
renderReactionPicker: (
|
||||||
props: React.ComponentProps<typeof SmartReactionPicker>
|
props: React.ComponentProps<typeof SmartReactionPicker>
|
||||||
|
@ -140,7 +139,6 @@ export type PropsType = {
|
||||||
toggleCallLinkPendingParticipantModal: (contactId: string) => void;
|
toggleCallLinkPendingParticipantModal: (contactId: string) => void;
|
||||||
toggleScreenRecordingPermissionsDialog: () => unknown;
|
toggleScreenRecordingPermissionsDialog: () => unknown;
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
isConversationTooBigToRing: boolean;
|
|
||||||
pauseVoiceNotePlayer: () => void;
|
pauseVoiceNotePlayer: () => void;
|
||||||
} & Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
} & Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
||||||
|
|
||||||
|
@ -153,9 +151,9 @@ type ActiveCallManagerPropsType = {
|
||||||
| 'bounceAppIconStop'
|
| 'bounceAppIconStop'
|
||||||
| 'declineCall'
|
| 'declineCall'
|
||||||
| 'hasInitialLoadCompleted'
|
| 'hasInitialLoadCompleted'
|
||||||
| 'incomingCall'
|
|
||||||
| 'notifyForCall'
|
| 'notifyForCall'
|
||||||
| 'playRingtone'
|
| 'playRingtone'
|
||||||
|
| 'ringingCall'
|
||||||
| 'setIsCallActive'
|
| 'setIsCallActive'
|
||||||
| 'stopRingtone'
|
| 'stopRingtone'
|
||||||
| 'isConversationTooBigToRing'
|
| 'isConversationTooBigToRing'
|
||||||
|
@ -544,8 +542,6 @@ export function CallManager({
|
||||||
hangUpActiveCall,
|
hangUpActiveCall,
|
||||||
hasInitialLoadCompleted,
|
hasInitialLoadCompleted,
|
||||||
i18n,
|
i18n,
|
||||||
incomingCall,
|
|
||||||
isConversationTooBigToRing,
|
|
||||||
getIsSharingPhoneNumberWithEverybody,
|
getIsSharingPhoneNumberWithEverybody,
|
||||||
me,
|
me,
|
||||||
notifyForCall,
|
notifyForCall,
|
||||||
|
@ -556,6 +552,7 @@ export function CallManager({
|
||||||
renderDeviceSelection,
|
renderDeviceSelection,
|
||||||
renderEmojiPicker,
|
renderEmojiPicker,
|
||||||
renderReactionPicker,
|
renderReactionPicker,
|
||||||
|
ringingCall,
|
||||||
selectPresentingSource,
|
selectPresentingSource,
|
||||||
sendGroupCallRaiseHand,
|
sendGroupCallRaiseHand,
|
||||||
sendGroupCallReaction,
|
sendGroupCallReaction,
|
||||||
|
@ -583,16 +580,13 @@ export function CallManager({
|
||||||
setIsCallActive(isCallActive);
|
setIsCallActive(isCallActive);
|
||||||
}, [isCallActive, setIsCallActive]);
|
}, [isCallActive, setIsCallActive]);
|
||||||
|
|
||||||
const shouldRing = getShouldRing({
|
// It's important not to use the ringingCall itself, because that changes
|
||||||
activeCall,
|
const ringingCallId = ringingCall?.conversation.id;
|
||||||
incomingCall,
|
|
||||||
isConversationTooBigToRing,
|
|
||||||
hasInitialLoadCompleted,
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldRing) {
|
if (hasInitialLoadCompleted && ringingCallId) {
|
||||||
log.info('CallManager: Playing ringtone');
|
log.info('CallManager: Playing ringtone');
|
||||||
playRingtone();
|
playRingtone();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
log.info('CallManager: Stopping ringtone');
|
log.info('CallManager: Stopping ringtone');
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
|
@ -601,7 +595,7 @@ export function CallManager({
|
||||||
|
|
||||||
stopRingtone();
|
stopRingtone();
|
||||||
return noop;
|
return noop;
|
||||||
}, [shouldRing, playRingtone, stopRingtone]);
|
}, [hasInitialLoadCompleted, playRingtone, ringingCallId, stopRingtone]);
|
||||||
|
|
||||||
const mightBeRingingOutgoingGroupCall =
|
const mightBeRingingOutgoingGroupCall =
|
||||||
isGroupOrAdhocActiveCall(activeCall) &&
|
isGroupOrAdhocActiveCall(activeCall) &&
|
||||||
|
@ -680,7 +674,7 @@ export function CallManager({
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the future, we may want to show the incoming call bar when a call is active.
|
// In the future, we may want to show the incoming call bar when a call is active.
|
||||||
if (incomingCall) {
|
if (ringingCall) {
|
||||||
return (
|
return (
|
||||||
<IncomingCallBar
|
<IncomingCallBar
|
||||||
acceptCall={acceptCall}
|
acceptCall={acceptCall}
|
||||||
|
@ -689,107 +683,10 @@ export function CallManager({
|
||||||
declineCall={declineCall}
|
declineCall={declineCall}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
notifyForCall={notifyForCall}
|
notifyForCall={notifyForCall}
|
||||||
{...incomingCall}
|
{...ringingCall}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRinging(callState: CallState | undefined): boolean {
|
|
||||||
return callState === CallState.Prering || callState === CallState.Ringing;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isConnected(connectionState: GroupCallConnectionState): boolean {
|
|
||||||
return (
|
|
||||||
connectionState === GroupCallConnectionState.Connecting ||
|
|
||||||
connectionState === GroupCallConnectionState.Connected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isJoined(joinState: GroupCallJoinState): boolean {
|
|
||||||
return joinState !== GroupCallJoinState.NotJoined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasRemoteParticipants(
|
|
||||||
remoteParticipants: Array<GroupCallParticipantInfoType>
|
|
||||||
): boolean {
|
|
||||||
return remoteParticipants.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLonelyGroup(conversation: CallingConversationType): boolean {
|
|
||||||
return (conversation.sortedGroupMembers?.length ?? 0) < 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getShouldRing({
|
|
||||||
activeCall,
|
|
||||||
incomingCall,
|
|
||||||
isConversationTooBigToRing,
|
|
||||||
hasInitialLoadCompleted,
|
|
||||||
}: Readonly<
|
|
||||||
Pick<
|
|
||||||
PropsType,
|
|
||||||
| 'activeCall'
|
|
||||||
| 'incomingCall'
|
|
||||||
| 'isConversationTooBigToRing'
|
|
||||||
| 'hasInitialLoadCompleted'
|
|
||||||
>
|
|
||||||
>): boolean {
|
|
||||||
if (!hasInitialLoadCompleted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (incomingCall != null) {
|
|
||||||
// don't ring a large group
|
|
||||||
if (isConversationTooBigToRing) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeCall != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (incomingCall.callMode === CallMode.Direct) {
|
|
||||||
return (
|
|
||||||
isRinging(incomingCall.callState) &&
|
|
||||||
incomingCall.callEndedReason == null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (incomingCall.callMode === CallMode.Group) {
|
|
||||||
return (
|
|
||||||
!isConnected(incomingCall.connectionState) &&
|
|
||||||
!isJoined(incomingCall.joinState) &&
|
|
||||||
!isLonelyGroup(incomingCall.conversation)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adhoc calls can't be incoming.
|
|
||||||
|
|
||||||
throw missingCaseError(incomingCall);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeCall != null) {
|
|
||||||
switch (activeCall.callMode) {
|
|
||||||
case CallMode.Direct:
|
|
||||||
return (
|
|
||||||
activeCall.callState === CallState.Prering ||
|
|
||||||
activeCall.callState === CallState.Ringing
|
|
||||||
);
|
|
||||||
case CallMode.Group:
|
|
||||||
case CallMode.Adhoc:
|
|
||||||
return (
|
|
||||||
activeCall.outgoingRing &&
|
|
||||||
isConnected(activeCall.connectionState) &&
|
|
||||||
isJoined(activeCall.joinState) &&
|
|
||||||
!hasRemoteParticipants(activeCall.remoteParticipants) &&
|
|
||||||
!isLonelyGroup(activeCall.conversation)
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw missingCaseError(activeCall);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,15 +3,22 @@
|
||||||
|
|
||||||
// Note that this file should not important any binary addons or Node.js modules
|
// Note that this file should not important any binary addons or Node.js modules
|
||||||
// because it can be imported by storybook
|
// because it can be imported by storybook
|
||||||
import { CallState, GroupCallConnectionState } from '../../types/Calling';
|
import {
|
||||||
|
CallState,
|
||||||
|
GroupCallConnectionState,
|
||||||
|
GroupCallJoinState,
|
||||||
|
} from '../../types/Calling';
|
||||||
import { CallMode } from '../../types/CallDisposition';
|
import { CallMode } from '../../types/CallDisposition';
|
||||||
|
|
||||||
|
import type { CallingConversationType } from '../../types/Calling';
|
||||||
import type { AciString } from '../../types/ServiceId';
|
import type { AciString } from '../../types/ServiceId';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
|
||||||
import type {
|
import type {
|
||||||
DirectCallStateType,
|
DirectCallStateType,
|
||||||
CallsByConversationType,
|
CallsByConversationType,
|
||||||
GroupCallPeekInfoType,
|
GroupCallPeekInfoType,
|
||||||
GroupCallStateType,
|
GroupCallStateType,
|
||||||
|
GroupCallParticipantInfoType,
|
||||||
|
ActiveCallStateType,
|
||||||
} from './calling';
|
} from './calling';
|
||||||
|
|
||||||
export const MAX_CALL_PARTICIPANTS_FOR_DEFAULT_MUTE = 8;
|
export const MAX_CALL_PARTICIPANTS_FOR_DEFAULT_MUTE = 8;
|
||||||
|
@ -19,28 +26,53 @@ export const MAX_CALL_PARTICIPANTS_FOR_DEFAULT_MUTE = 8;
|
||||||
// In theory, there could be multiple incoming calls, or an incoming call while there's
|
// In theory, there could be multiple incoming calls, or an incoming call while there's
|
||||||
// an active call. In practice, the UI is not ready for this, and RingRTC doesn't
|
// an active call. In practice, the UI is not ready for this, and RingRTC doesn't
|
||||||
// support it for direct calls.
|
// support it for direct calls.
|
||||||
export const getIncomingCall = (
|
// Adhoc calls can not be incoming, so we don't look for them here.
|
||||||
|
export const getRingingCall = (
|
||||||
callsByConversation: Readonly<CallsByConversationType>,
|
callsByConversation: Readonly<CallsByConversationType>,
|
||||||
|
activeCallState: ActiveCallStateType | undefined,
|
||||||
ourAci: AciString
|
ourAci: AciString
|
||||||
): undefined | DirectCallStateType | GroupCallStateType =>
|
): DirectCallStateType | GroupCallStateType | undefined => {
|
||||||
Object.values(callsByConversation).find(call => {
|
const callList = Object.values(callsByConversation);
|
||||||
switch (call.callMode) {
|
const ringingDirect = callList.find(call => {
|
||||||
case CallMode.Direct:
|
if (call.callMode !== CallMode.Direct) {
|
||||||
return call.isIncoming && call.callState === CallState.Ringing;
|
return false;
|
||||||
case CallMode.Group:
|
|
||||||
return (
|
|
||||||
call.ringerAci &&
|
|
||||||
call.connectionState === GroupCallConnectionState.NotConnected &&
|
|
||||||
isAnybodyElseInGroupCall(call.peekInfo, ourAci)
|
|
||||||
);
|
|
||||||
case CallMode.Adhoc:
|
|
||||||
// Adhoc calls cannot be incoming.
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
throw missingCaseError(call);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return isRinging(call.callState) && call.callEndedReason == null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (ringingDirect) {
|
||||||
|
return ringingDirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
return callList.find(call => {
|
||||||
|
if (call.callMode !== CallMode.Group) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outgoing - ringerAci is not set for outgoing group calls
|
||||||
|
if (
|
||||||
|
activeCallState?.state === 'Active' &&
|
||||||
|
activeCallState.outgoingRing &&
|
||||||
|
activeCallState.conversationId === call.conversationId &&
|
||||||
|
isConnected(call.connectionState) &&
|
||||||
|
isJoined(call.joinState) &&
|
||||||
|
!hasRemoteParticipants(call.remoteParticipants)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incoming
|
||||||
|
return (
|
||||||
|
call.ringerAci &&
|
||||||
|
call.ringerAci !== ourAci &&
|
||||||
|
!isConnected(call.connectionState) &&
|
||||||
|
!isJoined(call.joinState) &&
|
||||||
|
isAnybodyElseInGroupCall(call.peekInfo, ourAci)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const isAnybodyElseInGroupCall = (
|
export const isAnybodyElseInGroupCall = (
|
||||||
peekInfo: undefined | Readonly<Pick<GroupCallPeekInfoType, 'acis'>>,
|
peekInfo: undefined | Readonly<Pick<GroupCallPeekInfoType, 'acis'>>,
|
||||||
ourAci: AciString
|
ourAci: AciString
|
||||||
|
@ -60,3 +92,28 @@ export const isGroupCallActiveOnServer = (
|
||||||
): boolean => {
|
): boolean => {
|
||||||
return Boolean(peekInfo?.eraId);
|
return Boolean(peekInfo?.eraId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isLonelyGroup(conversation: CallingConversationType): boolean {
|
||||||
|
return (conversation.sortedGroupMembers?.length ?? 0) < 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRinging(callState: CallState | undefined): boolean {
|
||||||
|
return callState === CallState.Prering || callState === CallState.Ringing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConnected(connectionState: GroupCallConnectionState): boolean {
|
||||||
|
return (
|
||||||
|
connectionState === GroupCallConnectionState.Connecting ||
|
||||||
|
connectionState === GroupCallConnectionState.Connected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isJoined(joinState: GroupCallJoinState): boolean {
|
||||||
|
return joinState !== GroupCallJoinState.NotJoined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasRemoteParticipants(
|
||||||
|
remoteParticipants: Array<GroupCallParticipantInfoType>
|
||||||
|
): boolean {
|
||||||
|
return remoteParticipants.length > 0;
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
||||||
GroupCallStateType,
|
GroupCallStateType,
|
||||||
ActiveCallStateType,
|
ActiveCallStateType,
|
||||||
} from '../ducks/calling';
|
} from '../ducks/calling';
|
||||||
import { getIncomingCall as getIncomingCallHelper } from '../ducks/callingHelpers';
|
import { getRingingCall as getRingingCallHelper } from '../ducks/callingHelpers';
|
||||||
import type { PresentedSource } from '../../types/Calling';
|
import type { PresentedSource } from '../../types/Calling';
|
||||||
import { CallMode } from '../../types/CallDisposition';
|
import { CallMode } from '../../types/CallDisposition';
|
||||||
import { isCallLinkAdmin, type CallLinkType } from '../../types/CallLink';
|
import { isCallLinkAdmin, type CallLinkType } from '../../types/CallLink';
|
||||||
|
@ -152,25 +152,27 @@ export const isInFullScreenCall = createSelector(
|
||||||
Boolean(activeCallState && !activeCallState.pip)
|
Boolean(activeCallState && !activeCallState.pip)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getIncomingCall = createSelector(
|
export const getRingingCall = createSelector(
|
||||||
getCallsByConversation,
|
getCallsByConversation,
|
||||||
|
getActiveCallState,
|
||||||
getUserACI,
|
getUserACI,
|
||||||
(
|
(
|
||||||
callsByConversation: CallsByConversationType,
|
callsByConversation: CallsByConversationType,
|
||||||
|
activeCallState: ActiveCallStateType | undefined,
|
||||||
ourAci: AciString | undefined
|
ourAci: AciString | undefined
|
||||||
): undefined | DirectCallStateType | GroupCallStateType => {
|
): undefined | DirectCallStateType | GroupCallStateType => {
|
||||||
if (!ourAci) {
|
if (!ourAci) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getIncomingCallHelper(callsByConversation, ourAci);
|
return getRingingCallHelper(callsByConversation, activeCallState, ourAci);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const areAnyCallsActiveOrRinging = createSelector(
|
export const areAnyCallsActiveOrRinging = createSelector(
|
||||||
getActiveCall,
|
getActiveCall,
|
||||||
getIncomingCall,
|
getRingingCall,
|
||||||
(activeCall, incomingCall): boolean => Boolean(activeCall || incomingCall)
|
(activeCall, ringingCall): boolean => Boolean(activeCall || ringingCall)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getPresentingSource = createSelector(
|
export const getPresentingSource = createSelector(
|
||||||
|
|
|
@ -48,15 +48,16 @@ import {
|
||||||
getActiveCallState,
|
getActiveCallState,
|
||||||
getAvailableCameras,
|
getAvailableCameras,
|
||||||
getCallLinkSelector,
|
getCallLinkSelector,
|
||||||
getIncomingCall,
|
getRingingCall,
|
||||||
} from '../selectors/calling';
|
} from '../selectors/calling';
|
||||||
import { getConversationSelector, getMe } from '../selectors/conversations';
|
import { getConversationSelector, getMe } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl, getUserACI } from '../selectors/user';
|
||||||
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
||||||
import { renderEmojiPicker } from './renderEmojiPicker';
|
import { renderEmojiPicker } from './renderEmojiPicker';
|
||||||
import { renderReactionPicker } from './renderReactionPicker';
|
import { renderReactionPicker } from './renderReactionPicker';
|
||||||
import { isSharingPhoneNumberWithEverybody as getIsSharingPhoneNumberWithEverybody } from '../../util/phoneNumberSharingMode';
|
import { isSharingPhoneNumberWithEverybody as getIsSharingPhoneNumberWithEverybody } from '../../util/phoneNumberSharingMode';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import { isLonelyGroup } from '../ducks/callingHelpers';
|
||||||
|
|
||||||
function renderDeviceSelection(): JSX.Element {
|
function renderDeviceSelection(): JSX.Element {
|
||||||
return <SmartCallingDeviceSelection />;
|
return <SmartCallingDeviceSelection />;
|
||||||
|
@ -364,56 +365,62 @@ const mapStateToCallLinkProp = (state: StateType): CallLinkType | undefined => {
|
||||||
return callLink;
|
return callLink;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToIncomingCallProp = (
|
const mapStateToRingingCallProp = (
|
||||||
state: StateType
|
state: StateType
|
||||||
): DirectIncomingCall | GroupIncomingCall | null => {
|
): DirectIncomingCall | GroupIncomingCall | null => {
|
||||||
const call = getIncomingCall(state);
|
const ourAci = getUserACI(state);
|
||||||
if (!call) {
|
const ringingCall = getRingingCall(state);
|
||||||
|
if (!ringingCall) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = getConversationSelector(state)(call.conversationId);
|
const conversation = getConversationSelector(state)(
|
||||||
|
ringingCall.conversationId
|
||||||
|
);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
log.error('The incoming call has no corresponding conversation');
|
log.error('The incoming call has no corresponding conversation');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (call.callMode) {
|
switch (ringingCall.callMode) {
|
||||||
case CallMode.Direct:
|
case CallMode.Direct:
|
||||||
return {
|
return {
|
||||||
callMode: CallMode.Direct as const,
|
callMode: CallMode.Direct as const,
|
||||||
callState: call.callState,
|
callState: ringingCall.callState,
|
||||||
callEndedReason: call.callEndedReason,
|
callEndedReason: ringingCall.callEndedReason,
|
||||||
conversation,
|
conversation,
|
||||||
isVideoCall: call.isVideoCall,
|
isVideoCall: ringingCall.isVideoCall,
|
||||||
};
|
};
|
||||||
case CallMode.Group: {
|
case CallMode.Group: {
|
||||||
if (!call.ringerAci) {
|
if (getIsConversationTooBigToRing(conversation)) {
|
||||||
log.error('The incoming group call has no ring state');
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLonelyGroup(conversation)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationSelector = getConversationSelector(state);
|
const conversationSelector = getConversationSelector(state);
|
||||||
const ringer = conversationSelector(call.ringerAci);
|
const ringer = conversationSelector(ringingCall.ringerAci || ourAci);
|
||||||
const otherMembersRung = (conversation.sortedGroupMembers ?? []).filter(
|
const otherMembersRung = (conversation.sortedGroupMembers ?? []).filter(
|
||||||
c => c.id !== ringer.id && !c.isMe
|
c => c.id !== ringer.id && !c.isMe
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callMode: CallMode.Group as const,
|
callMode: CallMode.Group as const,
|
||||||
connectionState: call.connectionState,
|
connectionState: ringingCall.connectionState,
|
||||||
joinState: call.joinState,
|
joinState: ringingCall.joinState,
|
||||||
conversation,
|
conversation,
|
||||||
otherMembersRung,
|
otherMembersRung,
|
||||||
ringer,
|
ringer,
|
||||||
remoteParticipants: call.remoteParticipants,
|
remoteParticipants: ringingCall.remoteParticipants,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case CallMode.Adhoc:
|
case CallMode.Adhoc:
|
||||||
log.error('Cannot handle an incoming adhoc call');
|
log.error('Cannot handle an incoming adhoc call');
|
||||||
return null;
|
return null;
|
||||||
default:
|
default:
|
||||||
throw missingCaseError(call);
|
throw missingCaseError(ringingCall);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -421,13 +428,10 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const activeCall = useSelector(mapStateToActiveCallProp);
|
const activeCall = useSelector(mapStateToActiveCallProp);
|
||||||
const callLink = useSelector(mapStateToCallLinkProp);
|
const callLink = useSelector(mapStateToCallLinkProp);
|
||||||
const incomingCall = useSelector(mapStateToIncomingCallProp);
|
const ringingCall = useSelector(mapStateToRingingCallProp);
|
||||||
const availableCameras = useSelector(getAvailableCameras);
|
const availableCameras = useSelector(getAvailableCameras);
|
||||||
const hasInitialLoadCompleted = useSelector(getHasInitialLoadCompleted);
|
const hasInitialLoadCompleted = useSelector(getHasInitialLoadCompleted);
|
||||||
const me = useSelector(getMe);
|
const me = useSelector(getMe);
|
||||||
const isConversationTooBigToRing = incomingCall
|
|
||||||
? getIsConversationTooBigToRing(incomingCall.conversation)
|
|
||||||
: false;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
approveUser,
|
approveUser,
|
||||||
|
@ -493,8 +497,6 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
hangUpActiveCall={hangUpActiveCall}
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
hasInitialLoadCompleted={hasInitialLoadCompleted}
|
hasInitialLoadCompleted={hasInitialLoadCompleted}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
incomingCall={incomingCall}
|
|
||||||
isConversationTooBigToRing={isConversationTooBigToRing}
|
|
||||||
me={me}
|
me={me}
|
||||||
notifyForCall={notifyForCall}
|
notifyForCall={notifyForCall}
|
||||||
openSystemPreferencesAction={openSystemPreferencesAction}
|
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||||
|
@ -504,6 +506,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
renderDeviceSelection={renderDeviceSelection}
|
renderDeviceSelection={renderDeviceSelection}
|
||||||
renderEmojiPicker={renderEmojiPicker}
|
renderEmojiPicker={renderEmojiPicker}
|
||||||
renderReactionPicker={renderReactionPicker}
|
renderReactionPicker={renderReactionPicker}
|
||||||
|
ringingCall={ringingCall}
|
||||||
sendGroupCallRaiseHand={sendGroupCallRaiseHand}
|
sendGroupCallRaiseHand={sendGroupCallRaiseHand}
|
||||||
sendGroupCallReaction={sendGroupCallReaction}
|
sendGroupCallReaction={sendGroupCallReaction}
|
||||||
selectPresentingSource={selectPresentingSource}
|
selectPresentingSource={selectPresentingSource}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
getCallsByConversation,
|
getCallsByConversation,
|
||||||
getCallSelector,
|
getCallSelector,
|
||||||
getHasAnyAdminCallLinks,
|
getHasAnyAdminCallLinks,
|
||||||
getIncomingCall,
|
getRingingCall,
|
||||||
isInCall,
|
isInCall,
|
||||||
} from '../../../state/selectors/calling';
|
} from '../../../state/selectors/calling';
|
||||||
import type {
|
import type {
|
||||||
|
@ -181,15 +181,15 @@ describe('state/selectors/calling', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getIncomingCall', () => {
|
describe('getRingingCall', () => {
|
||||||
it('returns undefined if there are no calls', () => {
|
it('returns undefined if there are no calls', () => {
|
||||||
assert.isUndefined(getIncomingCall(getEmptyRootState()));
|
assert.isUndefined(getRingingCall(getEmptyRootState()));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns undefined if there is no incoming call', () => {
|
it('returns undefined if there is no incoming call', () => {
|
||||||
assert.isUndefined(getIncomingCall(getCallingState(stateWithDirectCall)));
|
assert.isUndefined(getRingingCall(getCallingState(stateWithDirectCall)));
|
||||||
assert.isUndefined(
|
assert.isUndefined(
|
||||||
getIncomingCall(getCallingState(stateWithActiveDirectCall))
|
getRingingCall(getCallingState(stateWithActiveDirectCall))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -209,19 +209,19 @@ describe('state/selectors/calling', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
assert.isUndefined(getIncomingCall(getCallingState(state)));
|
assert.isUndefined(getRingingCall(getCallingState(state)));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an incoming direct call', () => {
|
it('returns an incoming direct call', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
getIncomingCall(getCallingState(stateWithIncomingDirectCall)),
|
getRingingCall(getCallingState(stateWithIncomingDirectCall)),
|
||||||
incomingDirectCall
|
incomingDirectCall
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an incoming group call', () => {
|
it('returns an incoming group call', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
getIncomingCall(getCallingState(stateWithIncomingGroupCall)),
|
getRingingCall(getCallingState(stateWithIncomingGroupCall)),
|
||||||
incomingGroupCall
|
incomingGroupCall
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue