signal-desktop/ts/components/conversation/conversation-details/ConversationDetails.tsx

744 lines
24 KiB
TypeScript
Raw Normal View History

2023-01-03 19:55:46 +00:00
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode } from 'react';
2023-03-09 21:46:01 +00:00
import React, { useEffect, useState, useCallback } from 'react';
2023-08-09 00:53:06 +00:00
import classNames from 'classnames';
import { Button, ButtonIconType, ButtonVariant } from '../../Button';
import { Tooltip } from '../../Tooltip';
import type {
ConversationType,
PushPanelForConversationActionType,
ShowConversationType,
} from '../../../state/ducks/conversations';
2021-11-17 21:11:21 +00:00
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
import type { SmartChooseGroupMembersModalPropsType } from '../../../state/smart/ChooseGroupMembersModal';
import type { SmartConfirmAdditionsModalPropsType } from '../../../state/smart/ConfirmAdditionsModal';
import { assertDev } from '../../../util/assert';
2021-08-05 12:35:33 +00:00
import { getMutedUntilText } from '../../../util/getMutedUntilText';
2021-11-02 23:01:13 +00:00
import type { LocalizerType, ThemeType } from '../../../types/Util';
import type { BadgeType } from '../../../badges/types';
2021-03-11 21:29:31 +00:00
import { missingCaseError } from '../../../util/missingCaseError';
2022-11-16 20:18:02 +00:00
import { DurationInSeconds } from '../../../util/durations';
import { DisappearingTimerSelect } from '../../DisappearingTimerSelect';
2021-06-01 20:45:43 +00:00
import { PanelRow } from './PanelRow';
import { PanelSection } from './PanelSection';
2021-03-11 21:29:31 +00:00
import { AddGroupMembersModal } from './AddGroupMembersModal';
import { ConversationDetailsActions } from './ConversationDetailsActions';
import { ConversationDetailsHeader } from './ConversationDetailsHeader';
import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon';
2022-12-10 02:02:22 +00:00
import type { Props as ConversationDetailsMediaListPropsType } from './ConversationDetailsMediaList';
import { ConversationDetailsMediaList } from './ConversationDetailsMediaList';
import type { GroupV2Membership } from './ConversationDetailsMembershipList';
import { ConversationDetailsMembershipList } from './ConversationDetailsMembershipList';
import type {
GroupV2PendingMembership,
GroupV2RequestingMembership,
} from './PendingInvites';
2021-03-11 21:29:31 +00:00
import { EditConversationAttributesModal } from './EditConversationAttributesModal';
import { RequestState } from './util';
2021-05-28 16:15:17 +00:00
import { getCustomColorStyle } from '../../../util/getCustomColorStyle';
2021-07-20 20:18:35 +00:00
import { ConfirmationDialog } from '../../ConfirmationDialog';
import { ConversationNotificationsModal } from './ConversationNotificationsModal';
import type {
2021-08-06 00:17:05 +00:00
AvatarDataType,
DeleteAvatarFromDiskActionType,
ReplaceAvatarActionType,
SaveAvatarToDiskActionType,
} from '../../../types/Avatar';
2022-05-23 18:37:53 +00:00
import { isConversationMuted } from '../../../util/isConversationMuted';
import { ConversationDetailsGroups } from './ConversationDetailsGroups';
import { PanelType } from '../../../types/Panels';
2023-08-09 00:53:06 +00:00
import type { CallStatus } from '../../../types/CallDisposition';
import {
CallType,
type CallHistoryGroup,
CallDirection,
DirectCallStatus,
GroupCallStatus,
} from '../../../types/CallDisposition';
import { formatDate, formatTime } from '../../../util/timestamp';
import { NavTab } from '../../../state/ducks/nav';
function describeCallHistory(
i18n: LocalizerType,
type: CallType,
direction: CallDirection,
status: CallStatus
): string {
if (status === DirectCallStatus.Missed || status === GroupCallStatus.Missed) {
if (direction === CallDirection.Incoming) {
return i18n('icu:CallHistory__Description--Missed', { type });
}
return i18n('icu:CallHistory__Description--Unanswered', { type });
}
if (
status === DirectCallStatus.Declined ||
status === GroupCallStatus.Declined
) {
return i18n('icu:CallHistory__Description--Declined', { type });
}
return i18n('icu:CallHistory__Description--Default', { type, direction });
}
2021-03-11 21:29:31 +00:00
enum ModalState {
NothingOpen,
EditingGroupDescription,
EditingGroupTitle,
2021-03-11 21:29:31 +00:00
AddingGroupMembers,
MuteNotifications,
UnmuteNotifications,
2021-03-11 21:29:31 +00:00
}
export type StateProps = {
areWeASubscriber: boolean;
2021-11-02 23:01:13 +00:00
badges?: ReadonlyArray<BadgeType>;
2023-08-09 00:53:06 +00:00
callHistoryGroup?: CallHistoryGroup | null;
canEditGroupInfo: boolean;
canAddNewMembers: boolean;
conversation?: ConversationType;
hasGroupLink: boolean;
2021-11-17 21:11:21 +00:00
getPreferredBadge: PreferredBadgeSelectorType;
hasActiveCall: boolean;
i18n: LocalizerType;
isAdmin: boolean;
isGroup: boolean;
groupsInCommon: ReadonlyArray<ConversationType>;
maxGroupSize: number;
maxRecommendedGroupSize: number;
memberships: ReadonlyArray<GroupV2Membership>;
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
2023-08-09 00:53:06 +00:00
selectedNavTab: NavTab;
2021-11-02 23:01:13 +00:00
theme: ThemeType;
userAvatarData: ReadonlyArray<AvatarDataType>;
renderChooseGroupMembersModal: (
props: SmartChooseGroupMembersModalPropsType
) => JSX.Element;
renderConfirmAdditionsModal: (
props: SmartConfirmAdditionsModalPropsType
) => JSX.Element;
2021-08-06 00:17:05 +00:00
};
type ActionProps = {
2022-12-06 19:03:09 +00:00
acceptConversation: (id: string) => void;
addMembersToGroup: (
conversationId: string,
conversationIds: ReadonlyArray<string>,
opts: {
onSuccess?: () => unknown;
onFailure?: () => unknown;
}
) => unknown;
2022-12-06 19:03:09 +00:00
blockConversation: (id: string) => void;
2023-08-09 00:53:06 +00:00
2021-08-06 00:17:05 +00:00
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
getProfilesForConversation: (id: string) => unknown;
leaveGroup: (conversationId: string) => void;
loadRecentMediaItems: (id: string, limit: number) => void;
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
pushPanelForConversation: PushPanelForConversationActionType;
2021-08-06 00:17:05 +00:00
replaceAvatar: ReplaceAvatarActionType;
saveAvatarToDisk: SaveAvatarToDiskActionType;
searchInConversation: (id: string) => unknown;
setDisappearingMessages: (id: string, seconds: DurationInSeconds) => void;
setMuteExpiration: (id: string, muteExpiresAt: undefined | number) => unknown;
showContactModal: (contactId: string, conversationId?: string) => void;
showConversation: ShowConversationType;
toggleAboutContactModal: (contactId: string) => void;
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
toggleSafetyNumberModal: (conversationId: string) => unknown;
updateGroupAttributes: (
conversationId: string,
_: Readonly<{
avatar?: undefined | Uint8Array;
description?: string;
title?: string;
}>,
opts: {
onSuccess?: () => unknown;
onFailure?: () => unknown;
}
) => unknown;
2022-12-10 02:02:22 +00:00
} & Pick<ConversationDetailsMediaListPropsType, 'showLightboxWithMedia'>;
2021-08-06 00:17:05 +00:00
export type Props = StateProps & ActionProps;
export function getCannotLeaveBecauseYouAreLastAdmin(
memberships: ReadonlyArray<GroupV2Membership>,
isAdmin: boolean
): boolean {
const otherMemberships = memberships.filter(({ member }) => !member.isMe);
const isJustMe = otherMemberships.length === 0;
const isAnyoneElseAnAdmin = otherMemberships.some(
membership => membership.isAdmin
);
const cannotLeaveBecauseYouAreLastAdmin =
isAdmin && !isJustMe && !isAnyoneElseAnAdmin;
return cannotLeaveBecauseYouAreLastAdmin;
}
2022-11-18 00:45:19 +00:00
export function ConversationDetails({
2022-12-06 19:03:09 +00:00
acceptConversation,
addMembersToGroup,
areWeASubscriber,
2021-11-02 23:01:13 +00:00
badges,
2022-12-06 19:03:09 +00:00
blockConversation,
2023-08-09 00:53:06 +00:00
callHistoryGroup,
canEditGroupInfo,
canAddNewMembers,
conversation,
deleteAvatarFromDisk,
hasGroupLink,
2021-11-17 21:11:21 +00:00
getPreferredBadge,
getProfilesForConversation,
groupsInCommon,
hasActiveCall,
i18n,
isAdmin,
isGroup,
leaveGroup,
loadRecentMediaItems,
memberships,
maxGroupSize,
maxRecommendedGroupSize,
onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation,
pendingApprovalMemberships,
pendingMemberships,
pushPanelForConversation,
renderChooseGroupMembersModal,
renderConfirmAdditionsModal,
replaceAvatar,
saveAvatarToDisk,
searchInConversation,
2023-08-09 00:53:06 +00:00
selectedNavTab,
setDisappearingMessages,
setMuteExpiration,
showContactModal,
showConversation,
2022-12-10 02:02:22 +00:00
showLightboxWithMedia,
2021-11-02 23:01:13 +00:00
theme,
toggleAboutContactModal,
toggleSafetyNumberModal,
toggleAddUserToAnotherGroupModal,
updateGroupAttributes,
2021-08-06 00:17:05 +00:00
userAvatarData,
2022-11-18 00:45:19 +00:00
}: Props): JSX.Element {
2021-03-11 21:29:31 +00:00
const [modalState, setModalState] = useState<ModalState>(
ModalState.NothingOpen
);
2021-11-11 22:43:05 +00:00
const [editGroupAttributesRequestState, setEditGroupAttributesRequestState] =
useState<RequestState>(RequestState.Inactive);
const [addGroupMembersRequestState, setAddGroupMembersRequestState] =
useState<RequestState>(RequestState.Inactive);
if (conversation === undefined) {
throw new Error('ConversationDetails rendered without a conversation');
}
useEffect(() => {
getProfilesForConversation(conversation.id);
}, [conversation.id, getProfilesForConversation]);
const invitesCount =
pendingMemberships.length + pendingApprovalMemberships.length;
const cannotLeaveBecauseYouAreLastAdmin =
getCannotLeaveBecauseYouAreLastAdmin(memberships, isAdmin);
2023-03-09 21:46:01 +00:00
const onCloseModal = useCallback(() => {
setModalState(ModalState.NothingOpen);
setEditGroupAttributesRequestState(RequestState.Inactive);
}, []);
2021-03-11 21:29:31 +00:00
let modalNode: ReactNode;
switch (modalState) {
case ModalState.NothingOpen:
modalNode = undefined;
break;
case ModalState.EditingGroupDescription:
case ModalState.EditingGroupTitle:
2021-03-11 21:29:31 +00:00
modalNode = (
<EditConversationAttributesModal
2021-08-06 00:17:05 +00:00
avatarColor={conversation.color}
2021-03-11 21:29:31 +00:00
avatarPath={conversation.avatarPath}
2021-08-06 00:17:05 +00:00
conversationId={conversation.id}
2021-06-02 00:24:28 +00:00
groupDescription={conversation.groupDescription}
2021-03-11 21:29:31 +00:00
i18n={i18n}
initiallyFocusDescription={
modalState === ModalState.EditingGroupDescription
}
2021-03-11 21:29:31 +00:00
makeRequest={async (
options: Readonly<{
2021-09-24 00:49:05 +00:00
avatar?: undefined | Uint8Array;
2021-06-02 00:24:28 +00:00
description?: string;
2021-03-11 21:29:31 +00:00
title?: string;
}>
) => {
setEditGroupAttributesRequestState(RequestState.Active);
updateGroupAttributes(conversation.id, options, {
onSuccess: () => {
setModalState(ModalState.NothingOpen);
setEditGroupAttributesRequestState(RequestState.Inactive);
},
onFailure: () => {
setEditGroupAttributesRequestState(
RequestState.InactiveWithError
);
},
});
2021-03-11 21:29:31 +00:00
}}
2023-03-09 21:46:01 +00:00
onClose={onCloseModal}
2021-03-11 21:29:31 +00:00
requestState={editGroupAttributesRequestState}
title={conversation.title}
2021-08-06 00:17:05 +00:00
deleteAvatarFromDisk={deleteAvatarFromDisk}
replaceAvatar={replaceAvatar}
saveAvatarToDisk={saveAvatarToDisk}
userAvatarData={userAvatarData}
2021-03-11 21:29:31 +00:00
/>
);
break;
case ModalState.AddingGroupMembers:
modalNode = (
<AddGroupMembersModal
renderChooseGroupMembersModal={renderChooseGroupMembersModal}
renderConfirmAdditionsModal={renderConfirmAdditionsModal}
2021-03-11 21:29:31 +00:00
clearRequestError={() => {
setAddGroupMembersRequestState(oldRequestState => {
assertDev(
2021-03-11 21:29:31 +00:00
oldRequestState !== RequestState.Active,
'Should not be clearing an active request state'
);
return RequestState.Inactive;
});
}}
conversationIdsAlreadyInGroup={
new Set(memberships.map(membership => membership.member.id))
2021-03-11 21:29:31 +00:00
}
groupTitle={conversation.title}
i18n={i18n}
makeRequest={async conversationIds => {
setAddGroupMembersRequestState(RequestState.Active);
addMembersToGroup(conversation.id, conversationIds, {
onSuccess: () => {
setModalState(ModalState.NothingOpen);
setAddGroupMembersRequestState(RequestState.Inactive);
},
onFailure: () => {
setAddGroupMembersRequestState(RequestState.InactiveWithError);
},
});
2021-03-11 21:29:31 +00:00
}}
maxGroupSize={maxGroupSize}
maxRecommendedGroupSize={maxRecommendedGroupSize}
2023-03-09 21:46:01 +00:00
onClose={onCloseModal}
2021-03-11 21:29:31 +00:00
requestState={addGroupMembersRequestState}
/>
);
break;
case ModalState.MuteNotifications:
modalNode = (
<ConversationNotificationsModal
i18n={i18n}
id={conversation.id}
muteExpiresAt={conversation.muteExpiresAt}
2023-03-09 21:46:01 +00:00
onClose={onCloseModal}
setMuteExpiration={setMuteExpiration}
/>
);
break;
case ModalState.UnmuteNotifications:
modalNode = (
<ConfirmationDialog
2022-09-27 20:24:21 +00:00
dialogName="ConversationDetails.unmuteNotifications"
actions={[
{
action: () => setMuteExpiration(conversation.id, 0),
style: 'affirmative',
2023-03-30 00:03:25 +00:00
text: i18n('icu:unmute'),
},
]}
hasXButton
i18n={i18n}
2023-03-30 00:03:25 +00:00
title={i18n('icu:ConversationDetails__unmute--title')}
2023-03-09 21:46:01 +00:00
onClose={onCloseModal}
>
{getMutedUntilText(Number(conversation.muteExpiresAt), i18n)}
</ConfirmationDialog>
);
break;
2021-03-11 21:29:31 +00:00
default:
throw missingCaseError(modalState);
}
2022-05-23 18:37:53 +00:00
const isMuted = isConversationMuted(conversation);
return (
<div className="conversation-details-panel">
<ConversationDetailsHeader
areWeASubscriber={areWeASubscriber}
2021-11-02 23:01:13 +00:00
badges={badges}
canEdit={canEditGroupInfo}
conversation={conversation}
i18n={i18n}
isMe={conversation.isMe}
isGroup={isGroup}
memberships={memberships}
startEditing={(isGroupTitle: boolean) => {
setModalState(
isGroupTitle
? ModalState.EditingGroupTitle
: ModalState.EditingGroupDescription
);
}}
2021-11-02 23:01:13 +00:00
theme={theme}
toggleAboutContactModal={toggleAboutContactModal}
/>
<div className="ConversationDetails__header-buttons">
2023-08-09 00:53:06 +00:00
{selectedNavTab === NavTab.Calls && (
<Button
icon={ButtonIconType.message}
onClick={() => {
showConversation({
conversationId: conversation?.id,
switchToAssociatedView: true,
});
}}
variant={ButtonVariant.Details}
>
{i18n('icu:ConversationDetails__HeaderButton--Message')}
</Button>
)}
{!conversation.isMe && (
<>
<ConversationDetailsCallButton
disabled={hasActiveCall}
i18n={i18n}
onClick={() => onOutgoingVideoCallInConversation(conversation.id)}
type="video"
/>
{!isGroup && (
<ConversationDetailsCallButton
disabled={hasActiveCall}
i18n={i18n}
onClick={() =>
onOutgoingAudioCallInConversation(conversation.id)
}
type="audio"
/>
)}
</>
)}
<Button
2022-05-23 18:37:53 +00:00
icon={isMuted ? ButtonIconType.muted : ButtonIconType.unmuted}
onClick={() => {
2022-05-23 18:37:53 +00:00
if (isMuted) {
setModalState(ModalState.UnmuteNotifications);
} else {
setModalState(ModalState.MuteNotifications);
}
}}
variant={ButtonVariant.Details}
>
2023-03-30 00:03:25 +00:00
{isMuted ? i18n('icu:unmute') : i18n('icu:mute')}
</Button>
2023-08-09 00:53:06 +00:00
{selectedNavTab !== NavTab.Calls && (
<Button
icon={ButtonIconType.search}
onClick={() => {
searchInConversation(conversation.id);
}}
variant={ButtonVariant.Details}
>
{i18n('icu:search')}
</Button>
)}
</div>
2023-08-09 00:53:06 +00:00
{callHistoryGroup && (
<PanelSection title={formatDate(i18n, callHistoryGroup.timestamp)}>
2023-08-09 00:53:06 +00:00
<ol className="ConversationDetails__CallHistoryGroup__List">
{callHistoryGroup.children.map(child => {
return (
<li
key={child.callId}
className="ConversationDetails__CallHistoryGroup__Item"
>
<span
className={classNames(
'ConversationDetails__CallHistoryGroup__ItemIcon',
{
'ConversationDetails__CallHistoryGroup__ItemIcon--Audio':
callHistoryGroup.type === CallType.Audio,
'ConversationDetails__CallHistoryGroup__ItemIcon--Video':
callHistoryGroup.type !== CallType.Audio,
}
)}
/>
<span className="ConversationDetails__CallHistoryGroup__ItemLabel">
{describeCallHistory(
i18n,
callHistoryGroup.type,
callHistoryGroup.direction,
callHistoryGroup.status
)}
</span>
<span className="ConversationDetails__CallHistoryGroup__ItemTimestamp">
{formatTime(i18n, child.timestamp, Date.now(), false)}
</span>
</li>
);
})}
</ol>
</PanelSection>
)}
2021-05-28 16:15:17 +00:00
<PanelSection>
{!isGroup || canEditGroupInfo ? (
<PanelRow
icon={
<ConversationDetailsIcon
ariaLabel={i18n(
2023-03-30 00:03:25 +00:00
'icu:ConversationDetails--disappearing-messages-label'
)}
icon={IconType.timer}
/>
}
info={
isGroup
2023-03-30 00:03:25 +00:00
? i18n(
'icu:ConversationDetails--disappearing-messages-info--group'
)
: i18n(
2023-03-30 00:03:25 +00:00
'icu:ConversationDetails--disappearing-messages-info--direct'
)
}
2023-03-30 00:03:25 +00:00
label={i18n('icu:ConversationDetails--disappearing-messages-label')}
right={
<DisappearingTimerSelect
i18n={i18n}
2022-11-16 20:18:02 +00:00
value={conversation.expireTimer || DurationInSeconds.ZERO}
onChange={value =>
setDisappearingMessages(conversation.id, value)
}
2021-06-01 20:45:43 +00:00
/>
}
/>
2021-05-28 16:15:17 +00:00
) : null}
2023-08-09 00:53:06 +00:00
{selectedNavTab === NavTab.Chats && (
<PanelRow
icon={
<ConversationDetailsIcon
ariaLabel={i18n('icu:showChatColorEditor')}
icon={IconType.color}
/>
}
label={i18n('icu:showChatColorEditor')}
onClick={() => {
pushPanelForConversation({
type: PanelType.ChatColorEditor,
});
}}
right={
<div
className={`ConversationDetails__chat-color ConversationDetails__chat-color--${conversation.conversationColor}`}
style={{
...getCustomColorStyle(conversation.customColor),
}}
/>
}
/>
)}
{isGroup && (
<PanelRow
icon={
<ConversationDetailsIcon
2023-03-30 00:03:25 +00:00
ariaLabel={i18n('icu:ConversationDetails--notifications')}
icon={IconType.notifications}
/>
}
2023-03-30 00:03:25 +00:00
label={i18n('icu:ConversationDetails--notifications')}
onClick={() =>
pushPanelForConversation({
type: PanelType.NotificationSettings,
})
}
right={
conversation.muteExpiresAt
? getMutedUntilText(conversation.muteExpiresAt, i18n)
: undefined
}
/>
)}
{!isGroup && !conversation.isMe && (
2022-11-18 00:45:19 +00:00
<PanelRow
onClick={() => toggleSafetyNumberModal(conversation.id)}
icon={
<ConversationDetailsIcon
ariaLabel={i18n('icu:ConversationDetails__viewSafetyNumber')}
2022-11-18 00:45:19 +00:00
icon={IconType.verify}
/>
}
label={
<div className="ConversationDetails__safety-number">
{i18n('icu:ConversationDetails__viewSafetyNumber')}
2022-11-18 00:45:19 +00:00
</div>
}
/>
)}
</PanelSection>
{isGroup && (
<ConversationDetailsMembershipList
canAddNewMembers={canAddNewMembers}
conversationId={conversation.id}
2021-11-17 21:11:21 +00:00
getPreferredBadge={getPreferredBadge}
i18n={i18n}
memberships={memberships}
showContactModal={showContactModal}
startAddingNewMembers={() => {
setModalState(ModalState.AddingGroupMembers);
}}
2021-11-02 23:01:13 +00:00
theme={theme}
/>
)}
{isGroup && (
<PanelSection>
{isAdmin || hasGroupLink ? (
<PanelRow
icon={
<ConversationDetailsIcon
2023-03-30 00:03:25 +00:00
ariaLabel={i18n('icu:ConversationDetails--group-link')}
icon={IconType.link}
/>
}
2023-03-30 00:03:25 +00:00
label={i18n('icu:ConversationDetails--group-link')}
onClick={() =>
pushPanelForConversation({
type: PanelType.GroupLinkManagement,
})
}
2023-03-30 00:03:25 +00:00
right={hasGroupLink ? i18n('icu:on') : i18n('icu:off')}
/>
) : null}
<PanelRow
icon={
<ConversationDetailsIcon
2023-03-30 00:03:25 +00:00
ariaLabel={i18n(
'icu:ConversationDetails--requests-and-invites'
)}
icon={IconType.invites}
/>
}
2023-03-30 00:03:25 +00:00
label={i18n('icu:ConversationDetails--requests-and-invites')}
onClick={() =>
pushPanelForConversation({
type: PanelType.GroupInvites,
})
}
right={invitesCount}
/>
{isAdmin ? (
<PanelRow
icon={
<ConversationDetailsIcon
2023-03-30 00:03:25 +00:00
ariaLabel={i18n('icu:permissions')}
icon={IconType.lock}
/>
}
2023-03-30 00:03:25 +00:00
label={i18n('icu:permissions')}
onClick={() =>
pushPanelForConversation({
type: PanelType.GroupPermissions,
})
}
/>
) : null}
</PanelSection>
)}
<ConversationDetailsMediaList
conversation={conversation}
i18n={i18n}
loadRecentMediaItems={loadRecentMediaItems}
showAllMedia={() =>
pushPanelForConversation({
type: PanelType.AllMedia,
})
}
2022-12-10 02:02:22 +00:00
showLightboxWithMedia={showLightboxWithMedia}
/>
{!isGroup && !conversation.isMe && (
<ConversationDetailsGroups
contactId={conversation.id}
i18n={i18n}
groupsInCommon={groupsInCommon}
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
showConversation={showConversation}
/>
)}
{!conversation.isMe && (
<ConversationDetailsActions
2022-12-06 19:03:09 +00:00
acceptConversation={acceptConversation}
blockConversation={blockConversation}
cannotLeaveBecauseYouAreLastAdmin={cannotLeaveBecauseYouAreLastAdmin}
2022-12-06 19:03:09 +00:00
conversationId={conversation.id}
conversationTitle={conversation.title}
i18n={i18n}
isBlocked={Boolean(conversation.isBlocked)}
isGroup={isGroup}
left={Boolean(conversation.left)}
onLeave={() => leaveGroup(conversation.id)}
/>
)}
2021-03-11 21:29:31 +00:00
{modalNode}
</div>
);
2022-11-18 00:45:19 +00:00
}
function ConversationDetailsCallButton({
disabled,
i18n,
onClick,
type,
}: Readonly<{
disabled: boolean;
i18n: LocalizerType;
onClick: () => unknown;
type: 'audio' | 'video';
}>) {
const button = (
<Button
disabled={disabled}
icon={ButtonIconType[type]}
onClick={onClick}
variant={ButtonVariant.Details}
>
2023-03-30 00:03:25 +00:00
{type === 'audio' ? i18n('icu:audio') : i18n('icu:video')}
</Button>
);
if (disabled) {
return (
2023-03-30 00:03:25 +00:00
<Tooltip content={i18n('icu:calling__in-another-call-tooltip')}>
{button}
</Tooltip>
);
}
return button;
}