From cf63c2ca07c9a77bb92449ea4f3d78b8dd54fb8f Mon Sep 17 00:00:00 2001
From: Samuel Newman <mozzius@protonmail.com>
Date: Wed, 28 May 2025 22:09:28 +0300
Subject: Send FeedFeedback interactions in thread view (#8414)

---
 src/view/com/post-thread/PostThreadItem.tsx | 107 ++++++++++++++++++++--------
 1 file changed, 79 insertions(+), 28 deletions(-)

(limited to 'src/view/com/post-thread/PostThreadItem.tsx')

diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 82852aa62..77adebac9 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -1,4 +1,4 @@
-import React, {memo, useMemo} from 'react'
+import {memo, useCallback, useMemo, useState} from 'react'
 import {
   type GestureResponderEvent,
   StyleSheet,
@@ -6,7 +6,7 @@ import {
   View,
 } from 'react-native'
 import {
-  type AppBskyFeedDefs,
+  AppBskyFeedDefs,
   AppBskyFeedPost,
   type AppBskyFeedThreadgate,
   AtUri,
@@ -35,10 +35,12 @@ import {
   usePostShadow,
 } from '#/state/cache/post-shadow'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
+import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
 import {useLanguagePrefs} from '#/state/preferences'
 import {type ThreadPost} from '#/state/queries/post-thread'
 import {useSession} from '#/state/session'
 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
+import {useUnstablePostSource} from '#/state/unstable-post-source'
 import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
 import {Link, TextLink} from '#/view/com/util/Link'
@@ -201,18 +203,21 @@ let PostThreadItemLoaded = ({
   hideTopBorder?: boolean
   threadgateRecord?: AppBskyFeedThreadgate.Record
 }): React.ReactNode => {
+  const {currentAccount, hasSession} = useSession()
+  const source = useUnstablePostSource(post.uri)
+  const feedFeedback = useFeedFeedback(source?.feed, hasSession)
+
   const t = useTheme()
   const pal = usePalette('default')
   const {_, i18n} = useLingui()
   const langPrefs = useLanguagePrefs()
   const {openComposer} = useOpenComposer()
-  const [limitLines, setLimitLines] = React.useState(
+  const [limitLines, setLimitLines] = useState(
     () => countLines(richText?.text) >= MAX_POST_LINES,
   )
-  const {currentAccount} = useSession()
   const shadowedPostAuthor = useProfileShadow(post.author)
   const rootUri = record.reply?.root?.uri || post.uri
-  const postHref = React.useMemo(() => {
+  const postHref = useMemo(() => {
     const urip = new AtUri(post.uri)
     return makeProfileLink(post.author, 'post', urip.rkey)
   }, [post.uri, post.author])
@@ -220,12 +225,12 @@ let PostThreadItemLoaded = ({
   const authorHref = makeProfileLink(post.author)
   const authorTitle = post.author.handle
   const isThreadAuthor = getThreadAuthor(post, record) === currentAccount?.did
-  const likesHref = React.useMemo(() => {
+  const likesHref = useMemo(() => {
     const urip = new AtUri(post.uri)
     return makeProfileLink(post.author, 'post', urip.rkey, 'liked-by')
   }, [post.uri, post.author])
   const likesTitle = _(msg`Likes on this post`)
-  const repostsHref = React.useMemo(() => {
+  const repostsHref = useMemo(() => {
     const urip = new AtUri(post.uri)
     return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
   }, [post.uri, post.author])
@@ -233,7 +238,7 @@ let PostThreadItemLoaded = ({
   const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({
     threadgateRecord,
   })
-  const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => {
+  const additionalPostAlerts: AppModerationCause[] = useMemo(() => {
     const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri)
     const isControlledByViewer = new AtUri(rootUri).host === currentAccount?.did
     return isControlledByViewer && isPostHiddenByThreadgate
@@ -246,7 +251,7 @@ let PostThreadItemLoaded = ({
         ]
       : []
   }, [post, currentAccount?.did, threadgateHiddenReplies, rootUri])
-  const quotesHref = React.useMemo(() => {
+  const quotesHref = useMemo(() => {
     const urip = new AtUri(post.uri)
     return makeProfileLink(post.author, 'post', urip.rkey, 'quotes')
   }, [post.uri, post.author])
@@ -270,7 +275,15 @@ let PostThreadItemLoaded = ({
     [post, langPrefs.primaryLanguage],
   )
 
-  const onPressReply = React.useCallback(() => {
+  const onPressReply = () => {
+    if (source) {
+      feedFeedback.sendInteraction({
+        item: post.uri,
+        event: 'app.bsky.feed.defs#interactionReply',
+        feedContext: source.post.feedContext,
+        reqId: source.post.reqId,
+      })
+    }
     openComposer({
       replyTo: {
         uri: post.uri,
@@ -282,14 +295,46 @@ let PostThreadItemLoaded = ({
       },
       onPost: onPostReply,
     })
-  }, [openComposer, post, record, onPostReply, moderation])
+  }
 
-  const onPressShowMore = React.useCallback(() => {
+  const onOpenAuthor = () => {
+    if (source) {
+      feedFeedback.sendInteraction({
+        item: post.uri,
+        event: 'app.bsky.feed.defs#clickthroughAuthor',
+        feedContext: source.post.feedContext,
+        reqId: source.post.reqId,
+      })
+    }
+  }
+
+  const onOpenEmbed = () => {
+    if (source) {
+      feedFeedback.sendInteraction({
+        item: post.uri,
+        event: 'app.bsky.feed.defs#clickthroughEmbed',
+        feedContext: source.post.feedContext,
+        reqId: source.post.reqId,
+      })
+    }
+  }
+
+  const onPressShowMore = useCallback(() => {
     setLimitLines(false)
   }, [setLimitLines])
 
   const {isActive: live} = useActorStatus(post.author)
 
+  const reason = source?.post.reason
+  const viaRepost = useMemo(() => {
+    if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) {
+      return {
+        uri: reason.uri,
+        cid: reason.cid,
+      }
+    }
+  }, [reason])
+
   if (!record) {
     return <ErrorMessage message={_(msg`Invalid or unsupported post record`)} />
   }
@@ -309,10 +354,8 @@ let PostThreadItemLoaded = ({
               <View
                 style={[
                   styles.replyLine,
-                  {
-                    flexGrow: 1,
-                    backgroundColor: pal.colors.replyLine,
-                  },
+                  a.flex_grow,
+                  {backgroundColor: pal.colors.replyLine},
                 ]}
               />
             </View>
@@ -334,13 +377,15 @@ let PostThreadItemLoaded = ({
               moderation={moderation.ui('avatar')}
               type={post.author.associated?.labeler ? 'labeler' : 'user'}
               live={live}
+              onBeforePress={onOpenAuthor}
             />
             <View style={[a.flex_1]}>
               <View style={[a.flex_row, a.align_center]}>
                 <Link
                   style={[a.flex_shrink]}
                   href={authorHref}
-                  title={authorTitle}>
+                  title={authorTitle}
+                  onBeforePress={onOpenAuthor}>
                   <Text
                     emoji
                     style={[
@@ -413,6 +458,7 @@ let PostThreadItemLoaded = ({
                     embed={post.embed}
                     moderation={moderation}
                     viewContext={PostEmbedViewContext.ThreadHighlighted}
+                    onOpen={onOpenEmbed}
                   />
                 </View>
               )}
@@ -494,16 +540,21 @@ let PostThreadItemLoaded = ({
                   marginLeft: -5,
                 },
               ]}>
-              <PostControls
-                big
-                post={post}
-                record={record}
-                richText={richText}
-                onPressReply={onPressReply}
-                onPostReply={onPostReply}
-                logContext="PostThreadItem"
-                threadgateRecord={threadgateRecord}
-              />
+              <FeedFeedbackProvider value={feedFeedback}>
+                <PostControls
+                  big
+                  post={post}
+                  record={record}
+                  richText={richText}
+                  onPressReply={onPressReply}
+                  onPostReply={onPostReply}
+                  logContext="PostThreadItem"
+                  threadgateRecord={threadgateRecord}
+                  feedContext={source?.post?.feedContext}
+                  reqId={source?.post?.reqId}
+                  viaRepost={viaRepost}
+                />
+              </FeedFeedbackProvider>
             </View>
           </View>
         </View>
@@ -779,7 +830,7 @@ function ExpandedPostDetails({
   const isRootPost = !('reply' in post.record)
   const langPrefs = useLanguagePrefs()
 
-  const onTranslatePress = React.useCallback(
+  const onTranslatePress = useCallback(
     (e: GestureResponderEvent) => {
       e.preventDefault()
       openLink(translatorUrl, true)
-- 
cgit 1.4.1

