[Perf] Optimistically mark messages read
This commit is contained in:
parent
13b62d0199
commit
5a7bbb19b1
@ -140,6 +140,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const anchorIdRef = useRef<string>();
|
const anchorIdRef = useRef<string>();
|
||||||
const anchorTopRef = useRef<number>();
|
const anchorTopRef = useRef<number>();
|
||||||
const listItemElementsRef = useRef<HTMLDivElement[]>();
|
const listItemElementsRef = useRef<HTMLDivElement[]>();
|
||||||
|
// Not updated (as we want the unread divider to keep its position)
|
||||||
const memoUnreadDividerBeforeIdRef = useRef<number | undefined>(firstUnreadId);
|
const memoUnreadDividerBeforeIdRef = useRef<number | undefined>(firstUnreadId);
|
||||||
// Updated every time (to be used from intersection callback closure)
|
// Updated every time (to be used from intersection callback closure)
|
||||||
const memoFirstUnreadIdRef = useRef<number>();
|
const memoFirstUnreadIdRef = useRef<number>();
|
||||||
@ -485,7 +486,8 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
noAvatars={noAvatars}
|
noAvatars={noAvatars}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
anchorIdRef={anchorIdRef}
|
anchorIdRef={anchorIdRef}
|
||||||
memoFirstUnreadIdRef={memoUnreadDividerBeforeIdRef}
|
memoUnreadDividerBeforeIdRef={memoUnreadDividerBeforeIdRef}
|
||||||
|
memoFirstUnreadIdRef={memoFirstUnreadIdRef}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
type={type}
|
type={type}
|
||||||
threadTopMessageId={threadTopMessageId}
|
threadTopMessageId={threadTopMessageId}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ interface OwnProps {
|
|||||||
noAvatars: boolean;
|
noAvatars: boolean;
|
||||||
containerRef: RefObject<HTMLDivElement>;
|
containerRef: RefObject<HTMLDivElement>;
|
||||||
anchorIdRef: { current: string | undefined };
|
anchorIdRef: { current: string | undefined };
|
||||||
|
memoUnreadDividerBeforeIdRef: { current: number | undefined };
|
||||||
memoFirstUnreadIdRef: { current: number | undefined };
|
memoFirstUnreadIdRef: { current: number | undefined };
|
||||||
threadId: number;
|
threadId: number;
|
||||||
type: MessageListType;
|
type: MessageListType;
|
||||||
@ -49,6 +50,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
noAvatars,
|
noAvatars,
|
||||||
containerRef,
|
containerRef,
|
||||||
anchorIdRef,
|
anchorIdRef,
|
||||||
|
memoUnreadDividerBeforeIdRef,
|
||||||
memoFirstUnreadIdRef,
|
memoFirstUnreadIdRef,
|
||||||
threadId,
|
threadId,
|
||||||
type,
|
type,
|
||||||
@ -111,7 +113,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return compact([
|
return compact([
|
||||||
message.id === memoFirstUnreadIdRef.current && unreadDivider,
|
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
|
||||||
<ActionMessage
|
<ActionMessage
|
||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
message={message}
|
||||||
@ -162,7 +164,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
const key = type !== 'scheduled' ? originalId : `${message.date}_${originalId}`;
|
const key = type !== 'scheduled' ? originalId : `${message.date}_${originalId}`;
|
||||||
|
|
||||||
return compact([
|
return compact([
|
||||||
message.id === memoFirstUnreadIdRef.current ? unreadDivider : undefined,
|
message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider,
|
||||||
<Message
|
<Message
|
||||||
key={key}
|
key={key}
|
||||||
message={message}
|
message={message}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import useBackgroundMode from '../../../hooks/useBackgroundMode';
|
|||||||
|
|
||||||
const INTERSECTION_THROTTLE_FOR_MEDIA = IS_ANDROID ? 1000 : 350;
|
const INTERSECTION_THROTTLE_FOR_MEDIA = IS_ANDROID ? 1000 : 350;
|
||||||
const INTERSECTION_MARGIN_FOR_MEDIA = IS_SINGLE_COLUMN_LAYOUT ? 300 : 500;
|
const INTERSECTION_MARGIN_FOR_MEDIA = IS_SINGLE_COLUMN_LAYOUT ? 300 : 500;
|
||||||
|
const INTERSECTION_THROTTLE_FOR_READING = 150;
|
||||||
|
|
||||||
export default function useMessageObservers(
|
export default function useMessageObservers(
|
||||||
type: MessageListType,
|
type: MessageListType,
|
||||||
@ -29,6 +30,7 @@ export default function useMessageObservers(
|
|||||||
observe: observeIntersectionForReading, freeze: freezeForReading, unfreeze: unfreezeForReading,
|
observe: observeIntersectionForReading, freeze: freezeForReading, unfreeze: unfreezeForReading,
|
||||||
} = useIntersectionObserver({
|
} = useIntersectionObserver({
|
||||||
rootRef: containerRef,
|
rootRef: containerRef,
|
||||||
|
throttleMs: INTERSECTION_THROTTLE_FOR_READING,
|
||||||
}, (entries) => {
|
}, (entries) => {
|
||||||
if (type !== 'thread') {
|
if (type !== 'thread') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -50,13 +50,14 @@ import {
|
|||||||
selectEditingMessage,
|
selectEditingMessage,
|
||||||
selectScheduledMessage,
|
selectScheduledMessage,
|
||||||
selectNoWebPage,
|
selectNoWebPage,
|
||||||
|
selectFirstUnreadId,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { rafPromise, throttle } from '../../../util/schedulers';
|
import { debounce, rafPromise } from '../../../util/schedulers';
|
||||||
import { IS_IOS } from '../../../util/environment';
|
import { IS_IOS } from '../../../util/environment';
|
||||||
|
|
||||||
const uploadProgressCallbacks = new Map<number, ApiOnProgress>();
|
const uploadProgressCallbacks = new Map<number, ApiOnProgress>();
|
||||||
|
|
||||||
const runThrottledForMarkRead = throttle((cb) => cb(), 1000, true);
|
const runDebouncedForMarkRead = debounce((cb) => cb(), 1000, false);
|
||||||
|
|
||||||
addReducer('loadViewportMessages', (global, actions, payload) => {
|
addReducer('loadViewportMessages', (global, actions, payload) => {
|
||||||
const {
|
const {
|
||||||
@ -443,22 +444,43 @@ addReducer('markMessageListRead', (global, actions, payload) => {
|
|||||||
const { serverTimeOffset } = global;
|
const { serverTimeOffset } = global;
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
if (!currentMessageList) {
|
if (!currentMessageList) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { chatId, threadId } = currentMessageList;
|
const { chatId, threadId } = currentMessageList;
|
||||||
const chat = selectThreadOriginChat(global, chatId, threadId);
|
const chat = selectThreadOriginChat(global, chatId, threadId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { maxId } = payload!;
|
const { maxId } = payload!;
|
||||||
|
|
||||||
runThrottledForMarkRead(() => {
|
runDebouncedForMarkRead(() => {
|
||||||
void callApi('markMessageListRead', {
|
void callApi('markMessageListRead', {
|
||||||
serverTimeOffset, chat, threadId, maxId,
|
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) => {
|
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);
|
global = replaceThreadParam(global, chat.id, MAIN_THREAD_ID, 'scheduledIds', ids);
|
||||||
setGlobal(global);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user