Allow joining a call when already in a call, confirming first

This commit is contained in:
Scott Nonnenberg 2024-07-29 15:35:28 -07:00 committed by GitHub
parent f4a18414f1
commit 4ec3b98293
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 290 additions and 137 deletions

View file

@ -82,8 +82,11 @@ import {
isGroupOrAdhocCallMode,
isGroupOrAdhocCallState,
} from '../../util/isGroupOrAdhocCall';
import type { ShowErrorModalActionType } from './globalModals';
import { SHOW_ERROR_MODAL } from './globalModals';
import type {
ShowErrorModalActionType,
ToggleConfirmLeaveCallModalActionType,
} from './globalModals';
import { SHOW_ERROR_MODAL, toggleConfirmLeaveCallModal } from './globalModals';
import { ButtonVariant } from '../../components/Button';
import { getConversationIdForLogging } from '../../util/idForLogging';
import { DataReader, DataWriter } from '../../sql/Client';
@ -92,6 +95,7 @@ import type { CallHistoryAdd } from './callHistory';
import { addCallHistory } from './callHistory';
import { saveDraftRecordingIfNeeded } from './composer';
import type { CallHistoryDetails } from '../../types/CallDisposition';
import type { StartCallData } from '../../components/ConfirmLeaveCallModal';
// State
@ -1464,48 +1468,6 @@ function handleCallLinkUpdate(
};
}
/**
* When starting a lobby and there's an active call, if we're already in call then
* focus it (toggle pip), otherwise show an error.
* @returns {boolean} `true` if there was an active call and we handled it.
*/
function handleActiveCallOnStartLobby({
conversationId,
state,
dispatch,
}: {
conversationId: string;
state: RootStateType;
dispatch: ThunkDispatch<
RootStateType,
unknown,
ShowErrorModalActionType | TogglePipActionType
>;
}): boolean {
const { activeCallState } = state.calling;
if (!activeCallState) {
return false;
}
if (activeCallState.conversationId === conversationId) {
dispatch({
type: TOGGLE_PIP,
});
} else {
const i18n = getIntl(state);
dispatch({
type: SHOW_ERROR_MODAL,
payload: {
title: i18n('icu:calling__cant-join'),
description: i18n('icu:calling__dialog-already-in-call'),
buttonVariant: ButtonVariant.Primary,
},
});
}
return true;
}
function hangUpActiveCall(
reason: string
): ThunkAction<void, RootStateType, unknown, HangUpActionType> {
@ -2046,9 +2008,9 @@ function updateCallLinkRestrictions(
};
}
function startCallLinkLobbyByRoomId(
roomId: string
): StartCallLinkLobbyThunkActionType {
function startCallLinkLobbyByRoomId({
roomId,
}: StartCallLinkLobbyByRoomIdType): StartCallLinkLobbyThunkActionType {
return async (dispatch, getState) => {
const state = getState();
const callLink = getOwn(state.calling.callLinks, roomId);
@ -2082,6 +2044,7 @@ const _startCallLinkLobby = async ({
unknown,
| StartCallLinkLobbyActionType
| ShowErrorModalActionType
| ToggleConfirmLeaveCallModalActionType
| TogglePipActionType
>;
getState: () => RootStateType;
@ -2090,9 +2053,20 @@ const _startCallLinkLobby = async ({
const roomId = getRoomIdFromRootKey(callLinkRootKey);
const state = getState();
if (
handleActiveCallOnStartLobby({ conversationId: roomId, state, dispatch })
) {
const { activeCallState } = state.calling;
if (activeCallState && activeCallState.conversationId === roomId) {
dispatch({
type: TOGGLE_PIP,
});
return;
}
if (activeCallState) {
dispatch(
toggleConfirmLeaveCallModal({
type: 'adhoc-rootKey',
rootKey,
})
);
return;
}
@ -2181,6 +2155,34 @@ const _startCallLinkLobby = async ({
});
};
function leaveCurrentCallAndStartCallingLobby(
data: StartCallData
): ThunkAction<void, RootStateType, unknown, HangUpActionType> {
return async (dispatch, getState) => {
hangUpActiveCall(
'Leave call button pressed in ConfirmLeaveCurrentCallModal'
)(dispatch, getState, undefined);
const { type } = data;
if (type === 'conversation') {
const { conversationId, isVideoCall } = data;
startCallingLobby({ conversationId, isVideoCall })(
dispatch,
getState,
undefined
);
} else if (type === 'adhoc-roomId') {
const { roomId } = data;
startCallLinkLobbyByRoomId({ roomId })(dispatch, getState, undefined);
} else if (type === 'adhoc-rootKey') {
const { rootKey } = data;
startCallLinkLobby({ rootKey })(dispatch, getState, undefined);
} else {
throw missingCaseError(type);
}
};
}
function startCallingLobby({
conversationId,
isVideoCall,
@ -2188,7 +2190,9 @@ function startCallingLobby({
void,
RootStateType,
unknown,
StartCallingLobbyActionType | TogglePipActionType
| StartCallingLobbyActionType
| ToggleConfirmLeaveCallModalActionType
| TogglePipActionType
> {
return async (dispatch, getState) => {
const state = getState();
@ -2201,10 +2205,16 @@ function startCallingLobby({
"startCallingLobby: can't start lobby without a conversation"
);
strictAssert(
!state.calling.activeCallState,
"startCallingLobby: can't start lobby if a call is active"
);
if (state.calling.activeCallState) {
dispatch(
toggleConfirmLeaveCallModal({
type: 'conversation',
conversationId,
isVideoCall,
})
);
return;
}
// The group call device count is considered 0 for a direct call.
const groupCall = getGroupCall(
@ -2374,6 +2384,7 @@ export const actions = {
hangUpActiveCall,
handleCallLinkUpdate,
joinedAdhocCall,
leaveCurrentCallAndStartCallingLobby,
onOutgoingVideoCallInConversation,
onOutgoingAudioCallInConversation,
openSystemPreferencesAction,

View file

@ -48,6 +48,7 @@ import { ForwardMessagesModalType } from '../../components/ForwardMessagesModal'
import type { CallLinkType } from '../../types/CallLink';
import type { LocalizerType } from '../../types/I18N';
import { linkCallRoute } from '../../util/signalRoutes';
import type { StartCallData } from '../../components/ConfirmLeaveCallModal';
// State
@ -93,6 +94,7 @@ export type GlobalModalsStateType = ReadonlyDeep<{
aboutContactModalContactId?: string;
callLinkAddNameModalRoomId: string | null;
callLinkEditModalRoomId: string | null;
confirmLeaveCallModalState: StartCallData | null;
contactModalState?: ContactModalStateType;
deleteMessagesProps?: DeleteMessagesPropsType;
editHistoryMessages?: EditHistoryMessagesType;
@ -168,6 +170,8 @@ const TOGGLE_CONFIRMATION_MODAL = 'globalModals/TOGGLE_CONFIRMATION_MODAL';
const SHOW_EDIT_HISTORY_MODAL = 'globalModals/SHOW_EDIT_HISTORY_MODAL';
const CLOSE_EDIT_HISTORY_MODAL = 'globalModals/CLOSE_EDIT_HISTORY_MODAL';
const TOGGLE_USERNAME_ONBOARDING = 'globalModals/TOGGLE_USERNAME_ONBOARDING';
const TOGGLE_CONFIRM_LEAVE_CALL_MODAL =
'globalModals/TOGGLE_CONFIRM_LEAVE_CALL_MODAL';
export type ContactModalStateType = ReadonlyDeep<{
contactId: string;
@ -221,6 +225,11 @@ type ToggleForwardMessagesModalActionType = ReadonlyDeep<{
payload: ForwardMessagesPropsType | undefined;
}>;
export type ToggleConfirmLeaveCallModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_CONFIRM_LEAVE_CALL_MODAL;
payload: StartCallData | null;
}>;
type ToggleNotePreviewModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_NOTE_PREVIEW_MODAL;
payload: NotePreviewModalPropsType | null;
@ -385,6 +394,7 @@ export type GlobalModalsActionType = ReadonlyDeep<
| ToggleCallLinkAddNameModalActionType
| ToggleCallLinkEditModalActionType
| ToggleConfirmationModalActionType
| ToggleConfirmLeaveCallModalActionType
| ToggleDeleteMessagesModalActionType
| ToggleForwardMessagesModalActionType
| ToggleNotePreviewModalActionType
@ -426,6 +436,7 @@ export const actions = {
toggleCallLinkAddNameModal,
toggleCallLinkEditModal,
toggleConfirmationModal,
toggleConfirmLeaveCallModal,
toggleDeleteMessagesModal,
toggleForwardMessagesModal,
toggleNotePreviewModal,
@ -682,6 +693,15 @@ function showShareCallLinkViaSignal(
};
}
export function toggleConfirmLeaveCallModal(
payload: StartCallData | null
): ToggleConfirmLeaveCallModalActionType {
return {
type: TOGGLE_CONFIRM_LEAVE_CALL_MODAL,
payload,
};
}
function toggleNotePreviewModal(
payload: NotePreviewModalPropsType | null
): ToggleNotePreviewModalActionType {
@ -954,6 +974,7 @@ export function getEmptyState(): GlobalModalsStateType {
hasConfirmationModal: false,
callLinkAddNameModalRoomId: null,
callLinkEditModalRoomId: null,
confirmLeaveCallModalState: null,
editNicknameAndNoteModalProps: null,
isProfileEditorVisible: false,
isShortcutGuideModalVisible: false,
@ -979,6 +1000,13 @@ export function reducer(
};
}
if (action.type === TOGGLE_CONFIRM_LEAVE_CALL_MODAL) {
return {
...state,
confirmLeaveCallModalState: action.payload,
};
}
if (action.type === TOGGLE_NOTE_PREVIEW_MODAL) {
return {
...state,

View file

@ -32,6 +32,11 @@ export const getCallLinkAddNameModalRoomId = createSelector(
({ callLinkAddNameModalRoomId }) => callLinkAddNameModalRoomId
);
export const getConfirmLeaveCallModalState = createSelector(
getGlobalModalsState,
({ confirmLeaveCallModalState }) => confirmLeaveCallModalState
);
export const getContactModalState = createSelector(
getGlobalModalsState,
({ contactModalState }) => contactModalState

View file

@ -172,7 +172,8 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
markCallHistoryRead,
markCallsTabViewed,
} = useCallHistoryActions();
const { toggleCallLinkEditModal } = useGlobalModalActions();
const { toggleCallLinkEditModal, toggleConfirmLeaveCallModal } =
useGlobalModalActions();
const getCallHistoryGroupsCount = useCallback(
async (options: CallHistoryFilterOptions) => {
@ -257,6 +258,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
regionCode={regionCode}
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
startCallLinkLobbyByRoomId={startCallLinkLobbyByRoomId}
toggleConfirmLeaveCallModal={toggleConfirmLeaveCallModal}
togglePip={togglePip}
/>
);

View file

@ -0,0 +1,37 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { memo } from 'react';
import { useSelector } from 'react-redux';
import { useCallingActions } from '../ducks/calling';
import { getIntl } from '../selectors/user';
import { useGlobalModalActions } from '../ducks/globalModals';
import { getConfirmLeaveCallModalState } from '../selectors/globalModals';
import { ConfirmLeaveCallModal } from '../../components/ConfirmLeaveCallModal';
export const SmartConfirmLeaveCallModal = memo(
function SmartConfirmLeaveCallModal(): JSX.Element | null {
const i18n = useSelector(getIntl);
const confirmLeaveCallModalState = useSelector(
getConfirmLeaveCallModalState
);
const { leaveCurrentCallAndStartCallingLobby } = useCallingActions();
const { toggleConfirmLeaveCallModal } = useGlobalModalActions();
if (!confirmLeaveCallModalState) {
return null;
}
return (
<ConfirmLeaveCallModal
i18n={i18n}
data={confirmLeaveCallModalState}
leaveCurrentCallAndStartCallingLobby={
leaveCurrentCallAndStartCallingLobby
}
toggleConfirmLeaveCallModal={toggleConfirmLeaveCallModal}
/>
);
}
);

View file

@ -28,6 +28,7 @@ import { SmartEditNicknameAndNoteModal } from './EditNicknameAndNoteModal';
import { SmartNotePreviewModal } from './NotePreviewModal';
import { SmartCallLinkEditModal } from './CallLinkEditModal';
import { SmartCallLinkAddNameModal } from './CallLinkAddNameModal';
import { SmartConfirmLeaveCallModal } from './ConfirmLeaveCallModal';
function renderCallLinkAddNameModal(): JSX.Element {
return <SmartCallLinkAddNameModal />;
@ -37,6 +38,10 @@ function renderCallLinkEditModal(): JSX.Element {
return <SmartCallLinkEditModal />;
}
function renderConfirmLeaveCallModal(): JSX.Element {
return <SmartConfirmLeaveCallModal />;
}
function renderEditHistoryMessagesModal(): JSX.Element {
return <SmartEditHistoryMessagesModal />;
}
@ -102,6 +107,7 @@ export const SmartGlobalModalContainer = memo(
addUserToAnotherGroupModalContactId,
callLinkAddNameModalRoomId,
callLinkEditModalRoomId,
confirmLeaveCallModalState,
contactModalState,
deleteMessagesProps,
editHistoryMessages,
@ -182,6 +188,7 @@ export const SmartGlobalModalContainer = memo(
}
callLinkAddNameModalRoomId={callLinkAddNameModalRoomId}
callLinkEditModalRoomId={callLinkEditModalRoomId}
confirmLeaveCallModalState={confirmLeaveCallModalState}
contactModalState={contactModalState}
editHistoryMessages={editHistoryMessages}
editNicknameAndNoteModalProps={editNicknameAndNoteModalProps}
@ -206,6 +213,7 @@ export const SmartGlobalModalContainer = memo(
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
renderCallLinkAddNameModal={renderCallLinkAddNameModal}
renderCallLinkEditModal={renderCallLinkEditModal}
renderConfirmLeaveCallModal={renderConfirmLeaveCallModal}
renderContactModal={renderContactModal}
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
renderEditNicknameAndNoteModal={renderEditNicknameAndNoteModal}