diff --git a/src/App.native.tsx b/src/App.native.tsx
index f985b96a57..39ab7ca92c 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() {
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index b7c5a56334..8d13a826e7 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() {
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index 3cec5d5b29..3767ec1e5d 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 0000000000..59a9276ce8
--- /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(false)
+const apiContext = React.createContext((_: boolean) => {})
+
+export function Provider({children}: React.PropsWithChildren<{}>) {
+ const [state, setState] = React.useState(false)
+ return (
+
+ {children}
+
+ )
+}
+
+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 e766b589b0..10ed60212c 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(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 d98aa0fa71..b502f0b68b 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 db9c049659..47a525c04d 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({
{notificationCount}
- ) : undefined}
+ ) : hasNew ? (
+
+ ) : null}
)
}
diff --git a/src/view/shell/bottom-bar/BottomBarStyles.tsx b/src/view/shell/bottom-bar/BottomBarStyles.tsx
index 9255957cb4..d80914d095 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 127ff2b269..1855969cc4 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 ? (
<>
-
+
{({isActive}) => {
const Icon = isActive ? HomeFilled : Home
return (
@@ -105,7 +112,7 @@ export function BottomBarWeb() {
0
? unreadMessageCount.numUnread
: undefined
@@ -128,7 +135,7 @@ export function BottomBarWeb() {
+ 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 ? (
- {badge}
+ aria-label={_(msg`${notificationCount} unread items`)}>
+ {notificationCount}
- )}
+ ) : hasNew ? (
+
+ ) : null}
)
}
diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx
index 6655572f87..d367e1b98e 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}
+ ) : hasNew ? (
+
) : null}
{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() {
<>