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

@ -7242,12 +7242,12 @@
"description": "On the Calls Tab, when trying to join a different call when you're already in another one, this is the title of the confirmation dialog to leave the other call."
},
"icu:CallsList__LeaveCallDialogBody": {
"messageformat": "You must leave the current call before joining a new call.",
"description": "On the Calls Tab, when trying to join a different call when you're already in another one, this is the body of the confirmation dialog to leave the other call."
"messageformat": "You must leave the current call before starting or joining a new call.",
"description": "When trying to join a different call when you're already in another one, this is the body of the confirmation dialog to leave the other call."
},
"icu:CallsList__LeaveCallDialogButton--leave": {
"messageformat": "Leave call",
"description": "On the Calls Tab, when trying to join a different call when you're already in another one, this is the button to confirm leaving the other call."
"description": "When trying to join a different call when you're already in another one, this is the button to confirm leaving the other call."
},
"icu:CallsNewCall__EmptyState--noQuery": {
"messageformat": "No recent conversations.",

View file

@ -376,7 +376,6 @@
}
.CallsNewCall__ItemActionButton--join-call-disabled {
cursor: default;
opacity: 0.5;
}

View file

@ -65,7 +65,7 @@ import type {
PeekNotConnectedGroupCallType,
} from '../state/ducks/calling';
import { DAY, MINUTE, SECOND } from '../util/durations';
import { ConfirmationDialog } from './ConfirmationDialog';
import type { StartCallData } from './ConfirmLeaveCallModal';
function Timestamp({
i18n,
@ -148,7 +148,8 @@ type CallsListProps = Readonly<{
onOutgoingVideoCallInConversation: (conversationId: string) => void;
onChangeCallsTabSelectedView: (selectedView: CallsTabSelectedView) => void;
peekNotConnectedGroupCall: (options: PeekNotConnectedGroupCallType) => void;
startCallLinkLobbyByRoomId: (roomId: string) => void;
startCallLinkLobbyByRoomId: (options: { roomId: string }) => void;
toggleConfirmLeaveCallModal: (options: StartCallData | null) => void;
togglePip: () => void;
}>;
@ -179,7 +180,6 @@ export function CallsList({
getCall,
getCallLink,
getConversation,
hangUpActiveCall,
i18n,
selectedCallHistoryGroup,
onCreateCallLink,
@ -188,6 +188,7 @@ export function CallsList({
onChangeCallsTabSelectedView,
peekNotConnectedGroupCall,
startCallLinkLobbyByRoomId,
toggleConfirmLeaveCallModal,
togglePip,
}: CallsListProps): JSX.Element {
const infiniteLoaderRef = useRef<InfiniteLoader>(null);
@ -195,8 +196,6 @@ export function CallsList({
const [queryInput, setQueryInput] = useState('');
const [statusInput, setStatusInput] = useState(CallHistoryFilterStatus.All);
const [searchState, setSearchState] = useState(defaultInitState);
const [isLeaveCallDialogVisible, setIsLeaveCallDialogVisible] =
useState(false);
const prevOptionsRef = useRef<CallHistoryFilterOptions | null>(null);
@ -330,7 +329,7 @@ export function CallsList({
return true;
}
// Direct is not supported currently
// We can't tell from CallHistory alone whether a 1:1 call is active
return false;
},
[getCallByPeerId]
@ -358,12 +357,14 @@ export function CallsList({
return peerId === activeCallConversationId;
}
// Not supported currently
// For direct conversations, we know the call is active if it's the active call!
if (mode === CallMode.Direct) {
return false;
return Boolean(
conversation && conversation?.id === activeCallConversationId
);
}
// Group
// For group and adhoc calls, a call has to have members in it (see getIsCallActive)
return Boolean(
isActive &&
conversation &&
@ -773,6 +774,43 @@ export function CallsList({
strictAssert(false, 'Cannot format call');
}
const inCallAndNotThisOne = !isInCall && activeCall;
const callButton = (
<CallsNewCallButton
callType={item.type}
isActive={isActiveVisible}
isInCall={isInCall}
isEnabled={!inCallAndNotThisOne}
onClick={() => {
if (isInCall) {
togglePip();
} else if (activeCall) {
if (isAdhoc) {
toggleConfirmLeaveCallModal({
type: 'adhoc-roomId',
roomId: item.peerId,
});
} else {
toggleConfirmLeaveCallModal({
type: 'conversation',
conversationId: conversation.id,
isVideoCall: item.type !== CallType.Audio,
});
}
} else if (isAdhoc) {
startCallLinkLobbyByRoomId({ roomId: item.peerId });
} else if (conversation) {
if (item.type === CallType.Audio) {
onOutgoingAudioCallInConversation(conversation.id);
} else {
onOutgoingVideoCallInConversation(conversation.id);
}
}
}}
i18n={i18n}
/>
);
return (
<div
key={key}
@ -801,34 +839,7 @@ export function CallsList({
className="CallsList__ItemAvatar"
/>
}
trailing={
isCallButtonVisible ? (
<CallsNewCallButton
callType={item.type}
isActive={isActiveVisible}
isInCall={isInCall}
isEnabled={isInCall || !activeCall}
onClick={() => {
if (isInCall) {
togglePip();
} else if (activeCall) {
if (isActiveVisible) {
setIsLeaveCallDialogVisible(true);
}
} else if (isAdhoc) {
startCallLinkLobbyByRoomId(item.peerId);
} else if (conversation) {
if (item.type === CallType.Audio) {
onOutgoingAudioCallInConversation(conversation.id);
} else {
onOutgoingVideoCallInConversation(conversation.id);
}
}
}}
i18n={i18n}
/>
) : undefined
}
trailing={isCallButtonVisible ? callButton : undefined}
title={
<span
className="CallsList__ItemTitle"
@ -887,6 +898,7 @@ export function CallsList({
onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation,
startCallLinkLobbyByRoomId,
toggleConfirmLeaveCallModal,
togglePip,
i18n,
]
@ -913,31 +925,6 @@ export function CallsList({
return (
<>
{isLeaveCallDialogVisible && (
<ConfirmationDialog
dialogName="GroupCallRemoteParticipant.blockInfo"
cancelText={i18n('icu:cancel')}
i18n={i18n}
onClose={() => {
setIsLeaveCallDialogVisible(false);
}}
title={i18n('icu:CallsList__LeaveCallDialogTitle')}
actions={[
{
text: i18n('icu:CallsList__LeaveCallDialogButton--leave'),
style: 'affirmative',
action: () => {
hangUpActiveCall(
'Calls Tab leave active call to join different call'
);
},
},
]}
>
{i18n('icu:CallsList__LeaveCallDialogBody')}
</ConfirmationDialog>
)}
<NavSidebarSearchHeader>
<SearchInput
i18n={i18n}

View file

@ -21,6 +21,7 @@ import { SizeObserver } from '../hooks/useSizeObserver';
import { CallType } from '../types/CallDisposition';
import type { CallsTabSelectedView } from './CallsTab';
import { Tooltip, TooltipPlacement } from './Tooltip';
import { offsetDistanceModifier } from '../util/popperUtil';
type CallsNewCallProps = Readonly<{
hasActiveCall: boolean;
@ -53,17 +54,18 @@ export function CallsNewCallButton({
}): JSX.Element {
let innerContent: React.ReactNode | string;
let tooltipContent = '';
if (callType === CallType.Audio) {
innerContent = (
<span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Phone" />
);
} else if (isActive) {
if (!isEnabled) {
tooltipContent = i18n('icu:ContactModal--already-in-call');
}
// Note: isActive is only set for groups and adhoc calls
if (isActive) {
innerContent = isInCall
? i18n('icu:CallsNewCallButton--return')
: i18n('icu:joinOngoingCall');
if (!isEnabled) {
tooltipContent = i18n('icu:CallsNewCallButtonTooltip--in-another-call');
}
} else if (callType === CallType.Audio) {
innerContent = (
<span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Phone" />
);
} else {
innerContent = (
<span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Video" />
@ -97,6 +99,7 @@ export function CallsNewCallButton({
className="CallsNewCall__ItemActionButtonTooltip"
content={tooltipContent}
direction={TooltipPlacement.Top}
popperModifiers={[offsetDistanceModifier(15)]}
>
{buttonContent}
</Tooltip>

View file

@ -23,6 +23,7 @@ import type { UnreadStats } from '../util/countUnreadStats';
import type { WidthBreakpoint } from './_util';
import type { CallLinkType } from '../types/CallLink';
import type { CallStateType } from '../state/selectors/calling';
import type { StartCallData } from './ConfirmLeaveCallModal';
enum CallsTabSidebarView {
CallsListView,
@ -72,7 +73,8 @@ type CallsTabProps = Readonly<{
}) => JSX.Element;
regionCode: string | undefined;
savePreferredLeftPaneWidth: (preferredLeftPaneWidth: number) => void;
startCallLinkLobbyByRoomId: (roomId: string) => void;
startCallLinkLobbyByRoomId: (options: { roomId: string }) => void;
toggleConfirmLeaveCallModal: (options: StartCallData | null) => void;
togglePip: () => void;
}>;
@ -119,6 +121,7 @@ export function CallsTab({
regionCode,
savePreferredLeftPaneWidth,
startCallLinkLobbyByRoomId,
toggleConfirmLeaveCallModal,
togglePip,
}: CallsTabProps): JSX.Element {
const [sidebarView, setSidebarView] = useState(
@ -282,6 +285,7 @@ export function CallsTab({
}
peekNotConnectedGroupCall={peekNotConnectedGroupCall}
startCallLinkLobbyByRoomId={startCallLinkLobbyByRoomId}
toggleConfirmLeaveCallModal={toggleConfirmLeaveCallModal}
togglePip={togglePip}
/>
)}

View file

@ -0,0 +1,59 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { ConfirmationDialog } from './ConfirmationDialog';
import type { LocalizerType } from '../types/Util';
import type {
StartCallingLobbyType,
StartCallLinkLobbyByRoomIdType,
StartCallLinkLobbyType,
} from '../state/ducks/calling';
export type StartCallData =
| ({
type: 'conversation';
} & StartCallingLobbyType)
| ({ type: 'adhoc-roomId' } & StartCallLinkLobbyByRoomIdType)
| ({ type: 'adhoc-rootKey' } & StartCallLinkLobbyType);
type HousekeepingProps = {
i18n: LocalizerType;
};
type DispatchProps = {
toggleConfirmLeaveCallModal: (options: StartCallData | null) => void;
leaveCurrentCallAndStartCallingLobby: (options: StartCallData) => void;
};
export type Props = { data: StartCallData } & HousekeepingProps & DispatchProps;
export function ConfirmLeaveCallModal({
i18n,
data,
leaveCurrentCallAndStartCallingLobby,
toggleConfirmLeaveCallModal,
}: Props): JSX.Element | null {
return (
<ConfirmationDialog
dialogName="GroupCallRemoteParticipant.blockInfo"
cancelText={i18n('icu:cancel')}
i18n={i18n}
onClose={() => {
toggleConfirmLeaveCallModal(null);
}}
title={i18n('icu:CallsList__LeaveCallDialogTitle')}
actions={[
{
text: i18n('icu:CallsList__LeaveCallDialogButton--leave'),
style: 'affirmative',
action: () => {
leaveCurrentCallAndStartCallingLobby(data);
},
},
]}
>
{i18n('icu:CallsList__LeaveCallDialogBody')}
</ConfirmationDialog>
);
}

View file

@ -20,6 +20,7 @@ import { ButtonVariant } from './Button';
import { ConfirmationDialog } from './ConfirmationDialog';
import { SignalConnectionsModal } from './SignalConnectionsModal';
import { WhatsNewModal } from './WhatsNewModal';
import type { StartCallData } from './ConfirmLeaveCallModal';
// NOTE: All types should be required for this component so that the smart
// component gives you type errors when adding/removing props.
@ -35,6 +36,9 @@ export type PropsType = {
// CallLinkEditModal
callLinkEditModalRoomId: string | null;
renderCallLinkEditModal: () => JSX.Element;
// ConfirmLeaveCallModal
confirmLeaveCallModalState: StartCallData | null;
renderConfirmLeaveCallModal: () => JSX.Element;
// ContactModal
contactModalState: ContactModalStateType | undefined;
renderContactModal: () => JSX.Element;
@ -114,6 +118,9 @@ export function GlobalModalContainer({
// CallLinkEditModal
callLinkEditModalRoomId,
renderCallLinkEditModal,
// ConfirmLeaveCallModal
confirmLeaveCallModalState,
renderConfirmLeaveCallModal,
// ContactModal
contactModalState,
renderContactModal,
@ -196,6 +203,10 @@ export function GlobalModalContainer({
// The Rest
if (confirmLeaveCallModalState) {
return renderConfirmLeaveCallModal();
}
if (addUserToAnotherGroupModalContactId) {
return renderAddUserToAnotherGroup();
}

View file

@ -424,14 +424,14 @@ export function ConversationDetails({
{!conversation.isMe && (
<>
<ConversationDetailsCallButton
disabled={hasActiveCall}
hasActiveCall={hasActiveCall}
i18n={i18n}
onClick={() => onOutgoingVideoCallInConversation(conversation.id)}
type="video"
/>
{!isGroup && (
<ConversationDetailsCallButton
disabled={hasActiveCall}
hasActiveCall={hasActiveCall}
i18n={i18n}
onClick={() =>
onOutgoingAudioCallInConversation(conversation.id)
@ -733,19 +733,18 @@ export function ConversationDetails({
}
function ConversationDetailsCallButton({
disabled,
hasActiveCall,
i18n,
onClick,
type,
}: Readonly<{
disabled: boolean;
hasActiveCall: boolean;
i18n: LocalizerType;
onClick: () => unknown;
type: 'audio' | 'video';
}>) {
const button = (
<Button
disabled={disabled}
icon={ButtonIconType[type]}
onClick={onClick}
variant={ButtonVariant.Details}
@ -754,7 +753,7 @@ function ConversationDetailsCallButton({
</Button>
);
if (disabled) {
if (hasActiveCall) {
return (
<Tooltip content={i18n('icu:calling__in-another-call-tooltip')}>
{button}

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}