diff --git a/lib/handlers/dm-activity-handler.js b/lib/handlers/dm-activity-handler.js new file mode 100644 --- /dev/null +++ b/lib/handlers/dm-activity-handler.js @@ -0,0 +1,75 @@ +// @flow +import * as React from 'react'; + +import type { OutboundDMOperationSpecification } from '../shared/dm-ops/dm-op-utils'; +import { dmOperationSpecificationTypes } from '../shared/dm-ops/dm-op-utils.js'; +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 { threadTypeIsThick } from '../types/thread-types-enum.js'; +import { useSelector } from '../utils/redux-utils.js'; + +function useDMActivityHandler(activeThread: ?string): void { + const activeThreadInfo = useSelector(state => + activeThread ? state.threadStore.threadInfos[activeThread] : null, + ); + const activeThreadLatestMessage = useSelector(state => + activeThread + ? getMostRecentNonLocalMessageID(activeThread, state.messageStore) + : null, + ); + const processAndSendDMOperation = useProcessAndSendDMOperation(); + + const prevActiveThreadRef = React.useRef(); + const prevActiveThreadLatestMessageRef = React.useRef(); + + const viewerID = useSelector( + state => state.currentUserInfo && state.currentUserInfo.id, + ); + + React.useEffect(() => { + const prevActiveThread = prevActiveThreadRef.current; + const prevActiveThreadLatestMessage = + prevActiveThreadLatestMessageRef.current; + + prevActiveThreadRef.current = activeThread; + prevActiveThreadLatestMessageRef.current = activeThreadLatestMessage; + + if ( + !viewerID || + !activeThread || + !activeThreadInfo || + !threadTypeIsThick(activeThreadInfo.type) || + threadIsPending(activeThread) || + (activeThread === prevActiveThread && + activeThreadLatestMessage === prevActiveThreadLatestMessage) + ) { + return; + } + + const op: DMChangeThreadReadStatusOperation = { + type: 'change_thread_read_status', + time: Date.now(), + threadID: activeThread, + creatorID: viewerID, + unread: false, + }; + + const opSpecification: OutboundDMOperationSpecification = { + type: dmOperationSpecificationTypes.OUTBOUND, + op, + recipients: { type: 'self_devices' }, + }; + + void processAndSendDMOperation(opSpecification); + }, [ + activeThread, + viewerID, + processAndSendDMOperation, + activeThreadInfo, + activeThreadLatestMessage, + ]); +} + +export default useDMActivityHandler; diff --git a/lib/shared/version-utils.js b/lib/shared/version-utils.js --- a/lib/shared/version-utils.js +++ b/lib/shared/version-utils.js @@ -10,7 +10,7 @@ * A code version used for features that are waiting to be released * and we're not sure in which version they will be available. */ -const FUTURE_CODE_VERSION = 1000000; +const FUTURE_CODE_VERSION = 0; /** * A code version used for features that are waiting to be included diff --git a/native/components/dm-activity-handler.react.js b/native/components/dm-activity-handler.react.js new file mode 100644 --- /dev/null +++ b/native/components/dm-activity-handler.react.js @@ -0,0 +1,27 @@ +// @flow +import * as React from 'react'; + +import useDMActivityHandler from 'lib/handlers/dm-activity-handler.js'; +import { isLoggedIn } from 'lib/selectors/user-selectors.js'; + +import { activeMessageListSelector } from '../navigation/nav-selectors.js'; +import { NavContext } from '../navigation/navigation-context.js'; +import { useSelector } from '../redux/redux-utils.js'; + +function DMActivityHandler(): React.Node { + const active = useSelector( + state => isLoggedIn(state) && state.lifecycleState !== 'background', + ); + const navContext = React.useContext(NavContext); + const activeThread = React.useMemo(() => { + if (!active) { + return null; + } + return activeMessageListSelector(navContext); + }, [active, navContext]); + + useDMActivityHandler(activeThread); + return null; +} + +export default DMActivityHandler; diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -54,6 +54,7 @@ import { AutoJoinCommunityHandler } from './components/auto-join-community-handler.react.js'; import BackgroundIdentityLoginHandler from './components/background-identity-login-handler.react.js'; import ConnectFarcasterAlertHandler from './components/connect-farcaster-alert-handler.react.js'; +import DMActivityHandler from './components/dm-activity-handler.react.js'; import { FeatureFlagsProvider } from './components/feature-flags-provider.react.js'; import { NUXTipsContextProvider } from './components/nux-tips-context.react.js'; import PersistedStateGate from './components/persisted-state-gate.js'; @@ -373,6 +374,7 @@ detectUnsupervisedBackgroundRef } /> + diff --git a/web/app.react.js b/web/app.react.js --- a/web/app.react.js +++ b/web/app.react.js @@ -55,6 +55,7 @@ import { MemberListSidebarProvider } from './chat/member-list-sidebar/member-list-sidebar-provider.react.js'; import { AutoJoinCommunityHandler } from './components/auto-join-community-handler.react.js'; import CommunitiesRefresher from './components/communities-refresher.react.js'; +import DMActivityHandler from './components/dm-activity-handler.react.js'; import LogOutIfMissingCSATHandler from './components/log-out-if-missing-csat-handler.react.js'; import NavigationArrows from './components/navigation-arrows.react.js'; import MinVersionHandler from './components/version-handler.react.js'; @@ -256,6 +257,7 @@ + {content} diff --git a/web/components/dm-activity-handler.react.js b/web/components/dm-activity-handler.react.js new file mode 100644 --- /dev/null +++ b/web/components/dm-activity-handler.react.js @@ -0,0 +1,27 @@ +// @flow +import * as React from 'react'; + +import useDMActivityHandler from 'lib/handlers/dm-activity-handler.js'; +import { isLoggedIn } from 'lib/selectors/user-selectors.js'; + +import { useSelector } from '../redux/redux-utils.js'; +import { activeThreadSelector } from '../selectors/nav-selectors.js'; + +function DMActivityHandler(): React.Node { + const active = useSelector( + state => isLoggedIn(state) && state.lifecycleState !== 'background', + ); + const reduxActiveThread = useSelector(activeThreadSelector); + const windowActive = useSelector(state => state.windowActive); + const activeThread = React.useMemo(() => { + if (!active || !windowActive) { + return null; + } + return reduxActiveThread; + }, [active, windowActive, reduxActiveThread]); + + useDMActivityHandler(activeThread); + return null; +} + +export default DMActivityHandler;