diff options
-rw-r--r-- | src/state/queries/notifications/feed.ts | 18 | ||||
-rw-r--r-- | src/state/queries/post-feed.ts | 113 | ||||
-rw-r--r-- | src/view/com/posts/Feed.tsx | 30 | ||||
-rw-r--r-- | src/view/com/posts/FeedSlice.tsx | 34 |
4 files changed, 112 insertions, 83 deletions
diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index d2c150c31..3f9700c79 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -16,6 +16,7 @@ * 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead. */ +import {useEffect} from 'react' import {AppBskyFeedDefs} from '@atproto/api' import { useInfiniteQuery, @@ -49,7 +50,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { const unreads = useUnreadNotificationsApi() const enabled = opts?.enabled !== false - return useInfiniteQuery< + const query = useInfiniteQuery< FeedPage, Error, InfiniteData<FeedPage>, @@ -85,6 +86,21 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { getNextPageParam: lastPage => lastPage.cursor, enabled, }) + + useEffect(() => { + const {isFetching, hasNextPage, data} = query + + let count = 0 + for (const page of data?.pages || []) { + count += page.items.length + } + + if (!isFetching && hasNextPage && count < PAGE_SIZE) { + query.fetchNextPage() + } + }, [query]) + + return query } /** diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index de18865ea..dede8a956 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -1,5 +1,10 @@ -import {useCallback} from 'react' -import {AppBskyFeedDefs, AppBskyFeedPost, moderatePost} from '@atproto/api' +import {useCallback, useEffect} from 'react' +import { + AppBskyFeedDefs, + AppBskyFeedPost, + moderatePost, + PostModeration, +} from '@atproto/api' import { useInfiniteQuery, InfiniteData, @@ -24,6 +29,7 @@ import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const' import {getModerationOpts} from '#/state/queries/preferences/moderation' import {KnownError} from '#/view/com/posts/FeedErrorMessage' import {embedViewRecordToPostView, getEmbeddedPost} from './util' +import {useModerationOpts} from './preferences' type ActorDid = string type AuthorFilter = @@ -57,6 +63,7 @@ export interface FeedPostSliceItem { post: AppBskyFeedDefs.PostView record: AppBskyFeedPost.Record reason?: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource + moderation: PostModeration } export interface FeedPostSlice { @@ -79,16 +86,19 @@ export interface FeedPage { slices: FeedPostSlice[] } +const PAGE_SIZE = 30 + export function usePostFeedQuery( feedDesc: FeedDescriptor, params?: FeedParams, - opts?: {enabled?: boolean}, + opts?: {enabled?: boolean; ignoreFilterFor?: string}, ) { const queryClient = useQueryClient() const feedTuners = useFeedTuners(feedDesc) - const enabled = opts?.enabled !== false + const moderationOpts = useModerationOpts() + const enabled = opts?.enabled !== false && Boolean(moderationOpts) - return useInfiniteQuery< + const query = useInfiniteQuery< FeedPageUnselected, Error, InfiniteData<FeedPage>, @@ -108,7 +118,7 @@ export function usePostFeedQuery( cursor: undefined, } - const res = await api.fetch({cursor, limit: 30}) + const res = await api.fetch({cursor, limit: PAGE_SIZE}) precacheResolvedUris(queryClient, res.feed) // precache the handle->did resolution /* @@ -146,40 +156,79 @@ export function usePostFeedQuery( api: page.api, tuner, cursor: page.cursor, - slices: tuner.tune(page.feed).map(slice => ({ - _reactKey: slice._reactKey, - rootUri: slice.rootItem.post.uri, - isThread: - slice.items.length > 1 && - slice.items.every( - item => - item.post.author.did === slice.items[0].post.author.did, - ), - items: slice.items - .map((item, i) => { + slices: tuner + .tune(page.feed) + .map(slice => { + const moderations = slice.items.map(item => + moderatePost(item.post, moderationOpts!), + ) + + // apply moderation filter + for (let i = 0; i < slice.items.length; i++) { if ( - AppBskyFeedPost.isRecord(item.post.record) && - AppBskyFeedPost.validateRecord(item.post.record).success + moderations[i]?.content.filter && + slice.items[i].post.author.did !== opts?.ignoreFilterFor ) { - return { - _reactKey: `${slice._reactKey}-${i}`, - uri: item.post.uri, - post: item.post, - record: item.post.record, - reason: - i === 0 && slice.source ? slice.source : item.reason, - } + return undefined } - return undefined - }) - .filter(Boolean) as FeedPostSliceItem[], - })), + } + + return { + _reactKey: slice._reactKey, + rootUri: slice.rootItem.post.uri, + isThread: + slice.items.length > 1 && + slice.items.every( + item => + item.post.author.did === slice.items[0].post.author.did, + ), + items: slice.items + .map((item, i) => { + if ( + AppBskyFeedPost.isRecord(item.post.record) && + AppBskyFeedPost.validateRecord(item.post.record).success + ) { + return { + _reactKey: `${slice._reactKey}-${i}`, + uri: item.post.uri, + post: item.post, + record: item.post.record, + reason: + i === 0 && slice.source + ? slice.source + : item.reason, + moderation: moderations[i], + } + } + return undefined + }) + .filter(Boolean) as FeedPostSliceItem[], + } + }) + .filter(Boolean) as FeedPostSlice[], })), } }, - [feedTuners, params?.disableTuner], + [feedTuners, params?.disableTuner, moderationOpts, opts?.ignoreFilterFor], ), }) + + useEffect(() => { + const {isFetching, hasNextPage, data} = query + + let count = 0 + for (const page of data?.pages || []) { + for (const slice of page.slices) { + count += slice.items.length + } + } + + if (!isFetching && hasNextPage && count < PAGE_SIZE) { + query.fetchNextPage() + } + }, [query]) + + return query } export async function pollLatest(page: FeedPage | undefined) { diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 371f73b87..03fa95eea 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -27,7 +27,6 @@ import { usePostFeedQuery, pollLatest, } from '#/state/queries/post-feed' -import {useModerationOpts} from '#/state/queries/preferences' import {isWeb} from '#/platform/detection' import {listenPostCreated} from '#/state/events' import {useSession} from '#/state/session' @@ -82,8 +81,10 @@ let Feed = ({ const [isPTRing, setIsPTRing] = React.useState(false) const checkForNewRef = React.useRef<(() => void) | null>(null) - const moderationOpts = useModerationOpts() - const opts = React.useMemo(() => ({enabled}), [enabled]) + const opts = React.useMemo( + () => ({enabled, ignoreFilterFor}), + [enabled, ignoreFilterFor], + ) const { data, isFetching, @@ -144,7 +145,7 @@ let Feed = ({ const feedItems = React.useMemo(() => { let arr: any[] = [] - if (isFetched && moderationOpts) { + if (isFetched) { if (isError && isEmpty) { arr = arr.concat([ERROR_ITEM]) } @@ -162,7 +163,7 @@ let Feed = ({ arr.push(LOADING_ITEM) } return arr - }, [isFetched, isError, isEmpty, data, moderationOpts]) + }, [isFetched, isError, isEmpty, data]) // events // = @@ -224,24 +225,9 @@ let Feed = ({ } else if (item === LOADING_ITEM) { return <PostFeedLoadingPlaceholder /> } - return ( - <FeedSlice - slice={item} - // we check for this before creating the feedItems array - moderationOpts={moderationOpts!} - ignoreFilterFor={ignoreFilterFor} - /> - ) + return <FeedSlice slice={item} /> }, - [ - feed, - error, - onPressTryAgain, - onPressRetryLoadMore, - renderEmptyState, - moderationOpts, - ignoreFilterFor, - ], + [feed, error, onPressTryAgain, onPressRetryLoadMore, renderEmptyState], ) const shouldRenderEndOfFeed = diff --git a/src/view/com/posts/FeedSlice.tsx b/src/view/com/posts/FeedSlice.tsx index a3bacdc1e..c1a8c0e18 100644 --- a/src/view/com/posts/FeedSlice.tsx +++ b/src/view/com/posts/FeedSlice.tsx @@ -1,7 +1,7 @@ import React, {memo} from 'react' import {StyleSheet, View} from 'react-native' import {FeedPostSlice} from '#/state/queries/post-feed' -import {AtUri, moderatePost, ModerationOpts} from '@atproto/api' +import {AtUri} from '@atproto/api' import {Link} from '../util/Link' import {Text} from '../util/text/Text' import Svg, {Circle, Line} from 'react-native-svg' @@ -9,29 +9,7 @@ import {FeedItem} from './FeedItem' import {usePalette} from 'lib/hooks/usePalette' import {makeProfileLink} from 'lib/routes/links' -let FeedSlice = ({ - slice, - ignoreFilterFor, - moderationOpts, -}: { - slice: FeedPostSlice - ignoreFilterFor?: string - moderationOpts: ModerationOpts -}): React.ReactNode => { - const moderations = React.useMemo(() => { - return slice.items.map(item => moderatePost(item.post, moderationOpts)) - }, [slice, moderationOpts]) - - // apply moderation filter - for (let i = 0; i < slice.items.length; i++) { - if ( - moderations[i]?.content.filter && - slice.items[i].post.author.did !== ignoreFilterFor - ) { - return null - } - } - +let FeedSlice = ({slice}: {slice: FeedPostSlice}): React.ReactNode => { if (slice.isThread && slice.items.length > 3) { const last = slice.items.length - 1 return ( @@ -41,7 +19,7 @@ let FeedSlice = ({ post={slice.items[0].post} record={slice.items[0].record} reason={slice.items[0].reason} - moderation={moderations[0]} + moderation={slice.items[0].moderation} isThreadParent={isThreadParentAt(slice.items, 0)} isThreadChild={isThreadChildAt(slice.items, 0)} /> @@ -50,7 +28,7 @@ let FeedSlice = ({ post={slice.items[1].post} record={slice.items[1].record} reason={slice.items[1].reason} - moderation={moderations[1]} + moderation={slice.items[1].moderation} isThreadParent={isThreadParentAt(slice.items, 1)} isThreadChild={isThreadChildAt(slice.items, 1)} /> @@ -60,7 +38,7 @@ let FeedSlice = ({ post={slice.items[last].post} record={slice.items[last].record} reason={slice.items[last].reason} - moderation={moderations[last]} + moderation={slice.items[last].moderation} isThreadParent={isThreadParentAt(slice.items, last)} isThreadChild={isThreadChildAt(slice.items, last)} isThreadLastChild @@ -77,7 +55,7 @@ let FeedSlice = ({ post={slice.items[i].post} record={slice.items[i].record} reason={slice.items[i].reason} - moderation={moderations[i]} + moderation={slice.items[i].moderation} isThreadParent={isThreadParentAt(slice.items, i)} isThreadChild={isThreadChildAt(slice.items, i)} isThreadLastChild={ |