import { useMemo, useState } from 'react'; import { KeyboardAvoidingView, Platform, Pressable, Text, TextInput, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { StatusBar } from 'expo-status-bar'; import { ChevronRight, Globe, Lock, X } from 'lucide-react-native'; import { toast } from 'sonner-native'; import { ComposeDock } from '@/features/compose/ComposeDock'; import { useNetwork } from '@/hooks/use-networks'; import { particlePath } from '@/lib/particle-path'; import { generateRandomName } from '@/lib/random-name'; import { createStreamWithFirstParticle } from '@/lib/upload'; import { toUserMessage } from '@/lib/errors'; import { buildNetworkVisibility, parseVisibleTo, } from '@/lib/stream-visibility'; import { useAuthStore } from '@/stores/auth-store'; import type { RootStackScreenProps } from '@/navigation/types'; import { VisibilityPickerSheet } from './VisibilityPickerSheet'; const STREAM_NAME_MAX = 60; /** * Top-level stream creation. The user names the stream, picks visibility, and * composes the first particle on one screen — desktop's compose-overlay flow * collapsed into a touch-native single page. */ export function NewStreamScreen({ route, navigation, }: RootStackScreenProps<'NewStream'>) { const { networkId } = route.params; const network = useNetwork(networkId); const userId = useAuthStore((s) => s.user?.id); const suggestion = useMemo(() => generateRandomName(), []); const [name, setName] = useState(''); const [visibleTo, setVisibleTo] = useState(() => buildNetworkVisibility(networkId), ); const [pickerOpen, setPickerOpen] = useState(false); const effectiveName = name.trim() || suggestion; const handleStreamCreated = (streamId: string) => { navigation.replace('StreamView', { networkId, streamId }); }; const submitText = async (content: string) => { if (!userId) throw new Error('Not signed in.'); try { const { streamId } = await createStreamWithFirstParticle({ networkId, name: effectiveName, visibleTo, createdByHumanId: userId, firstParticle: { type: 'text', content }, }); handleStreamCreated(streamId); } catch (err) { toast.error(toUserMessage(err)); throw err; } }; const submitMedia = async ({ fileUri, mimeType, durationMs, source, }: { fileUri: string; mimeType: string; durationMs: number; source: 'camera' | 'screen'; }) => { if (!userId) throw new Error('Not signed in.'); try { const { streamId } = await createStreamWithFirstParticle({ networkId, name: effectiveName, visibleTo, createdByHumanId: userId, firstParticle: { type: 'media', fileUri, mimeType, durationMs, source, }, }); handleStreamCreated(streamId); } catch (err) { toast.error(toUserMessage(err)); throw err; } }; const placeholderPath = particlePath(networkId, []); const visibility = parseVisibleTo(visibleTo, networkId); const visibleSummary = visibility.mode === 'network' ? `Everyone in ${network?.name ?? 'this network'}` : `${visibility.humanIds.length} ${ visibility.humanIds.length === 1 ? 'person' : 'people' }`; return ( navigation.goBack()} hitSlop={12} accessibilityLabel="Cancel" > New stream Name setName(v.slice(0, STREAM_NAME_MAX))} placeholder={suggestion} placeholderTextColor="rgba(255,255,255,0.35)" autoCapitalize="none" autoCorrect={false} maxLength={STREAM_NAME_MAX} className="bg-white/10 rounded-xl px-4 py-3 text-white text-lg" /> Visible to setPickerOpen(true)} className="bg-white/10 active:bg-white/15 rounded-xl px-4 py-3 flex-row items-center gap-3" > {visibility.mode === 'network' ? ( ) : ( )} {visibleSummary} Hold the button below to record a voice or video message — that’s the first particle in your new stream. setPickerOpen(false)} networkId={networkId} networkName={network?.name} humans={network?.humans ?? []} selfHumanId={userId} visibleTo={visibleTo} onChange={setVisibleTo} /> ); }