about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/App.native.tsx31
-rw-r--r--src/App.web.tsx27
-rw-r--r--src/lib/statsig/gates.ts1
-rw-r--r--src/state/home-badge.tsx24
-rw-r--r--src/view/com/feeds/FeedPage.tsx8
-rw-r--r--src/view/com/util/load-latest/LoadLatestBtn.tsx6
-rw-r--r--src/view/shell/bottom-bar/BottomBar.tsx11
-rw-r--r--src/view/shell/bottom-bar/BottomBarStyles.tsx11
-rw-r--r--src/view/shell/bottom-bar/BottomBarWeb.tsx28
-rw-r--r--src/view/shell/desktop/LeftNav.tsx26
10 files changed, 136 insertions, 37 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx
index f985b96a5..39ab7ca92 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -32,6 +32,7 @@ import {
   ensureGeolocationResolved,
   Provider as GeolocationProvider,
 } from '#/state/geolocation'
+import {Provider as HomeBadgeProvider} from '#/state/home-badge'
 import {Provider as InvitesStateProvider} from '#/state/invites'
 import {Provider as LightboxStateProvider} from '#/state/lightbox'
 import {MessagesProvider} from '#/state/messages'
@@ -137,20 +138,22 @@ function InnerApp() {
                             <LoggedOutViewProvider>
                               <SelectedFeedProvider>
                                 <HiddenRepliesProvider>
-                                  <UnreadNotifsProvider>
-                                    <BackgroundNotificationPreferencesProvider>
-                                      <MutedThreadsProvider>
-                                        <ProgressGuideProvider>
-                                          <GestureHandlerRootView
-                                            style={s.h100pct}>
-                                            <TestCtrls />
-                                            <Shell />
-                                            <NuxDialogs />
-                                          </GestureHandlerRootView>
-                                        </ProgressGuideProvider>
-                                      </MutedThreadsProvider>
-                                    </BackgroundNotificationPreferencesProvider>
-                                  </UnreadNotifsProvider>
+                                  <HomeBadgeProvider>
+                                    <UnreadNotifsProvider>
+                                      <BackgroundNotificationPreferencesProvider>
+                                        <MutedThreadsProvider>
+                                          <ProgressGuideProvider>
+                                            <GestureHandlerRootView
+                                              style={s.h100pct}>
+                                              <TestCtrls />
+                                              <Shell />
+                                              <NuxDialogs />
+                                            </GestureHandlerRootView>
+                                          </ProgressGuideProvider>
+                                        </MutedThreadsProvider>
+                                      </BackgroundNotificationPreferencesProvider>
+                                    </UnreadNotifsProvider>
+                                  </HomeBadgeProvider>
                                 </HiddenRepliesProvider>
                               </SelectedFeedProvider>
                             </LoggedOutViewProvider>
diff --git a/src/App.web.tsx b/src/App.web.tsx
index b7c5a5633..8d13a826e 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -22,6 +22,7 @@ import {
   ensureGeolocationResolved,
   Provider as GeolocationProvider,
 } from '#/state/geolocation'
+import {Provider as HomeBadgeProvider} from '#/state/home-badge'
 import {Provider as InvitesStateProvider} from '#/state/invites'
 import {Provider as LightboxStateProvider} from '#/state/lightbox'
 import {MessagesProvider} from '#/state/messages'
@@ -120,18 +121,20 @@ function InnerApp() {
                             <LoggedOutViewProvider>
                               <SelectedFeedProvider>
                                 <HiddenRepliesProvider>
-                                  <UnreadNotifsProvider>
-                                    <BackgroundNotificationPreferencesProvider>
-                                      <MutedThreadsProvider>
-                                        <SafeAreaProvider>
-                                          <ProgressGuideProvider>
-                                            <Shell />
-                                            <NuxDialogs />
-                                          </ProgressGuideProvider>
-                                        </SafeAreaProvider>
-                                      </MutedThreadsProvider>
-                                    </BackgroundNotificationPreferencesProvider>
-                                  </UnreadNotifsProvider>
+                                  <HomeBadgeProvider>
+                                    <UnreadNotifsProvider>
+                                      <BackgroundNotificationPreferencesProvider>
+                                        <MutedThreadsProvider>
+                                          <SafeAreaProvider>
+                                            <ProgressGuideProvider>
+                                              <Shell />
+                                              <NuxDialogs />
+                                            </ProgressGuideProvider>
+                                          </SafeAreaProvider>
+                                        </MutedThreadsProvider>
+                                      </BackgroundNotificationPreferencesProvider>
+                                    </UnreadNotifsProvider>
+                                  </HomeBadgeProvider>
                                 </HiddenRepliesProvider>
                               </SelectedFeedProvider>
                             </LoggedOutViewProvider>
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index 3cec5d5b2..3767ec1e5 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -2,3 +2,4 @@ export type Gate =
   // Keep this alphabetic please.
   | 'debug_show_feedcontext' // DISABLED DUE TO EME
   | 'post_feed_lang_window' // DISABLED DUE TO EME
+  | 'remove_show_latest_button'
diff --git a/src/state/home-badge.tsx b/src/state/home-badge.tsx
new file mode 100644
index 000000000..59a9276ce
--- /dev/null
+++ b/src/state/home-badge.tsx
@@ -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)
+}
diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx
index e766b589b..10ed60212 100644
--- a/src/view/com/feeds/FeedPage.tsx
+++ b/src/view/com/feeds/FeedPage.tsx
@@ -14,6 +14,7 @@ import {s} from '#/lib/styles'
 import {isNative} from '#/platform/detection'
 import {listenSoftReset} from '#/state/events'
 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
