Refactor Home feed pager rendering logic (#2768)

* Use new persistence API for selected feed

* Refactor Home feeds pager data source
zio/stable
dan 2024-02-07 02:50:44 +00:00 committed by GitHub
parent f393dda528
commit 4583521b11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 81 deletions

View File

@ -111,6 +111,7 @@ export function transform(legacy: Partial<LegacySchema>): Schema {
}, },
hiddenPosts: defaults.hiddenPosts, hiddenPosts: defaults.hiddenPosts,
externalEmbeds: defaults.externalEmbeds, externalEmbeds: defaults.externalEmbeds,
lastSelectedHomeFeed: defaults.lastSelectedHomeFeed,
} }
} }

View File

@ -56,6 +56,7 @@ export const schema = z.object({
}), }),
hiddenPosts: z.array(z.string()).optional(), // should move to server hiddenPosts: z.array(z.string()).optional(), // should move to server
useInAppBrowser: z.boolean().optional(), useInAppBrowser: z.boolean().optional(),
lastSelectedHomeFeed: z.string().optional(),
}) })
export type Schema = z.infer<typeof schema> export type Schema = z.infer<typeof schema>
@ -89,4 +90,5 @@ export const defaults: Schema = {
}, },
hiddenPosts: [], hiddenPosts: [],
useInAppBrowser: undefined, useInAppBrowser: undefined,
lastSelectedHomeFeed: undefined,
} }

View File

