From 26287e46b5a08360ad4868729cfbc15e62370479 Mon Sep 17 00:00:00 2001 From: Natalie Bridgers Date: Sat, 1 Aug 2026 14:15:52 -0500 Subject: [PATCH 1/4] replace 'you are live' toast with ring + dropdown on header avatar when the signed-in user is live, the top-right avatar gets a red ring and becomes a dropdown trigger with a link to the live dashboard. the dropdown uses rn-primitives directly so it stays an anchored popover on native too, where the shared DropdownMenuContent renders a bottom sheet. --- js/app/src/router.tsx | 196 +++++++++++++++++++++++++++++++++--------- js/app/src/shell.tsx | 55 +----------- 2 files changed, 157 insertions(+), 94 deletions(-) diff --git a/js/app/src/router.tsx b/js/app/src/router.tsx index d115b43e..77f61627 100644 --- a/js/app/src/router.tsx +++ b/js/app/src/router.tsx @@ -1,6 +1,7 @@ import { LiquidGlassView } from "@callstack/liquid-glass"; import "@expo/metro-runtime"; import { useNavigation } from "@react-navigation/native"; +import * as DropdownMenuPrimitive from "@rn-primitives/dropdown-menu"; import { Button, Text, @@ -10,6 +11,7 @@ import { } from "@streamplace/components"; import { Provider } from "components"; import { ImageBackground } from "expo-image"; +import { useLiveUser } from "hooks/useLiveUser"; import { useSidebarControl } from "hooks/useSidebarControl"; import { ArrowLeft, @@ -20,10 +22,12 @@ import { Upload, User, } from "lucide-react-native"; +import { ReactNode } from "react"; import { ImageSourcePropType, Platform, Pressable, + StyleSheet, useWindowDimensions, View, } from "react-native"; @@ -35,6 +39,7 @@ import { configureReanimatedLogger, ReanimatedLogLevel, } from "react-native-reanimated"; +import { convertNavigationParams } from "src/navigation-helper"; import "src/navigation-types"; import Shell from "src/shell"; import { useStore } from "store"; @@ -132,8 +137,90 @@ export const NavigationButton = ({ canGoBack }: { canGoBack?: boolean }) => { ); }; +// Red ring shown around the avatar while the user is live +const LiveRing = ({ children }: { children: ReactNode }) => { + const { theme } = useTheme(); + return ( + + {children} + + ); +}; + +// Anchored popover menu with a link to the live dashboard. Built on +// rn-primitives directly so it stays a regular dropdown on native too, +// where the shared DropdownMenuContent renders as a bottom sheet. +const LiveAvatarDropdown = ({ children }: { children: ReactNode }) => { + const { theme } = useTheme(); + const navigation = useNavigation(); + return ( + + {children} + + + + { + const to = convertNavigationParams({ + screen: "LiveDashboard", + }); + // @ts-expect-error - dynamic navigation with LinkParams + navigation.navigate(to.screen, to.params); + }} + > + + + + Go to Live Dashboard + + + + + + + + ); +}; + export const LGAvatarButton = () => { const userProfile = useUserProfile(); + const userIsLive = useLiveUser(); if (Platform.OS !== "ios") return ; @@ -147,40 +234,56 @@ export const LGAvatarButton = () => { source = { uri: userProfile.avatar }; opacity = 0; } - return ( - - + - - - - + + + + ); + + if (userProfile && userIsLive) { + return ( + + + {glassAvatar} + + + ); + } + + return ( + + {glassAvatar} ); }; export const AvatarButton = () => { const userProfile = useUserProfile(); + const userIsLive = useLiveUser(); const openLoginModal = useStore((state) => state.openLoginModal); const openPDSModal = useStore((state) => state.openPdsModal); const { theme } = useTheme(); @@ -192,24 +295,35 @@ export const AvatarButton = () => { if (userProfile) { source = { uri: userProfile.avatar }; + const avatar = ( + + + + ); + if (userIsLive) { + return ( + + + {avatar} + + + ); + } return ( - - - - + + {avatar} ); } diff --git a/js/app/src/shell.tsx b/js/app/src/shell.tsx index 927defc0..2ab9bb87 100644 --- a/js/app/src/shell.tsx +++ b/js/app/src/shell.tsx @@ -2,7 +2,7 @@ import { BottomTabIcon, createBottomTabNavigator, } from "@react-navigation/bottom-tabs"; -import { useLinkTo, useNavigation } from "@react-navigation/native"; +import { useLinkTo } from "@react-navigation/native"; import { createNativeStackNavigator, NativeStackHeaderBackProps, @@ -14,7 +14,6 @@ import { usePrimaryColor, useSiteTitle, useTheme, - useToast, zero, } from "@streamplace/components"; import { Settings } from "components"; @@ -41,11 +40,10 @@ import WebhookManager from "components/settings/webhook-manager"; import { SidebarOverlay } from "components/sidebar/sidebar-overlay"; import UploadProgressIndicator from "components/upload/upload-progress-indicator"; import { useBlueskyNotifications } from "hooks/useBlueskyNotifications"; -import { useLiveUser } from "hooks/useLiveUser"; import usePlatform from "hooks/usePlatform"; import { useIsLargeScreen, useSidebarControl } from "hooks/useSidebarControl"; import { Clapperboard, Cog, Home, Video } from "lucide-react-native"; -import { useEffect, useRef, useState } from "react"; +import { useEffect } from "react"; import { Platform, StatusBar, View } from "react-native"; import Animated, { useAnimatedStyle } from "react-native-reanimated"; import { SFSymbols7_0 } from "sf-symbols-typescript"; @@ -550,7 +548,6 @@ function TabNavigator() { export default function Shell() { const { isNative } = usePlatform(); const sidebar = useSidebarControl(); - const navigation = useNavigation(); const hydrate = useStore((state) => state.hydrate); const initPushNotifications = useStore( (state) => state.initPushNotifications, @@ -567,11 +564,8 @@ export default function Shell() { const closePdsModal = useStore((state) => state.closePdsModal); const loginAction = useStore((state) => state.login); const openLoginLink = useStore((state) => state.openLoginLink); - const livePopupShown = useRef(false); const z = useTheme(); - const toast = useToast(); - // Top-level hydration and initialization useEffect(() => { hydrate(); @@ -613,53 +607,8 @@ export default function Shell() { return () => clearInterval(handle); }, []); - const userIsLive = useLiveUser(); useBlueskyNotifications(); - // Track current route - const [currentRouteName, setCurrentRouteName] = useState< - string | undefined - >(); - - useEffect(() => { - const unsubscribe = navigation.addListener("state", () => { - const state = navigation.getState(); - if (state?.routes) { - const currentRoute = state.routes[state.index]; - console.log("setCurrentRouteName", currentRoute?.name); - setCurrentRouteName(currentRoute?.name); - } - }); - return unsubscribe; - }, [navigation]); - - const noLivePopupRoutes = - currentRouteName === "LiveDashboard" || - currentRouteName === "GoLiveTab" || - currentRouteName === "MobileGoLive"; - - // Show "You are live!" toast once per live session - useEffect(() => { - if (!userIsLive) { - livePopupShown.current = false; - return; - } - if (!noLivePopupRoutes && !livePopupShown.current) { - livePopupShown.current = true; - toast.show("You are live!", "Do you want to go to your Live Dashboard?", { - actionLabel: "Go", - onAction: () => { - navigation.navigate("MainTabs" as any, { - screen: "HomeTab", - params: { screen: "LiveDashboard" }, - }); - }, - variant: "error", - duration: 8, - }); - } - }, [userIsLive, noLivePopupRoutes]); - // Animate content margin when sidebar is active (web only) const animatedContentStyle = useAnimatedStyle(() => { if (isNative || !sidebar.isActive) { -- 2.51.2 From e9f52e2a330c47e06fe661f04f08859e64ec36fe Mon Sep 17 00:00:00 2001 From: Natalie Bridgers Date: Sat, 1 Aug 2026 14:50:32 -0500 Subject: [PATCH 2/4] format lexicons.json? Signed-off-by: Natalie Bridgers --- lexicons.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lexicons.json b/lexicons.json index 34aca92e..94261f2b 100644 --- a/lexicons.json +++ b/lexicons.json @@ -217,4 +217,4 @@ "cid": "bafyreic3l2rmh2ugirt3jz372wcvy333m7t2ynlyzj2k54oshijs6lxdfu" } } -} \ No newline at end of file +} -- 2.51.2 From 3b73a0270d5b57ec2bf9c737f20cca897faf425c Mon Sep 17 00:00:00 2001 From: Natalie Bridgers Date: Sat, 1 Aug 2026 14:55:10 -0500 Subject: [PATCH 3/4] Set header background color in shell navigation Signed-off-by: Natalie Bridgers --- js/app/src/shell.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/app/src/shell.tsx b/js/app/src/shell.tsx index 2ab9bb87..5d2ab638 100644 --- a/js/app/src/shell.tsx +++ b/js/app/src/shell.tsx @@ -647,6 +647,9 @@ export default function Shell() { headerTitleStyle: { fontFamily: z.theme.typography.universal.base.fontFamily, }, + headerStyle: { + backgroundColor: z.theme.colors.background, + }, }} > {/* Main tabs (initial screen for all platforms) */} -- 2.51.2 From 8e298eb4d309a8834589b9421675ed5823098540 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sat, 1 Aug 2026 19:08:31 -0700 Subject: [PATCH 4/4] make fix --- lexicons.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lexicons.json b/lexicons.json index 94261f2b..34aca92e 100644 --- a/lexicons.json +++ b/lexicons.json @@ -217,4 +217,4 @@ "cid": "bafyreic3l2rmh2ugirt3jz372wcvy333m7t2ynlyzj2k54oshijs6lxdfu" } } -} +} \ No newline at end of file -- 2.51.2