import { ActivityIndicator, Pressable, Text, View } from "react-native"; import { EllipsisVertical, Globe, Headphones, Maximize2, Minimize2, } from "lucide-react-native"; import type { Human, Particle } from "@/api/types"; import { parseVisibleTo } from "@/lib/stream-visibility"; import { cn } from "@/lib/utils"; import { Avatar } from "@/components/Avatar"; import { useOpenHuddle } from "@/features/huddle/use-open-huddle"; import { useStreamPresence } from "./stream-presence-context"; interface StreamTopActionsProps { networkId: string; streamParticle: Particle & { type: "stream" }; humans: Human[]; videoFit: "cover" | "contain"; onToggleVideoFit: () => void; onOpenMembers: () => void; onOpenActions: () => void; /** True when current particle is a video — fit toggle hidden otherwise. */ showFitToggle: boolean; } const MAX_AVATARS = 3; /** * Top-right cluster on StreamView: visibility avatars (with presence ring), * fit/fill toggle, and actions menu trigger. Mirrors desktop's stream-top-bar * but compact for the mobile chrome. */ export function StreamTopActions({ networkId, streamParticle, humans, videoFit, onToggleVideoFit, onOpenMembers, onOpenActions, showFitToggle, }: StreamTopActionsProps) { const { onlineHumanIds } = useStreamPresence(); const { open: openHuddle, loading: huddleLoading } = useOpenHuddle(); const huddleCount = streamParticle.huddle_active_participants?.length ?? 0; const huddleActive = huddleCount > 0; const visibility = parseVisibleTo(streamParticle.visible_to, networkId); const memberIds = visibility.mode === "network" ? humans.map((h) => h.id) : visibility.humanIds; const shown = memberIds.slice(0, MAX_AVATARS); const overflow = memberIds.length - shown.length; return ( {visibility.mode === "network" && memberIds.length === 0 ? ( ) : ( {shown.map((id, idx) => ( {/* The stack ring matches the chrome's translucent bg so it reads as a separator without painting hard black halos. */} ))} )} {overflow > 0 ? ( +{overflow} ) : null} void openHuddle( networkId, streamParticle.id, streamParticle.properties.name, ) } disabled={huddleLoading} accessibilityLabel={huddleActive ? "Join huddle" : "Start huddle"} className={cn( "h-8 items-center justify-center rounded-full px-2.5 flex-row gap-1", huddleActive ? "bg-red-500/90 active:bg-red-600" : "bg-white/10 active:bg-white/20", )} > {huddleLoading ? ( ) : ( <> {huddleActive ? ( {huddleCount} ) : null} )} {showFitToggle ? ( {videoFit === "cover" ? ( ) : ( )} ) : null} ); }