diff options
Diffstat (limited to 'src/screens')
-rw-r--r-- | src/screens/Search/Explore.tsx | 14 | ||||
-rw-r--r-- | src/screens/Search/modules/ExploreFeedPreviews.tsx | 264 |
2 files changed, 9 insertions, 269 deletions
diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index 33d8d343c..b8fc644e1 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -15,6 +15,10 @@ import {logger} from '#/logger' import {type MetricEvents} from '#/logger/metrics' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorSearchPaginated} from '#/state/queries/actor-search' +import { + type FeedPreviewItem, + useFeedPreviews, +} from '#/state/queries/explore-feed-previews' import {useGetPopularFeedsQuery} from '#/state/queries/feed' import {usePreferencesQuery} from '#/state/queries/preferences' import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' @@ -48,10 +52,6 @@ import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' import * as ModuleHeader from './components/ModuleHeader' import { - type FeedPreviewItem, - useFeedPreviews, -} from './modules/ExploreFeedPreviews' -import { SuggestedAccountsTabBar, SuggestedProfileCard, useLoadEnoughProfiles, @@ -900,7 +900,7 @@ export function Explore({ <List data={items} renderItem={renderItem} - keyExtractor={item => item.key} + keyExtractor={keyExtractor} desktopFixedHeight contentContainerStyle={{paddingBottom: 100}} keyboardShouldPersistTaps="handled" @@ -914,6 +914,10 @@ export function Explore({ ) } +function keyExtractor(item: FeedPreviewItem) { + return item.key +} + const viewabilityConfig: ViewabilityConfig = { itemVisiblePercentThreshold: 100, } diff --git a/src/screens/Search/modules/ExploreFeedPreviews.tsx b/src/screens/Search/modules/ExploreFeedPreviews.tsx deleted file mode 100644 index 30aa00a3f..000000000 --- a/src/screens/Search/modules/ExploreFeedPreviews.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import {useMemo} from 'react' -import {type AppBskyFeedDefs, moderatePost} from '@atproto/api' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useInfiniteQuery} from '@tanstack/react-query' - -import {CustomFeedAPI} from '#/lib/api/feed/custom' -import {aggregateUserInterests} from '#/lib/api/feed/utils' -import {FeedTuner} from '#/lib/api/feed-manip' -import {cleanError} from '#/lib/strings/errors' -import {useModerationOpts} from '#/state/preferences/moderation-opts' -import { - type FeedPostSlice, - type FeedPostSliceItem, -} from '#/state/queries/post-feed' -import {usePreferencesQuery} from '#/state/queries/preferences' -import {useAgent} from '#/state/session' - -const RQKEY_ROOT = 'feed-previews' -const RQKEY = (feeds: string[]) => [RQKEY_ROOT, feeds] - -const LIMIT = 8 // sliced to 6, overfetch to account for moderation - -export type FeedPreviewItem = - | { - type: 'topBorder' - key: string - } - | { - type: 'preview:loading' - key: string - } - | { - type: 'preview:error' - key: string - message: string - error: string - } - | { - type: 'preview:loadMoreError' - key: string - } - | { - type: 'preview:empty' - key: string - } - | { - type: 'preview:header' - key: string - feed: AppBskyFeedDefs.GeneratorView - } - | { - type: 'preview:footer' - key: string - } - // copied from PostFeed.tsx - | { - type: 'preview:sliceItem' - key: string - slice: FeedPostSlice - indexInSlice: number - showReplyTo: boolean - hideTopBorder: boolean - } - | { - type: 'preview:sliceViewFullThread' - key: string - uri: string - } - -export function useFeedPreviews(feeds: AppBskyFeedDefs.GeneratorView[]) { - const uris = feeds.map(feed => feed.uri) - const {_} = useLingui() - const agent = useAgent() - const {data: preferences} = usePreferencesQuery() - const userInterests = aggregateUserInterests(preferences) - const moderationOpts = useModerationOpts() - const enabled = feeds.length > 0 - - const query = useInfiniteQuery({ - enabled, - queryKey: RQKEY(uris), - queryFn: async ({pageParam}) => { - const feed = feeds[pageParam] - const api = new CustomFeedAPI({ - agent, - feedParams: {feed: feed.uri}, - userInterests, - }) - const data = await api.fetch({cursor: undefined, limit: LIMIT}) - return { - feed, - posts: data.feed, - } - }, - initialPageParam: 0, - getNextPageParam: (_p, _a, count) => - count < feeds.length ? count + 1 : undefined, - }) - - const {data, isFetched, isError, isPending, error} = query - - return { - query, - data: useMemo<FeedPreviewItem[]>(() => { - const items: FeedPreviewItem[] = [] - - if (!enabled) return items - - const isEmpty = - !isPending && !data?.pages?.some(page => page.posts.length) - - if (isFetched) { - if (isError && isEmpty) { - items.push({ - type: 'preview:error', - key: 'error', - message: _(msg`An error occurred while fetching the feed.`), - error: cleanError(error), - }) - } else if (isEmpty) { - items.push({ - type: 'preview:empty', - key: 'empty', - }) - } else if (data) { - for (let pageIndex = 0; pageIndex < data.pages.length; pageIndex++) { - const page = data.pages[pageIndex] - // default feed tuner - we just want it to slice up the feed - const tuner = new FeedTuner([]) - const slices: FeedPreviewItem[] = [] - - let rowIndex = 0 - for (const item of tuner.tune(page.posts)) { - if (item.isFallbackMarker) continue - - const moderations = item.items.map(item => - moderatePost(item.post, moderationOpts!), - ) - - // apply moderation filters - item.items = item.items.filter((_, i) => { - return !moderations[i]?.ui('contentList').filter - }) - - const slice = { - _reactKey: item._reactKey, - _isFeedPostSlice: true, - isFallbackMarker: false, - isIncompleteThread: item.isIncompleteThread, - feedContext: item.feedContext, - reason: item.reason, - feedPostUri: item.feedPostUri, - items: item.items.slice(0, 6).map((subItem, i) => { - const feedPostSliceItem: FeedPostSliceItem = { - _reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`, - uri: subItem.post.uri, - post: subItem.post, - record: subItem.record, - moderation: moderations[i], - parentAuthor: subItem.parentAuthor, - isParentBlocked: subItem.isParentBlocked, - isParentNotFound: subItem.isParentNotFound, - } - return feedPostSliceItem - }), - } - if (slice.isIncompleteThread && slice.items.length >= 3) { - const beforeLast = slice.items.length - 2 - const last = slice.items.length - 1 - slices.push({ - type: 'preview:sliceItem', - key: slice.items[0]._reactKey, - slice: slice, - indexInSlice: 0, - showReplyTo: false, - hideTopBorder: rowIndex === 0, - }) - slices.push({ - type: 'preview:sliceViewFullThread', - key: slice._reactKey + '-viewFullThread', - uri: slice.items[0].uri, - }) - slices.push({ - type: 'preview:sliceItem', - key: slice.items[beforeLast]._reactKey, - slice: slice, - indexInSlice: beforeLast, - showReplyTo: - slice.items[beforeLast].parentAuthor?.did !== - slice.items[beforeLast].post.author.did, - hideTopBorder: false, - }) - slices.push({ - type: 'preview:sliceItem', - key: slice.items[last]._reactKey, - slice: slice, - indexInSlice: last, - showReplyTo: false, - hideTopBorder: false, - }) - } else { - for (let i = 0; i < slice.items.length; i++) { - slices.push({ - type: 'preview:sliceItem', - key: slice.items[i]._reactKey, - slice: slice, - indexInSlice: i, - showReplyTo: i === 0, - hideTopBorder: i === 0 && rowIndex === 0, - }) - } - } - - rowIndex++ - } - - if (slices.length > 0) { - if (pageIndex > 0) { - items.push({ - type: 'topBorder', - key: `topBorder-${page.feed.uri}`, - }) - } - items.push( - { - type: 'preview:footer', - key: `footer-${page.feed.uri}`, - }, - { - type: 'preview:header', - key: `header-${page.feed.uri}`, - feed: page.feed, - }, - ...slices, - ) - } - } - } else if (isError && !isEmpty) { - items.push({ - type: 'preview:loadMoreError', - key: 'loadMoreError', - }) - } - } else { - items.push({ - type: 'preview:loading', - key: 'loading', - }) - } - - return items - }, [ - enabled, - data, - isFetched, - isError, - isPending, - moderationOpts, - _, - error, - ]), - } -} |