import React, { memo, useEffect } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ApiChat, ApiFormattedText, ApiMessage, ApiMessageOutgoingStatus, ApiTopic, ApiTypingStatus, ApiUser, ApiUserStatus, } from '../../../api/types'; import type { ChatAnimationTypes } from './hooks'; import { MAIN_THREAD_ID } from '../../../api/types'; import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment'; import { getMessageAction, getPrivateChatUserId, isUserId, selectIsChatMuted, } from '../../../global/helpers'; import { selectCanAnimateInterface, selectChat, selectChatMessage, selectCurrentMessageList, selectDraft, selectNotifyExceptions, selectNotifySettings, selectOutgoingStatus, selectTabState, selectThreadParam, selectTopicFromMessage, selectUser, selectUserStatus, } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { createLocationHash } from '../../../util/routing'; import useLastCallback from '../../../hooks/useLastCallback'; import useChatContextActions from '../../../hooks/useChatContextActions'; import useFlag from '../../../hooks/useFlag'; import useChatListEntry from './hooks/useChatListEntry'; import { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import useAppLayout from '../../../hooks/useAppLayout'; import ListItem from '../../ui/ListItem'; import Avatar from '../../common/Avatar'; import LastMessageMeta from '../../common/LastMessageMeta'; import DeleteChatModal from '../../common/DeleteChatModal'; import ReportModal from '../../common/ReportModal'; import FullNameTitle from '../../common/FullNameTitle'; import ChatFolderModal from '../ChatFolderModal.async'; import MuteChatModal from '../MuteChatModal.async'; import ChatCallStatus from './ChatCallStatus'; import ChatBadge from './ChatBadge'; import AvatarBadge from './AvatarBadge'; import './Chat.scss'; type OwnProps = { chatId: string; folderId?: number; orderDiff: number; animationType: ChatAnimationTypes; isPinned?: boolean; offsetTop: number; observeIntersection?: ObserveFn; onDragEnter?: (chatId: string) => void; }; type StateProps = { chat?: ApiChat; isMuted?: boolean; user?: ApiUser; userStatus?: ApiUserStatus; actionTargetUserIds?: string[]; actionTargetMessage?: ApiMessage; actionTargetChatId?: string; lastMessageSender?: ApiUser | ApiChat; lastMessageOutgoingStatus?: ApiMessageOutgoingStatus; draft?: ApiFormattedText; isSelected?: boolean; isSelectedForum?: boolean; canScrollDown?: boolean; canChangeFolder?: boolean; lastMessageTopic?: ApiTopic; typingStatus?: ApiTypingStatus; withInterfaceAnimations?: boolean; }; const Chat: FC = ({ chatId, folderId, orderDiff, animationType, isPinned, observeIntersection, chat, isMuted, user, userStatus, actionTargetUserIds, lastMessageSender, lastMessageOutgoingStatus, actionTargetMessage, actionTargetChatId, offsetTop, draft, withInterfaceAnimations, isSelected, isSelectedForum, canScrollDown, canChangeFolder, lastMessageTopic, typingStatus, onDragEnter, }) => { const { openChat, focusLastMessage, loadTopics, openForumPanel, closeForumPanel, } = getActions(); const { isMobile } = useAppLayout(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(); const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag(); const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag(); const [isReportModalOpen, openReportModal, closeReportModal] = useFlag(); const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag(); const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag(); const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag(); const [shouldRenderReportModal, markRenderReportModal, unmarkRenderReportModal] = useFlag(); const { lastMessage, isForum } = chat || {}; const { renderSubtitle, ref } = useChatListEntry({ chat, chatId, lastMessage, typingStatus, draft, actionTargetMessage, actionTargetUserIds, actionTargetChatId, lastMessageTopic, lastMessageSender, observeIntersection, animationType, withInterfaceAnimations, orderDiff, }); const handleClick = useLastCallback(() => { if (isForum) { if (isSelectedForum) { closeForumPanel(undefined, { forceOnHeavyAnimation: true }); } else { openForumPanel({ chatId }, { forceOnHeavyAnimation: true }); } return; } openChat({ id: chatId, shouldReplaceHistory: true }, { forceOnHeavyAnimation: true }); if (isSelected && canScrollDown) { focusLastMessage(); } }); const handleDragEnter = useLastCallback((e) => { e.preventDefault(); onDragEnter?.(chatId); }); const handleDelete = useLastCallback(() => { markRenderDeleteModal(); openDeleteModal(); }); const handleMute = useLastCallback(() => { markRenderMuteModal(); openMuteModal(); }); const handleChatFolderChange = useLastCallback(() => { markRenderChatFolderModal(); openChatFolderModal(); }); const handleReport = useLastCallback(() => { markRenderReportModal(); openReportModal(); }); const contextActions = useChatContextActions({ chat, user, handleDelete, handleMute, handleChatFolderChange, handleReport, folderId, isPinned, isMuted, canChangeFolder, }); const isIntersecting = useIsIntersecting(ref, chat ? observeIntersection : undefined); // Load the forum topics to display unread count badge useEffect(() => { if (isIntersecting && isForum && chat && chat.listedTopicIds === undefined) { loadTopics({ chatId }); } }, [chat, chatId, isForum, isIntersecting]); if (!chat) { return undefined; } const className = buildClassName( 'Chat chat-item-clickable', isUserId(chatId) ? 'private' : 'group', isForum && 'forum', isSelected && 'selected', isSelectedForum && 'selected-forum', ); return (
{chat.isCallActive && chat.isCallNotEmpty && ( )}
{isMuted && }
{chat.lastMessage && ( )}
{renderSubtitle()}
{shouldRenderDeleteModal && ( )} {shouldRenderMuteModal && ( )} {shouldRenderChatFolderModal && ( )} {shouldRenderReportModal && ( )} ); }; export default memo(withGlobal( (global, { chatId }): StateProps => { const chat = selectChat(global, chatId); if (!chat) { return {}; } const { senderId, replyToMessageId, isOutgoing } = chat.lastMessage || {}; const lastMessageSender = senderId ? (selectUser(global, senderId) || selectChat(global, senderId)) : undefined; const lastMessageAction = chat.lastMessage ? getMessageAction(chat.lastMessage) : undefined; const actionTargetMessage = lastMessageAction && replyToMessageId ? selectChatMessage(global, chat.id, replyToMessageId) : undefined; const { targetUserIds: actionTargetUserIds, targetChatId: actionTargetChatId } = lastMessageAction || {}; const privateChatUserId = getPrivateChatUserId(chat); const { chatId: currentChatId, threadId: currentThreadId, type: messageListType, } = selectCurrentMessageList(global) || {}; const isSelected = chatId === currentChatId && currentThreadId === MAIN_THREAD_ID; const isSelectedForum = (chat.isForum && chatId === currentChatId) || chatId === selectTabState(global).forumPanelChatId; const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined; const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined; const lastMessageTopic = chat.lastMessage && selectTopicFromMessage(global, chat.lastMessage); const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus'); return { chat, isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)), lastMessageSender, actionTargetUserIds, actionTargetChatId, actionTargetMessage, draft: selectDraft(global, chatId, MAIN_THREAD_ID), isSelected, isSelectedForum, canScrollDown: isSelected && messageListType === 'thread', canChangeFolder: (global.chatFolders.orderedIds?.length || 0) > 1, ...(isOutgoing && chat.lastMessage && { lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage), }), user, userStatus, lastMessageTopic, typingStatus, withInterfaceAnimations: selectCanAnimateInterface(global), }; }, )(Chat));