Page MenuHomePhabricator

D13246.id44000.diff
No OneTemporary

D13246.id44000.diff

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,107 @@
+// @flow
+import invariant from 'invariant';
+import * as React from 'react';
+
+import { updateActivityActionTypes } from '../actions/activity-actions.js';
+import {
+ type OutboundDMOperationSpecification,
+ 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 { ActivityUpdateSuccessPayload } from '../types/activity-types.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';
+
+function useUpdateDMActivity(): (
+ viewerID: string,
+ activeThreadInfo: RawThreadInfo,
+) => Promise<ActivityUpdateSuccessPayload> {
+ const processAndSendDMOperation = useProcessAndSendDMOperation();
+ return React.useCallback(
+ async (viewerID: string, activeThreadInfo: RawThreadInfo) => {
+ invariant(
+ threadTypeIsThick(activeThreadInfo.type),
+ 'thread must be thick',
+ );
+ const op: DMChangeThreadReadStatusOperation = {
+ type: 'change_thread_read_status',
+ time: Date.now(),
+ threadID: activeThreadInfo.id,
+ creatorID: viewerID,
+ unread: false,
+ };
+
+ const opSpecification: OutboundDMOperationSpecification = {
+ type: dmOperationSpecificationTypes.OUTBOUND,
+ op,
+ recipients: { type: 'self_devices' },
+ };
+
+ await processAndSendDMOperation(opSpecification);
+ return { activityUpdates: {}, result: { unfocusedToUnread: [] } };
+ },
+ [processAndSendDMOperation],
+ );
+}
+
+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<?string>();
+ const prevActiveThreadLatestMessageRef = React.useRef<?string>();
+
+ const viewerID = useSelector(
+ state => state.currentUserInfo && state.currentUserInfo.id,
+ );
+ const updateDMActivity = useUpdateDMActivity();
+ const dispatchActionPromise = useDispatchActionPromise();
+
+ 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;
+ }
+
+ void dispatchActionPromise(
+ updateActivityActionTypes,
+ updateDMActivity(viewerID, activeThreadInfo),
+ );
+ }, [
+ updateDMActivity,
+ dispatchActionPromise,
+ activeThread,
+ viewerID,
+ processAndSendDMOperation,
+ activeThreadInfo,
+ activeThreadLatestMessage,
+ ]);
+}
+
+export default useDMActivityHandler;
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
}
/>
+ <DMActivityHandler />
<VersionSupportedChecker />
<PlatformDetailsSynchronizer />
<BackgroundIdentityLoginHandler />
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 @@
<TunnelbrokerDeviceTokenHandler />
<FarcasterDataHandler />
<AutoJoinCommunityHandler />
+ <DMActivityHandler />
{content}
</ChatMentionContextProvider>
</MessageSearchStateProvider>
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;

File Metadata

Mime Type
text/plain
Expires
Fri, Dec 27, 12:31 AM (10 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2709556
Default Alt Text
D13246.id44000.diff (8 KB)

Event Timeline