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 (
(
),
}),
}}
/>
(
),
}),
headerShown: true,
headerTransparent: true,
}}
/>
(
),
}),
headerShown: false,
}}
/>
);
}
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);
}}
/>
>
);
}