import React, { FC, useEffect, useState, memo, useMemo, useCallback, } from '../../lib/teact/teact'; import { withGlobal } from '../../lib/teact/teactn'; import { MAIN_THREAD_ID } from '../../api/types'; import { GlobalActions, MessageListType } from '../../global/types'; import { ThemeKey } from '../../types'; import { MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, MOBILE_SCREEN_MAX_WIDTH, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN, SAFE_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN, SAFE_SCREEN_WIDTH_FOR_CHAT_INFO, CONTENT_TYPES_FOR_QUICK_UPLOAD, ANIMATION_LEVEL_MAX, ANIMATION_END_DELAY, DARK_THEME_BG_COLOR, LIGHT_THEME_BG_COLOR, } from '../../config'; import { IS_MOBILE_SCREEN, IS_TOUCH_ENV, MASK_IMAGE_DISABLED } from '../../util/environment'; import { DropAreaState } from './composer/DropArea'; import { selectChat, selectCurrentMessageList, selectCurrentTextSearch, selectIsChatBotNotStarted, selectIsInSelectMode, selectIsRightColumnShown, selectPinnedIds, } from '../../modules/selectors'; import { getCanPostInChat, getMessageSendingRestrictionReason, isChatPrivate } from '../../modules/helpers'; import captureEscKeyListener from '../../util/captureEscKeyListener'; import { pick } from '../../util/iteratees'; import buildClassName from '../../util/buildClassName'; import useCustomBackground from '../../hooks/useCustomBackground'; import useWindowSize from '../../hooks/useWindowSize'; import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms'; import useLang from '../../hooks/useLang'; import Transition from '../ui/Transition'; import MiddleHeader from './MiddleHeader'; import MessageList from './MessageList'; import ScrollDownButton from './ScrollDownButton'; import Composer from './composer/Composer'; import Button from '../ui/Button'; import MobileSearch from './MobileSearch.async'; import MessageSelectToolbar from './MessageSelectToolbar.async'; import UnpinAllMessagesModal from '../common/UnpinAllMessagesModal.async'; import './MiddleColumn.scss'; type StateProps = { chatId?: number; threadId?: number; messageListType?: MessageListType; isPrivate?: boolean; isPinnedMessageList?: boolean; canPost?: boolean; messageSendingRestrictionReason?: string; hasPinnedOrAudioMessage?: boolean; pinnedMessagesCount?: number; theme: ThemeKey; customBackground?: string; backgroundColor?: string; patternColor?: string; isRightColumnShown?: boolean; isBackgroundBlurred?: boolean; isMobileSearchActive?: boolean; isSelectModeActive?: boolean; animationLevel?: number; }; type DispatchProps = Pick; const CLOSE_ANIMATION_DURATION = IS_MOBILE_SCREEN ? 450 + ANIMATION_END_DELAY : undefined; function canBeQuicklyUploaded(item: DataTransferItem) { return item.kind === 'file' && item.type && CONTENT_TYPES_FOR_QUICK_UPLOAD.includes(item.type); } const MiddleColumn: FC = ({ chatId, threadId, messageListType, isPrivate, isPinnedMessageList, canPost, messageSendingRestrictionReason, hasPinnedOrAudioMessage, pinnedMessagesCount, customBackground, theme, backgroundColor, patternColor, isRightColumnShown, isBackgroundBlurred, isMobileSearchActive, isSelectModeActive, animationLevel, openChat, unpinAllMessages, loadUser, }) => { const { width: windowWidth } = useWindowSize(); const [dropAreaState, setDropAreaState] = useState(DropAreaState.None); const [isFabShown, setIsFabShown] = useState(); const [isNotchShown, setIsNotchShown] = useState(); const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false); const hasTools = hasPinnedOrAudioMessage && ( windowWidth < MOBILE_SCREEN_MAX_WIDTH || ( isRightColumnShown && windowWidth > MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN && windowWidth < SAFE_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN ) || ( windowWidth >= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN && windowWidth < SAFE_SCREEN_WIDTH_FOR_CHAT_INFO ) ); const renderingChatId = usePrevDuringAnimation(chatId, CLOSE_ANIMATION_DURATION); const renderingThreadId = usePrevDuringAnimation(threadId, CLOSE_ANIMATION_DURATION); const renderingMessageListType = usePrevDuringAnimation(messageListType, CLOSE_ANIMATION_DURATION); const renderingCanPost = usePrevDuringAnimation(canPost, CLOSE_ANIMATION_DURATION); const renderingHasTools = usePrevDuringAnimation(hasTools, CLOSE_ANIMATION_DURATION); const renderingIsFabShown = usePrevDuringAnimation(isFabShown, CLOSE_ANIMATION_DURATION); useEffect(() => { return chatId ? captureEscKeyListener(() => { openChat({ id: undefined }); }) : undefined; }, [chatId, openChat]); useEffect(() => { setDropAreaState(DropAreaState.None); setIsFabShown(undefined); setIsNotchShown(undefined); }, [chatId]); useEffect(() => { if (isPrivate) { loadUser({ userId: chatId }); } }, [chatId, isPrivate, loadUser]); const handleDragEnter = useCallback((e: React.DragEvent) => { if (IS_TOUCH_ENV) { return; } const { items } = e.dataTransfer || {}; const shouldDrawQuick = items && Array.from(items).every(canBeQuicklyUploaded); setDropAreaState(shouldDrawQuick ? DropAreaState.QuickFile : DropAreaState.Document); }, []); const handleHideDropArea = useCallback(() => { setDropAreaState(DropAreaState.None); }, []); const handleOpenUnpinModal = useCallback(() => { setIsUnpinModalOpen(true); }, []); const closeUnpinModal = useCallback(() => { setIsUnpinModalOpen(false); }, []); const handleUnpinAllMessages = useCallback(() => { unpinAllMessages({ chatId }); closeUnpinModal(); openChat({ id: chatId }); }, [unpinAllMessages, openChat, closeUnpinModal, chatId]); const customBackgroundValue = useCustomBackground(theme, customBackground); const className = buildClassName( renderingHasTools && 'has-header-tools', customBackground && 'custom-bg-image', backgroundColor && 'custom-bg-color', customBackground && isBackgroundBlurred && 'blurred', MASK_IMAGE_DISABLED ? 'mask-image-disabled' : 'mask-image-enabled', ); const messagingDisabledClassName = buildClassName( 'messaging-disabled', !isSelectModeActive && 'shown', ); // CSS Variables calculation doesn't work properly with transforms, so we calculate transform values in JS const { composerHiddenScale, toolbarHiddenScale, composerTranslateX, toolbarTranslateX, unpinHiddenScale, toolbarForUnpinHiddenScale, } = useMemo( () => calculateMiddleFooterTransforms(windowWidth, renderingCanPost), [renderingCanPost, windowWidth], ); const lang = useLang(); const footerClassName = buildClassName( 'middle-column-footer', !renderingCanPost && 'no-composer', renderingCanPost && isNotchShown && !isSelectModeActive && 'with-notch', ); return (
{renderingChatId && renderingThreadId && ( <>
{() => ( <>
{renderingCanPost && ( )} {isPinnedMessageList && (
)} {!isPinnedMessageList && !renderingCanPost && messageSendingRestrictionReason && (
{messageSendingRestrictionReason}
)}
)}
{IS_MOBILE_SCREEN && } )} {chatId && ( )}
); }; export default memo(withGlobal( (global): StateProps => { const { theme } = global.settings.byKey; const { isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor, } = global.settings.themes[theme] || {}; const currentMessageList = selectCurrentMessageList(global); const { chats: { listIds } } = global; const state: StateProps = { theme, customBackground, backgroundColor, patternColor, isRightColumnShown: selectIsRightColumnShown(global), isBackgroundBlurred, isMobileSearchActive: Boolean(IS_MOBILE_SCREEN && selectCurrentTextSearch(global)), isSelectModeActive: selectIsInSelectMode(global), animationLevel: global.settings.byKey.animationLevel, }; if (!currentMessageList || !listIds.active) { return state; } const { chatId, threadId, type: messageListType } = currentMessageList; const chat = selectChat(global, chatId); const pinnedIds = selectPinnedIds(global, chatId); const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer; const canPost = chat && getCanPostInChat(chat, threadId); const isBotNotStarted = selectIsChatBotNotStarted(global, chatId); const isPinnedMessageList = messageListType === 'pinned'; return { ...state, chatId, threadId, messageListType, isPrivate: isChatPrivate(chatId), canPost: !isPinnedMessageList && (!chat || canPost) && (!isBotNotStarted || IS_MOBILE_SCREEN), isPinnedMessageList, messageSendingRestrictionReason: chat && getMessageSendingRestrictionReason(chat), hasPinnedOrAudioMessage: ( threadId !== MAIN_THREAD_ID || Boolean(pinnedIds && pinnedIds.length) || Boolean(audioChatId && audioMessageId) ), pinnedMessagesCount: pinnedIds ? pinnedIds.length : 0, }; }, (setGlobal, actions): DispatchProps => pick(actions, [ 'openChat', 'unpinAllMessages', 'loadUser', ]), )(MiddleColumn));