Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

[Experiment] Remove "Load Latest" button (#7120)

* Remove "show latest" behind the gate

* Add HomeBadgeProvider

* Update provider state from home feed tabs

* Add Home badge to native

* Add Home badge to mobile web

* Add Home badge to desktop web

authored by

dan and committed by
GitHub
1ac307bc 80c0125d

+136 -37
+17 -14
src/App.native.tsx
··· 32 32 ensureGeolocationResolved, 33 33 Provider as GeolocationProvider, 34 34 } from '#/state/geolocation' 35 + import {Provider as HomeBadgeProvider} from '#/state/home-badge' 35 36 import {Provider as InvitesStateProvider} from '#/state/invites' 36 37 import {Provider as LightboxStateProvider} from '#/state/lightbox' 37 38 import {MessagesProvider} from '#/state/messages' ··· 137 138 <LoggedOutViewProvider> 138 139 <SelectedFeedProvider> 139 140 <HiddenRepliesProvider> 140 - <UnreadNotifsProvider> 141 - <BackgroundNotificationPreferencesProvider> 142 - <MutedThreadsProvider> 143 - <ProgressGuideProvider> 144 - <GestureHandlerRootView 145 - style={s.h100pct}> 146 - <TestCtrls /> 147 - <Shell /> 148 - <NuxDialogs /> 149 - </GestureHandlerRootView> 150 - </ProgressGuideProvider> 151 - </MutedThreadsProvider> 152 - </BackgroundNotificationPreferencesProvider> 153 - </UnreadNotifsProvider> 141 + <HomeBadgeProvider> 142 + <UnreadNotifsProvider> 143 + <BackgroundNotificationPreferencesProvider> 144 + <MutedThreadsProvider> 145 + <ProgressGuideProvider> 146 + <GestureHandlerRootView 147 + style={s.h100pct}> 148 + <TestCtrls /> 149 + <Shell /> 150 + <NuxDialogs /> 151 + </GestureHandlerRootView> 152 + </ProgressGuideProvider> 153 + </MutedThreadsProvider> 154 + </BackgroundNotificationPreferencesProvider> 155 + </UnreadNotifsProvider> 156 + </HomeBadgeProvider> 154 157 </HiddenRepliesProvider> 155 158 </SelectedFeedProvider> 156 159 </LoggedOutViewProvider>
+15 -12
src/App.web.tsx
··· 22 22 ensureGeolocationResolved, 23 23 Provider as GeolocationProvider, 24 24 } from '#/state/geolocation' 25 + import {Provider as HomeBadgeProvider} from '#/state/home-badge' 25 26 import {Provider as InvitesStateProvider} from '#/state/invites' 26 27 import {Provider as LightboxStateProvider} from '#/state/lightbox' 27 28 import {MessagesProvider} from '#/state/messages' ··· 120 121 <LoggedOutViewProvider> 121 122 <SelectedFeedProvider> 122 123 <HiddenRepliesProvider> 123 - <UnreadNotifsProvider> 124 - <BackgroundNotificationPreferencesProvider> 125 - <MutedThreadsProvider> 126 - <SafeAreaProvider> 127 - <ProgressGuideProvider> 128 - <Shell /> 129 - <NuxDialogs /> 130 - </ProgressGuideProvider> 131 - </SafeAreaProvider> 132 - </MutedThreadsProvider> 133 - </BackgroundNotificationPreferencesProvider> 134 - </UnreadNotifsProvider> 124 + <HomeBadgeProvider> 125 + <UnreadNotifsProvider> 126 + <BackgroundNotificationPreferencesProvider> 127 + <MutedThreadsProvider> 128 + <SafeAreaProvider> 129 + <ProgressGuideProvider> 130 + <Shell /> 131 + <NuxDialogs /> 132 + </ProgressGuideProvider> 133 + </SafeAreaProvider> 134 + </MutedThreadsProvider> 135 + </BackgroundNotificationPreferencesProvider> 136 + </UnreadNotifsProvider> 137 + </HomeBadgeProvider> 135 138 </HiddenRepliesProvider> 136 139 </SelectedFeedProvider> 137 140 </LoggedOutViewProvider>
+1
src/lib/statsig/gates.ts
··· 2 2 // Keep this alphabetic please. 3 3 | 'debug_show_feedcontext' // DISABLED DUE TO EME 4 4 | 'post_feed_lang_window' // DISABLED DUE TO EME 5 + | 'remove_show_latest_button'
+24
src/state/home-badge.tsx
··· 1 + import React from 'react' 2 + 3 + type StateContext = boolean 4 + type ApiContext = (hasNew: boolean) => void 5 + 6 + const stateContext = React.createContext<StateContext>(false) 7 + const apiContext = React.createContext<ApiContext>((_: boolean) => {}) 8 + 9 + export function Provider({children}: React.PropsWithChildren<{}>) { 10 + const [state, setState] = React.useState(false) 11 + return ( 12 + <stateContext.Provider value={state}> 13 + <apiContext.Provider value={setState}>{children}</apiContext.Provider> 14 + </stateContext.Provider> 15 + ) 16 + } 17 + 18 + export function useHomeBadge() { 19 + return React.useContext(stateContext) 20 + } 21 + 22 + export function useSetHomeBadge() { 23 + return React.useContext(apiContext) 24 + }
+8
src/view/com/feeds/FeedPage.tsx
··· 14 14 import {isNative} from '#/platform/detection' 15 15 import {listenSoftReset} from '#/state/events' 16 16 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 17 + import {useSetHomeBadge} from '#/state/home-badge' 17 18 import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 18 19 import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' 19 20 import {truncateAndInvalidate} from '#/state/queries/util' ··· 59 60 const feedFeedback = useFeedFeedback(feed, hasSession) 60 61 const scrollElRef = React.useRef<ListMethods>(null) 61 62 const [hasNew, setHasNew] = React.useState(false) 63 + const setHomeBadge = useSetHomeBadge() 64 + 65 + React.useEffect(() => { 66 + if (isPageFocused) { 67 + setHomeBadge(hasNew) 68 + } 69 + }, [isPageFocused, hasNew, setHomeBadge]) 62 70 63 71 const scrollToTop = React.useCallback(() => { 64 72 scrollElRef.current?.scrollToOffset({
+6
src/view/com/util/load-latest/LoadLatestBtn.tsx
··· 9 9 import {usePalette} from '#/lib/hooks/usePalette' 10 10 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 11 11 import {clamp} from '#/lib/numbers' 12 + import {useGate} from '#/lib/statsig/statsig' 12 13 import {colors} from '#/lib/styles' 13 14 import {isWeb} from '#/platform/detection' 14 15 import {useSession} from '#/state/session' ··· 33 34 34 35 // move button inline if it starts overlapping the left nav 35 36 const isTallViewport = useMediaQuery({minHeight: 700}) 37 + 38 + const gate = useGate() 39 + if (gate('remove_show_latest_button')) { 40 + return null 41 + } 36 42 37 43 // Adjust height of the fab if we have a session only on mobile web. If we don't have a session, we want to adjust 38 44 // it on both tablet and mobile since we are showing the bottom bar (see createNativeStackNavigatorWithAuth)
+10 -1
src/view/shell/bottom-bar/BottomBar.tsx
··· 15 15 import {usePalette} from '#/lib/hooks/usePalette' 16 16 import {clamp} from '#/lib/numbers' 17 17 import {getTabState, TabState} from '#/lib/routes/helpers' 18 + import {useGate} from '#/lib/statsig/statsig' 18 19 import {s} from '#/lib/styles' 19 20 import {emitSoftReset} from '#/state/events' 21 + import {useHomeBadge} from '#/state/home-badge' 20 22 import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' 21 23 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 22 24 import {useProfileQuery} from '#/state/queries/profile' ··· 73 75 const dedupe = useDedupe() 74 76 const accountSwitchControl = useDialogControl() 75 77 const playHaptic = useHaptics() 78 + const hasHomeBadge = useHomeBadge() 79 + const gate = useGate() 76 80 const iconWidth = 28 77 81 78 82 const showSignIn = React.useCallback(() => { ··· 153 157 /> 154 158 ) 155 159 } 160 + hasNew={hasHomeBadge && gate('remove_show_latest_button')} 156 161 onPress={onPressHome} 157 162 accessibilityRole="tab" 158 163 accessibilityLabel={_(msg`Home`)} ··· 334 339 testID?: string 335 340 icon: JSX.Element 336 341 notificationCount?: string 342 + hasNew?: boolean 337 343 onPress?: (event: GestureResponderEvent) => void 338 344 onLongPress?: (event: GestureResponderEvent) => void 339 345 } ··· 341 347 function Btn({ 342 348 testID, 343 349 icon, 350 + hasNew, 344 351 notificationCount, 345 352 onPress, 346 353 onLongPress, ··· 363 370 <View style={[styles.notificationCount, a.rounded_full]}> 364 371 <Text style={styles.notificationCountLabel}>{notificationCount}</Text> 365 372 </View> 366 - ) : undefined} 373 + ) : hasNew ? ( 374 + <View style={[styles.hasNewBadge, a.rounded_full]} /> 375 + ) : null} 367 376 </PressableScale> 368 377 ) 369 378 }
+11
src/view/shell/bottom-bar/BottomBarStyles.tsx
··· 44 44 color: colors.white, 45 45 fontVariant: ['tabular-nums'], 46 46 }, 47 + hasNewBadge: { 48 + position: 'absolute', 49 + left: '52%', 50 + marginLeft: 4, 51 + top: 10, 52 + width: 8, 53 + height: 8, 54 + backgroundColor: colors.blue3, 55 + borderRadius: 6, 56 + zIndex: 1, 57 + }, 47 58 ctrlIcon: { 48 59 marginLeft: 'auto', 49 60 marginRight: 'auto',
+19 -9
src/view/shell/bottom-bar/BottomBarWeb.tsx
··· 9 9 import {getCurrentRoute, isTab} from '#/lib/routes/helpers' 10 10 import {makeProfileLink} from '#/lib/routes/links' 11 11 import {CommonNavigatorParams} from '#/lib/routes/types' 12 + import {useGate} from '#/lib/statsig/statsig' 13 + import {useHomeBadge} from '#/state/home-badge' 12 14 import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' 13 15 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 14 16 import {useSession} from '#/state/session' ··· 51 53 52 54 const unreadMessageCount = useUnreadMessageCount() 53 55 const notificationCountStr = useUnreadNotifications() 56 + const hasHomeBadge = useHomeBadge() 57 + const gate = useGate() 54 58 55 59 const showSignIn = React.useCallback(() => { 56 60 closeAllActiveElements() ··· 75 79 ]}> 76 80 {hasSession ? ( 77 81 <> 78 - <NavItem routeName="Home" href="/"> 82 + <NavItem 83 + routeName="Home" 84 + href="/" 85 + hasNew={hasHomeBadge && gate('remove_show_latest_button')}> 79 86 {({isActive}) => { 80 87 const Icon = isActive ? HomeFilled : Home 81 88 return ( ··· 105 112 <NavItem 106 113 routeName="Messages" 107 114 href="/messages" 108 - badge={ 115 + notificationCount={ 109 116 unreadMessageCount.count > 0 110 117 ? unreadMessageCount.numUnread 111 118 : undefined ··· 128 135 <NavItem 129 136 routeName="Notifications" 130 137 href="/notifications" 131 - badge={notificationCountStr}> 138 + notificationCount={notificationCountStr}> 132 139 {({isActive}) => { 133 140 const Icon = isActive ? BellFilled : Bell 134 141 return ( ··· 220 227 children: (props: {isActive: boolean}) => React.ReactChild 221 228 href: string 222 229 routeName: string 223 - badge?: string 224 - }> = ({children, href, routeName, badge}) => { 230 + hasNew?: boolean 231 + notificationCount?: string 232 + }> = ({children, href, routeName, hasNew, notificationCount}) => { 225 233 const {_} = useLingui() 226 234 const {currentAccount} = useSession() 227 235 const currentRoute = useNavigationState(state => { ··· 246 254 aria-label={routeName} 247 255 accessible={true}> 248 256 {children({isActive})} 249 - {!!badge && ( 257 + {notificationCount ? ( 250 258 <View 251 259 style={styles.notificationCount} 252 - aria-label={_(msg`${badge} unread items`)}> 253 - <Text style={styles.notificationCountLabel}>{badge}</Text> 260 + aria-label={_(msg`${notificationCount} unread items`)}> 261 + <Text style={styles.notificationCountLabel}>{notificationCount}</Text> 254 262 </View> 255 - )} 263 + ) : hasNew ? ( 264 + <View style={styles.hasNewBadge} /> 265 + ) : null} 256 266 </Link> 257 267 ) 258 268 }
+25 -1
src/view/shell/desktop/LeftNav.tsx
··· 14 14 import {getCurrentRoute, isTab} from '#/lib/routes/helpers' 15 15 import {makeProfileLink} from '#/lib/routes/links' 16 16 import {CommonNavigatorParams} from '#/lib/routes/types' 17 + import {useGate} from '#/lib/statsig/statsig' 17 18 import {isInvalidHandle} from '#/lib/strings/handles' 18 19 import {emitSoftReset} from '#/state/events' 20 + import {useHomeBadge} from '#/state/home-badge' 19 21 import {useFetchHandle} from '#/state/queries/handle' 20 22 import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' 21 23 import {useUnreadNotifications} from '#/state/queries/notifications/unread' ··· 100 102 101 103 interface NavItemProps { 102 104 count?: string 105 + hasNew?: boolean 103 106 href: string 104 107 icon: JSX.Element 105 108 iconFilled: JSX.Element 106 109 label: string 107 110 } 108 - function NavItem({count, href, icon, iconFilled, label}: NavItemProps) { 111 + function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) { 109 112 const t = useTheme() 110 113 const {_} = useLingui() 111 114 const {currentAccount} = useSession() ··· 214 217 {count} 215 218 </Text> 216 219 </View> 220 + ) : hasNew ? ( 221 + <View 222 + style={[ 223 + a.absolute, 224 + a.rounded_full, 225 + { 226 + backgroundColor: t.palette.primary_500, 227 + width: 8, 228 + height: 8, 229 + right: -1, 230 + top: -3, 231 + }, 232 + isTablet && { 233 + right: 6, 234 + top: 4, 235 + }, 236 + ]} 237 + /> 217 238 ) : null} 218 239 </View> 219 240 {gtTablet && ( ··· 322 343 const {_} = useLingui() 323 344 const {isDesktop, isTablet} = useWebMediaQueries() 324 345 const numUnreadNotifications = useUnreadNotifications() 346 + const hasHomeBadge = useHomeBadge() 347 + const gate = useGate() 325 348 326 349 if (!hasSession && !isDesktop) { 327 350 return null ··· 348 371 <> 349 372 <NavItem 350 373 href="/" 374 + hasNew={hasHomeBadge && gate('remove_show_latest_button')} 351 375 icon={ 352 376 <Home 353 377 aria-hidden={true}