diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 035a49316..466ee0b76 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -1,6 +1,6 @@ import type { FC } from '../../lib/teact/teact'; import React, { - memo, useCallback, useEffect, useMemo, useRef, useState, + memo, useCallback, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../global'; @@ -55,17 +55,17 @@ import resetScroll, { patchChromiumScroll } from '../../util/resetScroll'; import fastSmoothScroll, { isAnimatingScroll } from '../../util/fastSmoothScroll'; import renderText from '../common/helpers/renderText'; +import { useStateRef } from '../../hooks/useStateRef'; import useSyncEffect from '../../hooks/useSyncEffect'; import useStickyDates from './hooks/useStickyDates'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import useLang from '../../hooks/useLang'; -import useWindowSize from '../../hooks/useWindowSize'; import useInterval from '../../hooks/useInterval'; import useNativeCopySelectedMessages from '../../hooks/useNativeCopySelectedMessages'; import useMedia from '../../hooks/useMedia'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; -import useResizeObserver from '../../hooks/useResizeObserver'; +import useContainerHeight from './hooks/useContainerHeight'; import Loading from '../ui/Loading'; import MessageListContent from './MessageListContent'; @@ -182,10 +182,11 @@ const MessageList: FC = ({ // We update local cached `scrollOffsetRef` when opening chat. // Then we update global version every second on scrolling. - const scrollOffsetRef = useRef((type === 'thread' - && selectScrollOffset(getGlobal(), chatId, threadId)) + const scrollOffsetRef = useRef( + (type === 'thread' && selectScrollOffset(getGlobal(), chatId, threadId)) || selectLastScrollOffset(getGlobal(), chatId, threadId) - || 0); + || 0, + ); const anchorIdRef = useRef(); const anchorTopRef = useRef(); @@ -196,8 +197,6 @@ const MessageList: FC = ({ const isScrollTopJustUpdatedRef = useRef(false); const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage)); - const [containerHeight, setContainerHeight] = useState(); - const botInfoPhotoUrl = useMedia(botInfo?.photo ? getBotCoverMediaHash(botInfo.photo) : undefined); const botInfoGifUrl = useMedia(botInfo?.gif ? getDocumentMediaHash(botInfo.gif) : undefined); const botInfoDimensions = botInfo?.photo ? getPhotoFullDimensions(botInfo.photo) : botInfo?.gif @@ -247,8 +246,11 @@ const MessageList: FC = ({ return undefined; } - const viewportIds = threadTopMessageId && threadFirstMessageId !== threadTopMessageId + const viewportIds = ( + threadTopMessageId + && threadFirstMessageId !== threadTopMessageId && (!messageIds[0] || threadFirstMessageId === messageIds[0]) + ) ? [threadTopMessageId, ...messageIds] : messageIds; @@ -342,18 +344,7 @@ const MessageList: FC = ({ updateStickyDates, hasTools, getForceNextPinnedInHeader, onPinnedIntersectionChange, type, chatId, threadId, ]); - // Container resize observer (caused by Composer reply/webpage panels) - const handleResize = useCallback((entry: ResizeObserverEntry) => { - setContainerHeight(entry.contentRect.height); - }, []); - useResizeObserver(containerRef, handleResize); - - // Memorize height for scroll animation - const { height: windowHeight } = useWindowSize(); - - useEffect(() => { - containerRef.current!.dataset.normalHeight = String(containerRef.current!.offsetHeight); - }, [windowHeight, canPost]); + const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive); // Initial message loading useEffect(() => { @@ -377,8 +368,7 @@ const MessageList: FC = ({ } }, [isChatLoaded, messageIds, loadMoreAround, focusingId, isRestricted]); - // Remember scroll position before repositioning it - useSyncEffect(() => { + const rememberScrollPositionRef = useStateRef(() => { if (!messageIds || !listItemElementsRef.current) { return; } @@ -395,13 +385,25 @@ const MessageList: FC = ({ anchorIdRef.current = anchor.id; anchorTopRef.current = anchor.getBoundingClientRect().top; - // This should match deps for `useLayoutEffectWithPrevDeps` below - }, [messageIds, isViewportNewest, containerHeight, hasTools]); + }); + + useSyncEffect( + () => rememberScrollPositionRef.current(), + // This will run before modifying content and should match deps for `useLayoutEffectWithPrevDeps` below + [messageIds, isViewportNewest, hasTools, rememberScrollPositionRef], + ); + useEffect( + () => rememberScrollPositionRef.current(), + // This is only needed to react on signal updates + [getContainerHeight, rememberScrollPositionRef], + ); // Handles updated message list, takes care of scroll repositioning - useLayoutEffectWithPrevDeps(([ - prevMessageIds, prevIsViewportNewest, prevContainerHeight, - ]) => { + useLayoutEffectWithPrevDeps(([prevMessageIds, prevIsViewportNewest]) => { + const containerHeight = getContainerHeight(); + const prevContainerHeight = prevContainerHeightRef.current; + prevContainerHeightRef.current = containerHeight; + const container = containerRef.current!; listItemElementsRef.current = Array.from(container.querySelectorAll('.message-list-item')); @@ -476,7 +478,7 @@ const MessageList: FC = ({ console.time('scrollTop'); } - const isResized = prevContainerHeight !== undefined && prevContainerHeight !== containerHeight; + const isResized = prevContainerHeight && prevContainerHeight !== containerHeight; const anchor = anchorIdRef.current && container.querySelector(`#${anchorIdRef.current}`); const unreadDivider = ( !anchor @@ -523,7 +525,7 @@ const MessageList: FC = ({ console.timeEnd('scrollTop'); } // This should match deps for `useSyncEffect` above - }, [messageIds, isViewportNewest, containerHeight, hasTools]); + }, [messageIds, isViewportNewest, getContainerHeight, prevContainerHeightRef, hasTools]); useEffectWithPrevDeps(([prevIsSelectModeActive]) => { if (prevIsSelectModeActive !== undefined) { diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index d9c773f43..bd03a8b7b 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -208,6 +208,14 @@ const MiddleColumn: FC = ({ const [isNotchShown, setIsNotchShown] = useState(); const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false); + const { + onIntersectionChanged, + onFocusPinnedMessage, + getCurrentPinnedIndexes, + getLoadingPinnedId, + getForceNextPinnedInHeader, + } = usePinnedMessage(chatId, threadId, pinnedIds); + const isMobileSearchActive = isMobile && hasCurrentTextSearch; const closeAnimationDuration = isMobile ? LAYER_ANIMATION_DURATION_MS : undefined; const hasTools = hasPinned && ( @@ -235,6 +243,10 @@ const MiddleColumn: FC = ({ const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration); const renderingShouldJoinToSend = usePrevDuringAnimation(shouldJoinToSend, closeAnimationDuration); const renderingShouldSendJoinRequest = usePrevDuringAnimation(shouldSendJoinRequest, closeAnimationDuration); + const renderingOnPinnedIntersectionChange = usePrevDuringAnimation( + chatId ? onIntersectionChanged : undefined, + closeAnimationDuration, + ); const prevTransitionKey = usePrevious(currentTransitionKey); @@ -356,14 +368,6 @@ const MiddleColumn: FC = ({ const customBackgroundValue = useCustomBackground(theme, customBackground); - const { - onIntersectionChanged, - onFocusPinnedMessage, - getCurrentPinnedIndexes, - getLoadingPinnedId, - getForceNextPinnedInHeader, - } = usePinnedMessage(chatId, threadId, pinnedIds); - const className = buildClassName( renderingHasTools && 'has-header-tools', MASK_IMAGE_DISABLED ? 'mask-image-disabled' : 'mask-image-enabled', @@ -449,13 +453,13 @@ const MiddleColumn: FC = ({ style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined} />
- {renderingChatId && renderingThreadId && ( + {Boolean(renderingChatId && renderingThreadId) && ( <>
= ({ >
{renderingCanPost && ( = ({
)} - {isMobile - && (renderingCanSubscribe || (renderingShouldJoinToSend && !renderingShouldSendJoinRequest)) && ( + {( + isMobile && (renderingCanSubscribe || (renderingShouldJoinToSend && !renderingShouldSendJoinRequest)) + ) && (