diff --git a/src/lib/constants.ts b/src/lib/constants.ts index bb49387c4c..970317b754 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -89,3 +89,6 @@ export const BSKY_FEED_OWNER_DIDS = [ 'did:plc:vpkhqolt662uhesyj6nxm7ys', 'did:plc:q6gjnaw2blty4crticxkmujt', ] + +export const DISCOVER_FEED_URI = + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot' diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 314799f288..ccd2399f8c 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -8,5 +8,5 @@ export type Gate = | 'new_search' | 'receive_updates' | 'show_follow_back_label' - | 'start_session_with_following' | 'use_new_suggestions_endpoint' + | 'reduced_onboarding_and_home_algo' diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 0d3de89697..79df1156e1 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -13,6 +13,8 @@ import { useQuery, } from '@tanstack/react-query' +import {DISCOVER_FEED_URI} from '#/lib/constants' +import {useGate} from '#/lib/statsig/statsig' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {STALE} from '#/state/queries' @@ -199,10 +201,13 @@ export function useSearchPopularFeedsMutation() { }) } -const FOLLOWING_FEED_STUB: FeedSourceInfo = { +/** + * The following feed, with fallbacks to Discover + */ +const HOME_FEED_STUB: FeedSourceInfo = { type: 'feed', displayName: 'Following', - uri: '', + uri: 'home', route: { href: '/', name: 'Home', @@ -219,7 +224,24 @@ const FOLLOWING_FEED_STUB: FeedSourceInfo = { const DISCOVER_FEED_STUB: FeedSourceInfo = { type: 'feed', displayName: 'Discover', - uri: '', + uri: DISCOVER_FEED_URI, // TODO may not want this + route: { + href: '/', + name: 'Home', + params: {}, + }, + cid: '', + avatar: '', + description: new RichText({text: ''}), + creatorDid: '', + creatorHandle: '', + likeCount: 0, + likeUri: '', +} +const HOME_ALGO_FEED_STUB: FeedSourceInfo = { + type: 'feed', + displayName: 'Home', + uri: 'home-algo', route: { href: '/', name: 'Home', @@ -239,7 +261,17 @@ const pinnedFeedInfosQueryKeyRoot = 'pinnedFeedsInfos' export function usePinnedFeedsInfos() { const {hasSession} = useSession() const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery() - const pinnedUris = preferences?.feeds?.pinned ?? [] + const isHomeAlgoExperimentEnabled = useGate( + 'reduced_onboarding_and_home_algo', + ) + const homeAlgo = preferences?.homeAlgo + const pinnedUris = (preferences?.feeds?.pinned ?? []).filter(f => { + if (isHomeAlgoExperimentEnabled && hasSession && homeAlgo) { + // remove duplicate feed + return f !== homeAlgo.uri + } + return true + }) return useQuery({ staleTime: STALE.INFINITY, @@ -285,7 +317,15 @@ export function usePinnedFeedsInfos() { ) // The returned result will have the original order. - const result = [hasSession ? FOLLOWING_FEED_STUB : DISCOVER_FEED_STUB] + let result = [hasSession ? HOME_FEED_STUB : DISCOVER_FEED_STUB] + + if (isHomeAlgoExperimentEnabled && hasSession && preferences?.homeAlgo) { + const {enabled, uri} = preferences.homeAlgo + if (enabled && uri) { + result = [HOME_ALGO_FEED_STUB, HOME_FEED_STUB] + } + } + await Promise.allSettled([feedsPromise, ...listsPromises]) for (let pinnedUri of pinnedUris) { if (resolved.has(pinnedUri)) { diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 3453a77648..41ab4100aa 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -44,6 +44,7 @@ type AuthorFilter = type FeedUri = string type ListUri = string export type FeedDescriptor = + | 'home-algo' | 'home' | 'following' | `author|${ActorDid}|${AuthorFilter}` diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts index 4cb4d1e964..5894af32e7 100644 --- a/src/state/queries/preferences/const.ts +++ b/src/state/queries/preferences/const.ts @@ -1,8 +1,8 @@ -import { - UsePreferencesQueryResponse, - ThreadViewPreferences, -} from '#/state/queries/preferences/types' import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' +import { + ThreadViewPreferences, + UsePreferencesQueryResponse, +} from '#/state/queries/preferences/types' export const DEFAULT_HOME_FEED_PREFS: UsePreferencesQueryResponse['feedViewPrefs'] = { @@ -45,4 +45,5 @@ export const DEFAULT_LOGGED_OUT_PREFERENCES: UsePreferencesQueryResponse = { threadViewPrefs: DEFAULT_THREAD_VIEW_PREFS, userAge: 13, // TODO(pwi) interests: {tags: []}, + homeAlgo: {enabled: undefined}, } diff --git a/src/view/com/home/HomeHeader.tsx b/src/view/com/home/HomeHeader.tsx index aa3ecb7fc2..8380087b30 100644 --- a/src/view/com/home/HomeHeader.tsx +++ b/src/view/com/home/HomeHeader.tsx @@ -1,12 +1,13 @@ import React from 'react' -import {RenderTabBarFnProps} from 'view/com/pager/Pager' -import {HomeHeaderLayout} from './HomeHeaderLayout' -import {FeedSourceInfo} from '#/state/queries/feed' import {useNavigation} from '@react-navigation/native' + +import {usePalette} from '#/lib/hooks/usePalette' +import {FeedSourceInfo} from '#/state/queries/feed' import {NavigationProp} from 'lib/routes/types' import {isWeb} from 'platform/detection' +import {RenderTabBarFnProps} from 'view/com/pager/Pager' import {TabBar} from '../pager/TabBar' -import {usePalette} from '#/lib/hooks/usePalette' +import {HomeHeaderLayout} from './HomeHeaderLayout' export function HomeHeader( props: RenderTabBarFnProps & { @@ -20,7 +21,9 @@ export function HomeHeader( const pal = usePalette('default') const hasPinnedCustom = React.useMemo(() => { - return feeds.some(tab => tab.uri !== '') + return feeds.some( + tab => !['home', 'home-algo', 'following'].includes(tab.uri), + ) }, [feeds]) const items = React.useMemo(() => { diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index fbaa49a321..6716dd6abf 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -53,19 +53,34 @@ function HomeScreenReady({ pinnedFeedInfos: FeedSourceInfo[] }) { useOTAUpdates() + const isHomeAlgoExperimentEnabled = useGate( + 'reduced_onboarding_and_home_algo', + ) const allFeeds = React.useMemo(() => { const feeds: FeedDescriptor[] = [] - feeds.push('home') for (const {uri} of pinnedFeedInfos) { if (uri.includes('app.bsky.feed.generator')) { feeds.push(`feedgen|${uri}`) } else if (uri.includes('app.bsky.graph.list')) { feeds.push(`list|${uri}`) + } else if (uri === 'home-algo') { + // TODO can I ever end up here without a feed? + if ( + isHomeAlgoExperimentEnabled && + preferences.homeAlgo.enabled && + preferences.homeAlgo.uri + ) { + feeds.push(`feedgen|${preferences.homeAlgo.uri}`) + } + } else if (uri === 'home') { + feeds.push('home') + } else if (uri === 'following') { + feeds.push('following') } } return feeds - }, [pinnedFeedInfos]) + }, [pinnedFeedInfos, isHomeAlgoExperimentEnabled, preferences.homeAlgo]) const rawSelectedFeed = useSelectedFeed() const setSelectedFeed = useSetSelectedFeed() @@ -186,7 +201,6 @@ function HomeScreenReady({ return }, []) - const [homeFeed, ...customFeeds] = allFeeds const homeFeedParams = React.useMemo(() => { return { mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), @@ -206,16 +220,21 @@ function HomeScreenReady({ onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}> - - {customFeeds.map(feed => { + {allFeeds.map(feed => { + if (feed === 'home') { + return ( + + ) + } + return ( { const uri = feedInfo.uri let feed: FeedDescriptor - if (!uri) { + if (uri === 'home') { feed = 'home' + } else if (uri === 'home-algo') { + feed = 'home-algo' } else if (uri.includes('app.bsky.feed.generator')) { feed = `feedgen|${uri}` } else if (uri.includes('app.bsky.graph.list')) {