Contact info modal for call link join requests

This commit is contained in:
ayumi-signal 2024-09-11 12:30:50 -07:00 committed by GitHub
parent 390eab2556
commit 84896d0fbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 519 additions and 6 deletions

View file

@ -7437,6 +7437,14 @@
"messageformat": "Require admin approval", "messageformat": "Require admin approval",
"description": "Call Link Edit Modal > Require admin approval checkbox > Label" "description": "Call Link Edit Modal > Require admin approval checkbox > Label"
}, },
"icu:CallLinkPendingParticipantModal__ApproveButtonLabel": {
"messageformat": "Approve entry",
"description": "Call Link Pending Participant Modal > Approve Join Request Button Label"
},
"icu:CallLinkPendingParticipantModal__DenyButtonLabel": {
"messageformat": "Deny entry",
"description": "Call Link Pending Participant Modal > Deny Join Request Button Label"
},
"icu:CallLinkRestrictionsSelect__Option--Off": { "icu:CallLinkRestrictionsSelect__Option--Off": {
"messageformat": "Off", "messageformat": "Off",
"description": "Call Link Edit Modal > Require admin approval checkbox > Option > Off" "description": "Call Link Edit Modal > Require admin approval checkbox > Option > Off"

View file

@ -0,0 +1,106 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// Overriding default style
.module-Modal__body_inner.CallLinkPendingParticipantModal__body_inner {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
margin-top: 4px;
margin-bottom: 14px;
padding-inline: 8px;
}
.CallLinkPendingParticipantModal__NameButton {
@include button-reset();
@include font-title-1;
display: flex;
flex-direction: row;
align-items: baseline;
max-width: 100%;
margin-top: 12px;
font-weight: 500;
color: $color-gray-05;
cursor: pointer;
}
.CallLinkPendingParticipantModal__InContactsIcon {
height: 22px;
width: 22px;
@include any-theme {
background-color: currentColor;
}
}
.CallLinkPendingParticipantModal__AboutIcon {
display: inline-block;
height: 20px;
width: 20px;
position: relative;
inset-block-start: 2px;
@include color-svg(
'../images/icons/v3/chevron/chevron-right-bold.svg',
$color-gray-25
);
}
.CallLinkPendingParticipantModal__SharedGroupInfo {
margin-top: 10px;
color: $color-gray-25;
text-align: center;
}
.CallLinkPendingParticipantModal__Hr {
width: 100%;
margin-block: 24px;
border: none;
height: 1px;
background: $color-gray-65;
}
.CallLinkPendingParticipantModal__ActionButton {
@include button-reset;
display: flex;
padding-block: 6px;
width: 100%;
align-items: center;
color: $color-gray-05;
}
.CallLinkPendingParticipantModal__ActionButton:last-child {
margin-bottom: 0;
}
.CallLinkPendingParticipantModal__ActionButton:focus {
@include keyboard-mode {
background: $color-gray-65;
}
}
.CallLinkPendingParticipantModal__ButtonIcon {
display: flex;
justify-content: center;
align-items: center;
margin-inline-end: 12px;
width: 20px;
}
.CallLinkPendingParticipantModal__ButtonIconContent {
width: 20px;
height: 20px;
}
.CallLinkPendingParticipantModal__ButtonIconContent--approve {
@include color-svg(
'../images/icons/v3/check/check-circle.svg',
$color-gray-25
);
}
.CallLinkPendingParticipantModal__ButtonIconContent--deny {
@include color-svg('../images/icons/v3/x/x-circle.svg', $color-gray-25);
}

View file

@ -34,11 +34,36 @@
margin-inline-start: 8px; margin-inline-start: 8px;
} }
.CallingPendingParticipants__ParticipantButton {
@include button-reset();
}
.CallingPendingParticipants__ParticipantButton:focus {
@include keyboard-mode {
outline: 3px solid $color-ultramarine;
outline-offset: 1px;
}
}
.CallingPendingParticipants__ParticipantName { .CallingPendingParticipants__ParticipantName {
@include font-body-1-bold; @include font-body-1-bold;
color: $color-gray-15; color: $color-gray-15;
} }
.CallingPendingParticipants__ParticipantAboutIcon {
display: inline-block;
height: 14px;
width: 14px;
position: relative;
inset-block-start: 3px;
@include color-svg(
'../images/icons/v3/chevron/chevron-right-bold.svg',
$color-gray-05
);
}
.CallingPendingParticipants__WouldLikeToJoin { .CallingPendingParticipants__WouldLikeToJoin {
@include font-body-2; @include font-body-2;
color: $color-gray-20; color: $color-gray-20;

View file

@ -54,6 +54,7 @@
@import './components/CallLinkAddNameModal.scss'; @import './components/CallLinkAddNameModal.scss';
@import './components/CallLinkDetails.scss'; @import './components/CallLinkDetails.scss';
@import './components/CallLinkEditModal.scss'; @import './components/CallLinkEditModal.scss';
@import './components/CallLinkPendingParticipantModal.scss';
@import './components/CallLinkRestrictionsSelect.scss'; @import './components/CallLinkRestrictionsSelect.scss';
@import './components/CallingRaisedHandsList.scss'; @import './components/CallingRaisedHandsList.scss';
@import './components/CallingRaisedHandsToasts.scss'; @import './components/CallingRaisedHandsToasts.scss';

View file

@ -0,0 +1,69 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { action } from '@storybook/addon-actions';
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import type { CallLinkPendingParticipantModalProps } from './CallLinkPendingParticipantModal';
import { CallLinkPendingParticipantModal } from './CallLinkPendingParticipantModal';
import type { ComponentMeta } from '../storybook/types';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
const i18n = setupI18n('en', enMessages);
const conversation = getDefaultConversation({
acceptedMessageRequest: true,
hasMessages: true,
});
const conversationWithSharedGroups = getDefaultConversation({
acceptedMessageRequest: true,
aboutText: 'likes to chat',
hasMessages: true,
sharedGroupNames: ['Axolotl lovers'],
});
const systemContact = getDefaultConversation({
acceptedMessageRequest: true,
systemGivenName: 'Alice',
phoneNumber: '+1 555 123-4567',
hasMessages: true,
});
export default {
title: 'Components/CallLinkPendingParticipantModal',
component: CallLinkPendingParticipantModal,
args: {
i18n,
conversation,
approveUser: action('approveUser'),
denyUser: action('denyUser'),
toggleAboutContactModal: action('toggleAboutContactModal'),
onClose: action('onClose'),
updateSharedGroups: action('updateSharedGroups'),
},
} satisfies ComponentMeta<CallLinkPendingParticipantModalProps>;
export function Default(
args: CallLinkPendingParticipantModalProps
): JSX.Element {
return <CallLinkPendingParticipantModal {...args} />;
}
export function SystemContact(
args: CallLinkPendingParticipantModalProps
): JSX.Element {
return (
<CallLinkPendingParticipantModal {...args} conversation={systemContact} />
);
}
export function WithSharedGroups(
args: CallLinkPendingParticipantModalProps
): JSX.Element {
return (
<CallLinkPendingParticipantModal
{...args}
conversation={conversationWithSharedGroups}
/>
);
}

View file

@ -0,0 +1,140 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, useEffect, useMemo } from 'react';
import { Modal } from './Modal';
import type { LocalizerType } from '../types/I18N';
import { Avatar, AvatarSize } from './Avatar';
import type { PendingUserActionPayloadType } from '../state/ducks/calling';
import type { ConversationType } from '../state/ducks/conversations';
import { InContactsIcon } from './InContactsIcon';
import { isInSystemContacts } from '../util/isInSystemContacts';
import { ThemeType } from '../types/Util';
import { Theme } from '../util/theme';
import { UserText } from './UserText';
import { SharedGroupNames } from './SharedGroupNames';
export type CallLinkPendingParticipantModalProps = {
readonly i18n: LocalizerType;
readonly conversation: ConversationType;
readonly approveUser: (payload: PendingUserActionPayloadType) => void;
readonly denyUser: (payload: PendingUserActionPayloadType) => void;
readonly onClose: () => void;
readonly toggleAboutContactModal: (conversationId: string) => void;
readonly updateSharedGroups: (conversationId: string) => void;
};
export function CallLinkPendingParticipantModal({
i18n,
conversation,
approveUser,
denyUser,
onClose,
toggleAboutContactModal,
updateSharedGroups,
}: CallLinkPendingParticipantModalProps): JSX.Element {
useEffect(() => {
// Kick off the expensive hydration of the current sharedGroupNames
updateSharedGroups(conversation.id);
}, [conversation.id, updateSharedGroups]);
const serviceId = useMemo(() => {
return conversation.serviceId;
}, [conversation]);
const handleApprove = useCallback(() => {
approveUser({ serviceId });
onClose();
}, [approveUser, onClose, serviceId]);
const handleDeny = useCallback(() => {
denyUser({ serviceId });
onClose();
}, [denyUser, onClose, serviceId]);
return (
<Modal
modalName="CallLinkPendingParticipantModal"
moduleClassName="CallLinkPendingParticipantModal"
hasXButton
i18n={i18n}
onClose={onClose}
theme={Theme.Dark}
>
<Avatar
acceptedMessageRequest={conversation.acceptedMessageRequest}
avatarUrl={conversation.avatarUrl}
badge={undefined}
color={conversation.color}
conversationType="direct"
i18n={i18n}
isMe={conversation.isMe}
profileName={conversation.profileName}
sharedGroupNames={conversation.sharedGroupNames}
size={AvatarSize.EIGHTY}
title={conversation.title}
theme={ThemeType.dark}
unblurredAvatarUrl={conversation.unblurredAvatarUrl}
/>
<button
type="button"
onClick={ev => {
ev.preventDefault();
ev.stopPropagation();
toggleAboutContactModal(conversation.id);
}}
className="CallLinkPendingParticipantModal__NameButton"
>
<div className="CallLinkPendingParticipantModal__Title">
<UserText text={conversation.title} />
{isInSystemContacts(conversation) && (
<span>
{' '}
<InContactsIcon
className="module-in-contacts-icon__icon CallLinkPendingParticipantModal__InContactsIcon"
i18n={i18n}
/>
</span>
)}
<span className="CallLinkPendingParticipantModal__AboutIcon" />
</div>
</button>
<div className="CallLinkPendingParticipantModal__SharedGroupInfo">
{conversation.sharedGroupNames?.length ? (
<SharedGroupNames
i18n={i18n}
sharedGroupNames={conversation.sharedGroupNames || []}
/>
) : (
i18n('icu:no-groups-in-common-warning')
)}
</div>
<div className="CallLinkPendingParticipantModal__Hr" />
<button
type="button"
className="CallLinkPendingParticipantModal__ActionButton"
onClick={handleApprove}
>
<div className="CallLinkPendingParticipantModal__ButtonIcon">
<div className="CallLinkPendingParticipantModal__ButtonIconContent CallLinkPendingParticipantModal__ButtonIconContent--approve" />
</div>
{i18n('icu:CallLinkPendingParticipantModal__ApproveButtonLabel')}
</button>
<button
type="button"
className="CallLinkPendingParticipantModal__ActionButton"
onClick={handleDeny}
>
<div className="CallLinkPendingParticipantModal__ButtonIcon">
<div className="CallLinkPendingParticipantModal__ButtonIconContent CallLinkPendingParticipantModal__ButtonIconContent--deny" />
</div>
{i18n('icu:CallLinkPendingParticipantModal__DenyButtonLabel')}
</button>
</Modal>
);
}

View file

@ -139,6 +139,9 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
stopRingtone: action('stop-ringtone'), stopRingtone: action('stop-ringtone'),
switchToPresentationView: action('switch-to-presentation-view'), switchToPresentationView: action('switch-to-presentation-view'),
switchFromPresentationView: action('switch-from-presentation-view'), switchFromPresentationView: action('switch-from-presentation-view'),
toggleCallLinkPendingParticipantModal: action(
'toggle-call-link-pending-participant-modal'
),
toggleParticipants: action('toggle-participants'), toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'), togglePip: action('toggle-pip'),
toggleScreenRecordingPermissionsDialog: action( toggleScreenRecordingPermissionsDialog: action(

View file

@ -139,6 +139,7 @@ export type PropsType = {
switchFromPresentationView: () => void; switchFromPresentationView: () => void;
hangUpActiveCall: (reason: string) => void; hangUpActiveCall: (reason: string) => void;
togglePip: () => void; togglePip: () => void;
toggleCallLinkPendingParticipantModal: (contactId: string) => void;
toggleScreenRecordingPermissionsDialog: () => unknown; toggleScreenRecordingPermissionsDialog: () => unknown;
toggleSettings: () => void; toggleSettings: () => void;
isConversationTooBigToRing: boolean; isConversationTooBigToRing: boolean;
@ -199,6 +200,7 @@ function ActiveCallManager({
startCall, startCall,
switchToPresentationView, switchToPresentationView,
switchFromPresentationView, switchFromPresentationView,
toggleCallLinkPendingParticipantModal,
toggleParticipants, toggleParticipants,
togglePip, togglePip,
toggleScreenRecordingPermissionsDialog, toggleScreenRecordingPermissionsDialog,
@ -475,6 +477,9 @@ function ActiveCallManager({
stickyControls={showParticipantsList} stickyControls={showParticipantsList}
switchToPresentationView={switchToPresentationView} switchToPresentationView={switchToPresentationView}
switchFromPresentationView={switchFromPresentationView} switchFromPresentationView={switchFromPresentationView}
toggleCallLinkPendingParticipantModal={
toggleCallLinkPendingParticipantModal
}
toggleScreenRecordingPermissionsDialog={ toggleScreenRecordingPermissionsDialog={
toggleScreenRecordingPermissionsDialog toggleScreenRecordingPermissionsDialog
} }
@ -571,6 +576,7 @@ export function CallManager({
switchToPresentationView, switchToPresentationView,
toggleParticipants, toggleParticipants,
togglePip, togglePip,
toggleCallLinkPendingParticipantModal,
toggleScreenRecordingPermissionsDialog, toggleScreenRecordingPermissionsDialog,
toggleSettings, toggleSettings,
}: PropsType): JSX.Element | null { }: PropsType): JSX.Element | null {
@ -661,6 +667,9 @@ export function CallManager({
startCall={startCall} startCall={startCall}
switchFromPresentationView={switchFromPresentationView} switchFromPresentationView={switchFromPresentationView}
switchToPresentationView={switchToPresentationView} switchToPresentationView={switchToPresentationView}
toggleCallLinkPendingParticipantModal={
toggleCallLinkPendingParticipantModal
}
toggleParticipants={toggleParticipants} toggleParticipants={toggleParticipants}
togglePip={togglePip} togglePip={togglePip}
toggleScreenRecordingPermissionsDialog={ toggleScreenRecordingPermissionsDialog={

View file

@ -217,6 +217,9 @@ const createProps = (
stickyControls: false, stickyControls: false,
switchToPresentationView: action('switch-to-presentation-view'), switchToPresentationView: action('switch-to-presentation-view'),
switchFromPresentationView: action('switch-from-presentation-view'), switchFromPresentationView: action('switch-from-presentation-view'),
toggleCallLinkPendingParticipantModal: action(
'toggle-call-link-pending-participant-modal'
),
toggleParticipants: action('toggle-participants'), toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'), togglePip: action('toggle-pip'),
toggleScreenRecordingPermissionsDialog: action( toggleScreenRecordingPermissionsDialog: action(

View file

@ -125,6 +125,7 @@ export type PropsType = {
stickyControls: boolean; stickyControls: boolean;
switchToPresentationView: () => void; switchToPresentationView: () => void;
switchFromPresentationView: () => void; switchFromPresentationView: () => void;
toggleCallLinkPendingParticipantModal: (contactId: string) => void;
toggleParticipants: () => void; toggleParticipants: () => void;
togglePip: () => void; togglePip: () => void;
toggleScreenRecordingPermissionsDialog: () => unknown; toggleScreenRecordingPermissionsDialog: () => unknown;
@ -214,6 +215,7 @@ export function CallScreen({
stickyControls, stickyControls,
switchToPresentationView, switchToPresentationView,
switchFromPresentationView, switchFromPresentationView,
toggleCallLinkPendingParticipantModal,
toggleParticipants, toggleParticipants,
togglePip, togglePip,
toggleScreenRecordingPermissionsDialog, toggleScreenRecordingPermissionsDialog,
@ -864,6 +866,9 @@ export function CallScreen({
approveUser={approveUser} approveUser={approveUser}
batchUserAction={batchUserAction} batchUserAction={batchUserAction}
denyUser={denyUser} denyUser={denyUser}
toggleCallLinkPendingParticipantModal={
toggleCallLinkPendingParticipantModal
}
/> />
) : null} ) : null}
{/* We render the local preview first and set the footer flex direction to row-reverse {/* We render the local preview first and set the footer flex direction to row-reverse

View file

@ -18,6 +18,9 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
approveUser: action('approve-user'), approveUser: action('approve-user'),
batchUserAction: action('batch-user-action'), batchUserAction: action('batch-user-action'),
denyUser: action('deny-user'), denyUser: action('deny-user'),
toggleCallLinkPendingParticipantModal: action(
'toggle-call-link-pending-participant-modal'
),
...storyProps, ...storyProps,
}); });

View file

@ -35,6 +35,9 @@ export type PropsType = {
readonly approveUser: (payload: PendingUserActionPayloadType) => void; readonly approveUser: (payload: PendingUserActionPayloadType) => void;
readonly batchUserAction: (payload: BatchUserActionPayloadType) => void; readonly batchUserAction: (payload: BatchUserActionPayloadType) => void;
readonly denyUser: (payload: PendingUserActionPayloadType) => void; readonly denyUser: (payload: PendingUserActionPayloadType) => void;
readonly toggleCallLinkPendingParticipantModal: (
conversationId: string
) => void;
}; };
export function CallingPendingParticipants({ export function CallingPendingParticipants({
@ -44,6 +47,7 @@ export function CallingPendingParticipants({
approveUser, approveUser,
batchUserAction, batchUserAction,
denyUser, denyUser,
toggleCallLinkPendingParticipantModal,
}: PropsType): JSX.Element | null { }: PropsType): JSX.Element | null {
const [isExpanded, setIsExpanded] = useState(defaultIsExpanded ?? false); const [isExpanded, setIsExpanded] = useState(defaultIsExpanded ?? false);
const [confirmDialogState, setConfirmDialogState] = const [confirmDialogState, setConfirmDialogState] =
@ -241,7 +245,15 @@ export function CallingPendingParticipants({
className="module-calling-participants-list__contact" className="module-calling-participants-list__contact"
key={index} key={index}
> >
<div className="module-calling-participants-list__avatar-and-name"> <button
type="button"
onClick={ev => {
ev.preventDefault();
ev.stopPropagation();
toggleCallLinkPendingParticipantModal(participant.id);
}}
className="module-calling-participants-list__avatar-and-name CallingPendingParticipants__ParticipantButton"
>
<Avatar <Avatar
acceptedMessageRequest={participant.acceptedMessageRequest} acceptedMessageRequest={participant.acceptedMessageRequest}
avatarUrl={participant.avatarUrl} avatarUrl={participant.avatarUrl}
@ -268,7 +280,7 @@ export function CallingPendingParticipants({
/> />
</span> </span>
) : null} ) : null}
</div> </button>
{renderApprovalButtons(participant)} {renderApprovalButtons(participant)}
</li> </li>
))} ))}
@ -303,7 +315,15 @@ export function CallingPendingParticipants({
return ( return (
<div className="CallingPendingParticipants CallingPendingParticipants--Compact module-calling-participants-list"> <div className="CallingPendingParticipants CallingPendingParticipants--Compact module-calling-participants-list">
<div className="CallingPendingParticipants__CompactParticipant"> <div className="CallingPendingParticipants__CompactParticipant">
<div className="module-calling-participants-list__avatar-and-name"> <button
type="button"
onClick={ev => {
ev.preventDefault();
ev.stopPropagation();
toggleCallLinkPendingParticipantModal(participant.id);
}}
className="module-calling-participants-list__avatar-and-name CallingPendingParticipants__ParticipantButton"
>
<Avatar <Avatar
acceptedMessageRequest={participant.acceptedMessageRequest} acceptedMessageRequest={participant.acceptedMessageRequest}
avatarUrl={participant.avatarUrl} avatarUrl={participant.avatarUrl}
@ -326,12 +346,13 @@ export function CallingPendingParticipants({
i18n={i18n} i18n={i18n}
/> />
) : null} ) : null}
<span className="CallingPendingParticipants__ParticipantAboutIcon" />
</div> </div>
<div className="CallingPendingParticipants__WouldLikeToJoin"> <div className="CallingPendingParticipants__WouldLikeToJoin">
{i18n('icu:CallingPendingParticipants__WouldLikeToJoin')} {i18n('icu:CallingPendingParticipants__WouldLikeToJoin')}
</div> </div>
</div> </div>
</div> </button>
{renderApprovalButtons(participant)} {renderApprovalButtons(participant)}
</div> </div>
{participants.length > 1 && ( {participants.length > 1 && (

View file

@ -36,6 +36,9 @@ export type PropsType = {
// CallLinkEditModal // CallLinkEditModal
callLinkEditModalRoomId: string | null; callLinkEditModalRoomId: string | null;
renderCallLinkEditModal: () => JSX.Element; renderCallLinkEditModal: () => JSX.Element;
// CallLinkPendingParticipantModal
callLinkPendingParticipantContactId: string | undefined;
renderCallLinkPendingParticipantModal: () => JSX.Element;
// ConfirmLeaveCallModal // ConfirmLeaveCallModal
confirmLeaveCallModalState: StartCallData | null; confirmLeaveCallModalState: StartCallData | null;
renderConfirmLeaveCallModal: () => JSX.Element; renderConfirmLeaveCallModal: () => JSX.Element;
@ -118,6 +121,9 @@ export function GlobalModalContainer({
// CallLinkEditModal // CallLinkEditModal
callLinkEditModalRoomId, callLinkEditModalRoomId,
renderCallLinkEditModal, renderCallLinkEditModal,
// CallLinkPendingParticipantModal
callLinkPendingParticipantContactId,
renderCallLinkPendingParticipantModal,
// ConfirmLeaveCallModal // ConfirmLeaveCallModal
confirmLeaveCallModalState, confirmLeaveCallModalState,
renderConfirmLeaveCallModal, renderConfirmLeaveCallModal,
@ -268,6 +274,12 @@ export function GlobalModalContainer({
return renderContactModal(); return renderContactModal();
} }
// This needs to be after the about contact modal because the pending participant modal
// opens the about contact modal
if (callLinkPendingParticipantContactId) {
return renderCallLinkPendingParticipantModal();
}
if (isStoriesSettingsVisible) { if (isStoriesSettingsVisible) {
return renderStoriesSettings(); return renderStoriesSettings();
} }

View file

@ -38,6 +38,12 @@ const conversationWithAbout = getDefaultConversation({
aboutText: '😀 About Me', aboutText: '😀 About Me',
hasMessages: true, hasMessages: true,
}); });
const conversationWithSharedGroups = getDefaultConversation({
acceptedMessageRequest: true,
aboutText: 'likes to chat',
hasMessages: true,
sharedGroupNames: ['Axolotl lovers'],
});
const systemContact = getDefaultConversation({ const systemContact = getDefaultConversation({
acceptedMessageRequest: true, acceptedMessageRequest: true,
systemGivenName: 'Alice', systemGivenName: 'Alice',
@ -110,3 +116,13 @@ export function SystemContact(args: PropsType): JSX.Element {
/> />
); );
} }
export function WithSharedGroups(args: PropsType): JSX.Element {
return (
<AboutContactModal
{...args}
conversation={conversationWithSharedGroups}
isSignalConnection
/>
);
}

View file

@ -94,6 +94,7 @@ export type GlobalModalsStateType = ReadonlyDeep<{
aboutContactModalContactId?: string; aboutContactModalContactId?: string;
callLinkAddNameModalRoomId: string | null; callLinkAddNameModalRoomId: string | null;
callLinkEditModalRoomId: string | null; callLinkEditModalRoomId: string | null;
callLinkPendingParticipantContactId: string | undefined;
confirmLeaveCallModalState: StartCallData | null; confirmLeaveCallModalState: StartCallData | null;
contactModalState?: ContactModalStateType; contactModalState?: ContactModalStateType;
deleteMessagesProps?: DeleteMessagesPropsType; deleteMessagesProps?: DeleteMessagesPropsType;
@ -149,6 +150,8 @@ const TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL =
const TOGGLE_CALL_LINK_ADD_NAME_MODAL = const TOGGLE_CALL_LINK_ADD_NAME_MODAL =
'globalModals/TOGGLE_CALL_LINK_ADD_NAME_MODAL'; 'globalModals/TOGGLE_CALL_LINK_ADD_NAME_MODAL';
const TOGGLE_CALL_LINK_EDIT_MODAL = 'globalModals/TOGGLE_CALL_LINK_EDIT_MODAL'; const TOGGLE_CALL_LINK_EDIT_MODAL = 'globalModals/TOGGLE_CALL_LINK_EDIT_MODAL';
const TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL =
'globalModals/TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL';
const TOGGLE_ABOUT_MODAL = 'globalModals/TOGGLE_ABOUT_MODAL'; const TOGGLE_ABOUT_MODAL = 'globalModals/TOGGLE_ABOUT_MODAL';
const TOGGLE_SIGNAL_CONNECTIONS_MODAL = const TOGGLE_SIGNAL_CONNECTIONS_MODAL =
'globalModals/TOGGLE_SIGNAL_CONNECTIONS_MODAL'; 'globalModals/TOGGLE_SIGNAL_CONNECTIONS_MODAL';
@ -266,6 +269,11 @@ type ToggleCallLinkEditModalActionType = ReadonlyDeep<{
payload: string | null; payload: string | null;
}>; }>;
type ToggleCallLinkPendingParticipantModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL;
payload: string | undefined;
}>;
type ToggleAboutContactModalActionType = ReadonlyDeep<{ type ToggleAboutContactModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_ABOUT_MODAL; type: typeof TOGGLE_ABOUT_MODAL;
payload: string | undefined; payload: string | undefined;
@ -393,6 +401,7 @@ export type GlobalModalsActionType = ReadonlyDeep<
| ToggleAddUserToAnotherGroupModalActionType | ToggleAddUserToAnotherGroupModalActionType
| ToggleCallLinkAddNameModalActionType | ToggleCallLinkAddNameModalActionType
| ToggleCallLinkEditModalActionType | ToggleCallLinkEditModalActionType
| ToggleCallLinkPendingParticipantModalActionType
| ToggleConfirmationModalActionType | ToggleConfirmationModalActionType
| ToggleConfirmLeaveCallModalActionType | ToggleConfirmLeaveCallModalActionType
| ToggleDeleteMessagesModalActionType | ToggleDeleteMessagesModalActionType
@ -435,6 +444,7 @@ export const actions = {
toggleAddUserToAnotherGroupModal, toggleAddUserToAnotherGroupModal,
toggleCallLinkAddNameModal, toggleCallLinkAddNameModal,
toggleCallLinkEditModal, toggleCallLinkEditModal,
toggleCallLinkPendingParticipantModal,
toggleConfirmationModal, toggleConfirmationModal,
toggleConfirmLeaveCallModal, toggleConfirmLeaveCallModal,
toggleDeleteMessagesModal, toggleDeleteMessagesModal,
@ -757,6 +767,15 @@ function toggleCallLinkEditModal(
}; };
} }
function toggleCallLinkPendingParticipantModal(
contactId?: string
): ToggleCallLinkPendingParticipantModalActionType {
return {
type: TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL,
payload: contactId,
};
}
function toggleAboutContactModal( function toggleAboutContactModal(
contactId?: string contactId?: string
): ToggleAboutContactModalActionType { ): ToggleAboutContactModalActionType {
@ -974,6 +993,7 @@ export function getEmptyState(): GlobalModalsStateType {
hasConfirmationModal: false, hasConfirmationModal: false,
callLinkAddNameModalRoomId: null, callLinkAddNameModalRoomId: null,
callLinkEditModalRoomId: null, callLinkEditModalRoomId: null,
callLinkPendingParticipantContactId: undefined,
confirmLeaveCallModalState: null, confirmLeaveCallModalState: null,
editNicknameAndNoteModalProps: null, editNicknameAndNoteModalProps: null,
isProfileEditorVisible: false, isProfileEditorVisible: false,
@ -1109,6 +1129,13 @@ export function reducer(
}; };
} }
if (action.type === TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL) {
return {
...state,
callLinkPendingParticipantContactId: action.payload,
};
}
if (action.type === TOGGLE_DELETE_MESSAGES_MODAL) { if (action.type === TOGGLE_DELETE_MESSAGES_MODAL) {
return { return {
...state, ...state,

View file

@ -32,6 +32,12 @@ export const getCallLinkAddNameModalRoomId = createSelector(
({ callLinkAddNameModalRoomId }) => callLinkAddNameModalRoomId ({ callLinkAddNameModalRoomId }) => callLinkAddNameModalRoomId
); );
export const getCallLinkPendingParticipantContactId = createSelector(
getGlobalModalsState,
({ callLinkPendingParticipantContactId }) =>
callLinkPendingParticipantContactId
);
export const getConfirmLeaveCallModalState = createSelector( export const getConfirmLeaveCallModalState = createSelector(
getGlobalModalsState, getGlobalModalsState,
({ confirmLeaveCallModalState }) => confirmLeaveCallModalState ({ confirmLeaveCallModalState }) => confirmLeaveCallModalState

View file

@ -0,0 +1,41 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { memo } from 'react';
import { useSelector } from 'react-redux';
import { CallLinkPendingParticipantModal } from '../../components/CallLinkPendingParticipantModal';
import { useCallingActions } from '../ducks/calling';
import { getIntl } from '../selectors/user';
import { useConversationsActions } from '../ducks/conversations';
import { useGlobalModalActions } from '../ducks/globalModals';
import { getConversationSelector } from '../selectors/conversations';
import { getCallLinkPendingParticipantContactId } from '../selectors/globalModals';
import { strictAssert } from '../../util/assert';
export const SmartCallLinkPendingParticipantModal = memo(
function SmartCallLinkPendingParticipantModal(): JSX.Element | null {
const contactId = useSelector(getCallLinkPendingParticipantContactId);
strictAssert(contactId, 'Expected contactId to be set');
const i18n = useSelector(getIntl);
const getConversation = useSelector(getConversationSelector);
const { updateSharedGroups } = useConversationsActions();
const { approveUser, denyUser } = useCallingActions();
const { toggleAboutContactModal, toggleCallLinkPendingParticipantModal } =
useGlobalModalActions();
const conversation = getConversation(contactId);
return (
<CallLinkPendingParticipantModal
i18n={i18n}
conversation={conversation}
approveUser={approveUser}
denyUser={denyUser}
onClose={toggleCallLinkPendingParticipantModal}
updateSharedGroups={updateSharedGroups}
toggleAboutContactModal={toggleAboutContactModal}
/>
);
}
);

View file

@ -458,8 +458,11 @@ export const SmartCallManager = memo(function SmartCallManager() {
toggleSettings, toggleSettings,
} = useCallingActions(); } = useCallingActions();
const { pauseVoiceNotePlayer } = useAudioPlayerActions(); const { pauseVoiceNotePlayer } = useAudioPlayerActions();
const { showContactModal, showShareCallLinkViaSignal } = const {
useGlobalModalActions(); showContactModal,
showShareCallLinkViaSignal,
toggleCallLinkPendingParticipantModal,
} = useGlobalModalActions();
return ( return (
<CallManager <CallManager
@ -513,6 +516,9 @@ export const SmartCallManager = memo(function SmartCallManager() {
stopRingtone={stopRingtone} stopRingtone={stopRingtone}
switchFromPresentationView={switchFromPresentationView} switchFromPresentationView={switchFromPresentationView}
switchToPresentationView={switchToPresentationView} switchToPresentationView={switchToPresentationView}
toggleCallLinkPendingParticipantModal={
toggleCallLinkPendingParticipantModal
}
toggleParticipants={toggleParticipants} toggleParticipants={toggleParticipants}
togglePip={togglePip} togglePip={togglePip}
toggleScreenRecordingPermissionsDialog={ toggleScreenRecordingPermissionsDialog={

View file

@ -29,6 +29,7 @@ import { SmartNotePreviewModal } from './NotePreviewModal';
import { SmartCallLinkEditModal } from './CallLinkEditModal'; import { SmartCallLinkEditModal } from './CallLinkEditModal';
import { SmartCallLinkAddNameModal } from './CallLinkAddNameModal'; import { SmartCallLinkAddNameModal } from './CallLinkAddNameModal';
import { SmartConfirmLeaveCallModal } from './ConfirmLeaveCallModal'; import { SmartConfirmLeaveCallModal } from './ConfirmLeaveCallModal';
import { SmartCallLinkPendingParticipantModal } from './CallLinkPendingParticipantModal';
function renderCallLinkAddNameModal(): JSX.Element { function renderCallLinkAddNameModal(): JSX.Element {
return <SmartCallLinkAddNameModal />; return <SmartCallLinkAddNameModal />;
@ -38,6 +39,10 @@ function renderCallLinkEditModal(): JSX.Element {
return <SmartCallLinkEditModal />; return <SmartCallLinkEditModal />;
} }
function renderCallLinkPendingParticipantModal(): JSX.Element {
return <SmartCallLinkPendingParticipantModal />;
}
function renderConfirmLeaveCallModal(): JSX.Element { function renderConfirmLeaveCallModal(): JSX.Element {
return <SmartConfirmLeaveCallModal />; return <SmartConfirmLeaveCallModal />;
} }
@ -107,6 +112,7 @@ export const SmartGlobalModalContainer = memo(
addUserToAnotherGroupModalContactId, addUserToAnotherGroupModalContactId,
callLinkAddNameModalRoomId, callLinkAddNameModalRoomId,
callLinkEditModalRoomId, callLinkEditModalRoomId,
callLinkPendingParticipantContactId,
confirmLeaveCallModalState, confirmLeaveCallModalState,
contactModalState, contactModalState,
deleteMessagesProps, deleteMessagesProps,
@ -188,6 +194,9 @@ export const SmartGlobalModalContainer = memo(
} }
callLinkAddNameModalRoomId={callLinkAddNameModalRoomId} callLinkAddNameModalRoomId={callLinkAddNameModalRoomId}
callLinkEditModalRoomId={callLinkEditModalRoomId} callLinkEditModalRoomId={callLinkEditModalRoomId}
callLinkPendingParticipantContactId={
callLinkPendingParticipantContactId
}
confirmLeaveCallModalState={confirmLeaveCallModalState} confirmLeaveCallModalState={confirmLeaveCallModalState}
contactModalState={contactModalState} contactModalState={contactModalState}
editHistoryMessages={editHistoryMessages} editHistoryMessages={editHistoryMessages}
@ -213,6 +222,9 @@ export const SmartGlobalModalContainer = memo(
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup} renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
renderCallLinkAddNameModal={renderCallLinkAddNameModal} renderCallLinkAddNameModal={renderCallLinkAddNameModal}
renderCallLinkEditModal={renderCallLinkEditModal} renderCallLinkEditModal={renderCallLinkEditModal}
renderCallLinkPendingParticipantModal={
renderCallLinkPendingParticipantModal
}
renderConfirmLeaveCallModal={renderConfirmLeaveCallModal} renderConfirmLeaveCallModal={renderConfirmLeaveCallModal}
renderContactModal={renderContactModal} renderContactModal={renderContactModal}
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal} renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}