// Copyright 2018 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { FunctionComponent, ReactNode } from 'react'; import React, { useCallback } from 'react'; import classNames from 'classnames'; import { BaseConversationListItem, HEADER_NAME_CLASS_NAME, HEADER_CONTACT_NAME_CLASS_NAME, MESSAGE_TEXT_CLASS_NAME, } from './BaseConversationListItem'; import { MessageBody } from '../conversation/MessageBody'; import { ContactName } from '../conversation/ContactName'; import { TypingAnimation } from '../conversation/TypingAnimation'; import type { LocalizerType, ThemeType } from '../../types/Util'; import type { ConversationType } from '../../state/ducks/conversations'; import type { BadgeType } from '../../badges/types'; import { isSignalConversation } from '../../util/isSignalConversation'; import { RenderLocation } from '../conversation/MessageTextRenderer'; const EMPTY_OBJECT = Object.freeze(Object.create(null)); const MESSAGE_STATUS_ICON_CLASS_NAME = `${MESSAGE_TEXT_CLASS_NAME}__status-icon`; export const MessageStatuses = [ 'sending', 'sent', 'delivered', 'read', 'paused', 'error', 'partial-sent', ] as const; export type MessageStatusType = typeof MessageStatuses[number]; export type PropsData = Pick< ConversationType, | 'acceptedMessageRequest' | 'avatarPath' | 'badges' | 'color' | 'draftPreview' | 'groupId' | 'id' | 'isMe' // NOTE: Passed for CI, not used for rendering | 'isPinned' | 'isSelected' | 'lastMessage' | 'lastUpdated' | 'markedUnread' | 'muteExpiresAt' | 'phoneNumber' | 'profileName' | 'removalStage' | 'sharedGroupNames' | 'shouldShowDraft' | 'title' | 'type' | 'typingContactId' | 'unblurredAvatarPath' | 'unreadCount' | 'unreadMentionsCount' | 'serviceId' > & { badge?: BadgeType; }; type PropsHousekeeping = { buttonAriaLabel?: string; i18n: LocalizerType; onClick: (id: string) => void; theme: ThemeType; }; export type Props = PropsData & PropsHousekeeping; export const ConversationListItem: FunctionComponent = React.memo( function ConversationListItem({ acceptedMessageRequest, avatarPath, badge, buttonAriaLabel, color, draftPreview, groupId, i18n, id, isMe, isSelected, lastMessage, lastUpdated, markedUnread, muteExpiresAt, onClick, phoneNumber, profileName, removalStage, sharedGroupNames, shouldShowDraft, theme, title, type, typingContactId, unblurredAvatarPath, unreadCount, unreadMentionsCount, serviceId, }) { const isMuted = Boolean(muteExpiresAt && Date.now() < muteExpiresAt); const headerName = ( <> {isMe ? ( ) : ( )} {isMuted &&
} ); let messageText: ReactNode = null; let messageStatusIcon: ReactNode = null; if (!acceptedMessageRequest && removalStage !== 'justNotification') { messageText = ( {i18n('icu:ConversationListItem--message-request')} ); } else if (typingContactId) { messageText = ; } else if (shouldShowDraft && draftPreview) { messageText = ( <> {i18n('icu:ConversationListItem--draft-prefix')} ); } else if (lastMessage?.deletedForEveryone) { messageText = ( {i18n('icu:message--deletedForEveryone')} ); } else if (lastMessage) { messageText = ( ); if (lastMessage.status) { messageStatusIcon = (
); } } const onClickItem = useCallback(() => onClick(id), [onClick, id]); return ( ); } );