295 lines
8.3 KiB
TypeScript
295 lines
8.3 KiB
TypeScript
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import type { FunctionComponent } from 'react';
|
|
import React, { useMemo, useState } from 'react';
|
|
|
|
import { HEADER_CONTACT_NAME_CLASS_NAME } from './BaseConversationListItem';
|
|
import type { ConversationType } from '../../state/ducks/conversations';
|
|
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';
|
|
import { ContextMenu } from '../ContextMenu';
|
|
import { Intl } from '../Intl';
|
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
|
import { isSignalConversation } from '../../util/isSignalConversation';
|
|
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
|
|
|
export type ContactListItemConversationType = Pick<
|
|
ConversationType,
|
|
| 'about'
|
|
| 'acceptedMessageRequest'
|
|
| 'avatarPath'
|
|
| 'badges'
|
|
| 'color'
|
|
| 'groupId'
|
|
| 'id'
|
|
| 'name'
|
|
| 'isMe'
|
|
| 'phoneNumber'
|
|
| 'profileName'
|
|
| 'sharedGroupNames'
|
|
| 'systemGivenName'
|
|
| 'systemFamilyName'
|
|
| 'title'
|
|
| 'type'
|
|
| 'unblurredAvatarPath'
|
|
| 'username'
|
|
| 'e164'
|
|
| 'serviceId'
|
|
>;
|
|
|
|
type PropsDataType = ContactListItemConversationType & {
|
|
badge: undefined | BadgeType;
|
|
};
|
|
|
|
type PropsHousekeepingType = {
|
|
i18n: LocalizerType;
|
|
onClick?: (id: string) => void;
|
|
onAudioCall?: (id: string) => void;
|
|
onVideoCall?: (id: string) => void;
|
|
onRemove?: (id: string) => void;
|
|
onBlock?: (id: string) => void;
|
|
hasContextMenu: boolean;
|
|
theme: ThemeType;
|
|
};
|
|
|
|
type PropsType = PropsDataType & PropsHousekeepingType;
|
|
|
|
export const ContactListItem: FunctionComponent<PropsType> = React.memo(
|
|
function ContactListItem({
|
|
about,
|
|
acceptedMessageRequest,
|
|
avatarPath,
|
|
badge,
|
|
color,
|
|
hasContextMenu,
|
|
i18n,
|
|
id,
|
|
isMe,
|
|
name,
|
|
onClick,
|
|
onAudioCall,
|
|
onVideoCall,
|
|
onRemove,
|
|
onBlock,
|
|
phoneNumber,
|
|
profileName,
|
|
sharedGroupNames,
|
|
systemGivenName,
|
|
systemFamilyName,
|
|
theme,
|
|
title,
|
|
type,
|
|
unblurredAvatarPath,
|
|
serviceId,
|
|
}) {
|
|
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
|
|
? [
|
|
{
|
|
icon: 'ContactListItem__context-menu__phone-icon',
|
|
label: i18n('icu:ContactListItem__menu__audio-call'),
|
|
onClick: () => onAudioCall(id),
|
|
},
|
|
]
|
|
: []),
|
|
...(!isMe && onVideoCall
|
|
? [
|
|
{
|
|
icon: 'ContactListItem__context-menu__video-icon',
|
|
label: i18n('icu:ContactListItem__menu__video-call'),
|
|
onClick: () => onVideoCall(id),
|
|
},
|
|
]
|
|
: []),
|
|
...(!isMe && onRemove
|
|
? [
|
|
{
|
|
icon: 'ContactListItem__context-menu__delete-icon',
|
|
label: i18n('icu:ContactListItem__menu__remove'),
|
|
onClick: () => setConfirmingRemoving(true),
|
|
},
|
|
]
|
|
: []),
|
|
...(!isMe && onBlock
|
|
? [
|
|
{
|
|
icon: 'ContactListItem__context-menu__block-icon',
|
|
label: i18n('icu:ContactListItem__menu__block'),
|
|
onClick: () => setConfirmingBlocking(true),
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
[id, i18n, isMe, onClick, onAudioCall, onVideoCall, onRemove, onBlock]
|
|
);
|
|
|
|
const headerName = isMe ? (
|
|
<ContactName
|
|
isMe={isMe}
|
|
module={HEADER_CONTACT_NAME_CLASS_NAME}
|
|
title={i18n('icu:noteToSelf')}
|
|
/>
|
|
) : (
|
|
<ContactName
|
|
isSignalConversation={isSignalConversation({ id, serviceId })}
|
|
module={HEADER_CONTACT_NAME_CLASS_NAME}
|
|
title={title}
|
|
/>
|
|
);
|
|
|
|
const messageText =
|
|
about && !isMe ? <About className="" text={about} /> : undefined;
|
|
|
|
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}
|
|
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}
|
|
</>
|
|
);
|
|
}
|
|
);
|