signal-desktop/ts/components/conversationList/ContactListItem.tsx

296 lines
8.3 KiB
TypeScript
Raw Normal View History

// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react';
2023-04-05 20:48:00 +00:00
import React, { useMemo, useState } from 'react';
import { HEADER_CONTACT_NAME_CLASS_NAME } from './BaseConversationListItem';
import type { ConversationType } from '../../state/ducks/conversations';
2021-11-17 21:11:21 +00:00
import type { BadgeType } from '../../badges/types';
import type { LocalizerType, ThemeType } from '../../types/Util';
import { ContactName } from '../conversation/ContactName';
import { About } from '../conversation/About';
import { ListTile } from '../ListTile';
import { Avatar, AvatarSize } from '../Avatar';
2023-04-05 20:48:00 +00:00
import { ContextMenu } from '../ContextMenu';
import { Intl } from '../Intl';
import { ConfirmationDialog } from '../ConfirmationDialog';
2022-11-09 02:38:19 +00:00
import { isSignalConversation } from '../../util/isSignalConversation';
2023-04-05 20:48:00 +00:00
import { isInSystemContacts } from '../../util/isInSystemContacts';
2021-11-17 21:11:21 +00:00
export type ContactListItemConversationType = Pick<
ConversationType,
| 'about'
| 'acceptedMessageRequest'
| 'avatarPath'
2021-11-17 21:11:21 +00:00
| 'badges'
| 'color'
2023-01-13 00:24:59 +00:00
| 'groupId'
| 'id'
2023-04-05 20:48:00 +00:00
| 'name'
| 'isMe'
| 'phoneNumber'
| 'profileName'
| 'sharedGroupNames'
2023-04-05 20:48:00 +00:00
| 'systemGivenName'
| 'systemFamilyName'
| 'title'
| 'type'
| 'unblurredAvatarPath'
2022-06-17 00:38:28 +00:00
| 'username'
| 'e164'
2023-08-16 20:54:39 +00:00
| 'serviceId'
>;
2021-11-17 21:11:21 +00:00
type PropsDataType = ContactListItemConversationType & {
badge: undefined | BadgeType;
};
type PropsHousekeepingType = {
i18n: LocalizerType;
2021-03-03 20:09:58 +00:00
onClick?: (id: string) => void;
2023-04-05 20:48:00 +00:00
onAudioCall?: (id: string) => void;
onVideoCall?: (id: string) => void;
onRemove?: (id: string) => void;
onBlock?: (id: string) => void;
hasContextMenu: boolean;
2021-11-17 21:11:21 +00:00
theme: ThemeType;
};
type PropsType = PropsDataType & PropsHousekeepingType;
export const ContactListItem: FunctionComponent<PropsType> = React.memo(
2021-08-11 19:29:07 +00:00
function ContactListItem({
about,
acceptedMessageRequest,
avatarPath,
2021-11-17 21:11:21 +00:00
badge,
color,
2023-04-05 20:48:00 +00:00
hasContextMenu,
i18n,
id,
isMe,
2023-04-05 20:48:00 +00:00
name,
onClick,
2023-04-05 20:48:00 +00:00
onAudioCall,
onVideoCall,
onRemove,
onBlock,
phoneNumber,
profileName,
sharedGroupNames,
2023-04-05 20:48:00 +00:00
systemGivenName,
systemFamilyName,
2021-11-17 21:11:21 +00:00
theme,
title,
type,
unblurredAvatarPath,
2023-08-16 20:54:39 +00:00
serviceId,
2021-08-11 19:29:07 +00:00
}) {
2023-04-05 20:48:00 +00:00
const [isConfirmingBlocking, setConfirmingBlocking] = useState(false);
const [isConfirmingRemoving, setConfirmingRemoving] = useState(false);
const menuOptions = useMemo(
() => [
...(onClick
? [
{
icon: 'ContactListItem__context-menu__chat-icon',
label: i18n('icu:ContactListItem__menu__message'),
onClick: () => onClick(id),
},
]
: []),
...(!isMe && onAudioCall
2023-04-05 20:48:00 +00:00
? [
{
icon: 'ContactListItem__context-menu__phone-icon',
label: i18n('icu:ContactListItem__menu__audio-call'),
onClick: () => onAudioCall(id),
},
]
: []),
...(!isMe && onVideoCall
2023-04-05 20:48:00 +00:00
? [
{
icon: 'ContactListItem__context-menu__video-icon',
label: i18n('icu:ContactListItem__menu__video-call'),
onClick: () => onVideoCall(id),
},
]
: []),
...(!isMe && onRemove
2023-04-05 20:48:00 +00:00
? [
{
icon: 'ContactListItem__context-menu__delete-icon',
label: i18n('icu:ContactListItem__menu__remove'),
onClick: () => setConfirmingRemoving(true),
},
]
: []),
...(!isMe && onBlock
2023-04-05 20:48:00 +00:00
? [
{
icon: 'ContactListItem__context-menu__block-icon',
label: i18n('icu:ContactListItem__menu__block'),
onClick: () => setConfirmingBlocking(true),
},
]
: []),
],
[id, i18n, isMe, onClick, onAudioCall, onVideoCall, onRemove, onBlock]
2023-04-05 20:48:00 +00:00
);
const headerName = isMe ? (
2023-03-02 06:57:35 +00:00
<ContactName
isMe={isMe}
module={HEADER_CONTACT_NAME_CLASS_NAME}
2023-03-30 00:03:25 +00:00
title={i18n('icu:noteToSelf')}
2023-03-02 06:57:35 +00:00
/>
) : (
2022-11-09 02:38:19 +00:00
<ContactName
2023-08-16 20:54:39 +00:00
isSignalConversation={isSignalConversation({ id, serviceId })}
2022-11-09 02:38:19 +00:00
module={HEADER_CONTACT_NAME_CLASS_NAME}
title={title}
/>
);
const messageText =
about && !isMe ? <About className="" text={about} /> : undefined;
2023-04-05 20:48:00 +00:00
let trailing: JSX.Element | undefined;
if (hasContextMenu) {
trailing = (
<ContextMenu
i18n={i18n}
menuOptions={menuOptions}
popperOptions={{ placement: 'bottom-start', strategy: 'absolute' }}
moduleClassName="ContactListItem__context-menu"
ariaLabel={i18n('icu:ContactListItem__menu')}
portalToRoot
/>
);
}
let blockConfirmation: JSX.Element | undefined;
let removeConfirmation: JSX.Element | undefined;
if (isConfirmingBlocking) {
blockConfirmation = (
<ConfirmationDialog
dialogName="ContactListItem.blocking"
i18n={i18n}
onClose={() => setConfirmingBlocking(false)}
title={
<Intl
i18n={i18n}
id="icu:MessageRequests--block-direct-confirm-title"
components={{
title: <ContactName key="name" title={title} />,
}}
/>
}
actions={[
{
text: i18n('icu:MessageRequests--block'),
action: () => onBlock?.(id),
style: 'negative',
},
]}
>
{i18n('icu:MessageRequests--block-direct-confirm-body')}
</ConfirmationDialog>
);
}
if (isConfirmingRemoving) {
if (
isInSystemContacts({ type, name, systemGivenName, systemFamilyName })
) {
removeConfirmation = (
<ConfirmationDialog
key="ContactListItem.systemContact"
dialogName="ContactListItem.systemContact"
i18n={i18n}
2023-04-05 20:48:00 +00:00
onClose={() => setConfirmingRemoving(false)}
title={
<Intl
i18n={i18n}
id="icu:ContactListItem__remove-system--title"
components={{
title: <ContactName key="name" title={title} />,
}}
/>
}
cancelText={i18n('icu:Confirmation--confirm')}
>
{i18n('icu:ContactListItem__remove-system--body')}
</ConfirmationDialog>
);
} else {
removeConfirmation = (
<ConfirmationDialog
key="ContactListItem.removing"
dialogName="ContactListItem.removing"
i18n={i18n}
onClose={() => setConfirmingRemoving(false)}
title={
<Intl
i18n={i18n}
id="icu:ContactListItem__remove--title"
components={{
title: <ContactName key="name" title={title} />,
}}
/>
}
actions={[
{
text: i18n('icu:ContactListItem__remove--confirm'),
action: () => onRemove?.(id),
style: 'negative',
},
]}
>
{i18n('icu:ContactListItem__remove--body')}
</ConfirmationDialog>
);
}
}
return (
<>
<ListTile
moduleClassName="ContactListItem"
leading={
<Avatar
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
color={color}
conversationType={type}
noteToSelf={Boolean(isMe)}
i18n={i18n}
isMe={isMe}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
sharedGroupNames={sharedGroupNames}
size={AvatarSize.THIRTY_TWO}
unblurredAvatarPath={unblurredAvatarPath}
// This is here to appease the type checker.
{...(badge ? { badge, theme } : { badge: undefined })}
/>
}
trailing={trailing}
title={headerName}
subtitle={messageText}
subtitleMaxLines={1}
onClick={onClick ? () => onClick(id) : undefined}
/>
{blockConfirmation}
{removeConfirmation}
</>
);
}
);