tweak ordering of explore components (#8432)
* tweak ordering of explore components * use better feed suggestions, metrics * tweak default limit * tweak * remove unnecessary if
This commit is contained in:
@@ -21,8 +21,7 @@ import {
|
|||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {atoms as a} from '#/alf'
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonIcon,
|
ButtonIcon,
|
||||||
@@ -40,6 +39,7 @@ import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from './icons/Trash'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
view: AppBskyFeedDefs.GeneratorView
|
view: AppBskyFeedDefs.GeneratorView
|
||||||
|
onPress?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Default(props: Props) {
|
export function Default(props: Props) {
|
||||||
|
|||||||
@@ -151,6 +151,12 @@ export type MetricEvents = {
|
|||||||
'feed:share': {
|
'feed:share': {
|
||||||
feedUrl: string
|
feedUrl: string
|
||||||
}
|
}
|
||||||
|
'feed:suggestion:seen': {
|
||||||
|
feedUrl: string
|
||||||
|
}
|
||||||
|
'feed:suggestion:press': {
|
||||||
|
feedUrl: string
|
||||||
|
}
|
||||||
'discover:showMore': {
|
'discover:showMore': {
|
||||||
feedContext: string
|
feedContext: string
|
||||||
}
|
}
|
||||||
|
|||||||
+179
-93
@@ -10,7 +10,6 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import * as bcp47Match from 'bcp-47-match'
|
import * as bcp47Match from 'bcp-47-match'
|
||||||
|
|
||||||
import {useGate} from '#/lib/statsig/statsig'
|
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
@@ -35,7 +34,6 @@ import {
|
|||||||
createSuggestedStarterPacksQueryKey,
|
createSuggestedStarterPacksQueryKey,
|
||||||
useSuggestedStarterPacksQuery,
|
useSuggestedStarterPacksQuery,
|
||||||
} from '#/state/queries/useSuggestedStarterPacksQuery'
|
} from '#/state/queries/useSuggestedStarterPacksQuery'
|
||||||
import {useProgressGuide} from '#/state/shell/progress-guide'
|
|
||||||
import {isThreadChildAt, isThreadParentAt} from '#/view/com/posts/PostFeed'
|
import {isThreadChildAt, isThreadParentAt} from '#/view/com/posts/PostFeed'
|
||||||
import {PostFeedItem} from '#/view/com/posts/PostFeedItem'
|
import {PostFeedItem} from '#/view/com/posts/PostFeedItem'
|
||||||
import {ViewFullThread} from '#/view/com/posts/ViewFullThread'
|
import {ViewFullThread} from '#/view/com/posts/ViewFullThread'
|
||||||
@@ -61,11 +59,12 @@ import {Button} from '#/components/Button'
|
|||||||
import * as FeedCard from '#/components/FeedCard'
|
import * as FeedCard from '#/components/FeedCard'
|
||||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron'
|
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {type Props as IcoProps} from '#/components/icons/common'
|
import {
|
||||||
import {type Props as SVGIconProps} from '#/components/icons/common'
|
type Props as IcoProps,
|
||||||
|
type Props as SVGIconProps,
|
||||||
|
} from '#/components/icons/common'
|
||||||
import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle'
|
import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle'
|
||||||
import {StarterPack} from '#/components/icons/StarterPack'
|
import {StarterPack} from '#/components/icons/StarterPack'
|
||||||
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending'
|
|
||||||
import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle'
|
import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
@@ -216,8 +215,6 @@ export function Explore({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {data: preferences, error: preferencesError} = usePreferencesQuery()
|
const {data: preferences, error: preferencesError} = usePreferencesQuery()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const gate = useGate()
|
|
||||||
const guide = useProgressGuide('follow-10')
|
|
||||||
const [selectedInterest, setSelectedInterest] = useState<string | null>(null)
|
const [selectedInterest, setSelectedInterest] = useState<string | null>(null)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -284,9 +281,10 @@ export function Explore({
|
|||||||
hasPressedLoadMoreFeeds,
|
hasPressedLoadMoreFeeds,
|
||||||
])
|
])
|
||||||
|
|
||||||
const {data: suggestedFeeds} = useGetSuggestedFeedsQuery({
|
const {data: suggestedFeeds, error: suggestedFeedsError} =
|
||||||
enabled: useFullExperience,
|
useGetSuggestedFeedsQuery({
|
||||||
})
|
enabled: useFullExperience,
|
||||||
|
})
|
||||||
const {
|
const {
|
||||||
data: feedPreviewSlices,
|
data: feedPreviewSlices,
|
||||||
query: {
|
query: {
|
||||||
@@ -442,13 +440,11 @@ export function Explore({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (feeds && preferences) {
|
if (useFullExperience) {
|
||||||
// Currently the responses contain duplicate items.
|
if (suggestedFeeds && preferences) {
|
||||||
// Needs to be fixed on backend, but let's dedupe to be safe.
|
let seen = new Set()
|
||||||
let seen = new Set()
|
const feedItems: ExploreScreenItems[] = []
|
||||||
const feedItems: ExploreScreenItems[] = []
|
for (const feed of suggestedFeeds.feeds) {
|
||||||
for (const page of feeds.pages) {
|
|
||||||
for (const feed of page.feeds) {
|
|
||||||
if (!seen.has(feed.uri)) {
|
if (!seen.has(feed.uri)) {
|
||||||
seen.add(feed.uri)
|
seen.add(feed.uri)
|
||||||
feedItems.push({
|
feedItems.push({
|
||||||
@@ -458,78 +454,172 @@ export function Explore({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// feeds errors can occur during pagination, so feeds is truthy
|
// feeds errors can occur during pagination, so feeds is truthy
|
||||||
if (feedsError) {
|
if (suggestedFeedsError) {
|
||||||
i.push({
|
i.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
key: 'feedsError',
|
key: 'feedsError',
|
||||||
message: _(msg`Failed to load suggested feeds`),
|
message: _(msg`Failed to load suggested feeds`),
|
||||||
error: cleanError(feedsError),
|
error: cleanError(feedsError),
|
||||||
})
|
})
|
||||||
} else if (preferencesError) {
|
} else if (preferencesError) {
|
||||||
i.push({
|
i.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
key: 'preferencesError',
|
key: 'preferencesError',
|
||||||
message: _(msg`Failed to load feeds preferences`),
|
message: _(msg`Failed to load feeds preferences`),
|
||||||
error: cleanError(preferencesError),
|
error: cleanError(preferencesError),
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
if (feedItems.length === 0) {
|
|
||||||
if (!hasNextFeedsPage) {
|
|
||||||
i.pop()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// This query doesn't follow the limit very well, so the first press of the
|
if (feedItems.length === 0) {
|
||||||
// load more button just unslices the array back to ~10 items
|
i.pop()
|
||||||
if (!hasPressedLoadMoreFeeds) {
|
|
||||||
i.push(...feedItems.slice(0, 3))
|
|
||||||
} else {
|
} else {
|
||||||
i.push(...feedItems)
|
// This query doesn't follow the limit very well, so the first press of the
|
||||||
|
// load more button just unslices the array back to ~10 items
|
||||||
|
if (!hasPressedLoadMoreFeeds) {
|
||||||
|
i.push(...feedItems.slice(0, 6))
|
||||||
|
} else {
|
||||||
|
i.push(...feedItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [index, item] of feedItems.entries()) {
|
||||||
|
if (item.type !== 'feed') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// don't log the ones we've already sent
|
||||||
|
if (hasPressedLoadMoreFeeds && index < 6) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.metric(
|
||||||
|
'feed:suggestion:seen',
|
||||||
|
{feedUrl: item.feed.uri},
|
||||||
|
{statsig: false},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasPressedLoadMoreFeeds) {
|
||||||
|
i.push({
|
||||||
|
type: 'loadMore',
|
||||||
|
key: 'loadMoreFeeds',
|
||||||
|
message: _(msg`Load more suggested feeds`),
|
||||||
|
isLoadingMore: isLoadingMoreFeeds,
|
||||||
|
onLoadMore: onLoadMoreFeeds,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasNextFeedsPage) {
|
} else {
|
||||||
|
if (feedsError) {
|
||||||
i.push({
|
i.push({
|
||||||
type: 'loadMore',
|
type: 'error',
|
||||||
key: 'loadMoreFeeds',
|
key: 'feedsError',
|
||||||
message: _(msg`Load more suggested feeds`),
|
message: _(msg`Failed to load suggested feeds`),
|
||||||
isLoadingMore: isLoadingMoreFeeds,
|
error: cleanError(feedsError),
|
||||||
onLoadMore: onLoadMoreFeeds,
|
|
||||||
})
|
})
|
||||||
|
} else if (preferencesError) {
|
||||||
|
i.push({
|
||||||
|
type: 'error',
|
||||||
|
key: 'preferencesError',
|
||||||
|
message: _(msg`Failed to load feeds preferences`),
|
||||||
|
error: cleanError(preferencesError),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (feedsError) {
|
if (feeds && preferences) {
|
||||||
i.push({
|
// Currently the responses contain duplicate items.
|
||||||
type: 'error',
|
// Needs to be fixed on backend, but let's dedupe to be safe.
|
||||||
key: 'feedsError',
|
let seen = new Set()
|
||||||
message: _(msg`Failed to load suggested feeds`),
|
const feedItems: ExploreScreenItems[] = []
|
||||||
error: cleanError(feedsError),
|
for (const page of feeds.pages) {
|
||||||
})
|
for (const feed of page.feeds) {
|
||||||
} else if (preferencesError) {
|
if (!seen.has(feed.uri)) {
|
||||||
i.push({
|
seen.add(feed.uri)
|
||||||
type: 'error',
|
feedItems.push({
|
||||||
key: 'preferencesError',
|
type: 'feed',
|
||||||
message: _(msg`Failed to load feeds preferences`),
|
key: feed.uri,
|
||||||
error: cleanError(preferencesError),
|
feed,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// feeds errors can occur during pagination, so feeds is truthy
|
||||||
|
if (feedsError) {
|
||||||
|
i.push({
|
||||||
|
type: 'error',
|
||||||
|
key: 'feedsError',
|
||||||
|
message: _(msg`Failed to load suggested feeds`),
|
||||||
|
error: cleanError(feedsError),
|
||||||
|
})
|
||||||
|
} else if (preferencesError) {
|
||||||
|
i.push({
|
||||||
|
type: 'error',
|
||||||
|
key: 'preferencesError',
|
||||||
|
message: _(msg`Failed to load feeds preferences`),
|
||||||
|
error: cleanError(preferencesError),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (feedItems.length === 0) {
|
||||||
|
if (!hasNextFeedsPage) {
|
||||||
|
i.pop()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This query doesn't follow the limit very well, so the first press of the
|
||||||
|
// load more button just unslices the array back to ~10 items
|
||||||
|
if (!hasPressedLoadMoreFeeds) {
|
||||||
|
i.push(...feedItems.slice(0, 3))
|
||||||
|
} else {
|
||||||
|
i.push(...feedItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasNextFeedsPage) {
|
||||||
|
i.push({
|
||||||
|
type: 'loadMore',
|
||||||
|
key: 'loadMoreFeeds',
|
||||||
|
message: _(msg`Load more suggested feeds`),
|
||||||
|
isLoadingMore: isLoadingMoreFeeds,
|
||||||
|
onLoadMore: onLoadMoreFeeds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'})
|
if (feedsError) {
|
||||||
|
i.push({
|
||||||
|
type: 'error',
|
||||||
|
key: 'feedsError',
|
||||||
|
message: _(msg`Failed to load suggested feeds`),
|
||||||
|
error: cleanError(feedsError),
|
||||||
|
})
|
||||||
|
} else if (preferencesError) {
|
||||||
|
i.push({
|
||||||
|
type: 'error',
|
||||||
|
key: 'preferencesError',
|
||||||
|
message: _(msg`Failed to load feeds preferences`),
|
||||||
|
error: cleanError(preferencesError),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return i
|
return i
|
||||||
}, [
|
}, [
|
||||||
feeds,
|
|
||||||
_,
|
_,
|
||||||
|
useFullExperience,
|
||||||
|
suggestedFeeds,
|
||||||
|
preferences,
|
||||||
|
suggestedFeedsError,
|
||||||
|
preferencesError,
|
||||||
feedsError,
|
feedsError,
|
||||||
hasNextFeedsPage,
|
hasNextFeedsPage,
|
||||||
hasPressedLoadMoreFeeds,
|
hasPressedLoadMoreFeeds,
|
||||||
isLoadingMoreFeeds,
|
isLoadingMoreFeeds,
|
||||||
onLoadMoreFeeds,
|
onLoadMoreFeeds,
|
||||||
preferences,
|
feeds,
|
||||||
preferencesError,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const suggestedStarterPacksModule = useMemo(() => {
|
const suggestedStarterPacksModule = useMemo(() => {
|
||||||
const i: ExploreScreenItems[] = []
|
const i: ExploreScreenItems[] = []
|
||||||
i.push({
|
i.push({
|
||||||
@@ -589,7 +679,6 @@ export function Explore({
|
|||||||
]
|
]
|
||||||
}, [showInterestsNux])
|
}, [showInterestsNux])
|
||||||
|
|
||||||
const isNewUser = guide?.guide === 'follow-10' && !guide.isComplete
|
|
||||||
const items = useMemo<ExploreScreenItems[]>(() => {
|
const items = useMemo<ExploreScreenItems[]>(() => {
|
||||||
const i: ExploreScreenItems[] = []
|
const i: ExploreScreenItems[] = []
|
||||||
|
|
||||||
@@ -599,25 +688,10 @@ export function Explore({
|
|||||||
i.push(...interestsNuxModule)
|
i.push(...interestsNuxModule)
|
||||||
|
|
||||||
if (useFullExperience) {
|
if (useFullExperience) {
|
||||||
if (isNewUser) {
|
i.push(trendingTopicsModule)
|
||||||
i.push(...suggestedFollowsModule)
|
i.push(...suggestedFeedsModule)
|
||||||
i.push(...suggestedStarterPacksModule)
|
i.push(...suggestedFollowsModule)
|
||||||
i.push({
|
i.push(...suggestedStarterPacksModule)
|
||||||
type: 'header',
|
|
||||||
key: 'trending-topics-header',
|
|
||||||
title: _(msg`Trending topics`),
|
|
||||||
icon: Graph,
|
|
||||||
bottomBorder: true,
|
|
||||||
})
|
|
||||||
i.push(trendingTopicsModule)
|
|
||||||
} else {
|
|
||||||
i.push(trendingTopicsModule)
|
|
||||||
i.push(...suggestedFollowsModule)
|
|
||||||
i.push(...suggestedStarterPacksModule)
|
|
||||||
}
|
|
||||||
if (gate('explore_show_suggested_feeds')) {
|
|
||||||
i.push(...suggestedFeedsModule)
|
|
||||||
}
|
|
||||||
i.push(...feedPreviewsModule)
|
i.push(...feedPreviewsModule)
|
||||||
} else {
|
} else {
|
||||||
i.push(...suggestedFollowsModule)
|
i.push(...suggestedFollowsModule)
|
||||||
@@ -625,16 +699,13 @@ export function Explore({
|
|||||||
|
|
||||||
return i
|
return i
|
||||||
}, [
|
}, [
|
||||||
_,
|
|
||||||
topBorder,
|
topBorder,
|
||||||
isNewUser,
|
|
||||||
suggestedFollowsModule,
|
suggestedFollowsModule,
|
||||||
suggestedStarterPacksModule,
|
suggestedStarterPacksModule,
|
||||||
suggestedFeedsModule,
|
suggestedFeedsModule,
|
||||||
trendingTopicsModule,
|
trendingTopicsModule,
|
||||||
feedPreviewsModule,
|
feedPreviewsModule,
|
||||||
interestsNuxModule,
|
interestsNuxModule,
|
||||||
gate,
|
|
||||||
useFullExperience,
|
useFullExperience,
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -731,7 +802,17 @@ export function Explore({
|
|||||||
a.px_lg,
|
a.px_lg,
|
||||||
a.py_lg,
|
a.py_lg,
|
||||||
]}>
|
]}>
|
||||||
<FeedCard.Default view={item.feed} />
|
<FeedCard.Default
|
||||||
|
view={item.feed}
|
||||||
|
onPress={() => {
|
||||||
|
if (!useFullExperience) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.metric('feed:suggestion:press', {
|
||||||
|
feedUrl: item.feed.uri,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -918,11 +999,16 @@ export function Explore({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
t,
|
t.atoms.border_contrast_low,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
t.atoms.bg,
|
||||||
|
t.palette.negative_400,
|
||||||
focusSearchInput,
|
focusSearchInput,
|
||||||
moderationOpts,
|
|
||||||
selectedInterest,
|
selectedInterest,
|
||||||
|
moderationOpts,
|
||||||
interestsDisplayNames,
|
interestsDisplayNames,
|
||||||
|
useFullExperience,
|
||||||
_,
|
_,
|
||||||
fetchNextPageFeedPreviews,
|
fetchNextPageFeedPreviews,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {STALE} from '#/state/queries'
|
|||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
|
||||||
export const DEFAULT_LIMIT = 5
|
export const DEFAULT_LIMIT = 15
|
||||||
|
|
||||||
export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds']
|
export const createGetSuggestedFeedsQueryKey = () => ['suggested-feeds']
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user