+import {useSetHomeBadge} from '#/state/home-badge'
 import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
 import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
 import {truncateAndInvalidate} from '#/state/queries/util'
@@ -59,6 +60,13 @@ export function FeedPage({
   const feedFeedback = useFeedFeedback(feed, hasSession)
   const scrollElRef = React.useRef<ListMethods>(null)
   const [hasNew, setHasNew] = React.useState(false)
+  const setHomeBadge = useSetHomeBadge()
+
+  React.useEffect(() => {
+    if (isPageFocused) {
+      setHomeBadge(hasNew)
+    }
+  }, [isPageFocused, hasNew, setHomeBadge])
 
   const scrollToTop = React.useCallback(() => {
     scrollElRef.current?.scrollToOffset({
diff --git a/src/view/com/util/load-latest/LoadLatestBtn.tsx b/src/view/com/util/load-latest/LoadLatestBtn.tsx
index d98aa0fa7..b502f0b68 100644
--- a/src/view/com/util/load-latest/LoadLatestBtn.tsx
+++ b/src/view/com/util/load-latest/LoadLatestBtn.tsx
@@ -9,6 +9,7 @@ import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
 import {usePalette} from '#/lib/hooks/usePalette'
 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {clamp} from '#/lib/numbers'
+import {useGate} from '#/lib/statsig/statsig'
 import {colors} from '#/lib/styles'
 import {isWeb} from '#/platform/detection'
 import {useSession} from '#/state/session'
@@ -34,6 +35,11 @@ export function LoadLatestBtn({
   // move button inline if it starts overlapping the left nav
   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
   // it on both tablet and mobile since we are showing the bottom bar (see createNativeStackNavigatorWithAuth)
   const showBottomBar = hasSession ? isMobile : isTabletOrMobile
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index db9c04965..47a525c04 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -15,8 +15,10 @@ import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
 import {usePalette} from '#/lib/hooks/usePalette'
 import {clamp} from '#/lib/numbers'
 import {getTabState, TabState} from '#/lib/routes/helpers'
+import {useGate} from '#/lib/statsig/statsig'
 import {s} from '#/lib/styles'
 import {emitSoftReset} from '#/state/events'
+import {useHomeBadge} from '#/state/home-badge'
 import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations'
 import {useUnreadNotifications} from '#/state/queries/notifications/unread'
 import {useProfileQuery} from '#/state/queries/profile'
@@ -73,6 +75,8 @@ export function BottomBar({navigation}: BottomTabBarProps) {
   const dedupe = useDedupe()
   const accountSwitchControl = useDialogControl()
   const playHaptic = useHaptics()
+  const hasHomeBadge = useHomeBadge()
+  const gate = useGate()
   const iconWidth = 28
 
   const showSignIn = React.useCallback(() => {
@@ -153,6 +157,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
                   />
                 )
               }
+              hasNew={hasHomeBadge && gate('remove_show_latest_button')}
               onPress={onPressHome}
               accessibilityRole="tab"
               accessibilityLabel={_(msg`Home`)}
@@ -334,6 +339,7 @@ interface BtnProps
   testID?: string
   icon: JSX.Element
   notificationCount?: string
+  hasNew?: boolean
   onPress?: (event: GestureResponderEvent) => void
   onLongPress?: (event: GestureResponderEvent) => void
 }
@@ -341,6 +347,7 @@ interface BtnProps
 function Btn({
   testID,
   icon,
+  hasNew,
   notificationCount,
   onPress,
   onLongPress,
@@ -363,7 +370,9 @@ function Btn({
         <View style={[styles.notificationCount, a.rounded_full]}>
           <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
         </View>
-      ) : undefined}
+      ) : hasNew ? (
+        <View style={[styles.hasNewBadge, a.rounded_full]} />
+      ) : null}
     </PressableScale>
   )
 }
diff --git a/src/view/shell/bottom-bar/BottomBarStyles.tsx b/src/view/shell/bottom-bar/BottomBarStyles.tsx
index 9255957cb..d80914d09 100644
--- a/src/view/shell/bottom-bar/BottomBarStyles.tsx
+++ b/src/view/shell/bottom-bar/BottomBarStyles.tsx
@@ -44,6 +44,17 @@ export const styles = StyleSheet.create({
     color: colors.white,
     fontVariant: ['tabular-nums'],
   },
+  hasNewBadge: {
+    position: 'absolute',
+    left: '52%',
+    marginLeft: 4,
+    top: 10,
+    width: 8,
+    height: 8,
+    backgroundColor: colors.blue3,
+    borderRadius: 6,
+    zIndex: 1,
+  },
   ctrlIcon: {
     marginLeft: 'auto',
     marginRight: 'auto',
diff --git a/src/view/shell/bottom-bar/BottomBarWeb.tsx b/src/view/shell/bottom-bar/BottomBarWeb.tsx
index 127ff2b26..1855969cc 100644
--- a/src/view/shell/bottom-bar/BottomBarWeb.tsx
+++ b/src/view/shell/bottom-bar/BottomBarWeb.tsx
@@ -9,6 +9,8 @@ import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransfo
 import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
 import {makeProfileLink} from '#/lib/routes/links'
 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 {useUnreadNotifications} from '#/state/queries/notifications/unread'
 import {useSession} from '#/state/session'
@@ -51,6 +53,8 @@ export function BottomBarWeb() {
 
   const unreadMessageCount = useUnreadMessageCount()
   const notificationCountStr = useUnreadNotifications()
+  const hasHomeBadge = useHomeBadge()
+  const gate = useGate()
 
   const showSignIn = React.useCallback(() => {
     closeAllActiveElements()
@@ -75,7 +79,10 @@ export function BottomBarWeb() {
       ]}>
       {hasSession ? (
         <>
-          <NavItem routeName="Home" href="/">
+          <NavItem
+            routeName="Home"
+            href="/"
+            hasNew={hasHomeBadge && gate('remove_show_latest_button')}>
             {({isActive}) => {
               const Icon = isActive ? HomeFilled : Home
               return (
@@ -105,7 +112,7 @@ export function BottomBarWeb() {
               <NavItem
                 routeName="Messages"
                 href="/messages"
-                badge={
+                notificationCount={
                   unreadMessageCount.count > 0
                     ? unreadMessageCount.numUnread
                     : undefined
@@ -128,7 +135,7 @@ export function BottomBarWeb() {
               <NavItem
                 routeName="Notifications"
                 href="/notifications"
-                badge={notificationCountStr}>
+                notificationCount={notificationCountStr}>
                 {({isActive}) => {
                   const Icon = isActive ? BellFilled : Bell
                   return (
@@ -220,8 +227,9 @@ const NavItem: React.FC<{
   children: (props: {isActive: boolean}) => React.ReactChild
   href: string
   routeName: string
-  badge?: string
-}> = ({children, href, routeName, badge}) => {
+  hasNew?: boolean
+  notificationCount?: string
+}> = ({children, href, routeName, hasNew, notificationCount}) => {
   const {_} = useLingui()
   const {currentAccount} = useSession()
   const currentRoute = useNavigationState(state => {
@@ -246,13 +254,15 @@ const NavItem: React.FC<{
       aria-label={routeName}
       accessible={true}>
       {children({isActive})}
-      {!!badge && (
+      {notificationCount ? (
         <View
           style={styles.notificationCount}
-          aria-label={_(msg`${badge} unread items`)}>
-          <Text style={styles.notificationCountLabel}>{badge}</Text>
+          aria-label={_(msg`${notificationCount} unread items`)}>
+          <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
         </View>
-      )}
+      ) : hasNew ? (
+        <View style={styles.hasNewBadge} />
+      ) : null}
     </Link>
   )
 }
diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx
index 6655572f8..d367e1b98 100644
--- a/src/view/shell/desktop/LeftNav.tsx
+++ b/src/view/shell/desktop/LeftNav.tsx
@@ -14,8 +14,10 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
 import {makeProfileLink} from '#/lib/routes/links'
 import {CommonNavigatorParams} from '#/lib/routes/types'
+import {useGate} from '#/lib/statsig/statsig'
 import {isInvalidHandle} from '#/lib/strings/handles'
 import {emitSoftReset} from '#/state/events'
+import {useHomeBadge} from '#/state/home-badge'
 import {useFetchHandle} from '#/state/queries/handle'
 import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations'
 import {useUnreadNotifications} from '#/state/queries/notifications/unread'
@@ -100,12 +102,13 @@ function ProfileCard() {
 
 interface NavItemProps {
   count?: string
+  hasNew?: boolean
   href: string
   icon: JSX.Element
   iconFilled: JSX.Element
   label: string
 }
-function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
+function NavItem({count, hasNew, href, icon, iconFilled, label}: NavItemProps) {
   const t = useTheme()
   const {_} = useLingui()
   const {currentAccount} = useSession()
@@ -214,6 +217,24 @@ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
               {count}
             </Text>
           </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}
       </View>
       {gtTablet && (
@@ -322,6 +343,8 @@ export function DesktopLeftNav() {
   const {_} = useLingui()
   const {isDesktop, isTablet} = useWebMediaQueries()
   const numUnreadNotifications = useUnreadNotifications()
+  const hasHomeBadge = useHomeBadge()
+  const gate = useGate()
 
   if (!hasSession && !isDesktop) {
     return null
@@ -348,6 +371,7 @@ export function DesktopLeftNav() {
         <>
           <NavItem
             href="/"
+            hasNew={hasHomeBadge && gate('remove_show_latest_button')}
             icon={
               <Home
                 aria-hidden={true}