diff --git a/src/screens/PostThread/index.tsx b/src/screens/PostThread/index.tsx index 64d6529728..bc2578b895 100644 --- a/src/screens/PostThread/index.tsx +++ b/src/screens/PostThread/index.tsx @@ -14,7 +14,11 @@ import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' -import {useFeedFeedback} from '#/state/feed-feedback' +import { + FeedFeedbackProvider, + type StateContext as FeedFeedbackStateContext, + useFeedFeedback, +} from '#/state/feed-feedback' import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences' import { PostThreadContextProvider, @@ -56,6 +60,9 @@ import {IS_NATIVE} from '#/env' const PARENT_CHUNK_SIZE = IS_NATIVE ? 5 : 20 const CHILDREN_CHUNK_SIZE = 50 +const analyticsOnlyOnItemSeen: FeedFeedbackStateContext['onItemSeen'] = () => {} +const analyticsOnlySendInteraction: FeedFeedbackStateContext['sendInteraction'] = + () => {} export function PostThread({uri}: {uri: string}) { const ax = useAnalytics() @@ -569,68 +576,71 @@ export function PostThread({uri}: {uri: string}) { onRetry={thread.actions.refetch} /> ) : ( - { - // Track post:view for parent posts and replies (non-anchor posts) - if (item.type === 'threadPost' && item.depth !== 0) { - trackThreadItemView(item.value.post) + + { + // Track post:view for parent posts and replies (non-anchor posts) + if (item.type === 'threadPost' && item.depth !== 0) { + trackThreadItemView(item.value.post) + } + }} + /** + * NATIVE ONLY + * {@link https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition} + */ + maintainVisibleContentPosition={{minIndexForVisible: 0}} + desktopFixedHeight + sideBorders={false} + ListFooterComponent={ + } - }} - /** - * NATIVE ONLY - * {@link https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition} - */ - maintainVisibleContentPosition={{minIndexForVisible: 0}} - desktopFixedHeight - sideBorders={false} - ListFooterComponent={ - - } - initialNumToRender={initialNumToRender} - /** - * Default: 21 - * - * Smaller for placeholder data so we don't waste time rendering skeletons - */ - windowSize={thread.state.isPlaceholderData ? 1 : 7} - /** - * Default: 10 - */ - maxToRenderPerBatch={5} - /** - * Default: 50 - */ - updateCellsBatchingPeriod={100} - /> + initialNumToRender={initialNumToRender} + /** + * Default: 21 + * + * Smaller for placeholder data so we don't waste time rendering skeletons + */ + windowSize={thread.state.isPlaceholderData ? 1 : 7} + /** + * Default: 10 + */ + maxToRenderPerBatch={5} + /** + * Default: 50 + */ + updateCellsBatchingPeriod={100} + /> + )} {!gtMobile && canReply && hasSession && ( @@ -640,6 +650,36 @@ export function PostThread({uri}: {uri: string}) { ) } +function AnalyticsOnlyFeedFeedbackProvider({ + children, + feedDescriptor, +}: React.PropsWithChildren<{ + feedDescriptor: FeedFeedbackStateContext['feedDescriptor'] +}>) { + /* + * Non-anchor posts (parents and replies) are not the post the user tapped + * from a feed, so they should not report feed interactions. But they should + * still carry the originating feedDescriptor for analytics, so events like + * post:clickQuotePost can be attributed to the feed the thread was opened + * from. This inert context value provides feedDescriptor while keeping + * interaction reporting disabled (enabled: false makes sendInteraction and + * onItemSeen no-ops). The anchor renders its own full provider that nests + * inside and overrides this for its subtree. + */ + const value = useMemo( + () => ({ + enabled: false, + onItemSeen: analyticsOnlyOnItemSeen, + sendInteraction: analyticsOnlySendInteraction, + feedDescriptor, + feedSourceInfo: undefined, + }), + [feedDescriptor], + ) + + return {children} +} + function MobileComposePrompt({onPressReply}: {onPressReply: () => unknown}) { const {footerHeight} = useShellLayout()