Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add React Query and hook up to existing functionality (#1358)

* add React Query and hook up to existing functionality

* wire in remote data, add error message

* remove hard-coded feeds

* oops fix logic

* add loading state

* fix loading on mobile

---------

Co-authored-by: Eric Bailey <git@esb.lol>

authored by

Ansh
Eric Bailey
and committed by
GitHub
188d4893 84b7edd9

+108 -153
+1
package.json
··· 53 53 "@segment/analytics-react-native": "^2.10.1", 54 54 "@segment/sovran-react-native": "^0.4.5", 55 55 "@sentry/react-native": "5.5.0", 56 + "@tanstack/react-query": "^4.33.0", 56 57 "@tiptap/core": "^2.0.0-beta.220", 57 58 "@tiptap/extension-document": "^2.0.0-beta.220", 58 59 "@tiptap/extension-hard-break": "^2.0.3",
+15 -11
src/App.native.tsx
··· 16 16 import * as analytics from 'lib/analytics/analytics' 17 17 import * as Toast from './view/com/util/Toast' 18 18 import {handleLink} from './Navigation' 19 + import {QueryClientProvider} from '@tanstack/react-query' 20 + import {queryClient} from 'lib/react-query' 19 21 20 22 SplashScreen.preventAutoHideAsync() 21 23 ··· 51 53 return null 52 54 } 53 55 return ( 54 - <ThemeProvider theme={rootStore.shell.colorMode}> 55 - <RootSiblingParent> 56 - <analytics.Provider> 57 - <RootStoreProvider value={rootStore}> 58 - <GestureHandlerRootView style={s.h100pct}> 59 - <Shell /> 60 - </GestureHandlerRootView> 61 - </RootStoreProvider> 62 - </analytics.Provider> 63 - </RootSiblingParent> 64 - </ThemeProvider> 56 + <QueryClientProvider client={queryClient}> 57 + <ThemeProvider theme={rootStore.shell.colorMode}> 58 + <RootSiblingParent> 59 + <analytics.Provider> 60 + <RootStoreProvider value={rootStore}> 61 + <GestureHandlerRootView style={s.h100pct}> 62 + <Shell /> 63 + </GestureHandlerRootView> 64 + </RootStoreProvider> 65 + </analytics.Provider> 66 + </RootSiblingParent> 67 + </ThemeProvider> 68 + </QueryClientProvider> 65 69 ) 66 70 }) 67 71
+16 -12
src/App.web.tsx
··· 9 9 import {ToastContainer} from './view/com/util/Toast.web' 10 10 import {ThemeProvider} from 'lib/ThemeContext' 11 11 import {observer} from 'mobx-react-lite' 12 + import {QueryClientProvider} from '@tanstack/react-query' 13 + import {queryClient} from 'lib/react-query' 12 14 13 15 const App = observer(function AppImpl() { 14 16 const [rootStore, setRootStore] = useState<RootStoreModel | undefined>( ··· 30 32 } 31 33 32 34 return ( 33 - <ThemeProvider theme={rootStore.shell.colorMode}> 34 - <RootSiblingParent> 35 - <analytics.Provider> 36 - <RootStoreProvider value={rootStore}> 37 - <SafeAreaProvider> 38 - <Shell /> 39 - </SafeAreaProvider> 40 - <ToastContainer /> 41 - </RootStoreProvider> 42 - </analytics.Provider> 43 - </RootSiblingParent> 44 - </ThemeProvider> 35 + <QueryClientProvider client={queryClient}> 36 + <ThemeProvider theme={rootStore.shell.colorMode}> 37 + <RootSiblingParent> 38 + <analytics.Provider> 39 + <RootStoreProvider value={rootStore}> 40 + <SafeAreaProvider> 41 + <Shell /> 42 + </SafeAreaProvider> 43 + <ToastContainer /> 44 + </RootStoreProvider> 45 + </analytics.Provider> 46 + </RootSiblingParent> 47 + </ThemeProvider> 48 + </QueryClientProvider> 45 49 ) 46 50 }) 47 51
-107
src/lib/constants.ts
··· 148 148 export const HITSLOP_20 = createHitslop(20) 149 149 export const HITSLOP_30 = createHitslop(30) 150 150 export const BACK_HITSLOP = HITSLOP_30 151 - 152 - export const RECOMMENDED_FEEDS = [ 153 - { 154 - did: 'did:plc:hsqwcidfez66lwm3gxhfv5in', 155 - rkey: 'aaaf2pqeodmpy', 156 - }, 157 - { 158 - did: 'did:plc:gekdk2nd47gkk3utfz2xf7cn', 159 - rkey: 'aaap4tbjcfe5y', 160 - }, 161 - { 162 - did: 'did:plc:5rw2on4i56btlcajojaxwcat', 163 - rkey: 'aaao6g552b33o', 164 - }, 165 - { 166 - did: 'did:plc:jfhpnnst6flqway4eaeqzj2a', 167 - rkey: 'for-science', 168 - }, 169 - { 170 - did: 'did:plc:7q4nnnxawajbfaq7to5dpbsy', 171 - rkey: 'bsky-news', 172 - }, 173 - { 174 - did: 'did:plc:jcoy7v3a2t4rcfdh6i4kza25', 175 - rkey: 'astro', 176 - }, 177 - { 178 - did: 'did:plc:tenurhgjptubkk5zf5qhi3og', 179 - rkey: 'h-nba', 180 - }, 181 - { 182 - did: 'did:plc:vpkhqolt662uhesyj6nxm7ys', 183 - rkey: 'devfeed', 184 - }, 185 - { 186 - did: 'did:plc:cndfx4udwgvpjaakvxvh7wm5', 187 - rkey: 'flipboard-tech', 188 - }, 189 - { 190 - did: 'did:plc:w4xbfzo7kqfes5zb7r6qv3rw', 191 - rkey: 'blacksky', 192 - }, 193 - { 194 - did: 'did:plc:lptjvw6ut224kwrj7ub3sqbe', 195 - rkey: 'aaaotfjzjplna', 196 - }, 197 - { 198 - did: 'did:plc:gkvpokm7ec5j5yxls6xk4e3z', 199 - rkey: 'formula-one', 200 - }, 201 - { 202 - did: 'did:plc:q6gjnaw2blty4crticxkmujt', 203 - rkey: 'positivifeed', 204 - }, 205 - { 206 - did: 'did:plc:l72uci4styb4jucsgcrrj5ap', 207 - rkey: 'aaao5dzfm36u4', 208 - }, 209 - { 210 - did: 'did:plc:k3jkadxv5kkjgs6boyon7m6n', 211 - rkey: 'aaaavlyvqzst2', 212 - }, 213 - { 214 - did: 'did:plc:nkahctfdi6bxk72umytfwghw', 215 - rkey: 'aaado2uvfsc6w', 216 - }, 217 - { 218 - did: 'did:plc:epihigio3d7un7u3gpqiy5gv', 219 - rkey: 'aaaekwsc7zsvs', 220 - }, 221 - { 222 - did: 'did:plc:qiknc4t5rq7yngvz7g4aezq7', 223 - rkey: 'aaaejxlobe474', 224 - }, 225 - { 226 - did: 'did:plc:mlq4aycufcuolr7ax6sezpc4', 227 - rkey: 'aaaoudweck6uy', 228 - }, 229 - { 230 - did: 'did:plc:rcez5hcvq3vzlu5x7xrjyccg', 231 - rkey: 'aaadzjxbcddzi', 232 - }, 233 - { 234 - did: 'did:plc:lnxbuzaenlwjrncx6sc4cfdr', 235 - rkey: 'aaab2vesjtszc', 236 - }, 237 - { 238 - did: 'did:plc:x3cya3wkt4n6u4ihmvpsc5if', 239 - rkey: 'aaacynbxwimok', 240 - }, 241 - { 242 - did: 'did:plc:abv47bjgzjgoh3yrygwoi36x', 243 - rkey: 'aaagt6amuur5e', 244 - }, 245 - { 246 - did: 'did:plc:ffkgesg3jsv2j7aagkzrtcvt', 247 - rkey: 'aaacjerk7gwek', 248 - }, 249 - { 250 - did: 'did:plc:geoqe3qls5mwezckxxsewys2', 251 - rkey: 'aaai43yetqshu', 252 - }, 253 - { 254 - did: 'did:plc:2wqomm3tjqbgktbrfwgvrw34', 255 - rkey: 'authors', 256 - }, 257 - ]
+3
src/lib/react-query.ts
··· 1 + import {QueryClient} from '@tanstack/react-query' 2 + 3 + export const queryClient = new QueryClient()
+56 -14
src/view/com/auth/onboarding/RecommendedFeeds.tsx
··· 1 1 import React from 'react' 2 - import {FlatList, StyleSheet, View} from 'react-native' 2 + import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' 3 3 import {observer} from 'mobx-react-lite' 4 4 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 5 5 import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints' ··· 10 10 import {RecommendedFeedsItem} from './RecommendedFeedsItem' 11 11 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 12 12 import {usePalette} from 'lib/hooks/usePalette' 13 - import {RECOMMENDED_FEEDS} from 'lib/constants' 13 + import {useQuery} from '@tanstack/react-query' 14 + import {useStores} from 'state/index' 15 + import {CustomFeedModel} from 'state/models/feeds/custom-feed' 16 + import {ErrorMessage} from 'view/com/util/error/ErrorMessage' 14 17 15 18 type Props = { 16 19 next: () => void ··· 18 21 export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ 19 22 next, 20 23 }: Props) { 24 + const store = useStores() 21 25 const pal = usePalette('default') 22 26 const {isTabletOrMobile} = useWebMediaQueries() 27 + const {isLoading, data: recommendedFeeds} = useQuery({ 28 + staleTime: Infinity, // fixed list rn, never refetch 29 + queryKey: ['onboarding', 'recommended_feeds'], 30 + async queryFn() { 31 + try { 32 + const { 33 + data: {feeds}, 34 + success, 35 + } = await store.agent.app.bsky.feed.getSuggestedFeeds() 36 + 37 + if (!success) return 38 + 39 + return (feeds.length ? feeds : []).map(feed => { 40 + return new CustomFeedModel(store, feed) 41 + }) 42 + } catch (e) { 43 + return 44 + } 45 + }, 46 + }) 47 + 48 + const hasFeeds = recommendedFeeds && recommendedFeeds.length 23 49 24 50 const title = ( 25 51 <> ··· 86 112 horizontal 87 113 titleStyle={isTabletOrMobile ? undefined : {minWidth: 470}} 88 114 contentStyle={{paddingHorizontal: 0}}> 89 - <FlatList 90 - data={RECOMMENDED_FEEDS} 91 - renderItem={({item}) => <RecommendedFeedsItem {...item} />} 92 - keyExtractor={item => item.did + item.rkey} 93 - style={{flex: 1}} 94 - /> 115 + {hasFeeds ? ( 116 + <FlatList 117 + data={recommendedFeeds} 118 + renderItem={({item}) => <RecommendedFeedsItem item={item} />} 119 + keyExtractor={item => item.uri} 120 + style={{flex: 1}} 121 + /> 122 + ) : isLoading ? ( 123 + <View> 124 + <ActivityIndicator size="large" /> 125 + </View> 126 + ) : ( 127 + <ErrorMessage message="Failed to load recommended feeds" /> 128 + )} 95 129 </TitleColumnLayout> 96 130 </TabletOrDesktop> 97 131 <Mobile> ··· 106 140 pinned feeds. 107 141 </Text> 108 142 109 - <FlatList 110 - data={RECOMMENDED_FEEDS} 111 - renderItem={({item}) => <RecommendedFeedsItem {...item} />} 112 - keyExtractor={item => item.did + item.rkey} 113 - style={{flex: 1}} 114 - /> 143 + {hasFeeds ? ( 144 + <FlatList 145 + data={recommendedFeeds} 146 + renderItem={({item}) => <RecommendedFeedsItem item={item} />} 147 + keyExtractor={item => item.uri} 148 + style={{flex: 1}} 149 + /> 150 + ) : isLoading ? ( 151 + <View> 152 + <ActivityIndicator size="large" /> 153 + </View> 154 + ) : ( 155 + <ErrorMessage message="Failed to load recommended feeds" /> 156 + )} 115 157 116 158 <Button 117 159 onPress={next}
+3 -8
src/view/com/auth/onboarding/RecommendedFeedsItem.tsx
··· 8 8 import * as Toast from 'view/com/util/Toast' 9 9 import {HeartIcon} from 'lib/icons' 10 10 import {usePalette} from 'lib/hooks/usePalette' 11 - import {useCustomFeed} from 'lib/hooks/useCustomFeed' 12 11 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 13 - import {makeRecordUri} from 'lib/strings/url-helpers' 14 12 import {sanitizeHandle} from 'lib/strings/handles' 13 + import {CustomFeedModel} from 'state/models/feeds/custom-feed' 15 14 16 15 export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ 17 - did, 18 - rkey, 16 + item, 19 17 }: { 20 - did: string 21 - rkey: string 18 + item: CustomFeedModel 22 19 }) { 23 20 const {isMobile} = useWebMediaQueries() 24 21 const pal = usePalette('default') 25 - const uri = makeRecordUri(did, 'app.bsky.feed.generator', rkey) 26 - const item = useCustomFeed(uri) 27 22 if (!item) return null 28 23 const onToggle = async () => { 29 24 if (item.isSaved) {
+14 -1
yarn.lock
··· 6005 6005 "@svgr/plugin-svgo" "^5.5.0" 6006 6006 loader-utils "^2.0.0" 6007 6007 6008 + "@tanstack/query-core@4.33.0": 6009 + version "4.33.0" 6010 + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.33.0.tgz#7756da9a75a424e521622b1d84eb55b7a2b33715" 6011 + integrity sha512-qYu73ptvnzRh6se2nyBIDHGBQvPY1XXl3yR769B7B6mIDD7s+EZhdlWHQ67JI6UOTFRaI7wupnTnwJ3gE0Mr/g== 6012 + 6013 + "@tanstack/react-query@^4.33.0": 6014 + version "4.33.0" 6015 + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.33.0.tgz#e927b0343a6ecaa948fee59e9ca98fe561062638" 6016 + integrity sha512-97nGbmDK0/m0B86BdiXzx3EW9RcDYKpnyL2+WwyuLHEgpfThYAnXFaMMmnTDuAO4bQJXEhflumIEUfKmP7ESGA== 6017 + dependencies: 6018 + "@tanstack/query-core" "4.33.0" 6019 + use-sync-external-store "^1.2.0" 6020 + 6008 6021 "@testing-library/jest-native@^5.4.1": 6009 6022 version "5.4.2" 6010 6023 resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-5.4.2.tgz#6b0c987cc57f8d900763e763025d00d26ccbc85f" ··· 19293 19306 detect-node-es "^1.1.0" 19294 19307 tslib "^2.0.0" 19295 19308 19296 - use-sync-external-store@^1.0.0: 19309 + use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: 19297 19310 version "1.2.0" 19298 19311 resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" 19299 19312 integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==