about summary refs log tree commit diff
path: root/src/view/com/posts
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-05-10 00:06:06 +0300
committerGitHub <noreply@github.com>2025-05-10 00:06:06 +0300
commita0bd8042621e108f47e09dd096cf0d73fe1cee53 (patch)
tree0cc120c864ae8fea7f513ff242a1097ece0f1b8b /src/view/com/posts
parent2e80fa3dac4d869640f5bce8ad43eb401c8e3141 (diff)
downloadvoidsky-a0bd8042621e108f47e09dd096cf0d73fe1cee53.tar.zst
Live (#8354)
Diffstat (limited to 'src/view/com/posts')
-rw-r--r--src/view/com/posts/AviFollowButton.tsx143
-rw-r--r--src/view/com/posts/AviFollowButton.web.tsx5
-rw-r--r--src/view/com/posts/PostFeed.tsx32
-rw-r--r--src/view/com/posts/PostFeedItem.tsx23
4 files changed, 41 insertions, 162 deletions
diff --git a/src/view/com/posts/AviFollowButton.tsx b/src/view/com/posts/AviFollowButton.tsx
deleted file mode 100644
index 1c894bffe..000000000
--- a/src/view/com/posts/AviFollowButton.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import React from 'react'
-import {View} from 'react-native'
-import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useNavigation} from '@react-navigation/native'
-
-import {NavigationProp} from '#/lib/routes/types'
-import {sanitizeDisplayName} from '#/lib/strings/display-names'
-import {useProfileShadow} from '#/state/cache/profile-shadow'
-import {useSession} from '#/state/session'
-import {
-  DropdownItem,
-  NativeDropdown,
-} from '#/view/com/util/forms/NativeDropdown'
-import * as Toast from '#/view/com/util/Toast'
-import {atoms as a, select, useTheme} from '#/alf'
-import {Button} from '#/components/Button'
-import {useFollowMethods} from '#/components/hooks/useFollowMethods'
-import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
-
-export function AviFollowButton({
-  author,
-  moderation,
-  children,
-}: {
-  author: AppBskyActorDefs.ProfileViewBasic
-  moderation: ModerationDecision
-  children: React.ReactNode
-}) {
-  const {_} = useLingui()
-  const t = useTheme()
-  const profile = useProfileShadow(author)
-  const {follow} = useFollowMethods({
-    profile: profile,
-    logContext: 'AvatarButton',
-  })
-  const {currentAccount, hasSession} = useSession()
-  const navigation = useNavigation<NavigationProp>()
-
-  const name = sanitizeDisplayName(
-    profile.displayName || profile.handle,
-    moderation.ui('displayName'),
-  )
-  const isFollowing =
-    profile.viewer?.following || profile.did === currentAccount?.did
-
-  function onPress() {
-    follow()
-    Toast.show(_(msg`Following ${name}`))
-  }
-
-  const items: DropdownItem[] = [
-    {
-      label: _(msg`View profile`),
-      onPress: () => {
-        navigation.navigate('Profile', {name: profile.did})
-      },
-      icon: {
-        ios: {
-          name: 'arrow.up.right.square',
-        },
-        android: '',
-        web: ['far', 'arrow-up-right-from-square'],
-      },
-    },
-    {
-      label: _(msg`Follow ${name}`),
-      onPress: onPress,
-      icon: {
-        ios: {
-          name: 'person.badge.plus',
-        },
-        android: '',
-        web: ['far', 'user-plus'],
-      },
-    },
-  ]
-
-  return hasSession ? (
-    <View style={a.relative}>
-      {children}
-
-      {!isFollowing && (
-        <Button
-          label={_(msg`Open ${name} profile shortcut menu`)}
-          style={[
-            a.rounded_full,
-            a.absolute,
-            {
-              bottom: -7,
-              right: -7,
-            },
-          ]}>
-          <NativeDropdown items={items}>
-            <View
-              style={[
-                {
-                  // An asymmetric hit slop
-                  // to prioritize bottom right taps.
-                  paddingTop: 2,
-                  paddingLeft: 2,
-                  paddingBottom: 6,
-                  paddingRight: 6,
-                },
-                a.align_center,
-                a.justify_center,
-                a.rounded_full,
-              ]}>
-              <View
-                style={[
-                  a.rounded_full,
-                  a.align_center,
-                  select(t.name, {
-                    light: t.atoms.bg_contrast_100,
-                    dim: t.atoms.bg_contrast_100,
-                    dark: t.atoms.bg_contrast_200,
-                  }),
-                  {
-                    borderWidth: 1,
-                    borderColor: t.atoms.bg.backgroundColor,
-                  },
-                ]}>
-                <Plus
-                  size="sm"
-                  fill={
-                    select(t.name, {
-                      light: t.atoms.bg_contrast_600,
-                      dim: t.atoms.bg_contrast_500,
-                      dark: t.atoms.bg_contrast_600,
-                    }).backgroundColor
-                  }
-                />
-              </View>
-            </View>
-          </NativeDropdown>
-        </Button>
-      )}
-    </View>
-  ) : (
-    children
-  )
-}
diff --git a/src/view/com/posts/AviFollowButton.web.tsx b/src/view/com/posts/AviFollowButton.web.tsx
deleted file mode 100644
index 90b2ddeec..000000000
--- a/src/view/com/posts/AviFollowButton.web.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react'
-
-export function AviFollowButton({children}: {children: React.ReactNode}) {
-  return children
-}
diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx
index 181b35026..b4c2b2710 100644
--- a/src/view/com/posts/PostFeed.tsx
+++ b/src/view/com/posts/PostFeed.tsx
@@ -1,4 +1,4 @@
-import React, {memo, useCallback} from 'react'
+import React, {memo, useCallback, useRef} from 'react'
 import {
   ActivityIndicator,
   AppState,
@@ -19,6 +19,7 @@ import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
 
+import {isStatusStillActive} from '#/lib/actor-status'
 import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
 import {logEvent} from '#/lib/statsig/statsig'
@@ -52,6 +53,7 @@ import {
 } from '#/components/feeds/PostFeedVideoGridRow'
 import {TrendingInterstitial} from '#/components/interstitials/Trending'
 import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos'
+import {temp__canBeLive, temp__isStatusValid} from '#/components/live/temp'
 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
 import {FeedShutdownMsg} from './FeedShutdownMsg'
 import {PostFeedErrorMessage} from './PostFeedErrorMessage'
@@ -775,6 +777,31 @@ let PostFeed = ({
     )
   }, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset])
 
