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