@ -16,45 +16,25 @@ import {usePinnedFeedsInfos, FeedSourceInfo} from '#/state/queries/feed'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {loadString, saveString} from '#/lib/storage'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {clamp} from '#/lib/numbers' import * as persisted from '#/state/persisted'
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
export function HomeScreen(props: Props) { export function HomeScreen(props: Props) {
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const {feeds: pinnedFeeds, isLoading: isPinnedFeedsLoading} = const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
usePinnedFeedsInfos() usePinnedFeedsInfos()
const {isDesktop} = useWebMediaQueries() const {isDesktop} = useWebMediaQueries()
const [initialPage, setInitialPage] = React.useState<string | undefined>( const [rawInitialFeed] = React.useState<string>(
undefined, () => persisted.get('lastSelectedHomeFeed') ?? 'home',
) )
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
React.useEffect(() => {
const loadLastActivePage = async () => {
try {
const lastActivePage =
(await loadString('lastActivePage')) ?? 'Following'
setInitialPage(lastActivePage)
} catch {
setInitialPage('Following')
}
}
loadLastActivePage()
}, [])
if (
preferences &&
pinnedFeeds &&
initialPage !== undefined &&
!isPinnedFeedsLoading
) {
return ( return (
<HomeScreenReady <HomeScreenReady
{...props} {...props}
preferences={preferences} preferences={preferences}
pinnedFeeds={pinnedFeeds} pinnedFeedInfos={pinnedFeedInfos}
initialPage={isDesktop ? 'Following' : initialPage} rawInitialFeed={isDesktop ? 'home' : rawInitialFeed}
/> />
) )
} else { } else {
@ -68,35 +48,17 @@ export function HomeScreen(props: Props) {
function HomeScreenReady({ function HomeScreenReady({
preferences, preferences,
pinnedFeeds, pinnedFeedInfos,
initialPage, rawInitialFeed,
}: Props & { }: Props & {
preferences: UsePreferencesQueryResponse preferences: UsePreferencesQueryResponse
pinnedFeeds: FeedSourceInfo[] pinnedFeedInfos: FeedSourceInfo[]
initialPage: string rawInitialFeed: string
}) { }) {
const {hasSession} = useSession() const allFeeds = React.useMemo(() => {
const setMinimalShellMode = useSetMinimalShellMode()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
const [selectedPage, setSelectedPage] = React.useState<string>(initialPage)
/**
* Used to ensure that we re-compute `customFeeds` AND force a re-render of
* the pager with the new order of feeds.
*/
const pinnedFeedOrderKey = JSON.stringify(preferences.feeds.pinned)
const selectedPageIndex = React.useMemo(() => {
const index = ['Following', ...preferences.feeds.pinned].indexOf(
selectedPage,
)
return Math.max(index, 0)
}, [preferences.feeds.pinned, selectedPage])
const customFeeds = React.useMemo(() => {
const pinned = pinnedFeeds
const feeds: FeedDescriptor[] = [] const feeds: FeedDescriptor[] = []
for (const {uri} of pinned) { feeds.push('home')
for (const {uri} of pinnedFeedInfos) {
if (uri.includes('app.bsky.feed.generator')) { if (uri.includes('app.bsky.feed.generator')) {
feeds.push(`feedgen|${uri}`) feeds.push(`feedgen|${uri}`)
} else if (uri.includes('app.bsky.graph.list')) { } else if (uri.includes('app.bsky.graph.list')) {
@ -104,41 +66,36 @@ function HomeScreenReady({
} }
} }
return feeds return feeds
}, [pinnedFeeds]) }, [pinnedFeedInfos])
const homeFeedParams = React.useMemo<FeedParams>(() => { const [rawSelectedFeed, setSelectedFeed] =
return { React.useState<string>(rawInitialFeed)
mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed as FeedDescriptor)
mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled const selectedIndex = Math.max(0, maybeFoundIndex)
? preferences.feeds.saved const selectedFeed = allFeeds[selectedIndex]
: [],
}
}, [preferences])
const {hasSession} = useSession()
const setMinimalShellMode = useSetMinimalShellMode()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
setMinimalShellMode(false) setMinimalShellMode(false)
setDrawerSwipeDisabled(selectedPageIndex > 0) setDrawerSwipeDisabled(selectedIndex > 0)
return () => { return () => {
setDrawerSwipeDisabled(false) setDrawerSwipeDisabled(false)
} }
}, [setDrawerSwipeDisabled, selectedPageIndex, setMinimalShellMode]), }, [setDrawerSwipeDisabled, selectedIndex, setMinimalShellMode]),
) )
const onPageSelected = React.useCallback( const onPageSelected = React.useCallback(
(index: number) => { (index: number) => {
setMinimalShellMode(false) setMinimalShellMode(false)
setDrawerSwipeDisabled(index > 0) setDrawerSwipeDisabled(index > 0)
const page = ['Following', ...preferences.feeds.pinned][index] const feed = allFeeds[index]
setSelectedPage(page) setSelectedFeed(feed)
saveString('lastActivePage', page) persisted.write('lastSelectedHomeFeed', feed)
}, },
[ [setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds],
setDrawerSwipeDisabled,
setSelectedPage,
setMinimalShellMode,
preferences.feeds.pinned,
],
) )
const onPressSelected = React.useCallback(() => { const onPressSelected = React.useCallback(() => {
@ -177,30 +134,40 @@ function HomeScreenReady({
return <CustomFeedEmptyState /> return <CustomFeedEmptyState />
}, []) }, [])
const [homeFeed, ...customFeeds] = allFeeds
const homeFeedParams = React.useMemo<FeedParams>(() => {
return {
mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled),
mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled
? preferences.feeds.saved
: [],
}
}, [preferences])
return hasSession ? ( return hasSession ? (
<Pager <Pager
key={pinnedFeedOrderKey} key={allFeeds.join(',')}
testID="homeScreen" testID="homeScreen"
initialPage={clamp(selectedPageIndex, 0, customFeeds.length)} initialPage={selectedIndex}
onPageSelected={onPageSelected} onPageSelected={onPageSelected}
onPageScrollStateChanged={onPageScrollStateChanged} onPageScrollStateChanged={onPageScrollStateChanged}
renderTabBar={renderTabBar}> renderTabBar={renderTabBar}>
<FeedPage <FeedPage
key="1" key={homeFeed}
testID="followingFeedPage" testID="followingFeedPage"
isPageFocused={selectedPageIndex === 0} isPageFocused={selectedFeed === homeFeed}
feed="home" feed={homeFeed}
feedParams={homeFeedParams} feedParams={homeFeedParams}
renderEmptyState={renderFollowingEmptyState} renderEmptyState={renderFollowingEmptyState}
renderEndOfFeed={FollowingEndOfFeed} renderEndOfFeed={FollowingEndOfFeed}
/> />
{customFeeds.map((f, index) => { {customFeeds.map(feed => {
return ( return (
<FeedPage <FeedPage
key={f} key={feed}
testID="customFeedPage" testID="customFeedPage"
isPageFocused={selectedPageIndex === 1 + index} isPageFocused={selectedFeed === feed}
feed={f} feed={feed}
renderEmptyState={renderCustomFeedEmptyState} renderEmptyState={renderCustomFeedEmptyState}
/> />
) )