diff --git a/lib/handlers/dm-activity-handler.js b/lib/handlers/dm-activity-handler.js index a749f7df41..b825f65406 100644 --- a/lib/handlers/dm-activity-handler.js +++ b/lib/handlers/dm-activity-handler.js @@ -1,6 +1,6 @@ // @flow -import invariant from 'invariant'; +import _debounce from 'lodash/debounce.js'; import * as React from 'react'; import { @@ -11,26 +11,23 @@ import { useProcessAndSendDMOperation } from '../shared/dm-ops/process-dm-ops.js import { getMostRecentNonLocalMessageID } from '../shared/message-utils.js'; import { threadIsPending } from '../shared/thread-utils.js'; import type { DMChangeThreadReadStatusOperation } from '../types/dm-ops.js'; -import type { RawThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; import { threadTypeIsThick } from '../types/thread-types-enum.js'; 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, + activeThread: string, ) => Promise { const processAndSendDMOperation = useProcessAndSendDMOperation(); return React.useCallback( - async (viewerID: string, activeThreadInfo: RawThreadInfo) => { - invariant( - threadTypeIsThick(activeThreadInfo.type), - 'thread must be thick', - ); + async (viewerID: string, activeThread: string) => { const op: DMChangeThreadReadStatusOperation = { type: 'change_thread_read_status', time: Date.now(), - threadID: activeThreadInfo.id, + threadID: activeThread, creatorID: viewerID, unread: false, }; @@ -47,6 +44,19 @@ function useUpdateDMActivity(): ( ); } +function getUpateActivityAfterLatestMessageChange( + viewerID: ?string, + activeThread: ?string, + updateDMActivity: (viewerID: string, activeThread: string) => Promise, +) { + return _debounce(() => { + if (!activeThread || !viewerID) { + return; + } + void updateDMActivity(viewerID, activeThread); + }, ACTIVITY_UPDATE_DURATION); +} + function useDMActivityHandler(activeThread: ?string): void { const activeThreadInfo = useSelector(state => activeThread ? state.threadStore.threadInfos[activeThread] : null, @@ -67,6 +77,14 @@ function useDMActivityHandler(activeThread: ?string): void { const updateDMActivity = useUpdateDMActivity(); const dispatchActionPromise = useDispatchActionPromise(); + const updateActivityAfterLatestMessageChangeRef = React.useRef( + getUpateActivityAfterLatestMessageChange( + viewerID, + activeThread, + updateDMActivity, + ), + ); + React.useEffect(() => { const prevActiveThread = prevActiveThreadRef.current; const prevActiveThreadLatestMessage = @@ -75,19 +93,39 @@ function useDMActivityHandler(activeThread: ?string): void { prevActiveThreadRef.current = activeThread; prevActiveThreadLatestMessageRef.current = activeThreadLatestMessage; + const activeThreadChanged = prevActiveThread !== activeThread; + if (activeThreadChanged) { + updateActivityAfterLatestMessageChangeRef.current = + getUpateActivityAfterLatestMessageChange( + viewerID, + activeThread, + updateDMActivity, + ); + } + if ( !viewerID || !activeThread || !activeThreadInfo || !threadTypeIsThick(activeThreadInfo.type) || - threadIsPending(activeThread) || - (activeThread === prevActiveThread && - activeThreadLatestMessage === prevActiveThreadLatestMessage) + threadIsPending(activeThread) + ) { + return; + } + + if (activeThreadInfo.currentUser.unread) { + void updateDMActivity(viewerID, activeThread); + return; + } + + if ( + activeThreadChanged || + activeThreadLatestMessage === prevActiveThreadLatestMessage ) { return; } - void updateDMActivity(viewerID, activeThreadInfo); + updateActivityAfterLatestMessageChangeRef.current(); }, [ updateDMActivity, dispatchActionPromise, diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js index 596aa94dd8..41db07b75b 100644 --- a/web/redux/redux-setup.js +++ b/web/redux/redux-setup.js @@ -58,6 +58,7 @@ import type { StoreOperations } from 'lib/types/store-ops-types.js'; 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 @@ function validateStateAndQueueOpsProcessing( '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];