Remove getSuggestedFollowsByActor fallbacks and use recIdStr (#9988)
This commit is contained in:
@@ -12,6 +12,7 @@ import {msg} from '@lingui/core/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {type NavigationProp} from '#/lib/routes/types'
|
import {type NavigationProp} from '#/lib/routes/types'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
@@ -19,8 +20,8 @@ import {useGetPopularFeedsQuery} from '#/state/queries/feed'
|
|||||||
import {type FeedDescriptor} from '#/state/queries/post-feed'
|
import {type FeedDescriptor} from '#/state/queries/post-feed'
|
||||||
import {useProfilesQuery} from '#/state/queries/profile'
|
import {useProfilesQuery} from '#/state/queries/profile'
|
||||||
import {
|
import {
|
||||||
|
suggestedFollowsByActorQueryKey,
|
||||||
useSuggestedFollowsByActorQuery,
|
useSuggestedFollowsByActorQuery,
|
||||||
useSuggestedFollowsQuery,
|
|
||||||
} from '#/state/queries/suggested-follows'
|
} from '#/state/queries/suggested-follows'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import * as userActionHistory from '#/state/userActionHistory'
|
import * as userActionHistory from '#/state/userActionHistory'
|
||||||
@@ -170,10 +171,12 @@ function useExperimentalSuggestedUsersQuery() {
|
|||||||
if (followSuggestions.length > 0) {
|
if (followSuggestions.length > 0) {
|
||||||
suggestedDids = [
|
suggestedDids = [
|
||||||
// It's ok if these will pick the same item (weighed by its frequency)
|
// It's ok if these will pick the same item (weighed by its frequency)
|
||||||
|
/* eslint-disable react-hooks/purity */
|
||||||
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
||||||
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
||||||
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
||||||
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
followSuggestions[Math.floor(Math.random() * followSuggestions.length)],
|
||||||
|
/* eslint-enable react-hooks/purity */
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
const seenDids = seen
|
const seenDids = seen
|
||||||
@@ -216,9 +219,6 @@ export function SuggestedFollows({feed}: {feed: FeedDescriptor}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SuggestedFollowsProfile({did}: {did: string}) {
|
export function SuggestedFollowsProfile({did}: {did: string}) {
|
||||||
const {gtMobile} = useBreakpoints()
|
|
||||||
const moderationOpts = useModerationOpts()
|
|
||||||
const maxLength = gtMobile ? 4 : 6
|
|
||||||
const {
|
const {
|
||||||
isLoading: isSuggestionsLoading,
|
isLoading: isSuggestionsLoading,
|
||||||
data,
|
data,
|
||||||
@@ -226,76 +226,37 @@ export function SuggestedFollowsProfile({did}: {did: string}) {
|
|||||||
} = useSuggestedFollowsByActorQuery({
|
} = useSuggestedFollowsByActorQuery({
|
||||||
did,
|
did,
|
||||||
})
|
})
|
||||||
const {
|
const queryClient = useQueryClient()
|
||||||
data: moreSuggestions,
|
|
||||||
fetchNextPage,
|
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
} = useSuggestedFollowsQuery({limit: 25})
|
|
||||||
|
|
||||||
const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set())
|
const onDismiss = useCallback(
|
||||||
|
(dismissedDid: string) => {
|
||||||
|
queryClient.setQueryData(
|
||||||
|
suggestedFollowsByActorQueryKey(did),
|
||||||
|
(previous: typeof data) => {
|
||||||
|
if (!previous) return previous
|
||||||
|
return {
|
||||||
|
...previous,
|
||||||
|
suggestions: previous.suggestions.filter(
|
||||||
|
s => s.did !== dismissedDid,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[did, queryClient],
|
||||||
|
)
|
||||||
|
|
||||||
const onDismiss = useCallback((dismissedDid: string) => {
|
const profiles = useMemo(() => {
|
||||||
setDismissedDids(prev => new Set(prev).add(dismissedDid))
|
return (data?.suggestions ?? []).map(profile => ({
|
||||||
}, [])
|
actor: profile,
|
||||||
|
recId: data?.recId,
|
||||||
// Combine profiles from the actor-specific query with fallback suggestions
|
}))
|
||||||
const allProfiles = useMemo(() => {
|
}, [data?.suggestions, data?.recId])
|
||||||
const actorProfiles = data?.suggestions ?? []
|
|
||||||
const fallbackProfiles =
|
|
||||||
moreSuggestions?.pages.flatMap(page =>
|
|
||||||
page.actors.map(actor => ({actor, recId: page.recId})),
|
|
||||||
) ?? []
|
|
||||||
|
|
||||||
// Dedupe by did, preferring actor-specific profiles
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const combined: {actor: bsky.profile.AnyProfileView; recId?: number}[] = []
|
|
||||||
|
|
||||||
for (const profile of actorProfiles) {
|
|
||||||
if (!seen.has(profile.did)) {
|
|
||||||
seen.add(profile.did)
|
|
||||||
combined.push({actor: profile, recId: data?.recId})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const profile of fallbackProfiles) {
|
|
||||||
if (!seen.has(profile.actor.did) && profile.actor.did !== did) {
|
|
||||||
seen.add(profile.actor.did)
|
|
||||||
combined.push(profile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined
|
|
||||||
}, [data?.suggestions, moreSuggestions?.pages, did, data?.recId])
|
|
||||||
|
|
||||||
const filteredProfiles = useMemo(() => {
|
|
||||||
return allProfiles.filter(p => !dismissedDids.has(p.actor.did))
|
|
||||||
}, [allProfiles, dismissedDids])
|
|
||||||
|
|
||||||
// Fetch more when running low
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
moderationOpts &&
|
|
||||||
filteredProfiles.length < maxLength &&
|
|
||||||
hasNextPage &&
|
|
||||||
!isFetchingNextPage
|
|
||||||
) {
|
|
||||||
void fetchNextPage()
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
filteredProfiles.length,
|
|
||||||
maxLength,
|
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
fetchNextPage,
|
|
||||||
moderationOpts,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfileGrid
|
<ProfileGrid
|
||||||
isSuggestionsLoading={isSuggestionsLoading}
|
isSuggestionsLoading={isSuggestionsLoading}
|
||||||
profiles={filteredProfiles}
|
profiles={profiles}
|
||||||
totalProfileCount={allProfiles.length}
|
|
||||||
error={error}
|
error={error}
|
||||||
viewContext="profile"
|
viewContext="profile"
|
||||||
onDismiss={onDismiss}
|
onDismiss={onDismiss}
|
||||||
@@ -304,21 +265,11 @@ export function SuggestedFollowsProfile({did}: {did: string}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SuggestedFollowsHome() {
|
export function SuggestedFollowsHome() {
|
||||||
const {gtMobile} = useBreakpoints()
|
|
||||||
const moderationOpts = useModerationOpts()
|
|
||||||
const maxLength = gtMobile ? 4 : 6
|
|
||||||
const {
|
const {
|
||||||
isLoading: isSuggestionsLoading,
|
isLoading: isSuggestionsLoading,
|
||||||
profiles: experimentalProfiles,
|
profiles: experimentalProfiles,
|
||||||
error: experimentalError,
|
error: experimentalError,
|
||||||
} = useExperimentalSuggestedUsersQuery()
|
} = useExperimentalSuggestedUsersQuery()
|
||||||
const {
|
|
||||||
data: moreSuggestions,
|
|
||||||
fetchNextPage,
|
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
error: suggestionsError,
|
|
||||||
} = useSuggestedFollowsQuery({limit: 25})
|
|
||||||
|
|
||||||
const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set())
|
const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set())
|
||||||
|
|
||||||
@@ -326,66 +277,29 @@ export function SuggestedFollowsHome() {
|
|||||||
setDismissedDids(prev => new Set(prev).add(did))
|
setDismissedDids(prev => new Set(prev).add(did))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Combine profiles from experimental query with paginated suggestions
|
|
||||||
const allProfiles = useMemo(() => {
|
const allProfiles = useMemo(() => {
|
||||||
const fallbackProfiles =
|
const result: Array<{
|
||||||
moreSuggestions?.pages.flatMap(page =>
|
|
||||||
page.actors.map(actor => ({actor, recId: page.recId})),
|
|
||||||
) ?? []
|
|
||||||
|
|
||||||
// Dedupe by did, preferring experimental profiles
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const combined: Array<{
|
|
||||||
actor: bsky.profile.AnyProfileView
|
actor: bsky.profile.AnyProfileView
|
||||||
recId?: number
|
recId?: string
|
||||||
}> = []
|
}> = []
|
||||||
|
|
||||||
for (const profile of experimentalProfiles) {
|
for (const profile of experimentalProfiles) {
|
||||||
if (!seen.has(profile.did)) {
|
result.push({actor: profile, recId: undefined})
|
||||||
seen.add(profile.did)
|
|
||||||
combined.push({actor: profile, recId: undefined})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const profile of fallbackProfiles) {
|
return result
|
||||||
if (!seen.has(profile.actor.did)) {
|
}, [experimentalProfiles])
|
||||||
seen.add(profile.actor.did)
|
|
||||||
combined.push(profile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined
|
|
||||||
}, [experimentalProfiles, moreSuggestions?.pages])
|
|
||||||
|
|
||||||
const filteredProfiles = useMemo(() => {
|
const filteredProfiles = useMemo(() => {
|
||||||
return allProfiles.filter(p => !dismissedDids.has(p.actor.did))
|
return allProfiles.filter(p => !dismissedDids.has(p.actor.did))
|
||||||
}, [allProfiles, dismissedDids])
|
}, [allProfiles, dismissedDids])
|
||||||
|
|
||||||
// Fetch more when running low
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
moderationOpts &&
|
|
||||||
filteredProfiles.length < maxLength &&
|
|
||||||
hasNextPage &&
|
|
||||||
!isFetchingNextPage
|
|
||||||
) {
|
|
||||||
void fetchNextPage()
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
filteredProfiles.length,
|
|
||||||
maxLength,
|
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
fetchNextPage,
|
|
||||||
moderationOpts,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfileGrid
|
<ProfileGrid
|
||||||
isSuggestionsLoading={isSuggestionsLoading}
|
isSuggestionsLoading={isSuggestionsLoading}
|
||||||
profiles={filteredProfiles}
|
profiles={filteredProfiles}
|
||||||
totalProfileCount={allProfiles.length}
|
totalProfileCount={allProfiles.length}
|
||||||
error={experimentalError || suggestionsError}
|
error={experimentalError}
|
||||||
viewContext="feed"
|
viewContext="feed"
|
||||||
onDismiss={onDismiss}
|
onDismiss={onDismiss}
|
||||||
/>
|
/>
|
||||||
@@ -400,14 +314,16 @@ export function ProfileGrid({
|
|||||||
viewContext = 'feed',
|
viewContext = 'feed',
|
||||||
onDismiss,
|
onDismiss,
|
||||||
isVisible = true,
|
isVisible = true,
|
||||||
|
onRequestHide,
|
||||||
}: {
|
}: {
|
||||||
isSuggestionsLoading: boolean
|
isSuggestionsLoading: boolean
|
||||||
profiles: {actor: bsky.profile.AnyProfileView; recId?: number}[]
|
profiles: {actor: bsky.profile.AnyProfileView; recId?: string}[]
|
||||||
totalProfileCount?: number
|
totalProfileCount?: number
|
||||||
error: Error | null
|
error: Error | null
|
||||||
viewContext: 'profile' | 'profileHeader' | 'feed'
|
viewContext: 'profile' | 'profileHeader' | 'feed'
|
||||||
onDismiss?: (did: string) => void
|
onDismiss?: (did: string) => void
|
||||||
isVisible?: boolean
|
isVisible?: boolean
|
||||||
|
onRequestHide?: () => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
@@ -651,6 +567,13 @@ export function ProfileGrid({
|
|||||||
|
|
||||||
// Use totalProfileCount (before dismissals) for minLength check on initial render.
|
// Use totalProfileCount (before dismissals) for minLength check on initial render.
|
||||||
const profileCountForMinCheck = totalProfileCount ?? profiles.length
|
const profileCountForMinCheck = totalProfileCount ?? profiles.length
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error || (!isLoading && profileCountForMinCheck < minLength)) {
|
||||||
|
onRequestHide?.()
|
||||||
|
}
|
||||||
|
}, [error, isLoading, onRequestHide, profileCountForMinCheck, minLength])
|
||||||
|
|
||||||
if (error || (!isLoading && profileCountForMinCheck < minLength)) {
|
if (error || (!isLoading && profileCountForMinCheck < minLength)) {
|
||||||
ax.logger.debug(`Not enough profiles to show suggested follows`)
|
ax.logger.debug(`Not enough profiles to show suggested follows`)
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ let ProfileHeaderStandard = ({
|
|||||||
const [, queueUnblock] = useProfileBlockMutationQueue(profile)
|
const [, queueUnblock] = useProfileBlockMutationQueue(profile)
|
||||||
const unblockPromptControl = Prompt.usePromptControl()
|
const unblockPromptControl = Prompt.usePromptControl()
|
||||||
const [showSuggestedFollows, setShowSuggestedFollows] = useState(false)
|
const [showSuggestedFollows, setShowSuggestedFollows] = useState(false)
|
||||||
|
const [hasSeenAllSuggestedFollows, setHasSeenAllSuggestedFollows] =
|
||||||
|
useState(false)
|
||||||
const isBlockedUser =
|
const isBlockedUser =
|
||||||
profile.viewer?.blocking ||
|
profile.viewer?.blocking ||
|
||||||
profile.viewer?.blockedBy ||
|
profile.viewer?.blockedBy ||
|
||||||
@@ -84,7 +86,8 @@ let ProfileHeaderStandard = ({
|
|||||||
try {
|
try {
|
||||||
await queueUnblock()
|
await queueUnblock()
|
||||||
Toast.show(_(msg({message: 'Account unblocked', context: 'toast'})))
|
Toast.show(_(msg({message: 'Account unblocked', context: 'toast'})))
|
||||||
} catch (e: any) {
|
} catch (err) {
|
||||||
|
const e = err as Error
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unblock account', {message: e})
|
logger.error('Failed to unblock account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`), {type: 'error'})
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), {type: 'error'})
|
||||||
@@ -92,6 +95,11 @@ let ProfileHeaderStandard = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onRequestHide = () => {
|
||||||
|
setHasSeenAllSuggestedFollows(true)
|
||||||
|
setShowSuggestedFollows(false)
|
||||||
|
}
|
||||||
|
|
||||||
const isMe = currentAccount?.did === profile.did
|
const isMe = currentAccount?.did === profile.did
|
||||||
|
|
||||||
const {isActive: live} = useActorStatus(profile)
|
const {isActive: live} = useActorStatus(profile)
|
||||||
@@ -192,7 +200,9 @@ let ProfileHeaderStandard = ({
|
|||||||
description={_(
|
description={_(
|
||||||
msg`The account will be able to interact with you after unblocking.`,
|
msg`The account will be able to interact with you after unblocking.`,
|
||||||
)}
|
)}
|
||||||
onConfirm={unblockAccount}
|
onConfirm={() => {
|
||||||
|
void unblockAccount()
|
||||||
|
}}
|
||||||
confirmButtonCta={
|
confirmButtonCta={
|
||||||
profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`)
|
profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`)
|
||||||
}
|
}
|
||||||
@@ -201,8 +211,9 @@ let ProfileHeaderStandard = ({
|
|||||||
</ProfileHeaderShell>
|
</ProfileHeaderShell>
|
||||||
|
|
||||||
<ProfileHeaderSuggestedFollows
|
<ProfileHeaderSuggestedFollows
|
||||||
isExpanded={showSuggestedFollows}
|
isExpanded={!hasSeenAllSuggestedFollows && showSuggestedFollows}
|
||||||
actorDid={profile.did}
|
actorDid={profile.did}
|
||||||
|
onRequestHide={onRequestHide}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -254,7 +265,8 @@ export function HeaderStandardButtons({
|
|||||||
)}`,
|
)}`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (err) {
|
||||||
|
const e = err as Error
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to follow', {message: String(e)})
|
logger.error('Failed to follow', {message: String(e)})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`), {
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), {
|
||||||
@@ -280,7 +292,8 @@ export function HeaderStandardButtons({
|
|||||||
),
|
),
|
||||||
{type: 'default'},
|
{type: 'default'},
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (err) {
|
||||||
|
const e = err as Error
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unfollow', {message: String(e)})
|
logger.error('Failed to unfollow', {message: String(e)})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`), {
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), {
|
||||||
@@ -295,7 +308,8 @@ export function HeaderStandardButtons({
|
|||||||
try {
|
try {
|
||||||
await queueUnblock()
|
await queueUnblock()
|
||||||
Toast.show(_(msg({message: 'Account unblocked', context: 'toast'})))
|
Toast.show(_(msg({message: 'Account unblocked', context: 'toast'})))
|
||||||
} catch (e: any) {
|
} catch (err) {
|
||||||
|
const e = err as Error
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unblock account', {message: e})
|
logger.error('Failed to unblock account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`), {type: 'error'})
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), {type: 'error'})
|
||||||
@@ -400,7 +414,9 @@ export function HeaderStandardButtons({
|
|||||||
description={_(
|
description={_(
|
||||||
msg`The account will be able to interact with you after unblocking.`,
|
msg`The account will be able to interact with you after unblocking.`,
|
||||||
)}
|
)}
|
||||||
onConfirm={unblockAccount}
|
onConfirm={() => {
|
||||||
|
void unblockAccount()
|
||||||
|
}}
|
||||||
confirmButtonCta={_(msg`Unblock`)}
|
confirmButtonCta={_(msg`Unblock`)}
|
||||||
confirmButtonColor="negative"
|
confirmButtonColor="negative"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
import {useCallback, useMemo} from 'react'
|
||||||
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation'
|
import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
|
||||||
import {
|
import {
|
||||||
|
suggestedFollowsByActorQueryKey,
|
||||||
useSuggestedFollowsByActorQuery,
|
useSuggestedFollowsByActorQuery,
|
||||||
useSuggestedFollowsQuery,
|
|
||||||
} from '#/state/queries/suggested-follows'
|
} from '#/state/queries/suggested-follows'
|
||||||
import {useBreakpoints} from '#/alf'
|
|
||||||
import {ProfileGrid} from '#/components/FeedInterstitials'
|
import {ProfileGrid} from '#/components/FeedInterstitials'
|
||||||
import {IS_ANDROID} from '#/env'
|
import {IS_ANDROID} from '#/env'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
@@ -14,15 +13,15 @@ import type * as bsky from '#/types/bsky'
|
|||||||
export function ProfileHeaderSuggestedFollows({
|
export function ProfileHeaderSuggestedFollows({
|
||||||
isExpanded,
|
isExpanded,
|
||||||
actorDid,
|
actorDid,
|
||||||
|
onRequestHide,
|
||||||
}: {
|
}: {
|
||||||
isExpanded: boolean
|
isExpanded: boolean
|
||||||
actorDid: string
|
actorDid: string
|
||||||
|
onRequestHide: () => void
|
||||||
}) {
|
}) {
|
||||||
const {allProfiles, filteredProfiles, onDismiss, isLoading, error} =
|
const {profiles, onDismiss, isLoading, error} =
|
||||||
useProfileHeaderSuggestions(actorDid)
|
useProfileHeaderSuggestions(actorDid)
|
||||||
|
|
||||||
if (!allProfiles.length && !isLoading) return null
|
|
||||||
|
|
||||||
/* NOTE (caidanw):
|
/* NOTE (caidanw):
|
||||||
* Android does not work well with this feature yet.
|
* Android does not work well with this feature yet.
|
||||||
* This issue stems from Android not allowing dragging on clickable elements in the profile header.
|
* This issue stems from Android not allowing dragging on clickable elements in the profile header.
|
||||||
@@ -34,92 +33,51 @@ export function ProfileHeaderSuggestedFollows({
|
|||||||
<AccordionAnimation isExpanded={isExpanded}>
|
<AccordionAnimation isExpanded={isExpanded}>
|
||||||
<ProfileGrid
|
<ProfileGrid
|
||||||
isSuggestionsLoading={isLoading}
|
isSuggestionsLoading={isLoading}
|
||||||
profiles={filteredProfiles}
|
profiles={profiles}
|
||||||
totalProfileCount={allProfiles.length}
|
totalProfileCount={profiles.length}
|
||||||
error={error}
|
error={error}
|
||||||
viewContext="profileHeader"
|
viewContext="profileHeader"
|
||||||
onDismiss={onDismiss}
|
onDismiss={onDismiss}
|
||||||
isVisible={isExpanded}
|
isVisible={isExpanded}
|
||||||
|
onRequestHide={onRequestHide}
|
||||||
/>
|
/>
|
||||||
</AccordionAnimation>
|
</AccordionAnimation>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function useProfileHeaderSuggestions(actorDid: string) {
|
function useProfileHeaderSuggestions(actorDid: string) {
|
||||||
const {gtMobile} = useBreakpoints()
|
|
||||||
const moderationOpts = useModerationOpts()
|
|
||||||
const maxLength = gtMobile ? 4 : 12
|
|
||||||
const {isLoading, data, error} = useSuggestedFollowsByActorQuery({
|
const {isLoading, data, error} = useSuggestedFollowsByActorQuery({
|
||||||
did: actorDid,
|
did: actorDid,
|
||||||
})
|
})
|
||||||
const {
|
const queryClient = useQueryClient()
|
||||||
data: moreSuggestions,
|
|
||||||
fetchNextPage,
|
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
} = useSuggestedFollowsQuery({limit: 25})
|
|
||||||
|
|
||||||
const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set())
|
const onDismiss = useCallback(
|
||||||
|
(dismissedDid: string) => {
|
||||||
|
queryClient.setQueryData(
|
||||||
|
suggestedFollowsByActorQueryKey(actorDid),
|
||||||
|
(previous: typeof data) => {
|
||||||
|
if (!previous) return previous
|
||||||
|
return {
|
||||||
|
...previous,
|
||||||
|
suggestions: previous.suggestions.filter(
|
||||||
|
s => s.did !== dismissedDid,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[actorDid, queryClient],
|
||||||
|
)
|
||||||
|
|
||||||
const onDismiss = useCallback((did: string) => {
|
const profiles = useMemo(() => {
|
||||||
setDismissedDids(prev => new Set(prev).add(did))
|
return (data?.suggestions ?? []).map(profile => ({
|
||||||
}, [])
|
actor: profile as bsky.profile.AnyProfileView,
|
||||||
|
recId: data?.recId,
|
||||||
// Combine profiles from the actor-specific query with fallback suggestions
|
}))
|
||||||
const allProfiles = useMemo(() => {
|
}, [data?.suggestions, data?.recId])
|
||||||
const actorProfiles = data?.suggestions ?? []
|
|
||||||
const fallbackProfiles =
|
|
||||||
moreSuggestions?.pages.flatMap(page =>
|
|
||||||
page.actors.map(actor => ({actor, recId: page.recId})),
|
|
||||||
) ?? []
|
|
||||||
|
|
||||||
// Dedupe by did, preferring actor-specific profiles
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const combined: {actor: bsky.profile.AnyProfileView; recId?: number}[] = []
|
|
||||||
|
|
||||||
for (const profile of actorProfiles) {
|
|
||||||
if (!seen.has(profile.did)) {
|
|
||||||
seen.add(profile.did)
|
|
||||||
combined.push({actor: profile, recId: data?.recId})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const profile of fallbackProfiles) {
|
|
||||||
if (!seen.has(profile.actor.did) && profile.actor.did !== actorDid) {
|
|
||||||
seen.add(profile.actor.did)
|
|
||||||
combined.push(profile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined
|
|
||||||
}, [data?.suggestions, moreSuggestions?.pages, actorDid, data?.recId])
|
|
||||||
|
|
||||||
const filteredProfiles = useMemo(() => {
|
|
||||||
return allProfiles.filter(p => !dismissedDids.has(p.actor.did))
|
|
||||||
}, [allProfiles, dismissedDids])
|
|
||||||
|
|
||||||
// Fetch more when running low
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
moderationOpts &&
|
|
||||||
filteredProfiles.length < maxLength &&
|
|
||||||
hasNextPage &&
|
|
||||||
!isFetchingNextPage
|
|
||||||
) {
|
|
||||||
void fetchNextPage()
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
filteredProfiles.length,
|
|
||||||
maxLength,
|
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
fetchNextPage,
|
|
||||||
moderationOpts,
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allProfiles,
|
profiles,
|
||||||
filteredProfiles,
|
|
||||||
onDismiss,
|
onDismiss,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import {
|
|||||||
type AppBskyActorGetProfiles,
|
type AppBskyActorGetProfiles,
|
||||||
type AppBskyActorProfile,
|
type AppBskyActorProfile,
|
||||||
type AppBskyGraphGetFollows,
|
type AppBskyGraphGetFollows,
|
||||||
|
type AtpAgent,
|
||||||
AtUri,
|
AtUri,
|
||||||
type BskyAgent,
|
|
||||||
type ComAtprotoRepoUploadBlob,
|
type ComAtprotoRepoUploadBlob,
|
||||||
type Un$Typed,
|
type Un$Typed,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -203,19 +203,19 @@ export function useProfileUpdateMutation() {
|
|||||||
(res => {
|
(res => {
|
||||||
if (typeof newUserAvatar !== 'undefined') {
|
if (typeof newUserAvatar !== 'undefined') {
|
||||||
if (newUserAvatar === null && res.data.avatar) {
|
if (newUserAvatar === null && res.data.avatar) {
|
||||||
// url hasnt cleared yet
|
// url hasn't cleared yet
|
||||||
return false
|
return false
|
||||||
} else if (res.data.avatar === profile.avatar) {
|
} else if (res.data.avatar === profile.avatar) {
|
||||||
// url hasnt changed yet
|
// url hasn't changed yet
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof newUserBanner !== 'undefined') {
|
if (typeof newUserBanner !== 'undefined') {
|
||||||
if (newUserBanner === null && res.data.banner) {
|
if (newUserBanner === null && res.data.banner) {
|
||||||
// url hasnt cleared yet
|
// url hasn't cleared yet
|
||||||
return false
|
return false
|
||||||
} else if (res.data.banner === profile.banner) {
|
} else if (res.data.banner === profile.banner) {
|
||||||
// url hasnt changed yet
|
// url hasn't changed yet
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,10 +231,10 @@ export function useProfileUpdateMutation() {
|
|||||||
},
|
},
|
||||||
async onSuccess(_, variables) {
|
async onSuccess(_, variables) {
|
||||||
// invalidate cache
|
// invalidate cache
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: RQKEY(variables.profile.did),
|
queryKey: RQKEY(variables.profile.did),
|
||||||
})
|
})
|
||||||
queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [profilesQueryKeyRoot, [variables.profile.did]],
|
queryKey: [profilesQueryKeyRoot, [variables.profile.did]],
|
||||||
})
|
})
|
||||||
await updateProfileVerificationCache({profile: variables.profile})
|
await updateProfileVerificationCache({profile: variables.profile})
|
||||||
@@ -329,7 +329,7 @@ export function useProfileFollowMutationQueue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (finalFollowingUri) {
|
if (finalFollowingUri) {
|
||||||
agent.app.bsky.graph
|
void agent.app.bsky.graph
|
||||||
.getSuggestedFollowsByActor({
|
.getSuggestedFollowsByActor({
|
||||||
actor: did,
|
actor: did,
|
||||||
})
|
})
|
||||||
@@ -474,7 +474,7 @@ function useProfileMuteMutation() {
|
|||||||
await agent.mute(did)
|
await agent.mute(did)
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
|
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -487,7 +487,7 @@ function useProfileUnmuteMutation() {
|
|||||||
await agent.unmute(did)
|
await agent.unmute(did)
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
|
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -527,7 +527,7 @@ export function useProfileBlockMutationQueue(
|
|||||||
updateProfileShadow(queryClient, did, {
|
updateProfileShadow(queryClient, did, {
|
||||||
blockingUri: finalBlockingUri,
|
blockingUri: finalBlockingUri,
|
||||||
})
|
})
|
||||||
queryClient.invalidateQueries({queryKey: [RQKEY_LIST_CONVOS]})
|
void queryClient.invalidateQueries({queryKey: [RQKEY_LIST_CONVOS]})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -565,7 +565,7 @@ function useProfileBlockMutation() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onSuccess(_, {did}) {
|
onSuccess(_, {did}) {
|
||||||
queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
|
void queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
|
||||||
resetProfilePostsQueries(queryClient, did, 1000)
|
resetProfilePostsQueries(queryClient, did, 1000)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -593,7 +593,7 @@ function useProfileUnblockMutation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function whenAppViewReady(
|
async function whenAppViewReady(
|
||||||
agent: BskyAgent,
|
agent: AtpAgent,
|
||||||
actor: string,
|
actor: string,
|
||||||
fn: (res: AppBskyActorGetProfile.Response) => boolean,
|
fn: (res: AppBskyActorGetProfile.Response) => boolean,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -2,107 +2,24 @@ import {
|
|||||||
type AppBskyActorDefs,
|
type AppBskyActorDefs,
|
||||||
type AppBskyActorGetSuggestions,
|
type AppBskyActorGetSuggestions,
|
||||||
type AppBskyGraphGetSuggestedFollowsByActor,
|
type AppBskyGraphGetSuggestedFollowsByActor,
|
||||||
moderateProfile,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
type InfiniteData,
|
type InfiniteData,
|
||||||
type QueryClient,
|
type QueryClient,
|
||||||
type QueryKey,
|
|
||||||
useInfiniteQuery,
|
|
||||||
useQuery,
|
useQuery,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {
|
|
||||||
aggregateUserInterests,
|
|
||||||
createBskyTopicsHeader,
|
|
||||||
} from '#/lib/api/feed/utils'
|
|
||||||
import {getContentLanguages} from '#/state/preferences/languages'
|
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {useAgent} from '#/state/session'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
|
||||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
|
||||||
|
|
||||||
const suggestedFollowsQueryKeyRoot = 'suggested-follows'
|
const suggestedFollowsQueryKeyRoot = 'suggested-follows'
|
||||||
const suggestedFollowsQueryKey = (options?: SuggestedFollowsOptions) => [
|
|
||||||
suggestedFollowsQueryKeyRoot,
|
|
||||||
options,
|
|
||||||
]
|
|
||||||
|
|
||||||
const suggestedFollowsByActorQueryKeyRoot = 'suggested-follows-by-actor'
|
const suggestedFollowsByActorQueryKeyRoot = 'suggested-follows-by-actor'
|
||||||
const suggestedFollowsByActorQueryKey = (did: string) => [
|
export const suggestedFollowsByActorQueryKey = (did: string) => [
|
||||||
suggestedFollowsByActorQueryKeyRoot,
|
suggestedFollowsByActorQueryKeyRoot,
|
||||||
did,
|
did,
|
||||||
]
|
]
|
||||||
|
|
||||||
type SuggestedFollowsOptions = {limit?: number; subsequentPageLimit?: number}
|
|
||||||
|
|
||||||
export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) {
|
|
||||||
const {currentAccount} = useSession()
|
|
||||||
const agent = useAgent()
|
|
||||||
const moderationOpts = useModerationOpts()
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
|
||||||
const limit = options?.limit || 25
|
|
||||||
|
|
||||||
return useInfiniteQuery<
|
|
||||||
AppBskyActorGetSuggestions.OutputSchema,
|
|
||||||
Error,
|
|
||||||
InfiniteData<AppBskyActorGetSuggestions.OutputSchema>,
|
|
||||||
QueryKey,
|
|
||||||
string | undefined
|
|
||||||
>({
|
|
||||||
enabled: !!moderationOpts && !!preferences,
|
|
||||||
staleTime: STALE.HOURS.ONE,
|
|
||||||
queryKey: suggestedFollowsQueryKey(options),
|
|
||||||
queryFn: async ({pageParam}) => {
|
|
||||||
const contentLangs = getContentLanguages().join(',')
|
|
||||||
const maybeDifferentLimit =
|
|
||||||
options?.subsequentPageLimit && pageParam
|
|
||||||
? options.subsequentPageLimit
|
|
||||||
: limit
|
|
||||||
const res = await agent.app.bsky.actor.getSuggestions(
|
|
||||||
{
|
|
||||||
limit: maybeDifferentLimit,
|
|
||||||
cursor: pageParam,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
...createBskyTopicsHeader(aggregateUserInterests(preferences)),
|
|
||||||
'Accept-Language': contentLangs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
res.data.actors = res.data.actors
|
|
||||||
.filter(
|
|
||||||
actor =>
|
|
||||||
!moderateProfile(actor, moderationOpts!).ui('profileList').filter,
|
|
||||||
)
|
|
||||||
.filter(actor => {
|
|
||||||
const viewer = actor.viewer
|
|
||||||
if (viewer) {
|
|
||||||
if (
|
|
||||||
viewer.following ||
|
|
||||||
viewer.muted ||
|
|
||||||
viewer.mutedByList ||
|
|
||||||
viewer.blockedBy ||
|
|
||||||
viewer.blocking
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (actor.did === currentAccount?.did) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return res.data
|
|
||||||
},
|
|
||||||
initialPageParam: undefined,
|
|
||||||
getNextPageParam: lastPage => lastPage.cursor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSuggestedFollowsByActorQuery({
|
export function useSuggestedFollowsByActorQuery({
|
||||||
did,
|
did,
|
||||||
enabled,
|
enabled,
|
||||||
@@ -120,10 +37,10 @@ export function useSuggestedFollowsByActorQuery({
|
|||||||
const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
|
const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
|
||||||
actor: did,
|
actor: did,
|
||||||
})
|
})
|
||||||
const suggestions = res.data.isFallback
|
const suggestions = res.data.suggestions.filter(
|
||||||
? []
|
profile => !profile.viewer?.following,
|
||||||
: res.data.suggestions.filter(profile => !profile.viewer?.following)
|
)
|
||||||
return {suggestions, recId: res.data.recId}
|
return {suggestions, recId: res.data.recIdStr}
|
||||||
},
|
},
|
||||||
enabled,
|
enabled,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import {
|
|||||||
AppBskyEmbedVideo,
|
AppBskyEmbedVideo,
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
|
import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
|
||||||
@@ -226,14 +225,16 @@ let PostFeed = ({
|
|||||||
savedFeedConfig?: AppBskyActorDefs.SavedFeed
|
savedFeedConfig?: AppBskyActorDefs.SavedFeed
|
||||||
initialNumToRender?: number
|
initialNumToRender?: number
|
||||||
isVideoFeed?: boolean
|
isVideoFeed?: boolean
|
||||||
|
lastFetchDate?: () => number
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {currentAccount, hasSession} = useSession()
|
const {currentAccount, hasSession} = useSession()
|
||||||
const initialNumToRender = useInitialNumToRender()
|
const initialNumToRender = useInitialNumToRender()
|
||||||
const feedFeedback = useFeedFeedbackContext()
|
const feedFeedback = useFeedFeedbackContext()
|
||||||
const [isPTRing, setIsPTRing] = useState(false)
|
const [isPTRing, setIsPTRing] = useState(false)
|
||||||
|
// eslint-disable-next-line react-hooks/purity
|
||||||
const lastFetchRef = useRef<number>(Date.now())
|
const lastFetchRef = useRef<number>(Date.now())
|
||||||
const [feedType, feedUriOrActorDid, feedTab] = feed.split('|')
|
const [feedType, feedUriOrActorDid, feedTab] = feed.split('|')
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
@@ -271,14 +272,17 @@ let PostFeed = ({
|
|||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
} = usePostFeedQuery(feed, feedParams, opts)
|
} = usePostFeedQuery(feed, feedParams, opts)
|
||||||
const lastFetchedAt = data?.pages[0].fetchedAt
|
const lastFetchedAt = data?.pages[0].fetchedAt
|
||||||
if (lastFetchedAt) {
|
|
||||||
lastFetchRef.current = lastFetchedAt
|
|
||||||
}
|
|
||||||
const isEmpty = useMemo(
|
const isEmpty = useMemo(
|
||||||
() => !isFetching && !data?.pages?.some(page => page.slices.length),
|
() => !isFetching && !data?.pages?.some(page => page.slices.length),
|
||||||
[isFetching, data],
|
[isFetching, data],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastFetchedAt) {
|
||||||
|
lastFetchRef.current = lastFetchedAt
|
||||||
|
}
|
||||||
|
}, [lastFetchedAt])
|
||||||
|
|
||||||
const checkForNew = useNonReactiveCallback(async () => {
|
const checkForNew = useNonReactiveCallback(async () => {
|
||||||
if (!data?.pages[0] || isFetching || !onHasNew || !enabled || disablePoll) {
|
if (!data?.pages[0] || isFetching || !onHasNew || !enabled || disablePoll) {
|
||||||
return
|
return
|
||||||
@@ -292,7 +296,7 @@ let PostFeed = ({
|
|||||||
try {
|
try {
|
||||||
if (await pollLatest(data.pages[0])) {
|
if (await pollLatest(data.pages[0])) {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
refetch()
|
void refetch()
|
||||||
} else {
|
} else {
|
||||||
onHasNew(true)
|
onHasNew(true)
|
||||||
}
|
}
|
||||||
@@ -333,7 +337,7 @@ let PostFeed = ({
|
|||||||
const timeSinceFirstLoad = Date.now() - lastFetchRef.current
|
const timeSinceFirstLoad = Date.now() - lastFetchRef.current
|
||||||
if (isEmpty || timeSinceFirstLoad > CHECK_LATEST_AFTER) {
|
if (isEmpty || timeSinceFirstLoad > CHECK_LATEST_AFTER) {
|
||||||
// check for new on enable (aka on focus)
|
// check for new on enable (aka on focus)
|
||||||
checkForNew()
|
void checkForNew()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [enabled, isEmpty, disablePoll, checkForNew])
|
}, [enabled, isEmpty, disablePoll, checkForNew])
|
||||||
@@ -343,14 +347,14 @@ let PostFeed = ({
|
|||||||
const subscription = AppState.addEventListener('change', nextAppState => {
|
const subscription = AppState.addEventListener('change', nextAppState => {
|
||||||
// check for new on app foreground
|
// check for new on app foreground
|
||||||
if (nextAppState === 'active') {
|
if (nextAppState === 'active') {
|
||||||
checkForNew()
|
void checkForNew()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
cleanup1 = () => subscription.remove()
|
cleanup1 = () => subscription.remove()
|
||||||
if (pollInterval) {
|
if (pollInterval) {
|
||||||
// check for new on interval
|
// check for new on interval
|
||||||
const i = setInterval(() => {
|
const i = setInterval(() => {
|
||||||
checkForNew()
|
void checkForNew()
|
||||||
}, pollInterval)
|
}, pollInterval)
|
||||||
cleanup2 = () => clearInterval(i)
|
cleanup2 = () => clearInterval(i)
|
||||||
}
|
}
|
||||||
@@ -363,7 +367,7 @@ let PostFeed = ({
|
|||||||
const followProgressGuide = useProgressGuide('follow-10')
|
const followProgressGuide = useProgressGuide('follow-10')
|
||||||
const followAndLikeProgressGuide = useProgressGuide('like-10-and-follow-7')
|
const followAndLikeProgressGuide = useProgressGuide('like-10-and-follow-7')
|
||||||
|
|
||||||
const showProgressIntersitial =
|
const showProgressInterstitial =
|
||||||
(followProgressGuide || followAndLikeProgressGuide) && !rightNavVisible
|
(followProgressGuide || followAndLikeProgressGuide) && !rightNavVisible
|
||||||
|
|
||||||
const {trendingVideoDisabled} = useTrendingSettings()
|
const {trendingVideoDisabled} = useTrendingSettings()
|
||||||
@@ -494,7 +498,7 @@ let PostFeed = ({
|
|||||||
if (hasSession) {
|
if (hasSession) {
|
||||||
if (feedKind === 'discover') {
|
if (feedKind === 'discover') {
|
||||||
if (sliceIndex === 0) {
|
if (sliceIndex === 0) {
|
||||||
if (showProgressIntersitial) {
|
if (showProgressInterstitial) {
|
||||||
arr.push({
|
arr.push({
|
||||||
type: 'interstitialProgressGuide',
|
type: 'interstitialProgressGuide',
|
||||||
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
|
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
|
||||||
@@ -672,7 +676,7 @@ let PostFeed = ({
|
|||||||
feedUriOrActorDid,
|
feedUriOrActorDid,
|
||||||
feedTab,
|
feedTab,
|
||||||
hasSession,
|
hasSession,
|
||||||
showProgressIntersitial,
|
showProgressInterstitial,
|
||||||
trendingVideoDisabled,
|
trendingVideoDisabled,
|
||||||
gtMobile,
|
gtMobile,
|
||||||
isVideoFeed,
|
isVideoFeed,
|
||||||
@@ -683,16 +687,12 @@ let PostFeed = ({
|
|||||||
blockedOrMutedAuthors,
|
blockedOrMutedAuthors,
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (enabled === false) {
|
|
||||||
setIsPTRing(false)
|
|
||||||
}
|
|
||||||
}, [enabled])
|
|
||||||
|
|
||||||
// events
|
// events
|
||||||
// =
|
// =
|
||||||
|
|
||||||
const onRefresh = useCallback(async () => {
|
const onRefresh = useCallback(async () => {
|
||||||
|
if (!enabled) return
|
||||||
|
|
||||||
ax.metric('feed:refresh', {
|
ax.metric('feed:refresh', {
|
||||||
feedType: feedType,
|
feedType: feedType,
|
||||||
feedUrl: feed,
|
feedUrl: feed,
|
||||||
@@ -706,7 +706,7 @@ let PostFeed = ({
|
|||||||
logger.error('Failed to refresh posts feed', {message: err})
|
logger.error('Failed to refresh posts feed', {message: err})
|
||||||
}
|
}
|
||||||
setIsPTRing(false)
|
setIsPTRing(false)
|
||||||
}, [ax, refetch, setIsPTRing, onHasNew, feed, feedType])
|
}, [ax, refetch, setIsPTRing, onHasNew, feed, feedType, enabled])
|
||||||
|
|
||||||
const onEndReached = useCallback(async () => {
|
const onEndReached = useCallback(async () => {
|
||||||
if (isFetching || !hasNextPage || isError) return
|
if (isFetching || !hasNextPage || isError) return
|
||||||
@@ -733,12 +733,12 @@ let PostFeed = ({
|
|||||||
])
|
])
|
||||||
|
|
||||||
const onPressTryAgain = useCallback(() => {
|
const onPressTryAgain = useCallback(() => {
|
||||||
refetch()
|
void refetch()
|
||||||
onHasNew?.(false)
|
onHasNew?.(false)
|
||||||
}, [refetch, onHasNew])
|
}, [refetch, onHasNew])
|
||||||
|
|
||||||
const onPressRetryLoadMore = useCallback(() => {
|
const onPressRetryLoadMore = useCallback(() => {
|
||||||
fetchNextPage()
|
void fetchNextPage()
|
||||||
}, [fetchNextPage])
|
}, [fetchNextPage])
|
||||||
|
|
||||||
// rendering
|
// rendering
|
||||||
@@ -760,9 +760,7 @@ let PostFeed = ({
|
|||||||
} else if (row.type === 'loadMoreError') {
|
} else if (row.type === 'loadMoreError') {
|
||||||
return (
|
return (
|
||||||
<LoadMoreRetryBtn
|
<LoadMoreRetryBtn
|
||||||
label={_(
|
label={l`There was an issue fetching posts. Tap here to try again.`}
|
||||||
msg`There was an issue fetching posts. Tap here to try again.`,
|
|
||||||
)}
|
|
||||||
onPress={onPressRetryLoadMore}
|
onPress={onPressRetryLoadMore}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -861,7 +859,7 @@ let PostFeed = ({
|
|||||||
error,
|
error,
|
||||||
onPressTryAgain,
|
onPressTryAgain,
|
||||||
savedFeedConfig,
|
savedFeedConfig,
|
||||||
_,
|
l,
|
||||||
onPressRetryLoadMore,
|
onPressRetryLoadMore,
|
||||||
feedType,
|
feedType,
|
||||||
feedUriOrActorDid,
|
feedUriOrActorDid,
|
||||||
@@ -997,19 +995,19 @@ let PostFeed = ({
|
|||||||
testID={testID ? `${testID}-flatlist` : undefined}
|
testID={testID ? `${testID}-flatlist` : undefined}
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={feedItems}
|
data={feedItems}
|
||||||
keyExtractor={item => item.key}
|
keyExtractor={(item: FeedRow) => item.key}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
ListFooterComponent={FeedFooter}
|
ListFooterComponent={FeedFooter}
|
||||||
ListHeaderComponent={ListHeaderComponent}
|
ListHeaderComponent={ListHeaderComponent}
|
||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={() => void onRefresh()}
|
||||||
headerOffset={headerOffset}
|
headerOffset={headerOffset}
|
||||||
progressViewOffset={progressViewOffset}
|
progressViewOffset={progressViewOffset}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
minHeight: Dimensions.get('window').height * 1.5,
|
minHeight: Dimensions.get('window').height * 1.5,
|
||||||
}}
|
}}
|
||||||
onScrolledDownChange={handleScrolledDownChange}
|
onScrolledDownChange={handleScrolledDownChange}
|
||||||
onEndReached={onEndReached}
|
onEndReached={() => void onEndReached()}
|
||||||
onEndReachedThreshold={2} // number of posts left to trigger load more
|
onEndReachedThreshold={2} // number of posts left to trigger load more
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
extraData={extraData}
|
extraData={extraData}
|
||||||
|
|||||||
Reference in New Issue
Block a user