diff options
Diffstat (limited to 'src/view/com/posts/Feed.tsx')
-rw-r--r-- | src/view/com/posts/Feed.tsx | 290 |
1 files changed, 156 insertions, 134 deletions
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 905c1e0e0..c623234b8 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -16,10 +16,10 @@ import {useQueryClient} from '@tanstack/react-query' import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' -import {logEvent, useGate} from '#/lib/statsig/statsig' +import {logEvent} from '#/lib/statsig/statsig' import {useTheme} from '#/lib/ThemeContext' import {logger} from '#/logger' -import {isWeb} from '#/platform/detection' +import {isIOS, isWeb} from '#/platform/detection' import {listenPostCreated} from '#/state/events' import {useFeedFeedbackContext} from '#/state/feed-feedback' import {STALE} from '#/state/queries' @@ -32,20 +32,17 @@ import { usePostFeedQuery, } from '#/state/queries/post-feed' import {useSession} from '#/state/session' -import { - ProgressGuide, - SuggestedFeeds, - SuggestedFollows, -} from '#/components/FeedInterstitials' +import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials' import {List, ListRef} from '../util/List' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FeedErrorMessage} from './FeedErrorMessage' +import {FeedItem} from './FeedItem' import {FeedShutdownMsg} from './FeedShutdownMsg' -import {FeedSlice} from './FeedSlice' +import {ViewFullThread} from './ViewFullThread' -type FeedItem = +type FeedRow = | { type: 'loading' key: string @@ -72,76 +69,29 @@ type FeedItem = slice: FeedPostSlice } | { - type: 'interstitialFeeds' + type: 'sliceItem' key: string - params: { - variant: 'default' | string - } - slot: number + slice: FeedPostSlice + indexInSlice: number + showReplyTo: boolean + } + | { + type: 'sliceViewFullThread' + key: string + uri: string } | { type: 'interstitialFollows' key: string - params: { - variant: 'default' | string - } - slot: number } | { type: 'interstitialProgressGuide' key: string - params: { - variant: 'default' | string - } - slot: number } -const feedInterstitialType = 'interstitialFeeds' -const followInterstitialType = 'interstitialFollows' -const progressGuideInterstitialType = 'interstitialProgressGuide' -const interstials: Record< - 'following' | 'discover' | 'profile', - (FeedItem & { - type: - | 'interstitialFeeds' - | 'interstitialFollows' - | 'interstitialProgressGuide' - })[] -> = { - following: [], - discover: [ - { - type: progressGuideInterstitialType, - params: { - variant: 'default', - }, - key: progressGuideInterstitialType, - slot: 0, - }, - { - type: followInterstitialType, - params: { - variant: 'default', - }, - key: followInterstitialType, - slot: 20, - }, - ], - profile: [ - { - type: followInterstitialType, - params: { - variant: 'default', - }, - key: followInterstitialType, - slot: 5, - }, - ], -} - -export function getFeedPostSlice(feedItem: FeedItem): FeedPostSlice | null { - if (feedItem.type === 'slice') { - return feedItem.slice +export function getFeedPostSlice(feedRow: FeedRow): FeedPostSlice | null { + if (feedRow.type === 'sliceItem') { + return feedRow.slice } else { return null } @@ -204,7 +154,6 @@ let Feed = ({ const checkForNewRef = React.useRef<(() => void) | null>(null) const lastFetchRef = React.useRef<number>(Date.now()) const [feedType, feedUri, feedTab] = feed.split('|') - const gate = useGate() const opts = React.useMemo( () => ({enabled, ignoreFilterFor}), @@ -303,8 +252,21 @@ let Feed = ({ } }, [pollInterval]) - const feedItems: FeedItem[] = React.useMemo(() => { - let arr: FeedItem[] = [] + const feedItems: FeedRow[] = React.useMemo(() => { + let feedKind: 'following' | 'discover' | 'profile' | undefined + if (feedType === 'following') { + feedKind = 'following' + } else if (feedUri === DISCOVER_FEED_URI) { + feedKind = 'discover' + } else if ( + feedType === 'author' && + (feedTab === 'posts_and_author_threads' || + feedTab === 'posts_with_replies') + ) { + feedKind = 'profile' + } + + let arr: FeedRow[] = [] if (KNOWN_SHUTDOWN_FEEDS.includes(feedUri)) { arr.push({ type: 'feedShutdownMsg', @@ -323,14 +285,77 @@ let Feed = ({ key: 'empty', }) } else if (data) { + let sliceIndex = -1 for (const page of data?.pages) { - arr = arr.concat( - page.slices.map(s => ({ - type: 'slice', - slice: s, - key: s._reactKey, - })), - ) + for (const slice of page.slices) { + sliceIndex++ + + if (hasSession) { + if (feedKind === 'discover') { + if (sliceIndex === 0) { + arr.push({ + type: 'interstitialProgressGuide', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } else if (sliceIndex === 20) { + arr.push({ + type: 'interstitialFollows', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } + } else if (feedKind === 'profile') { + if (sliceIndex === 5) { + arr.push({ + type: 'interstitialFollows', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } + } + } + + if (slice.isIncompleteThread && slice.items.length >= 3) { + const beforeLast = slice.items.length - 2 + const last = slice.items.length - 1 + arr.push({ + type: 'sliceItem', + key: slice.items[0]._reactKey, + slice: slice, + indexInSlice: 0, + showReplyTo: false, + }) + arr.push({ + type: 'sliceViewFullThread', + key: slice._reactKey + '-viewFullThread', + uri: slice.items[0].uri, + }) + arr.push({ + type: 'sliceItem', + key: slice.items[beforeLast]._reactKey, + slice: slice, + indexInSlice: beforeLast, + showReplyTo: + slice.items[beforeLast].parentAuthor?.did !== + slice.items[beforeLast].post.author.did, + }) + arr.push({ + type: 'sliceItem', + key: slice.items[last]._reactKey, + slice: slice, + indexInSlice: last, + showReplyTo: false, + }) + } else { + for (let i = 0; i < slice.items.length; i++) { + arr.push({ + type: 'sliceItem', + key: slice.items[i]._reactKey, + slice: slice, + indexInSlice: i, + showReplyTo: i === 0, + }) + } + } + } } } if (isError && !isEmpty) { @@ -346,45 +371,6 @@ let Feed = ({ }) } - if (hasSession) { - let feedKind: 'following' | 'discover' | 'profile' | undefined - if (feedType === 'following') { - feedKind = 'following' - } else if (feedUri === DISCOVER_FEED_URI) { - feedKind = 'discover' - } else if ( - feedType === 'author' && - (feedTab === 'posts_and_author_threads' || - feedTab === 'posts_with_replies') - ) { - feedKind = 'profile' - } - - if (feedKind) { - for (const interstitial of interstials[feedKind]) { - const shouldShow = - (interstitial.type === feedInterstitialType && - gate('suggested_feeds_interstitial')) || - interstitial.type === followInterstitialType || - interstitial.type === progressGuideInterstitialType - - if (shouldShow) { - const variant = 'default' // replace with experiment variant - const int = { - ...interstitial, - params: {variant}, - // overwrite key with unique value - key: [interstitial.type, variant, lastFetchedAt].join(':'), - } - - if (arr.length > interstitial.slot) { - arr.splice(interstitial.slot, 0, int) - } - } - } - } - } - return arr }, [ isFetched, @@ -395,7 +381,6 @@ let Feed = ({ feedType, feedUri, feedTab, - gate, hasSession, ]) @@ -403,7 +388,7 @@ let Feed = ({ // = const onRefresh = React.useCallback(async () => { - logEvent('feed:refresh:sampled', { + logEvent('feed:refresh', { feedType: feedType, feedUrl: feed, reason: 'pull-to-refresh', @@ -421,7 +406,7 @@ let Feed = ({ const onEndReached = React.useCallback(async () => { if (isFetching || !hasNextPage || isError) return - logEvent('feed:endReached:sampled', { + logEvent('feed:endReached', { feedType: feedType, feedUrl: feed, itemCount: feedItems.length, @@ -454,10 +439,10 @@ let Feed = ({ // = const renderItem = React.useCallback( - ({item, index}: ListRenderItemInfo<FeedItem>) => { - if (item.type === 'empty') { + ({item: row, index: rowIndex}: ListRenderItemInfo<FeedRow>) => { + if (row.type === 'empty') { return renderEmptyState() - } else if (item.type === 'error') { + } else if (row.type === 'error') { return ( <FeedErrorMessage feedDesc={feed} @@ -466,7 +451,7 @@ let Feed = ({ savedFeedConfig={savedFeedConfig} /> ) - } else if (item.type === 'loadMoreError') { + } else if (row.type === 'loadMoreError') { return ( <LoadMoreRetryBtn label={_( @@ -475,25 +460,48 @@ let Feed = ({ onPress={onPressRetryLoadMore} /> ) - } else if (item.type === 'loading') { + } else if (row.type === 'loading') { return <PostFeedLoadingPlaceholder /> - } else if (item.type === 'feedShutdownMsg') { + } else if (row.type === 'feedShutdownMsg') { return <FeedShutdownMsg feedUri={feedUri} /> - } else if (item.type === feedInterstitialType) { - return <SuggestedFeeds /> - } else if (item.type === followInterstitialType) { + } else if (row.type === 'interstitialFollows') { return <SuggestedFollows feed={feed} /> - } else if (item.type === progressGuideInterstitialType) { + } else if (row.type === 'interstitialProgressGuide') { return <ProgressGuide /> - } else if (item.type === 'slice') { - if (item.slice.isFallbackMarker) { + } else if (row.type === 'sliceItem') { + const slice = row.slice + if (slice.isFallbackMarker) { // HACK // tell the user we fell back to discover // see home.ts (feed api) for more info // -prf return <DiscoverFallbackHeader /> } - return <FeedSlice slice={item.slice} hideTopBorder={index === 0} /> + const indexInSlice = row.indexInSlice + const item = slice.items[indexInSlice] + return ( + <FeedItem + post={item.post} + record={item.record} + reason={indexInSlice === 0 ? slice.reason : undefined} + feedContext={slice.feedContext} + moderation={item.moderation} + parentAuthor={item.parentAuthor} + showReplyTo={row.showReplyTo} + isThreadParent={isThreadParentAt(slice.items, indexInSlice)} + isThreadChild={isThreadChildAt(slice.items, indexInSlice)} + isThreadLastChild={ + isThreadChildAt(slice.items, indexInSlice) && + slice.items.length === indexInSlice + 1 + } + isParentBlocked={item.isParentBlocked} + isParentNotFound={item.isParentNotFound} + hideTopBorder={rowIndex === 0 && indexInSlice === 0} + rootPost={slice.items[0].post} + /> + ) + } else if (row.type === 'sliceViewFullThread') { + return <ViewFullThread uri={row.uri} /> } else { return null } @@ -561,7 +569,7 @@ let Feed = ({ } initialNumToRender={initialNumToRenderOverride ?? initialNumToRender} windowSize={9} - maxToRenderPerBatch={5} + maxToRenderPerBatch={isIOS ? 5 : 1} updateCellsBatchingPeriod={40} onItemSeen={feedFeedback.onItemSeen} /> @@ -574,3 +582,17 @@ export {Feed} const styles = StyleSheet.create({ feedFooter: {paddingTop: 20}, }) + +function isThreadParentAt<T>(arr: Array<T>, i: number) { + if (arr.length === 1) { + return false + } + return i < arr.length - 1 +} + +function isThreadChildAt<T>(arr: Array<T>, i: number) { + if (arr.length === 1) { + return false + } + return i > 0 +} |