import type { FC } from '../../../lib/teact/teact'; import { memo, useEffect, useMemo } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiChat, ApiChatFolder, ApiDraft, ApiMessage, ApiMessageOutgoingStatus, ApiPeer, ApiTopic, ApiTypeStory, ApiTypingStatus, ApiUser, ApiUserStatus, } from '../../../api/types'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ChatAnimationTypes } from './hooks'; import { MAIN_THREAD_ID } from '../../../api/types'; import { StoryViewerOrigin } from '../../../types'; import { UNMUTE_TIMESTAMP } from '../../../config'; import { groupStatefulContent, isUserOnline, } from '../../../global/helpers'; import { getIsChatMuted } from '../../../global/helpers/notifications'; import { selectCanAnimateInterface, selectChat, selectChatLastMessage, selectChatLastMessageId, selectChatMessage, selectCurrentMessageList, selectDraft, selectIsCurrentUserFrozen, selectIsCurrentUserPremium, selectIsForumPanelClosed, selectIsForumPanelOpen, selectMonoforumChannel, selectNotifyDefaults, selectNotifyException, selectOutgoingStatus, selectPeer, selectPeerStory, selectSender, selectTabState, selectThreadParam, selectTopicFromMessage, selectTopicsInfo, selectUser, selectUserStatus, } from '../../../global/selectors'; import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import { isUserId } from '../../../util/entities/ids'; import { getChatFolderIds } from '../../../util/folderManager'; import { createLocationHash } from '../../../util/routing'; import useSelectorSignal from '../../../hooks/data/useSelectorSignal'; import useAppLayout from '../../../hooks/useAppLayout'; import useChatContextActions from '../../../hooks/useChatContextActions'; import useEnsureMessage from '../../../hooks/useEnsureMessage'; import useFlag from '../../../hooks/useFlag'; import { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated'; import useChatListEntry from './hooks/useChatListEntry'; import Avatar from '../../common/Avatar'; import DeleteChatModal from '../../common/DeleteChatModal'; import FullNameTitle from '../../common/FullNameTitle'; import Icon from '../../common/icons/Icon'; import StarIcon from '../../common/icons/StarIcon'; import LastMessageMeta from '../../common/LastMessageMeta'; import ListItem from '../../ui/ListItem'; import ChatFolderModal from '../ChatFolderModal.async'; import MuteChatModal from '../MuteChatModal.async'; import ChatBadge from './ChatBadge'; import ChatCallStatus from './ChatCallStatus'; import ChatTags from './ChatTags'; import './Chat.scss'; type OwnProps = { chatId: string; folderId?: number; orderDiff: number; animationType: ChatAnimationTypes; isPinned?: boolean; offsetTop?: number; isSavedDialog?: boolean; isPreview?: boolean; previewMessageId?: number; className?: string; observeIntersection?: ObserveFn; onDragEnter?: (chatId: string) => void; withTags?: boolean; }; type StateProps = { chat?: ApiChat; monoforumChannel?: ApiChat; lastMessageStory?: ApiTypeStory; listedTopicIds?: number[]; topics?: Record; isMuted?: boolean; user?: ApiUser; userStatus?: ApiUserStatus; lastMessageSender?: ApiPeer; lastMessageOutgoingStatus?: ApiMessageOutgoingStatus; draft?: ApiDraft; isSelected?: boolean; isSelectedForum?: boolean; isForumPanelOpen?: boolean; canScrollDown?: boolean; canChangeFolder?: boolean; lastMessageTopic?: ApiTopic; typingStatus?: ApiTypingStatus; withInterfaceAnimations?: boolean; lastMessageId?: number; lastMessage?: ApiMessage; currentUserId: string; isSynced?: boolean; isAccountFrozen?: boolean; folderIds?: number[]; orderedIds?: number[]; chatFoldersById?: Record; activeChatFolder?: number; areTagsEnabled?: boolean; }; const Chat: FC = ({ chatId, folderId, orderDiff, animationType, isPinned, listedTopicIds, topics, observeIntersection, chat, monoforumChannel, lastMessageStory, isMuted, user, userStatus, lastMessageSender, lastMessageOutgoingStatus, offsetTop, draft, withInterfaceAnimations, isSelected, isSelectedForum, isForumPanelOpen, canScrollDown, canChangeFolder, lastMessageTopic, typingStatus, lastMessageId, lastMessage, isSavedDialog, currentUserId, isPreview, previewMessageId, className, isSynced, onDragEnter, isAccountFrozen, folderIds, orderedIds, chatFoldersById, activeChatFolder, areTagsEnabled, withTags, }) => { const { openChat, openSavedDialog, toggleChatInfo, focusLastMessage, focusMessage, loadTopics, openForumPanel, closeForumPanel, setShouldCloseRightColumn, reportMessages, openFrozenAccountModal, updateChatMutedState, } = getActions(); const { isMobile } = useAppLayout(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(); const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag(); const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag(); const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag(); const [shouldRenderMuteModal, markRenderMuteModal, unmarkRenderMuteModal] = useFlag(); const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag(); const { isForum, isForumAsMessages, isMonoforum } = chat || {}; useEnsureMessage(isSavedDialog ? currentUserId : chatId, lastMessageId, lastMessage); const shouldRenderTags = areTagsEnabled && withTags && folderIds && folderIds.length > 1; const { renderSubtitle, ref } = useChatListEntry({ chat, chatId, lastMessage, typingStatus, draft, statefulMediaContent: groupStatefulContent({ story: lastMessageStory }), lastMessageTopic, lastMessageSender, observeIntersection, animationType, withInterfaceAnimations, orderDiff, isSavedDialog, isPreview, topics, noForumTitle: shouldRenderTags, }); const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed); const handleClick = useLastCallback(() => { const noForumTopicPanel = isMobile && isForumAsMessages; if (isMobile) { setShouldCloseRightColumn({ value: true }); } if (isPreview) { focusMessage({ chatId, messageId: previewMessageId!, }); return; } if (isSavedDialog) { openSavedDialog({ chatId, noForumTopicPanel: true }, { forceOnHeavyAnimation: true }); if (isMobile) { toggleChatInfo({ force: false }); } return; } if (isForum) { if (isForumPanelOpen) { closeForumPanel(undefined, { forceOnHeavyAnimation: true }); return; } else { if (!noForumTopicPanel) { openForumPanel({ chatId }, { forceOnHeavyAnimation: true }); } if (!isForumAsMessages) return; } } openChat({ id: chatId, noForumTopicPanel, shouldReplaceHistory: true }, { forceOnHeavyAnimation: true }); if (isSelected && canScrollDown) { focusLastMessage(); } }); const handleDragEnter = useLastCallback((e) => { e.preventDefault(); onDragEnter?.(chatId); }); const handleDelete = useLastCallback(() => { if (isAccountFrozen) { openFrozenAccountModal(); return; } markRenderDeleteModal(); openDeleteModal(); }); const handleMute = useLastCallback(() => { if (isAccountFrozen) { openFrozenAccountModal(); return; } markRenderMuteModal(); openMuteModal(); }); const handleUnmute = useLastCallback(() => { if (isAccountFrozen) { openFrozenAccountModal(); return; } updateChatMutedState({ chatId, mutedUntil: UNMUTE_TIMESTAMP }); }); const handleChatFolderChange = useLastCallback(() => { markRenderChatFolderModal(); openChatFolderModal(); }); const handleReport = useLastCallback(() => { if (isAccountFrozen) { openFrozenAccountModal(); return; } if (!chat) return; reportMessages({ chatId: chat.id, messageIds: [] }); }); const contextActions = useChatContextActions({ chat, user, handleDelete, handleMute, handleUnmute, handleChatFolderChange, handleReport, folderId, isPinned, isMuted, canChangeFolder, isSavedDialog, currentUserId, isPreview, topics, }); const isIntersecting = useIsIntersecting(ref, chat ? observeIntersection : undefined); // Load the forum topics to display unread count badge useEffect(() => { if (isIntersecting && isForum && isSynced && listedTopicIds === undefined) { loadTopics({ chatId }); } }, [chatId, listedTopicIds, isSynced, isForum, isIntersecting]); const isOnline = user && userStatus && isUserOnline(user, userStatus); const { hasShownClass: isAvatarOnlineShown } = useShowTransitionDeprecated(isOnline); const href = useMemo(() => { if (!IS_OPEN_IN_NEW_TAB_SUPPORTED) return undefined; if (isSavedDialog) { return `#${createLocationHash(currentUserId, 'thread', chatId)}`; } return `#${createLocationHash(chatId, 'thread', MAIN_THREAD_ID)}`; }, [chatId, currentUserId, isSavedDialog]); if (!chat) { return undefined; } const peer = user || chat; const chatClassName = buildClassName( 'Chat chat-item-clickable', isUserId(chatId) ? 'private' : 'group', isForum && 'forum', isSelected && 'selected', isSelectedForum && 'selected-forum', isPreview && 'standalone', shouldRenderTags && 'chat-item-with-tags', className, ); return (
{!isAvatarOnlineShown && Boolean(chat.subscriptionUntil) && ( )}
{chat.isCallActive && chat.isCallNotEmpty && ( )}
{isMuted && !isSavedDialog && }
{lastMessage && ( )}
{renderSubtitle()} {!isPreview && ( )}
{shouldRenderTags && ( )}
{shouldRenderDeleteModal && ( )} {shouldRenderMuteModal && ( )} {shouldRenderChatFolderModal && ( )} ); }; export default memo(withGlobal( (global, { chatId, isSavedDialog, isPreview, previewMessageId, }): Complete => { const chat = selectChat(global, chatId); const user = selectUser(global, chatId); if (!chat) { return { currentUserId: global.currentUserId!, } as Complete; } const folderIds = getChatFolderIds(chatId); const { areTagsEnabled } = global.chatFolders; const isPremium = selectIsCurrentUserPremium(global); const lastMessageId = previewMessageId || selectChatLastMessageId(global, chatId, isSavedDialog ? 'saved' : 'all'); const lastMessage = previewMessageId ? selectChatMessage(global, chatId, previewMessageId) : selectChatLastMessage(global, chatId, isSavedDialog ? 'saved' : 'all'); const { isOutgoing, forwardInfo } = lastMessage || {}; const savedDialogSender = isSavedDialog && forwardInfo?.fromId ? selectPeer(global, forwardInfo.fromId) : undefined; const messageSender = lastMessage ? selectSender(global, lastMessage) : undefined; const lastMessageSender = savedDialogSender || messageSender; const { chatId: currentChatId, threadId: currentThreadId, type: messageListType, } = selectCurrentMessageList(global) || {}; const isSelected = !isPreview && chatId === currentChatId && (isSavedDialog ? chatId === currentThreadId : currentThreadId === MAIN_THREAD_ID); const isSelectedForum = (chat.isForum && chatId === currentChatId) || chatId === selectTabState(global).forumPanelChatId; const userStatus = selectUserStatus(global, chatId); const lastMessageTopic = lastMessage && selectTopicFromMessage(global, lastMessage); const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus'); const topicsInfo = selectTopicsInfo(global, chatId); const storyData = lastMessage?.content.storyData; const lastMessageStory = storyData && selectPeerStory(global, storyData.peerId, storyData.id); const isAccountFrozen = selectIsCurrentUserFrozen(global); const monoforumChannel = selectMonoforumChannel(global, chatId); const activeChatFolder = selectTabState(global).activeChatFolder; return { chat, isMuted: getIsChatMuted(chat, selectNotifyDefaults(global), selectNotifyException(global, chat.id)), lastMessageSender, draft: selectDraft(global, chatId, MAIN_THREAD_ID), isSelected, isSelectedForum, isForumPanelOpen: selectIsForumPanelOpen(global), canScrollDown: isSelected && messageListType === 'thread', canChangeFolder: (global.chatFolders.orderedIds?.length || 0) > 1, lastMessageOutgoingStatus: isOutgoing && lastMessage ? selectOutgoingStatus(global, lastMessage) : undefined, user, userStatus, lastMessageTopic, typingStatus, withInterfaceAnimations: selectCanAnimateInterface(global), lastMessage, lastMessageId, currentUserId: global.currentUserId!, listedTopicIds: topicsInfo?.listedTopicIds, topics: topicsInfo?.topicsById, isSynced: global.isSynced, lastMessageStory, isAccountFrozen, monoforumChannel, folderIds, orderedIds: global.chatFolders.orderedIds, activeChatFolder, chatFoldersById: global.chatFolders.byId, areTagsEnabled: areTagsEnabled && isPremium, }; }, )(Chat));