Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Add shutdown message to for you feed (#3776)

authored by

Paul Frazee and committed by
GitHub
6f5b551b d0440d08

+174 -2
+4
src/lib/constants.ts
··· 111 111 'type' | 'value' | 'pinned' 112 112 >[] = [DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED] 113 113 114 + export const KNOWN_SHUTDOWN_FEEDS = [ 115 + 'at://did:plc:wqowuobffl66jv3kpsvo7ak4/app.bsky.feed.generator/the-algorithm', // for you by skygaze 116 + ] 117 + 114 118 export const GIF_SERVICE = 'https://gifs.bsky.app' 115 119 116 120 export const GIF_SEARCH = (params: string) =>
+11 -2
src/view/com/posts/Feed.tsx
··· 14 14 import {useQueryClient} from '@tanstack/react-query' 15 15 16 16 import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home' 17 + import {KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants' 17 18 import {logEvent} from '#/lib/statsig/statsig' 18 19 import {logger} from '#/logger' 19 20 import {isWeb} from '#/platform/detection' ··· 36 37 import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' 37 38 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' 38 39 import {FeedErrorMessage} from './FeedErrorMessage' 40 + import {FeedShutdownMsg} from './FeedShutdownMsg' 39 41 import {FeedSlice} from './FeedSlice' 40 42 41 43 const LOADING_ITEM = {_reactKey: '__loading__'} 42 44 const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} 43 45 const ERROR_ITEM = {_reactKey: '__error__'} 44 46 const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} 47 + const FEED_SHUTDOWN_MSG_ITEM = {_reactKey: '__feed_shutdown_msg_item__'} 45 48 46 49 // DISABLED need to check if this is causing random feed refreshes -prf 47 50 // const REFRESH_AFTER = STALE.HOURS.ONE ··· 96 99 const [isPTRing, setIsPTRing] = React.useState(false) 97 100 const checkForNewRef = React.useRef<(() => void) | null>(null) 98 101 const lastFetchRef = React.useRef<number>(Date.now()) 99 - const feedType = feed.split('|')[0] 102 + const [feedType, feedUri] = feed.split('|') 100 103 101 104 const opts = React.useMemo( 102 105 () => ({enabled, ignoreFilterFor}), ··· 196 199 197 200 const feedItems = React.useMemo(() => { 198 201 let arr: any[] = [] 202 + if (KNOWN_SHUTDOWN_FEEDS.includes(feedUri)) { 203 + arr = arr.concat([FEED_SHUTDOWN_MSG_ITEM]) 204 + } 199 205 if (isFetched) { 200 206 if (isError && isEmpty) { 201 207 arr = arr.concat([ERROR_ITEM]) ··· 213 219 arr.push(LOADING_ITEM) 214 220 } 215 221 return arr 216 - }, [isFetched, isError, isEmpty, data]) 222 + }, [isFetched, isError, isEmpty, data, feedUri]) 217 223 218 224 // events 219 225 // = ··· 296 302 ) 297 303 } else if (item === LOADING_ITEM) { 298 304 return <PostFeedLoadingPlaceholder /> 305 + } else if (item === FEED_SHUTDOWN_MSG_ITEM) { 306 + return <FeedShutdownMsg feedUri={feedUri} /> 299 307 } else if (item.rootUri === FALLBACK_MARKER_POST.post.uri) { 300 308 // HACK 301 309 // tell the user we fell back to discover ··· 307 315 }, 308 316 [ 309 317 feed, 318 + feedUri, 310 319 error, 311 320 onPressTryAgain, 312 321 onPressRetryLoadMore,
+159
src/view/com/posts/FeedShutdownMsg.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {PROD_DEFAULT_FEED} from '#/lib/constants' 7 + import {logger} from '#/logger' 8 + import { 9 + useAddSavedFeedsMutation, 10 + usePreferencesQuery, 11 + useRemoveFeedMutation, 12 + useUpdateSavedFeedsMutation, 13 + } from '#/state/queries/preferences' 14 + import {useSetSelectedFeed} from '#/state/shell/selected-feed' 15 + import * as Toast from '#/view/com/util/Toast' 16 + import {atoms as a, useTheme} from '#/alf' 17 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 18 + import {InlineLinkText} from '#/components/Link' 19 + import {Loader} from '#/components/Loader' 20 + import {Text} from '#/components/Typography' 21 + 22 + export function FeedShutdownMsg({feedUri}: {feedUri: string}) { 23 + const t = useTheme() 24 + const {_} = useLingui() 25 + const setSelectedFeed = useSetSelectedFeed() 26 + const {data: preferences} = usePreferencesQuery() 27 + const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} = 28 + useAddSavedFeedsMutation() 29 + const {mutateAsync: removeFeed, isPending: isRemovePending} = 30 + useRemoveFeedMutation() 31 + const {mutateAsync: updateSavedFeeds, isPending: isUpdateFeedPending} = 32 + useUpdateSavedFeedsMutation() 33 + 34 + const feedConfig = preferences?.savedFeeds?.find( 35 + f => f.value === feedUri && f.pinned, 36 + ) 37 + const discoverFeedConfig = preferences?.savedFeeds?.find( 38 + f => f.value === PROD_DEFAULT_FEED('whats-hot'), 39 + ) 40 + const hasFeedPinned = Boolean(feedConfig) 41 + const hasDiscoverPinned = Boolean(discoverFeedConfig?.pinned) 42 + 43 + const onRemoveFeed = React.useCallback(async () => { 44 + try { 45 + if (feedConfig) { 46 + await removeFeed(feedConfig) 47 + Toast.show(_(msg`Removed from your feeds`)) 48 + } 49 + } catch (err: any) { 50 + Toast.show( 51 + _( 52 + msg`There was an an issue updating your feeds, please check your internet connection and try again.`, 53 + ), 54 + ) 55 + logger.error('Failed up update feeds', {message: err}) 56 + } 57 + }, [removeFeed, feedConfig, _]) 58 + 59 + const onReplaceFeed = React.useCallback(async () => { 60 + try { 61 + if (!discoverFeedConfig) { 62 + await addSavedFeeds([ 63 + { 64 + type: 'feed', 65 + value: PROD_DEFAULT_FEED('whats-hot'), 66 + pinned: true, 67 + }, 68 + ]) 69 + } else { 70 + await updateSavedFeeds([ 71 + { 72 + ...discoverFeedConfig, 73 + pinned: true, 74 + }, 75 + ]) 76 + } 77 + setSelectedFeed(`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`) 78 + if (feedConfig) { 79 + await removeFeed(feedConfig) 80 + } 81 + Toast.show(_(msg`The feed has been replaced with Discover.`)) 82 + } catch (err: any) { 83 + Toast.show( 84 + _( 85 + msg`There was an an issue updating your feeds, please check your internet connection and try again.`, 86 + ), 87 + ) 88 + logger.error('Failed up update feeds', {message: err}) 89 + } 90 + }, [ 91 + addSavedFeeds, 92 + updateSavedFeeds, 93 + removeFeed, 94 + discoverFeedConfig, 95 + feedConfig, 96 + setSelectedFeed, 97 + _, 98 + ]) 99 + 100 + const isProcessing = 101 + isAddSavedFeedPending || isUpdateFeedPending || isRemovePending 102 + return ( 103 + <View 104 + style={[ 105 + a.py_3xl, 106 + a.px_2xl, 107 + a.gap_xl, 108 + t.atoms.border_contrast_low, 109 + a.border_t, 110 + ]}> 111 + <Text style={[a.text_5xl, a.font_bold, t.atoms.text, a.text_center]}> 112 + :( 113 + </Text> 114 + <Text style={[a.text_md, a.leading_snug, t.atoms.text, a.text_center]}> 115 + <Trans> 116 + This feed is no longer online. We are showing{' '} 117 + <InlineLinkText 118 + to="/profile/bsky.app/feed/whats-hot" 119 + style={[a.text_md]}> 120 + Discover 121 + </InlineLinkText>{' '} 122 + instead. 123 + </Trans> 124 + </Text> 125 + {hasFeedPinned ? ( 126 + <View style={[a.flex_row, a.justify_center, a.gap_sm]}> 127 + <Button 128 + variant="outline" 129 + color="primary" 130 + size="small" 131 + label={_(msg`Remove feed`)} 132 + disabled={isProcessing} 133 + onPress={onRemoveFeed}> 134 + <ButtonText> 135 + <Trans>Remove feed</Trans> 136 + </ButtonText> 137 + {isRemovePending && <ButtonIcon icon={Loader} />} 138 + </Button> 139 + {!hasDiscoverPinned && ( 140 + <Button 141 + variant="solid" 142 + color="primary" 143 + size="small" 144 + label={_(msg`Replace with Discover`)} 145 + disabled={isProcessing} 146 + onPress={onReplaceFeed}> 147 + <ButtonText> 148 + <Trans>Replace with Discover</Trans> 149 + </ButtonText> 150 + {(isAddSavedFeedPending || isUpdateFeedPending) && ( 151 + <ButtonIcon icon={Loader} /> 152 + )} 153 + </Button> 154 + )} 155 + </View> 156 + ) : undefined} 157 + </View> 158 + ) 159 + }