Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Add a new home feed-api wrapper and give a header indicating the fallback behavior (#2534)

* Add a new home feed-api wrapper and give a header indicating the fallback behavior

* Sneak in a quick fix: use the correct text color in the delete modal

* Use imported constant

authored by

Paul Frazee and committed by
GitHub
a7d617c7 7df0b7ad

+152 -6
+88
src/lib/api/feed/home.ts
··· 1 + import {AppBskyFeedDefs} from '@atproto/api' 2 + import {FeedAPI, FeedAPIResponse} from './types' 3 + import {FollowingFeedAPI} from './following' 4 + import {CustomFeedAPI} from './custom' 5 + import {PROD_DEFAULT_FEED} from '#/lib/constants' 6 + 7 + // HACK 8 + // the feed API does not include any facilities for passing down 9 + // non-post elements. adding that is a bit of a heavy lift, and we 10 + // have just one temporary usecase for it: flagging when the home feed 11 + // falls back to discover. 12 + // we use this fallback marker post to drive this instead. see Feed.tsx 13 + // for the usage. 14 + // -prf 15 + export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = { 16 + post: { 17 + uri: 'fallback-marker-post', 18 + cid: 'fake', 19 + record: {}, 20 + author: { 21 + did: 'did:fake', 22 + handle: 'fake.com', 23 + }, 24 + indexedAt: new Date().toISOString(), 25 + }, 26 + } 27 + 28 + export class HomeFeedAPI implements FeedAPI { 29 + following: FollowingFeedAPI 30 + discover: CustomFeedAPI 31 + usingDiscover = false 32 + itemCursor = 0 33 + 34 + constructor() { 35 + this.following = new FollowingFeedAPI() 36 + this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) 37 + } 38 + 39 + reset() { 40 + this.following = new FollowingFeedAPI() 41 + this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) 42 + this.usingDiscover = false 43 + this.itemCursor = 0 44 + } 45 + 46 + async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { 47 + if (this.usingDiscover) { 48 + return this.discover.peekLatest() 49 + } 50 + return this.following.peekLatest() 51 + } 52 + 53 + async fetch({ 54 + cursor, 55 + limit, 56 + }: { 57 + cursor: string | undefined 58 + limit: number 59 + }): Promise<FeedAPIResponse> { 60 + if (!cursor) { 61 + this.reset() 62 + } 63 + 64 + let returnCursor 65 + let posts: AppBskyFeedDefs.FeedViewPost[] = [] 66 + 67 + if (!this.usingDiscover) { 68 + const res = await this.following.fetch({cursor, limit}) 69 + returnCursor = res.cursor 70 + posts = posts.concat(res.feed) 71 + if (res.feed.length === 0 || !cursor) { 72 + posts.push(FALLBACK_MARKER_POST) 73 + this.usingDiscover = true 74 + } 75 + } 76 + 77 + if (this.usingDiscover) { 78 + const res = await this.discover.fetch({cursor, limit}) 79 + returnCursor = res.cursor 80 + posts = posts.concat(res.feed) 81 + } 82 + 83 + return { 84 + cursor: returnCursor, 85 + feed: posts, 86 + } 87 + } 88 + }
+1 -1
src/lib/api/feed/merge.ts
··· 26 26 27 27 reset() { 28 28 this.following = new MergeFeedSource_Following(this.feedTuners) 29 - this.customFeeds = [] // just empty the array, they will be captured in _fetchNext() 29 + this.customFeeds = [] 30 30 this.feedCursor = 0 31 31 this.itemCursor = 0 32 32 this.sampleCursor = 0
+6 -1
src/state/queries/post-feed.ts
··· 18 18 import {CustomFeedAPI} from 'lib/api/feed/custom' 19 19 import {ListFeedAPI} from 'lib/api/feed/list' 20 20 import {MergeFeedAPI} from 'lib/api/feed/merge' 21 + import {HomeFeedAPI} from '#/lib/api/feed/home' 21 22 import {logger} from '#/logger' 22 23 import {STALE} from '#/state/queries' 23 24 import {precacheFeedPosts as precacheResolvedUris} from './resolve-uri' ··· 338 339 feedTuners: FeedTunerFn[], 339 340 ) { 340 341 if (feedDesc === 'home') { 341 - return new MergeFeedAPI(params, feedTuners) 342 + if (params.mergeFeedEnabled) { 343 + return new MergeFeedAPI(params, feedTuners) 344 + } else { 345 + return new HomeFeedAPI() 346 + } 342 347 } else if (feedDesc === 'following') { 343 348 return new FollowingFeedAPI() 344 349 } else if (feedDesc.startsWith('author')) {
+5 -2
src/view/com/modals/DeleteAccount.tsx
··· 160 160 {/* TODO: Update this label to be more concise */} 161 161 <Text 162 162 type="lg" 163 - style={styles.description} 163 + style={[pal.text, styles.description]} 164 164 nativeID="confirmationCode"> 165 165 <Trans> 166 166 Check your inbox for an email with the confirmation code to ··· 180 180 msg`Input confirmation code for account deletion`, 181 181 )} 182 182 /> 183 - <Text type="lg" style={styles.description} nativeID="password"> 183 + <Text 184 + type="lg" 185 + style={[pal.text, styles.description]} 186 + nativeID="password"> 184 187 <Trans>Please enter your password as well:</Trans> 185 188 </Text> 186 189 <TextInput
+43
src/view/com/posts/DiscoverFallbackHeader.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {Trans} from '@lingui/macro' 4 + import {Text} from '../util/text/Text' 5 + import {usePalette} from '#/lib/hooks/usePalette' 6 + import {TextLink} from '../util/Link' 7 + import {InfoCircleIcon} from '#/lib/icons' 8 + 9 + export function DiscoverFallbackHeader() { 10 + const pal = usePalette('default') 11 + return ( 12 + <View 13 + style={[ 14 + { 15 + flexDirection: 'row', 16 + alignItems: 'center', 17 + paddingVertical: 12, 18 + paddingHorizontal: 12, 19 + borderTopWidth: 1, 20 + }, 21 + pal.border, 22 + pal.viewLight, 23 + ]}> 24 + <View style={{width: 68, paddingLeft: 12}}> 25 + <InfoCircleIcon size={36} style={pal.textLight} strokeWidth={1.5} /> 26 + </View> 27 + <View style={{flex: 1}}> 28 + <Text type="md" style={pal.text}> 29 + <Trans> 30 + We ran out of posts from your follows. Here's the latest from 31 + </Trans>{' '} 32 + <TextLink 33 + type="md-medium" 34 + href="/profile/bsky.app/feed/whats-hot" 35 + text="Discover" 36 + style={pal.link} 37 + /> 38 + . 39 + </Text> 40 + </View> 41 + </View> 42 + ) 43 + }
+8
src/view/com/posts/Feed.tsx
··· 30 30 import {STALE} from '#/state/queries' 31 31 import {msg} from '@lingui/macro' 32 32 import {useLingui} from '@lingui/react' 33 + import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' 34 + import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home' 33 35 34 36 const LOADING_ITEM = {_reactKey: '__loading__'} 35 37 const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} ··· 265 267 ) 266 268 } else if (item === LOADING_ITEM) { 267 269 return <PostFeedLoadingPlaceholder /> 270 + } else if (item.rootUri === FALLBACK_MARKER_POST.post.uri) { 271 + // HACK 272 + // tell the user we fell back to discover 273 + // see home.ts (feed api) for more info 274 + // -prf 275 + return <DiscoverFallbackHeader /> 268 276 } 269 277 return <FeedSlice slice={item} /> 270 278 },
+1 -2
src/view/screens/Home.tsx
··· 19 19 import {loadString, saveString} from '#/lib/storage' 20 20 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 21 21 import {clamp} from '#/lib/numbers' 22 - import {PROD_DEFAULT_FEED} from '#/lib/constants' 23 22 24 23 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> 25 24 export function HomeScreen(props: Props) { ··· 112 111 mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), 113 112 mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled 114 113 ? preferences.feeds.saved 115 - : [PROD_DEFAULT_FEED('whats-hot')], 114 + : [], 116 115 } 117 116 }, [preferences]) 118 117