diff --git a/lib/handlers/dm-activity-handler.js b/lib/handlers/dm-activity-handler.js --- a/lib/handlers/dm-activity-handler.js +++ b/lib/handlers/dm-activity-handler.js @@ -1,6 +1,7 @@ // @flow import invariant from 'invariant'; +import _debounce from 'lodash/debounce.js'; import * as React from 'react'; import { @@ -16,6 +17,8 @@ import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useSelector } from '../utils/redux-utils.js'; +const ACTIVITY_UPDATE_DURATION = 5000; + function useUpdateDMActivity(): ( viewerID: string, activeThreadInfo: RawThreadInfo, @@ -67,6 +70,12 @@ const updateDMActivity = useUpdateDMActivity(); const dispatchActionPromise = useDispatchActionPromise(); + const updateActivityAfterLatestMessageChange = React.useRef void) & { + cancel: () => void, + flush: () => mixed, + ... + })>(null); + React.useEffect(() => { const prevActiveThread = prevActiveThreadRef.current; const prevActiveThreadLatestMessage = @@ -75,19 +84,41 @@ prevActiveThreadRef.current = activeThread; prevActiveThreadLatestMessageRef.current = activeThreadLatestMessage; + const activeThreadChanged = prevActiveThread !== activeThread; + + if (activeThreadChanged) { + updateActivityAfterLatestMessageChange.current?.flush(); + } + if ( !viewerID || !activeThread || !activeThreadInfo || !threadTypeIsThick(activeThreadInfo.type) || - threadIsPending(activeThread) || - (activeThread === prevActiveThread && - activeThreadLatestMessage === prevActiveThreadLatestMessage) + threadIsPending(activeThread) + ) { + return; + } + + if (activeThreadInfo.currentUser.unread) { + updateActivityAfterLatestMessageChange.current?.cancel(); + void updateDMActivity(viewerID, activeThreadInfo); + return; + } + + if ( + activeThreadChanged || + activeThreadLatestMessage === prevActiveThreadLatestMessage ) { return; } - void updateDMActivity(viewerID, activeThreadInfo); + updateActivityAfterLatestMessageChange.current?.cancel(); + updateActivityAfterLatestMessageChange.current = _debounce(() => { + void updateDMActivity(viewerID, activeThreadInfo); + updateActivityAfterLatestMessageChange.current = null; + }, ACTIVITY_UPDATE_DURATION); + updateActivityAfterLatestMessageChange.current?.(); }, [ updateDMActivity, dispatchActionPromise, diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js --- a/web/redux/redux-setup.js +++ b/web/redux/redux-setup.js @@ -58,6 +58,7 @@ import type { SyncedMetadataStore } from 'lib/types/synced-metadata-types.js'; import type { GlobalThemeInfo } from 'lib/types/theme-types.js'; import type { ThreadActivityStore } from 'lib/types/thread-activity-types'; +import { threadTypeIsThick } from 'lib/types/thread-types-enum.js'; import type { ThreadStore } from 'lib/types/thread-types.js'; import type { TunnelbrokerDeviceToken } from 'lib/types/tunnelbroker-device-token-types.js'; import type { CurrentUserInfo, UserStore } from 'lib/types/user-types.js'; @@ -455,7 +456,8 @@ 'hasFocus' in document && document.hasFocus() && !state.navInfo.pendingThread && - state.threadStore.threadInfos[activeThread].currentUser.unread + state.threadStore.threadInfos[activeThread].currentUser.unread && + !threadTypeIsThick(state.threadStore.threadInfos[activeThread].type) ) { // Makes sure a currently focused thread is never unread const activeThreadInfo = state.threadStore.threadInfos[activeThread];