Bluesky app fork with some witchin' additions 💫

Refactor Home feed pager rendering logic (#2768)

* Use new persistence API for selected feed

* Refactor Home feeds pager data source

authored by danabra.mov and committed by

GitHub 4583521b f393dda5

+51 -81
+1
src/state/persisted/legacy.ts
··· 111 111 }, 112 112 hiddenPosts: defaults.hiddenPosts, 113 113 externalEmbeds: defaults.externalEmbeds, 114 + lastSelectedHomeFeed: defaults.lastSelectedHomeFeed, 114 115 } 115 116 } 116 117
+2
src/state/persisted/schema.ts
··· 56 56 }), 57 57 hiddenPosts: z.array(z.string()).optional(), // should move to server 58 58 useInAppBrowser: z.boolean().optional(), 59 + lastSelectedHomeFeed: z.string().optional(), 59 60 }) 60 61 export type Schema = z.infer<typeof schema> 61 62 ··· 89 90 }, 90 91 hiddenPosts: [], 91 92 useInAppBrowser: undefined, 93 + lastSelectedHomeFeed: undefined, 92 94 }
+48 -81
src/view/screens/Home.tsx
··· 16 16 import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 17 17 import {emitSoftReset} from '#/state/events' 18 18 import {useSession} from '#/state/session' 19 - import {loadString, saveString} from '#/lib/storage' 20 19 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 21 - import {clamp} from '#/lib/numbers' 20 + import * as persisted from '#/state/persisted' 22 21 23 22 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> 24 23 export function HomeScreen(props: Props) { 25 24 const {data: preferences} = usePreferencesQuery() 26 - const {feeds: pinnedFeeds, isLoading: isPinnedFeedsLoading} = 25 + const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} = 27 26 usePinnedFeedsInfos() 28 27 const {isDesktop} = useWebMediaQueries() 29 - const [initialPage, setInitialPage] = React.useState<string | undefined>( 30 - undefined, 28 + const [rawInitialFeed] = React.useState<string>( 29 + () => persisted.get('lastSelectedHomeFeed') ?? 'home', 31 30 ) 32 - 33 - React.useEffect(() => { 34 - const loadLastActivePage = async () => { 35 - try { 36 - const lastActivePage = 37 - (await loadString('lastActivePage')) ?? 'Following' 38 - setInitialPage(lastActivePage) 39 - } catch { 40 - setInitialPage('Following') 41 - } 42 - } 43 - loadLastActivePage() 44 - }, []) 45 - 46 - if ( 47 - preferences && 48 - pinnedFeeds && 49 - initialPage !== undefined && 50 - !isPinnedFeedsLoading 51 - ) { 31 + if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) { 52 32 return ( 53 33 <HomeScreenReady 54 34 {...props} 55 35 preferences={preferences} 56 - pinnedFeeds={pinnedFeeds} 57 - initialPage={isDesktop ? 'Following' : initialPage} 36 + pinnedFeedInfos={pinnedFeedInfos} 37 + rawInitialFeed={isDesktop ? 'home' : rawInitialFeed} 58 38 /> 59 39 ) 60 40 } else { ··· 68 48 69 49 function HomeScreenReady({ 70 50 preferences, 71 - pinnedFeeds, 72 - initialPage, 51 + pinnedFeedInfos, 52 + rawInitialFeed, 73 53 }: Props & { 74 54 preferences: UsePreferencesQueryResponse 75 - pinnedFeeds: FeedSourceInfo[] 76 - initialPage: string 55 + pinnedFeedInfos: FeedSourceInfo[] 56 + rawInitialFeed: string 77 57 }) { 78 - const {hasSession} = useSession() 79 - const setMinimalShellMode = useSetMinimalShellMode() 80 - const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() 81 - const [selectedPage, setSelectedPage] = React.useState<string>(initialPage) 82 - 83 - /** 84 - * Used to ensure that we re-compute `customFeeds` AND force a re-render of 85 - * the pager with the new order of feeds. 86 - */ 87 - const pinnedFeedOrderKey = JSON.stringify(preferences.feeds.pinned) 88 - 89 - const selectedPageIndex = React.useMemo(() => { 90 - const index = ['Following', ...preferences.feeds.pinned].indexOf( 91 - selectedPage, 92 - ) 93 - return Math.max(index, 0) 94 - }, [preferences.feeds.pinned, selectedPage]) 95 - 96 - const customFeeds = React.useMemo(() => { 97 - const pinned = pinnedFeeds 58 + const allFeeds = React.useMemo(() => { 98 59 const feeds: FeedDescriptor[] = [] 99 - for (const {uri} of pinned) { 60 + feeds.push('home') 61 + for (const {uri} of pinnedFeedInfos) { 100 62 if (uri.includes('app.bsky.feed.generator')) { 101 63 feeds.push(`feedgen|${uri}`) 102 64 } else if (uri.includes('app.bsky.graph.list')) { ··· 104 66 } 105 67 } 106 68 return feeds 107 - }, [pinnedFeeds]) 69 + }, [pinnedFeedInfos]) 108 70 109 - const homeFeedParams = React.useMemo<FeedParams>(() => { 110 - return { 111 - mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), 112 - mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled 113 - ? preferences.feeds.saved 114 - : [], 115 - } 116 - }, [preferences]) 71 + const [rawSelectedFeed, setSelectedFeed] = 72 + React.useState<string>(rawInitialFeed) 73 + const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed as FeedDescriptor) 74 + const selectedIndex = Math.max(0, maybeFoundIndex) 75 + const selectedFeed = allFeeds[selectedIndex] 117 76 77 + const {hasSession} = useSession() 78 + const setMinimalShellMode = useSetMinimalShellMode() 79 + const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() 118 80 useFocusEffect( 119 81 React.useCallback(() => { 120 82 setMinimalShellMode(false) 121 - setDrawerSwipeDisabled(selectedPageIndex > 0) 83 + setDrawerSwipeDisabled(selectedIndex > 0) 122 84 return () => { 123 85 setDrawerSwipeDisabled(false) 124 86 } 125 - }, [setDrawerSwipeDisabled, selectedPageIndex, setMinimalShellMode]), 87 + }, [setDrawerSwipeDisabled, selectedIndex, setMinimalShellMode]), 126 88 ) 127 89 128 90 const onPageSelected = React.useCallback( 129 91 (index: number) => { 130 92 setMinimalShellMode(false) 131 93 setDrawerSwipeDisabled(index > 0) 132 - const page = ['Following', ...preferences.feeds.pinned][index] 133 - setSelectedPage(page) 134 - saveString('lastActivePage', page) 94 + const feed = allFeeds[index] 95 + setSelectedFeed(feed) 96 + persisted.write('lastSelectedHomeFeed', feed) 135 97 }, 136 - [ 137 - setDrawerSwipeDisabled, 138 - setSelectedPage, 139 - setMinimalShellMode, 140 - preferences.feeds.pinned, 141 - ], 98 + [setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds], 142 99 ) 143 100 144 101 const onPressSelected = React.useCallback(() => { ··· 177 134 return <CustomFeedEmptyState /> 178 135 }, []) 179 136 137 + const [homeFeed, ...customFeeds] = allFeeds 138 + const homeFeedParams = React.useMemo<FeedParams>(() => { 139 + return { 140 + mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), 141 + mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled 142 + ? preferences.feeds.saved 143 + : [], 144 + } 145 + }, [preferences]) 146 + 180 147 return hasSession ? ( 181 148 <Pager 182 - key={pinnedFeedOrderKey} 149 + key={allFeeds.join(',')} 183 150 testID="homeScreen" 184 - initialPage={clamp(selectedPageIndex, 0, customFeeds.length)} 151 + initialPage={selectedIndex} 185 152 onPageSelected={onPageSelected} 186 153 onPageScrollStateChanged={onPageScrollStateChanged} 187 154 renderTabBar={renderTabBar}> 188 155 <FeedPage 189 - key="1" 156 + key={homeFeed} 190 157 testID="followingFeedPage" 191 - isPageFocused={selectedPageIndex === 0} 192 - feed="home" 158 + isPageFocused={selectedFeed === homeFeed} 159 + feed={homeFeed} 193 160 feedParams={homeFeedParams} 194 161 renderEmptyState={renderFollowingEmptyState} 195 162 renderEndOfFeed={FollowingEndOfFeed} 196 163 /> 197 - {customFeeds.map((f, index) => { 164 + {customFeeds.map(feed => { 198 165 return ( 199 166 <FeedPage 200 - key={f} 167 + key={feed} 201 168 testID="customFeedPage" 202 - isPageFocused={selectedPageIndex === 1 + index} 203 - feed={f} 169 + isPageFocused={selectedFeed === feed} 170 + feed={feed} 204 171 renderEmptyState={renderCustomFeedEmptyState} 205 172 /> 206 173 )