import type { FC } from '../../lib/teact/teact'; import type React from '../../lib/teact/teact'; import { memo, useRef, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ApiChat, ApiMessage, ApiSticker, ApiTypingStatus, } from '../../api/types'; import type { GlobalState } from '../../global/types'; import type { Signal } from '../../util/signals'; import { MAIN_THREAD_ID } from '../../api/types'; import { type MessageListType, StoryViewerOrigin, type ThreadId } from '../../types'; import { EDITABLE_INPUT_CSS_SELECTOR, MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, } from '../../config'; import { getIsSavedDialog, } from '../../global/helpers'; import { selectChat, selectChatMessage, selectCustomEmoji, selectIsChatWithSelf, selectIsInSelectMode, selectIsRightColumnShown, selectPeer, selectPinnedIds, selectScheduledIds, selectTabState, } from '../../global/selectors'; import { selectThreadInfo, selectThreadLocalStateParam } from '../../global/selectors/threads'; import { IS_TAURI } from '../../util/browser/globalEnvironment'; import { IS_MAC_OS } from '../../util/browser/windowEnvironment'; import buildClassName from '../../util/buildClassName'; import { isUserId } from '../../util/entities/ids'; import useAppLayout from '../../hooks/useAppLayout'; import useConnectionStatus from '../../hooks/useConnectionStatus'; import useLastCallback from '../../hooks/useLastCallback'; import useLongPress from '../../hooks/useLongPress'; import useOldLang from '../../hooks/useOldLang'; import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import useWindowSize from '../../hooks/window/useWindowSize'; import GroupChatInfo from '../common/GroupChatInfo'; import PrivateChatInfo from '../common/PrivateChatInfo'; import UnreadCounter from '../common/UnreadCounter'; import Button from '../ui/Button'; import Transition from '../ui/Transition'; import HeaderActions from './HeaderActions'; import AudioPlayer from './panes/AudioPlayer'; import HeaderPinnedMessage from './panes/HeaderPinnedMessage'; import './MiddleHeader.scss'; const BACK_BUTTON_INACTIVE_TIME = 450; const EMOJI_STATUS_SIZE = 22; const SEARCH_LONGTAP_THRESHOLD = 500; type OwnProps = { chatId: string; threadId: ThreadId; messageListType: MessageListType; isComments?: boolean; isMobile?: boolean; getCurrentPinnedIndex: Signal; getLoadingPinnedId: Signal; onFocusPinnedMessage: (messageId: number) => void; }; type StateProps = { chat?: ApiChat; isSavedDialog?: boolean; typingStatus?: ApiTypingStatus; isSelectModeActive?: boolean; isLeftColumnShown?: boolean; isRightColumnShown?: boolean; audioMessage?: ApiMessage; messagesCount?: number; isChatWithSelf?: boolean; shouldSkipHistoryAnimations?: boolean; currentTransitionKey: number; connectionState?: GlobalState['connectionState']; isSyncing?: boolean; isFetchingDifference?: boolean; emojiStatusSticker?: ApiSticker; emojiStatusSlug?: string; }; const MiddleHeader: FC = ({ chatId, threadId, messageListType, isMobile, typingStatus, isSelectModeActive, isLeftColumnShown, audioMessage, chat, messagesCount, isComments, isChatWithSelf, shouldSkipHistoryAnimations, currentTransitionKey, connectionState, isSyncing, isFetchingDifference, getCurrentPinnedIndex, getLoadingPinnedId, emojiStatusSticker, emojiStatusSlug, isSavedDialog, onFocusPinnedMessage, }) => { const { openThreadWithInfo, openChat, openPreviousChat, toggleLeftColumn, exitMessageSelectMode, openPremiumModal, openStickerSet, updateMiddleSearch, openUniqueGiftBySlug, } = getActions(); const lang = useOldLang(); const isBackButtonActive = useRef(true); const { isDesktop, isTablet } = useAppLayout(); const { width: windowWidth } = useWindowSize(); const isLeftColumnHideable = windowWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN; const shouldShowCloseButton = isTablet && isLeftColumnShown; const componentRef = useRef(); const handleOpenSearch = useLastCallback(() => { updateMiddleSearch({ chatId, threadId, update: {} }); }); const handleOpenChat = useLastCallback((event: React.MouseEvent | React.TouchEvent) => { if ((event.target as Element).closest('.title > .custom-emoji')) return; // Force close My Profile if clicked on Saved Messages header openThreadWithInfo({ chatId, threadId, isOwnProfile: false }); }); const { onMouseDown: handleLongPressMouseDown, onMouseUp: handleLongPressMouseUp, onMouseLeave: handleLongPressMouseLeave, onTouchStart: handleLongPressTouchStart, onTouchEnd: handleLongPressTouchEnd, } = useLongPress({ onStart: handleOpenSearch, onClick: handleOpenChat, threshold: SEARCH_LONGTAP_THRESHOLD, }); const setBackButtonActive = useLastCallback(() => { setTimeout(() => { isBackButtonActive.current = true; }, BACK_BUTTON_INACTIVE_TIME); }); const handleUserStatusClick = useLastCallback(() => { if (emojiStatusSlug) { openUniqueGiftBySlug({ slug: emojiStatusSlug }); return; } openPremiumModal({ fromUserId: chatId }); }); const handleChannelStatusClick = useLastCallback(() => { if (emojiStatusSlug) { openUniqueGiftBySlug({ slug: emojiStatusSlug }); return; } openStickerSet({ stickerSetInfo: emojiStatusSticker!.stickerSetInfo, }); }); const handleBackClick = useLastCallback((e: React.MouseEvent) => { if (!isBackButtonActive.current) return; // Workaround for missing UI when quickly clicking the Back button isBackButtonActive.current = false; if (isMobile) { const messageInput = document.querySelector(EDITABLE_INPUT_CSS_SELECTOR); messageInput?.blur(); } if (isSelectModeActive) { exitMessageSelectMode(); setBackButtonActive(); return; } if (messageListType === 'thread' && currentTransitionKey === 0) { if (!isTablet || shouldShowCloseButton) { e.stopPropagation(); // Stop propagation to prevent chat re-opening on tablets openChat({ id: undefined }, { forceOnHeavyAnimation: true }); } else { toggleLeftColumn(); } setBackButtonActive(); return; } openPreviousChat(); setBackButtonActive(); }); const prevTransitionKey = usePreviousDeprecated(currentTransitionKey); const cleanupExceptionKey = ( prevTransitionKey !== undefined && prevTransitionKey < currentTransitionKey ? prevTransitionKey : undefined ); const isAudioPlayerActive = Boolean(audioMessage); const isAudioPlayerRendering = isDesktop && isAudioPlayerActive; const isPinnedMessagesFullWidth = isAudioPlayerActive || !isDesktop; const { connectionStatusText } = useConnectionStatus(lang, connectionState, isSyncing || isFetchingDifference, true); function renderInfo() { if (messageListType === 'thread') { if (threadId === MAIN_THREAD_ID || isSavedDialog || chat?.isForum) { return renderChatInfo(); } } return ( <> {renderBackButton(currentTransitionKey === 0)}

{messagesCount !== undefined ? ( messageListType === 'thread' ? ( (messagesCount ? lang(isComments ? 'Comments' : 'Replies', messagesCount, 'i') : lang(isComments ? 'CommentsTitle' : 'RepliesTitle'))) : messageListType === 'pinned' ? (lang('PinnedMessagesCount', messagesCount, 'i')) : messageListType === 'scheduled' ? ( isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount, 'i') ) : undefined ) : lang('Loading')}

); } function renderChatInfo() { // TODO Implement count const savedMessagesStatus = isSavedDialog ? lang('SavedMessages') : undefined; const realChatId = isSavedDialog ? String(threadId) : chatId; const displayChatId = chat?.isMonoforum ? chat.linkedMonoforumId! : realChatId; return ( <> {(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, !isSavedDialog)}
{isUserId(displayChatId) ? ( ) : ( )}
); } function renderBackButton(asClose = false, withUnreadCounter = false) { return (
{withUnreadCounter && }
); } return (
{renderInfo()} {!isPinnedMessagesFullWidth && ( )}
{isAudioPlayerRendering && ( )}
); }; export default memo(withGlobal( (global, { chatId, threadId, messageListType, isMobile, }): Complete => { const { isLeftColumnShown, shouldSkipHistoryAnimations, audioPlayer, messageLists, } = selectTabState(global); const chat = selectChat(global, chatId); const peer = selectPeer(global, chatId); const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer; const audioMessage = audioChatId && audioMessageId ? selectChatMessage(global, audioChatId, audioMessageId) : undefined; let messagesCount: number | undefined; if (messageListType === 'pinned') { const pinnedIds = selectPinnedIds(global, chatId, threadId); messagesCount = pinnedIds?.length; } else if (messageListType === 'scheduled') { const scheduledIds = selectScheduledIds(global, chatId, threadId); messagesCount = scheduledIds?.length; } else if (messageListType === 'thread' && threadId !== MAIN_THREAD_ID) { const threadInfo = selectThreadInfo(global, chatId, threadId); messagesCount = threadInfo?.messagesCount || 0; } const typingStatus = selectThreadLocalStateParam(global, chatId, threadId, 'typingStatus'); const emojiStatus = peer?.emojiStatus; const emojiStatusSticker = emojiStatus && selectCustomEmoji(global, emojiStatus.documentId); const emojiStatusSlug = emojiStatus?.type === 'collectible' ? emojiStatus.slug : undefined; const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId); return { typingStatus, isLeftColumnShown, isRightColumnShown: selectIsRightColumnShown(global, isMobile), isSelectModeActive: selectIsInSelectMode(global), audioMessage, chat, messagesCount, isChatWithSelf: selectIsChatWithSelf(global, chatId), shouldSkipHistoryAnimations, currentTransitionKey: Math.max(0, messageLists.length - 1), connectionState: global.connectionState, isSyncing: global.isSyncing, isFetchingDifference: global.isFetchingDifference, emojiStatusSticker, emojiStatusSlug, isSavedDialog, }; }, )(MiddleHeader));