Allow joining a call when already in a call, confirming first
This commit is contained in:
parent
f4a18414f1
commit
4ec3b98293
14 changed files with 290 additions and 137 deletions
|
@ -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.",
|
||||
|
|
|
@ -376,7 +376,6 @@
|
|||
}
|
||||
|
||||
.CallsNewCall__ItemActionButton--join-call-disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
59
ts/components/ConfirmLeaveCallModal.tsx
Normal file
59
ts/components/ConfirmLeaveCallModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -32,6 +32,11 @@ export const getCallLinkAddNameModalRoomId = createSelector(
|
|||
({ callLinkAddNameModalRoomId }) => callLinkAddNameModalRoomId
|
||||
);
|
||||
|
||||
export const getConfirmLeaveCallModalState = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ confirmLeaveCallModalState }) => confirmLeaveCallModalState
|
||||
);
|
||||
|
||||
export const getContactModalState = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ contactModalState }) => contactModalState
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
37
ts/state/smart/ConfirmLeaveCallModal.tsx
Normal file
37
ts/state/smart/ConfirmLeaveCallModal.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -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}
|
||||
|
|
Loading…
Reference in a new issue