From 5a7bbb19b15cf87beafdc47335eaa3d2a2d6b22c Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 16 Aug 2021 20:42:21 +0300 Subject: [PATCH] [Perf] Optimistically mark messages read --- src/components/middle/MessageList.tsx | 4 +- src/components/middle/MessageListContent.tsx | 6 ++- .../middle/hooks/useMessageObservers.ts | 2 + src/modules/actions/api/messages.ts | 48 +++++++++++++++++-- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 01253e928..2cf4fd4d0 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -140,6 +140,7 @@ const MessageList: FC = ({ const anchorIdRef = useRef(); const anchorTopRef = useRef(); const listItemElementsRef = useRef(); + // Not updated (as we want the unread divider to keep its position) const memoUnreadDividerBeforeIdRef = useRef(firstUnreadId); // Updated every time (to be used from intersection callback closure) const memoFirstUnreadIdRef = useRef(); @@ -485,7 +486,8 @@ const MessageList: FC = ({ noAvatars={noAvatars} containerRef={containerRef} anchorIdRef={anchorIdRef} - memoFirstUnreadIdRef={memoUnreadDividerBeforeIdRef} + memoUnreadDividerBeforeIdRef={memoUnreadDividerBeforeIdRef} + memoFirstUnreadIdRef={memoFirstUnreadIdRef} threadId={threadId} type={type} threadTopMessageId={threadTopMessageId} diff --git a/src/components/middle/MessageListContent.tsx b/src/components/middle/MessageListContent.tsx index 90484350e..2ccda8041 100644 --- a/src/components/middle/MessageListContent.tsx +++ b/src/components/middle/MessageListContent.tsx @@ -26,6 +26,7 @@ interface OwnProps { noAvatars: boolean; containerRef: RefObject; anchorIdRef: { current: string | undefined }; + memoUnreadDividerBeforeIdRef: { current: number | undefined }; memoFirstUnreadIdRef: { current: number | undefined }; threadId: number; type: MessageListType; @@ -49,6 +50,7 @@ const MessageListContent: FC = ({ noAvatars, containerRef, anchorIdRef, + memoUnreadDividerBeforeIdRef, memoFirstUnreadIdRef, threadId, type, @@ -111,7 +113,7 @@ const MessageListContent: FC = ({ ); return compact([ - message.id === memoFirstUnreadIdRef.current && unreadDivider, + message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider, = ({ const key = type !== 'scheduled' ? originalId : `${message.date}_${originalId}`; return compact([ - message.id === memoFirstUnreadIdRef.current ? unreadDivider : undefined, + message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider, { if (type !== 'thread') { return; diff --git a/src/modules/actions/api/messages.ts b/src/modules/actions/api/messages.ts index 961650f1a..ee07af2bf 100644 --- a/src/modules/actions/api/messages.ts +++ b/src/modules/actions/api/messages.ts @@ -50,13 +50,14 @@ import { selectEditingMessage, selectScheduledMessage, selectNoWebPage, + selectFirstUnreadId, } from '../../selectors'; -import { rafPromise, throttle } from '../../../util/schedulers'; +import { debounce, rafPromise } from '../../../util/schedulers'; import { IS_IOS } from '../../../util/environment'; const uploadProgressCallbacks = new Map(); -const runThrottledForMarkRead = throttle((cb) => cb(), 1000, true); +const runDebouncedForMarkRead = debounce((cb) => cb(), 1000, false); addReducer('loadViewportMessages', (global, actions, payload) => { const { @@ -443,22 +444,43 @@ addReducer('markMessageListRead', (global, actions, payload) => { const { serverTimeOffset } = global; const currentMessageList = selectCurrentMessageList(global); if (!currentMessageList) { - return; + return undefined; } const { chatId, threadId } = currentMessageList; const chat = selectThreadOriginChat(global, chatId, threadId); if (!chat) { - return; + return undefined; } const { maxId } = payload!; - runThrottledForMarkRead(() => { + runDebouncedForMarkRead(() => { void callApi('markMessageListRead', { serverTimeOffset, chat, threadId, maxId, }); }); + + // TODO Support local marking read for threads + if (threadId !== MAIN_THREAD_ID) { + return undefined; + } + + const viewportIds = selectViewportIds(global, chatId, threadId); + const minId = selectFirstUnreadId(global, chatId, threadId); + if (!viewportIds || !minId || !chat.unreadCount) { + return undefined; + } + + const readCount = countSortedIds(viewportIds!, minId, maxId); + if (!readCount) { + return undefined; + } + + return updateChat(global, chatId, { + lastReadInboxMessageId: maxId, + unreadCount: Math.max(0, chat.unreadCount - readCount), + }); }); addReducer('markMessagesRead', (global, actions, payload) => { @@ -892,3 +914,19 @@ async function loadScheduledHistory(chat: ApiChat, historyHash?: number) { global = replaceThreadParam(global, chat.id, MAIN_THREAD_ID, 'scheduledIds', ids); setGlobal(global); } + +function countSortedIds(ids: number[], from: number, to: number) { + let count = 0; + + for (let i = 0, l = ids.length; i < l; i++) { + if (ids[i] >= from && ids[i] <= to) { + count++; + } + + if (ids[i] >= to) { + break; + } + } + + return count; +}