import { Pressable, Text, View } from "react-native"; import { EllipsisVertical, Globe, 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 { 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 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} {showFitToggle ? ( {videoFit === "cover" ? ( ) : ( )} ) : null} ); }