diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 4d5a28fc0..323031a78 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -4,12 +4,12 @@ import React, { import { getGlobal, withGlobal } from '../../lib/teact/teactn'; import { - ApiAction, ApiMessage, ApiRestrictionReason, MAIN_THREAD_ID, + ApiMessage, ApiRestrictionReason, MAIN_THREAD_ID, } from '../../api/types'; import { GlobalActions, MessageListType } from '../../global/types'; import { LoadMoreDirection } from '../../types'; -import { ANIMATION_END_DELAY, MESSAGE_LIST_SLICE } from '../../config'; +import { ANIMATION_END_DELAY, LOCAL_MESSAGE_ID_BASE, MESSAGE_LIST_SLICE } from '../../config'; import { selectChatMessages, selectIsViewportNewest, @@ -32,7 +32,7 @@ import { fastRaf, debounce, onTickEnd } from '../../util/schedulers'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import buildClassName from '../../util/buildClassName'; -import { groupMessages, MessageDateGroup } from './helpers/groupMessages'; +import { groupMessages } from './helpers/groupMessages'; import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; import useOnChange from '../../hooks/useOnChange'; import useStickyDates from './hooks/useStickyDates'; @@ -259,7 +259,7 @@ const MessageList: FC = ({ if (isReady) { containerRef.current!.dataset.normalHeight = String(containerRef.current!.offsetHeight); } - }, [windowHeight, isReady]); + }, [windowHeight, isReady, canPost]); // Initial message loading useEffect(() => { @@ -267,6 +267,12 @@ const MessageList: FC = ({ return; } + // Loading history while sending a message can return the same message and cause ambiguity + const isFirstMessageLocal = messageIds && messageIds[0] >= LOCAL_MESSAGE_ID_BASE; + if (isFirstMessageLocal) { + return; + } + const container = containerRef.current!; if (!messageIds || ( @@ -331,11 +337,13 @@ const MessageList: FC = ({ }, FOCUSING_DURATION); } - const hasFirstMessageChanged = messageIds && prevMessageIds && messageIds[0] !== prevMessageIds[0]; const hasLastMessageChanged = ( messageIds && prevMessageIds && messageIds[messageIds.length - 1] !== prevMessageIds[prevMessageIds.length - 1] ); - const wasMessageAdded = hasLastMessageChanged && !hasFirstMessageChanged; + const hasViewportShifted = ( + messageIds?.[0] !== prevMessageIds?.[0] && messageIds?.length === (MESSAGE_LIST_SLICE / 2 + 1) + ); + const wasMessageAdded = hasLastMessageChanged && !hasViewportShifted; const isAlreadyFocusing = messageIds && memoFocusingIdRef.current === messageIds[messageIds.length - 1]; const { scrollTop, scrollHeight, offsetHeight } = container; @@ -442,11 +450,12 @@ const MessageList: FC = ({ // Used to avoid flickering when deleting a greeting that has just been sent && (!listItemElementsRef.current || listItemElementsRef.current.length === 0) ) - || checkSingleMessageActionByType('contactSignUp', messageGroups) - || (lastMessage?.content.action && lastMessage.content.action.type === 'contactSignUp') + || (messageIds?.length === 1 && messagesById?.[messageIds[0]]?.content.action?.type === 'contactSignUp') + || (lastMessage?.content?.action?.type === 'contactSignUp') ); + const isGroupChatJustCreated = isGroupChat && isCreator - && checkSingleMessageActionByType('chatCreate', messageGroups); + && messageIds?.length === 1 && messagesById?.[messageIds[0]]?.content.action?.type === 'chatCreate'; const className = buildClassName( 'MessageList custom-scroll', @@ -512,16 +521,6 @@ const MessageList: FC = ({ ); }; -function checkSingleMessageActionByType(type: ApiAction['type'], messageGroups?: MessageDateGroup[]) { - return messageGroups - && messageGroups.length === 1 - && messageGroups[0].senderGroups.length === 1 - && messageGroups[0].senderGroups[0].length === 1 - && 'content' in messageGroups[0].senderGroups[0][0] - && messageGroups[0].senderGroups[0][0].content.action - && messageGroups[0].senderGroups[0][0].content.action.type === type; -} - export default memo(withGlobal( (global, { chatId, threadId, type }): StateProps => { const chat = selectChat(global, chatId); diff --git a/src/components/middle/hooks/useScrollHooks.ts b/src/components/middle/hooks/useScrollHooks.ts index eabc4d376..5c9019114 100644 --- a/src/components/middle/hooks/useScrollHooks.ts +++ b/src/components/middle/hooks/useScrollHooks.ts @@ -1,13 +1,13 @@ import { RefObject } from 'react'; import { getDispatch } from '../../../lib/teact/teactn'; -import { useCallback, useMemo, useRef } from '../../../lib/teact/teact'; +import { useMemo, useRef } from '../../../lib/teact/teact'; import { LoadMoreDirection } from '../../../types'; import { MessageListType } from '../../../global/types'; import { debounce } from '../../../util/schedulers'; import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver'; -import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../config'; +import { LOCAL_MESSAGE_ID_BASE, MESSAGE_LIST_SENSITIVE_AREA } from '../../../config'; import resetScroll from '../../../util/resetScroll'; import useOnChange from '../../../hooks/useOnChange'; @@ -42,7 +42,7 @@ export default function useScrollHooks( // eslint-disable-next-line no-null/no-null const fabTriggerRef = useRef(null); - const toggleScrollTools = useCallback(() => { + function toggleScrollTools() { if (!isActive) return; if (!messageIds || !messageIds.length) { @@ -64,7 +64,7 @@ export default function useScrollHooks( onFabToggle(isUnread ? !isAtBottom : !isNearBottom); onNotchToggle(!isAtBottom); - }, [isActive, messageIds, isViewportNewest, containerRef, onFabToggle, isUnread, onNotchToggle]); + } const { observe: observeIntersection, @@ -76,6 +76,12 @@ export default function useScrollHooks( return; } + // Loading history while sending a message can return the same message and cause ambiguity + const isFirstMessageLocal = messageIds[0] >= LOCAL_MESSAGE_ID_BASE; + if (isFirstMessageLocal) { + return; + } + const triggerEntry = entries.find(({ isIntersecting }) => isIntersecting); if (!triggerEntry) { return; diff --git a/src/modules/actions/api/messages.ts b/src/modules/actions/api/messages.ts index 10e5acd79..df60b3406 100644 --- a/src/modules/actions/api/messages.ts +++ b/src/modules/actions/api/messages.ts @@ -671,6 +671,7 @@ async function loadViewportMessages( global = isOutlying ? updateOutlyingIds(global, chatId, threadId, ids) : updateListedIds(global, chatId, threadId, ids); + global = addUsers(global, buildCollectionByKey(users, 'id')); global = addChats(global, buildCollectionByKey(chats, 'id')); global = updateThreadInfos(global, chatId, threadInfos); @@ -679,7 +680,7 @@ async function loadViewportMessages( const outlyingIds = selectOutlyingIds(global, chatId, threadId); if (isOutlying && listedIds && outlyingIds) { - if (areSortedArraysIntersecting(listedIds, outlyingIds)) { + if (!outlyingIds.length || areSortedArraysIntersecting(listedIds, outlyingIds)) { global = updateListedIds(global, chatId, threadId, outlyingIds); listedIds = selectListedIds(global, chatId, threadId); global = replaceThreadParam(global, chatId, threadId, 'outlyingIds', undefined);