import { BottomTabIcon, createBottomTabNavigator, } from "@react-navigation/bottom-tabs"; import { useLinkTo, useNavigation } from "@react-navigation/native"; import { createNativeStackNavigator, NativeStackHeaderBackProps, } from "@react-navigation/native-stack"; import { Text, useAccentColor, useDID, usePrimaryColor, useSiteTitle, useTheme, useToast, zero, } from "@streamplace/components"; import { Settings } from "components"; import Login from "components/login/login"; import LoginModal from "components/login/login-modal"; import PdsHostSelectorModal from "components/login/pds-host-selector-modal"; import { AboutCategorySettings } from "components/settings/about-category-settings"; import { AccountCategorySettings } from "components/settings/account-category-settings"; import { AdvancedCategorySettings } from "components/settings/advanced-category-settings"; import { DanmuCategorySettings } from "components/settings/danmu-category-settings"; import KeyManager from "components/settings/key-manager"; import { LanguagesCategorySettings } from "components/settings/languages-category-settings"; import MultistreamManager from "components/settings/multistream-manager"; import { PrivacyCategorySettings } from "components/settings/privacy-category-settings"; import RecommendationsManager from "components/settings/recommendations-manager"; import { StreamingCategorySettings } from "components/settings/streaming-category-settings"; import WebhookManager from "components/settings/webhook-manager"; import { SidebarOverlay } from "components/sidebar/sidebar-overlay"; import { useBlueskyNotifications } from "hooks/useBlueskyNotifications"; import { useLiveUser } from "hooks/useLiveUser"; import usePlatform from "hooks/usePlatform"; import { useIsLargeScreen, useSidebarControl } from "hooks/useSidebarControl"; import { Cog, Home, Video } from "lucide-react-native"; import { useEffect, useRef, useState } from "react"; import { Platform, StatusBar, View } from "react-native"; import Animated, { useAnimatedStyle } from "react-native-reanimated"; import { SFSymbols7_0 } from "sf-symbols-typescript"; import "src/navigation-types"; import AboutScreen from "src/screens/about"; import AppReturnScreen from "src/screens/app-return"; import PopoutChat from "src/screens/chat-popout"; import DanmuOBSScreen from "src/screens/danmu-obs"; import DownloadScreen from "src/screens/download"; import EmbedScreen from "src/screens/embed"; import HomeScreen from "src/screens/home"; import InfoWidgetEmbed from "src/screens/info-widget-embed"; import LaunchGoLive from "src/screens/launch-go-live"; import LiveDashboard from "src/screens/live-dashboard"; import MobileGoLive from "src/screens/mobile-go-live"; import MobileStream from "src/screens/mobile-stream"; import MultiScreen from "src/screens/multi"; import SupportScreen from "src/screens/support"; import { useStore } from "store"; import { useHydrated, useNotificationDestination, useNotificationToken, } from "store/hooks"; import { AvatarButton, LGAvatarButton, NavigationButton } from "./router"; const Tab = createBottomTabNavigator(); const RootStack = createNativeStackNavigator(); const HomeStack = createNativeStackNavigator(); const SettingsStack = createNativeStackNavigator(); function useBaseScreenOptions() { const z = useTheme(); return { headerShown: true, headerTransparent: Platform.OS === "ios", headerBackButtonDisplayMode: "minimal" as const, headerTitleStyle: { fontFamily: z.theme.typography.universal["2xl"].fontFamily, }, headerStyle: { backgroundColor: z.theme.colors.background, borderBottomColor: z.theme.colors.border, borderBottomWidth: 1, }, }; } // Home navigator (contains home + all general navigation screens) function HomeNavigator() { const title = useSiteTitle() || "Streamplace Station"; const baseScreenOptions = useBaseScreenOptions(); const isNative = Platform.OS !== "web"; const z = useTheme(); const did = useDID(); const headerScreenOptions = { headerShown: !isNative, headerLeft: isNative ? undefined : ({ canGoBack }: NativeStackHeaderBackProps) => ( ), headerRight: () => , ...(isNative && { headerTransparent: true, }), headerTitleStyle: { fontFamily: z.theme.typography.universal.base.fontFamily, }, }; return ( ( {title} ) : undefined, headerLeft: Platform.OS !== "ios" ? ({ canGoBack }) => : undefined, headerRight: () => , ...(Platform.OS === "ios" && { unstable_headerRightItems: () => [ { type: "custom", hidesSharedBackground: true, element: , }, ], }), }} /> ); } // Settings stack navigator function SettingsNavigator() { const baseScreenOptions = useBaseScreenOptions(); const z = useTheme(); const isNative = Platform.OS !== "web"; const headerScreenOptions = { headerShown: true, headerLeft: isNative ? undefined : ({ canGoBack }: NativeStackHeaderBackProps) => ( ), headerRight: () => , ...(isNative && { headerTransparent: true, }), headerTitleStyle: { fontFamily: z.theme.typography.universal.base.fontFamily, }, }; return ( ); } const IOS_ICONS: Record = { Home: "house.fill", GoLive: "video.fill", Settings: "gearshape.fill", }; const ANDROID_ICONS = { Home: "home", GoLive: "videocam", Settings: "settings", }; const getIcon = ( name: keyof typeof IOS_ICONS | keyof typeof ANDROID_ICONS, ): BottomTabIcon => { if (Platform.OS === "ios") { return { type: "sfSymbol", name: IOS_ICONS[name], }; } else { return { type: "materialSymbol", name: ANDROID_ICONS[name], }; } }; // Tab navigator (main app sections, navigation on web is handled in sidebar) function TabNavigator() { const { isNative, isBrowser } = usePlatform(); const accentColor = useAccentColor(); const primaryColor = usePrimaryColor(); const isLargeScreen = useIsLargeScreen(); const z = useTheme(); return ( ( ), }), }} /> ( ); } 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, ); const registerNotificationToken = useStore( (state) => state.registerNotificationToken, ); const clearNotification = useStore((state) => state.clearNotification); const pollMySegments = useStore((state) => state.pollMySegments); const showLoginModal = useStore((state) => state.showLoginModal); const closeLoginModal = useStore((state) => state.closeLoginModal); const showPdsModal = useStore((state) => state.showPdsModal); const openPdsModal = useStore((state) => state.openPdsModal); 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(); initPushNotifications(); }, []); const notificationToken = useNotificationToken(); const hydrated = useHydrated(); useEffect(() => { if (notificationToken) { registerNotificationToken(); } }, [notificationToken]); // Handle incoming push notification routing const notificationDestination = useNotificationDestination(); const linkTo = useLinkTo(); useEffect(() => { if (notificationDestination) { linkTo(notificationDestination); clearNotification(); } }, [notificationDestination]); // Poll for live streamers useEffect(() => { let handle: NodeJS.Timeout; handle = setInterval(() => { pollMySegments(); }, 2500); pollMySegments(); 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) { return { marginLeft: 0 }; } return { marginLeft: sidebar.animatedWidth.value, }; }); if (!hydrated) { return ; } return ( <> {!isNative && } ( ), headerRight: () => , ...(isNative && { headerTransparent: true, }), headerTitleStyle: { fontFamily: z.theme.typography.universal.base.fontFamily, }, }} > {/* Main tabs (initial screen for all platforms) */} {/* Full-screen screens that should NOT have tab bar accessible on mobile */} {/* Utility/embed screens */} { closePdsModal(); loginAction(pdsHost, openLoginLink); }} /> ); }