Live (#8354)
This commit is contained in:
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export function AviFollowButton({children}: {children: React.ReactNode}) {
|
||||
return children
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user