[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
This commit is contained in:
dan
2024-12-15 20:30:17 +00:00
committed by GitHub
parent 80c0125d6b
commit 1ac307bc42
10 changed files with 136 additions and 37 deletions
+17 -14
View File
@@ -32,6 +32,7 @@ import {
ensureGeolocationResolved, ensureGeolocationResolved,
Provider as GeolocationProvider, Provider as GeolocationProvider,
} from '#/state/geolocation' } from '#/state/geolocation'
import {Provider as HomeBadgeProvider} from '#/state/home-badge'
import {Provider as InvitesStateProvider} from '#/state/invites' import {Provider as InvitesStateProvider} from '#/state/invites'
import {Provider as LightboxStateProvider} from '#/state/lightbox' import {Provider as LightboxStateProvider} from '#/state/lightbox'
import {MessagesProvider} from '#/state/messages' import {MessagesProvider} from '#/state/messages'
@@ -137,20 +138,22 @@ function InnerApp() {
<LoggedOutViewProvider> <LoggedOutViewProvider>
<SelectedFeedProvider> <SelectedFeedProvider>
<HiddenRepliesProvider> <HiddenRepliesProvider>
<UnreadNotifsProvider> <HomeBadgeProvider>
<BackgroundNotificationPreferencesProvider> <UnreadNotifsProvider>
<MutedThreadsProvider> <BackgroundNotificationPreferencesProvider>
<ProgressGuideProvider> <MutedThreadsProvider>
<GestureHandlerRootView <ProgressGuideProvider>
style={s.h100pct}> <GestureHandlerRootView
<TestCtrls /> style={s.h100pct}>
<Shell /> <TestCtrls />
<NuxDialogs /> <Shell />
</GestureHandlerRootView> <NuxDialogs />
</ProgressGuideProvider> </GestureHandlerRootView>
</MutedThreadsProvider> </ProgressGuideProvider>
</BackgroundNotificationPreferencesProvider> </MutedThreadsProvider>
</UnreadNotifsProvider> </BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</HomeBadgeProvider>
</HiddenRepliesProvider> </HiddenRepliesProvider>
</SelectedFeedProvider> </SelectedFeedProvider>
</LoggedOutViewProvider> </LoggedOutViewProvider>
+15 -12
View File
@@ -22,6 +22,7 @@ import {
ensureGeolocationResolved, ensureGeolocationResolved,
Provider as GeolocationProvider, Provider as GeolocationProvider,
} from '#/state/geolocation' } from '#/state/geolocation'
import {Provider as HomeBadgeProvider} from '#/state/home-badge'
import {Provider as InvitesStateProvider} from '#/state/invites' import {Provider as InvitesStateProvider} from '#/state/invites'
import {Provider as LightboxStateProvider} from '#/state/lightbox' import {Provider as LightboxStateProvider} from '#/state/lightbox'
import {MessagesProvider} from '#/state/messages' import {MessagesProvider} from '#/state/messages'
@@ -120,18 +121,20 @@ function InnerApp() {
<LoggedOutViewProvider> <LoggedOutViewProvider>
<SelectedFeedProvider> <SelectedFeedProvider>
<HiddenRepliesProvider> <HiddenRepliesProvider>
<UnreadNotifsProvider> <HomeBadgeProvider>
<BackgroundNotificationPreferencesProvider> <UnreadNotifsProvider>
<MutedThreadsProvider> <BackgroundNotificationPreferencesProvider>
<SafeAreaProvider> <MutedThreadsProvider>
<ProgressGuideProvider> <SafeAreaProvider>
<Shell /> <ProgressGuideProvider>
<NuxDialogs /> <Shell />
</ProgressGuideProvider> <NuxDialogs />
</SafeAreaProvider> </ProgressGuideProvider>
</MutedThreadsProvider> </SafeAreaProvider>
</BackgroundNotificationPreferencesProvider> </MutedThreadsProvider>
</UnreadNotifsProvider> </BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</HomeBadgeProvider>
</HiddenRepliesProvider> </HiddenRepliesProvider>
</SelectedFeedProvider> </SelectedFeedProvider>
</LoggedOutViewProvider> </LoggedOutViewProvider>
+1
View File
@@ -2,3 +2,4 @@ export type Gate =
// Keep this alphabetic please. // Keep this alphabetic please.
| 'debug_show_feedcontext' // DISABLED DUE TO EME | 'debug_show_feedcontext' // DISABLED DUE TO EME
| 'post_feed_lang_window' // DISABLED DUE TO EME | 'post_feed_lang_window' // DISABLED DUE TO EME
| 'remove_show_latest_button'
+24
View File
@@ -0,0 +1,24 @@
import React from 'react'
type StateContext = boolean
type ApiContext = (hasNew: boolean) => void
const stateContext = React.createContext<StateContext>(false)
const apiContext = React.createContext<ApiContext>((_: boolean) => {})
export function Provider({children}: React.PropsWithChildren<{}>) {
const [state, setState] = React.useState(false)
return (
<stateContext.Provider value={state}>
<apiContext.Provider value={setState}>{children}</apiContext.Provider>
</stateContext.Provider>
)
}
export function useHomeBadge() {
return React.useContext(stateContext)
}
export function useSetHomeBadge() {
return React.useContext(apiContext)
}
+8
View File
@@ -14,6 +14,7 @@ import {s} from '#/lib/styles'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {listenSoftReset} from '#/state/events' import {listenSoftReset} from '#/state/events'
import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
import {useSetHomeBadge} from '#/state/home-badge'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {truncateAndInvalidate} from '#/state/queries/util' import {truncateAndInvalidate} from '#/state/queries/util'
@@ -59,6 +60,13 @@ export function FeedPage({
const feedFeedback = useFeedFeedback(feed, hasSession) const feedFeedback = useFeedFeedback(feed, hasSession)
const scrollElRef = React.useRef<ListMethods>(null) const scrollElRef = React.useRef<ListMethods>(null)
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const setHomeBadge = useSetHomeBadge()
React.useEffect(() => {
if (isPageFocused) {
setHomeBadge(hasNew)
}
}, [isPageFocused, hasNew, setHomeBadge])
const scrollToTop = React.useCallback(() => { const scrollToTop = React.useCallback(() => {
scrollElRef.current?.scrollToOffset({ scrollElRef.current?.scrollToOffset({
@@ -9,6 +9,7 @@ import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {clamp} from '#/lib/numbers' import {clamp} from '#/lib/numbers'
import {useGate} from '#/lib/statsig/statsig'
import {colors} from '#/lib/styles' import {colors} from '#/lib/styles'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -34,6 +35,11 @@ export function LoadLatestBtn({
// move button inline if it starts overlapping the left nav // move button inline if it starts overlapping the left nav
const isTallViewport = useMediaQuery({minHeight: 700}) const isTallViewport = useMediaQuery({minHeight: 700})
const gate = useGate()
if (gate('remove_show_latest_button')) {
return null
}
// 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 // 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
// it on both tablet and mobile since we are showing the bottom bar (see createNativeStackNavigatorWithAuth) // it on both tablet and mobile since we are showing the bottom bar (see createNativeStackNavigatorWithAuth)
const showBottomBar = hasSession ? isMobile : isTabletOrMobile const showBottomBar = hasSession ? isMobile : isTabletOrMobile
+10 -1
View File
@@ -15,8 +15,10 @@ import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {clamp} from '#/lib/numbers' import {clamp} from '#/lib/numbers'
import {getTabState, TabState} from '#/lib/routes/helpers' import {getTabState, TabState} from '#/lib/routes/helpers'
import {useGate} from '#/lib/statsig/statsig'
import {s} from '#/lib/styles' import {s} from '#/lib/styles'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {useHomeBadge} from '#/state/home-badge'
import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations'
import {useUnreadNotifications} from '#/state/queries/notifications/unread' import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
@@ -73,6 +75,8 @@ export function BottomBar({navigation}: BottomTabBarProps) {
const dedupe = useDedupe() const dedupe = useDedupe()
const accountSwitchControl = useDialogControl() const accountSwitchControl = useDialogControl()
const playHaptic = useHaptics() const playHaptic = useHaptics()
const hasHomeBadge = useHomeBadge()
const gate = useGate()
const iconWidth = 28 const iconWidth = 28
const showSignIn = React.useCallback(() => { const showSignIn = React.useCallback(() => {
@@ -153,6 +157,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
/> />
) )
} }
hasNew={hasHomeBadge && gate('remove_show_latest_button')}
onPress={onPressHome} onPress={onPressHome}
accessibilityRole="tab" accessibilityRole="tab"
accessibilityLabel={_(msg`Home`)} accessibilityLabel={_(msg`Home`)}
@@ -334,6 +339,7 @@ interface BtnProps
testID?: string testID?: string
icon: JSX.Element icon: JSX.Element
notificationCount?: string notificationCount?: string
hasNew?: boolean
onPress?: (event: GestureResponderEvent) => void onPress?: (event: GestureResponderEvent) => void
onLongPress?: (event: GestureResponderEvent) => void onLongPress?: (event: GestureResponderEvent) => void
} }
@@ -341,6 +347,7 @@ interface BtnProps
function Btn({ function Btn({
testID, testID,
icon, icon,
hasNew,
notificationCount, notificationCount,
onPress, onPress,
onLongPress, onLongPress,
@@ -363,7 +370,9 @@ function Btn({
<View style={[styles.notificationCount, a.rounded_full]}> <View style={[styles.notificationCount, a.rounded_full]}>
<Text style={styles.notificationCountLabel}>{notificationCount}</Text> <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
</View> </View>
) : undefined} ) : hasNew ? (
<View style={[styles.hasNewBadge, a.rounded_full]} />
) : null}
</PressableScale> </PressableScale>
) )
} }
@@ -44,6 +44,17 @@ export const styles = StyleSheet.create({
color: colors.white, color: colors.white,
fontVariant: ['tabular-nums'], fontVariant: ['tabular-nums'],
}, },
hasNewBadge: {
position: 'absolute',
left: '52%',
marginLeft: 4,
top: 10,
width: 8,
height: 8,
backgroundColor: colors.blue3,
borderRadius: 6,
zIndex: 1,
},
ctrlIcon: { ctrlIcon: {
marginLeft: 'auto', marginLeft: 'auto',
marginRight: 'auto', marginRight: 'auto',
+19 -9
View File
@@ -9,6 +9,8 @@ import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransfo
import {getCurrentRoute, isTab} from '#/lib/routes/helpers' import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {CommonNavigatorParams} from '#/lib/routes/types' import {CommonNavigatorParams} from '#/lib/routes/types'
import {useGate} from '#/lib/statsig/statsig'
import {useHomeBadge} from '#/state/home-badge'
import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations'
import {useUnreadNotifications} from '#/state/queries/notifications/unread' import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -51,6 +53,8 @@ export function BottomBarWeb() {
const unreadMessageCount = useUnreadMessageCount() const unreadMessageCount = useUnreadMessageCount()
const notificationCountStr = useUnreadNotifications() const notificationCountStr = useUnreadNotifications()
const hasHomeBadge = useHomeBadge()
const gate = useGate()
const showSignIn = React.useCallback(() => { const showSignIn = React.useCallback(() => {
closeAllActiveElements() closeAllActiveElements()
@@ -75,7 +79,10 @@ export function BottomBarWeb() {
]}> ]}>
{hasSession ? ( {hasSession ? (
<> <>
<NavItem routeName="Home" href="/"> <NavItem
routeName="Home"
href="/"
hasNew={hasHomeBadge && gate('remove_show_latest_button')}>
{({isActive}) => { {({isActive}) => {
const Icon = isActive ? HomeFilled : Home const Icon = isActive ? HomeFilled : Home
return ( return (
@@ -105,7 +112,7 @@ export function BottomBarWeb() {
<NavItem <NavItem
routeName="Messages" routeName="Messages"
href="/messages" href="/messages"
badge={ notificationCount={
unreadMessageCount.count > 0 unreadMessageCount.count > 0
? unreadMessageCount.numUnread ? unreadMessageCount.numUnread
: undefined : undefined
@@ -128,7 +135,7 @@ export function BottomBarWeb() {
<NavItem <NavItem
routeName="Notifications" routeName="Notifications"
href="/notifications" href="/notifications"
badge={notificationCountStr}> notificationCount={notificationCountStr}>
{({isActive}) => { {({isActive}) => {
const Icon = isActive ? BellFilled : Bell const Icon = isActive ? BellFilled : Bell
return ( return (
@@ -220,8 +227,9 @@ const NavItem: React.FC<{
children: (props: {isActive: boolean}) => React.ReactChild children: (props: {isActive: boolean}) => React.ReactChild
href: string href: string
routeName: string routeName: string
badge?: string hasNew?: boolean
}> = ({children, href, routeName, badge}) => { notificationCount?: string
}> = ({children, href, routeName, hasNew, notificationCount}) => {
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const currentRoute = useNavigationState(state => { const currentRoute = useNavigationState(state => {
@@ -246,13 +254,15 @@ const NavItem: React.FC<{
aria-label={routeName} aria-label={routeName}
accessible={true}> accessible={true}>
{children({isActive})} {children({isActive})}
{!!badge && ( {notificationCount ? (
<View <View
style={styles.notificationCount} style={styles.notificationCount}
aria-label={_(msg`${badge} unread items`)}> aria-label={_(msg`${notificationCount} unread items`)}>
<Text style={styles.notificationCountLabel}>{badge}</Text> <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
</View> </View>
)} ) : hasNew ? (
<View style={styles.hasNewBadge} />
) : null}
</Link> </Link>
) )
} }
+25 -1
View File
@@ -14,8 +14,10 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {getCurrentRoute, isTab} from '#/lib/routes/helpers' import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {CommonNavigatorParams} from '#/lib/routes/types' import {CommonNavigatorParams} from '#/lib/routes/types'
import {useGate} from '#/lib/statsig/statsig'
import {isInvalidHandle} from '#/lib/strings/handles' import {isInvalidHandle} from '#/lib/strings/handles'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {useHomeBadge} from '#/state/home-badge'
import {useFetchHandle} from '#/state/queries/handle' import {useFetchHandle} from '#/state/queries/handle'
import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations'
import {useUnreadNotifications} from '#/state/queries/notifications/unread' import {useUnreadNotifications} from '#/state/queries/notifications/unread'
@@ -100,12 +102,13 @@ function ProfileCard() {
interface NavItemProps { interface NavItemProps {
count?: string count?: string
hasNew?: boolean
href: string href: string
icon: JSX.Element icon: JSX.Element
iconFilled: JSX.Element iconFilled: JSX.Element
label: string label: string
} }
function NavItem({count, href, icon, iconFilled, label}: NavItemProps) { function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -214,6 +217,24 @@ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
{count} {count}
</Text> </Text>
</View> </View>
) : hasNew ? (
<View
style={[
a.absolute,
a.rounded_full,
{
backgroundColor: t.palette.primary_500,
width: 8,
height: 8,
right: -1,
top: -3,
},
isTablet && {
right: 6,
top: 4,
},
]}
/>
) : null} ) : null}
</View> </View>
{gtTablet && ( {gtTablet && (
@@ -322,6 +343,8 @@ export function DesktopLeftNav() {
const {_} = useLingui() const {_} = useLingui()
const {isDesktop, isTablet} = useWebMediaQueries() const {isDesktop, isTablet} = useWebMediaQueries()
const numUnreadNotifications = useUnreadNotifications() const numUnreadNotifications = useUnreadNotifications()
const hasHomeBadge = useHomeBadge()
const gate = useGate()
if (!hasSession && !isDesktop) { if (!hasSession && !isDesktop) {
return null return null
@@ -348,6 +371,7 @@ export function DesktopLeftNav() {
<> <>
<NavItem <NavItem
href="/" href="/"
hasNew={hasHomeBadge && gate('remove_show_latest_button')}
icon={ icon={
<Home <Home
aria-hidden={true} aria-hidden={true}