+  const seenActorWithStatusRef = useRef<Set<string>>(new Set())
+  const onItemSeen = useCallback(
+    (item: FeedRow) => {
+      feedFeedback.onItemSeen(item)
+      if (item.type === 'sliceItem') {
+        const actor = item.slice.items[item.indexInSlice].post.author
+        if (
+          actor.status &&
+          temp__canBeLive(actor) &&
+          temp__isStatusValid(actor.status) &&
+          isStatusStillActive(actor.status.expiresAt)
+        ) {
+          if (!seenActorWithStatusRef.current.has(actor.did)) {
+            seenActorWithStatusRef.current.add(actor.did)
+            logger.metric('live:view:post', {
+              subject: actor.did,
+              feed,
+            })
+          }
+        }
+      }
+    },
+    [feedFeedback, feed],
+  )
+
   return (
     <View testID={testID} style={style}>
       <List
@@ -797,7 +824,6 @@ let PostFeed = ({
         onEndReachedThreshold={2} // number of posts left to trigger load more
         removeClippedSubviews={true}
         extraData={extraData}
-        // @ts-ignore our .web version only -prf
         desktopFixedHeight={
           desktopFixedHeightOffset ? desktopFixedHeightOffset : true
         }
@@ -805,7 +831,7 @@ let PostFeed = ({
         windowSize={9}
         maxToRenderPerBatch={isIOS ? 5 : 1}
         updateCellsBatchingPeriod={40}
-        onItemSeen={feedFeedback.onItemSeen}
+        onItemSeen={onItemSeen}
       />
     </View>
   )
diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx
index 123a8b0c2..ceb653b9c 100644
--- a/src/view/com/posts/PostFeedItem.tsx
+++ b/src/view/com/posts/PostFeedItem.tsx
@@ -17,6 +17,7 @@ import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
 
+import {useActorStatus} from '#/lib/actor-status'
 import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types'
 import {MAX_POST_LINES} from '#/lib/constants'
 import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
@@ -53,7 +54,6 @@ import {RichText} from '#/components/RichText'
 import {SubtleWebHover} from '#/components/SubtleWebHover'
 import * as bsky from '#/types/bsky'
 import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link'
-import {AviFollowButton} from './AviFollowButton'
 
 interface FeedItemProps {
   record: AppBskyFeedPost.Record
@@ -251,6 +251,8 @@ let FeedItemInner = ({
     ? rootPost.threadgate.record
     : undefined
 
+  const {isActive: live} = useActorStatus(post.author)
+
   return (
     <Link
       testID={`feedItem-by-${post.author.handle}`}
@@ -381,15 +383,14 @@ let FeedItemInner = ({
 
       <View style={styles.layout}>
         <View style={styles.layoutAvi}>
-          <AviFollowButton author={post.author} moderation={moderation}>
-            <PreviewableUserAvatar
-              size={42}
-              profile={post.author}
-              moderation={moderation.ui('avatar')}
-              type={post.author.associated?.labeler ? 'labeler' : 'user'}
-              onBeforePress={onOpenAuthor}
-            />
-          </AviFollowButton>
+          <PreviewableUserAvatar
+            size={42}
+            profile={post.author}
+            moderation={moderation.ui('avatar')}
+            type={post.author.associated?.labeler ? 'labeler' : 'user'}
+            onBeforePress={onOpenAuthor}
+            live={live}
+          />
           {isThreadParent && (
             <View
               style={[
@@ -397,7 +398,7 @@ let FeedItemInner = ({
                 {
                   flexGrow: 1,
                   backgroundColor: pal.colors.replyLine,
-                  marginTop: 4,
+                  marginTop: live ? 8 : 4,
                 },
               ]}
             />