Live video on the AT Protocol
79
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add DesktopUi component for desktop livestream layout

+225
+225
js/app/components/mobile/DesktopUi.tsx
··· 1 + import { useNavigation } from "@react-navigation/native"; 2 + import { 3 + PlayerUI, 4 + Text, 5 + Toast, 6 + useAvatars, 7 + useCameraToggle, 8 + useLivestreamInfo, 9 + usePlayerDimensions, 10 + View, 11 + zero, 12 + } from "@streamplace/components"; 13 + import { ChevronLeft, MessageCircle, SwitchCamera } from "lucide-react-native"; 14 + import { useEffect } from "react"; 15 + import { Image, Pressable, TouchableWithoutFeedback } from "react-native"; 16 + import { useResponsiveLayout } from "./useResponsiveLayout"; 17 + 18 + const { borders, colors, gap, h, layout, position, w, px, py, r } = zero; 19 + 20 + export function DesktopUi({ 21 + showChat, 22 + setShowChat, 23 + }: { 24 + showChat: boolean; 25 + setShowChat: (show: boolean) => void; 26 + }) { 27 + const navigation = useNavigation(); 28 + const { 29 + ingest, 30 + profile, 31 + title, 32 + setTitle, 33 + showCountdown, 34 + setShowCountdown, 35 + recordSubmitted, 36 + setRecordSubmitted, 37 + ingestStarting, 38 + setIngestStarting, 39 + toggleGoLive, 40 + } = useLivestreamInfo(); 41 + const { width, height } = usePlayerDimensions(); 42 + const { doSetIngestCamera } = useCameraToggle(); 43 + const avatars = useAvatars(profile?.did ? [profile?.did] : []); 44 + 45 + // Desktop layout configuration 46 + const { shouldShowChatSidePanel, chatPanelWidth, safeAreaInsets } = 47 + useResponsiveLayout(); 48 + 49 + useEffect(() => { 50 + return () => { 51 + if (ingestStarting) { 52 + setIngestStarting(false); 53 + } 54 + }; 55 + }, [ingestStarting, setIngestStarting]); 56 + 57 + const isSelfAndNotLive = ingest === "new"; 58 + const isLive = ingest !== null && ingest !== "new"; 59 + 60 + return ( 61 + <> 62 + <TouchableWithoutFeedback> 63 + <View 64 + style={[layout.position.absolute, h.percent[100], w.percent[100]]} 65 + > 66 + {/* Main UI Overlay */} 67 + <View 68 + style={[layout.position.absolute, h.percent[100], w.percent[100]]} 69 + > 70 + {/* Top Left - Back Button and Profile */} 71 + <View 72 + style={[ 73 + { 74 + padding: 8, 75 + backgroundColor: "rgba(0, 0, 0, 0.6)", 76 + }, 77 + r[2], 78 + layout.position.absolute, 79 + position.left[2], 80 + { top: safeAreaInsets.top + 12 }, 81 + ]} 82 + > 83 + <View style={[layout.flex.row, layout.flex.center, gap.all[3]]}> 84 + <Pressable 85 + onPress={() => { 86 + navigation.canGoBack() 87 + ? navigation.goBack() 88 + : navigation.navigate("Home", { screen: "StreamList" }); 89 + }} 90 + > 91 + <ChevronLeft color="white" size={24} /> 92 + </Pressable> 93 + <Image 94 + source={ 95 + profile?.did 96 + ? { uri: avatars[profile?.did]?.avatar } 97 + : require("assets/images/goose.png") 98 + } 99 + style={[ 100 + { 101 + width: 40, 102 + height: 40, 103 + backgroundColor: "green", 104 + borderRadius: 20, 105 + }, 106 + borders.width.thin, 107 + borders.color.gray[700], 108 + ]} 109 + /> 110 + <Text 111 + style={{ fontSize: 16, fontWeight: "600", color: "white" }} 112 + > 113 + {profile?.handle} 114 + </Text> 115 + </View> 116 + </View> 117 + 118 + {/* Top Center - Viewers and Metrics */} 119 + {isLive && ( 120 + <View 121 + style={[ 122 + layout.position.absolute, 123 + { top: safeAreaInsets.top + 12 }, 124 + position.left[0], 125 + position.right[0], 126 + layout.flex.column, 127 + layout.flex.center, 128 + ]} 129 + > 130 + <View 131 + style={[ 132 + { 133 + padding: 12, 134 + backgroundColor: "rgba(0, 0, 0, 0.5)", 135 + }, 136 + r[3], 137 + layout.flex.row, 138 + layout.flex.center, 139 + gap.all[4], 140 + ]} 141 + > 142 + <PlayerUI.Viewers /> 143 + <PlayerUI.MetricsPanel showMetrics={isLive} /> 144 + </View> 145 + </View> 146 + )} 147 + 148 + {/* Top Right Corner - Context Menu/Camera Toggle */} 149 + <View 150 + style={[ 151 + { 152 + padding: 10, 153 + backgroundColor: "rgba(0, 0, 0, 0.5)", 154 + }, 155 + r[2], 156 + layout.position.absolute, 157 + position.right[2], 158 + { top: safeAreaInsets.top + 12 }, 159 + gap.all[4], 160 + ]} 161 + > 162 + {ingest === null ? ( 163 + <PlayerUI.ContextMenu /> 164 + ) : ( 165 + <Pressable onPress={doSetIngestCamera}> 166 + <SwitchCamera size={32} color={colors.gray[200]} /> 167 + </Pressable> 168 + )} 169 + </View> 170 + 171 + {/* Chat Toggle Button - shown when chat is collapsed */} 172 + {shouldShowChatSidePanel && !showChat && ( 173 + <Pressable 174 + style={[ 175 + { 176 + padding: 12, 177 + backgroundColor: "rgba(0, 0, 0, 0.7)", 178 + borderRadius: 8, 179 + }, 180 + layout.position.absolute, 181 + position.right[2], 182 + { top: safeAreaInsets.top + 70 }, 183 + layout.flex.row, 184 + layout.flex.center, 185 + gap.all[2], 186 + ]} 187 + onPress={() => setShowChat(true)} 188 + > 189 + <MessageCircle size={20} color={colors.gray[200]} /> 190 + <Text style={{ color: "white", fontSize: 14 }}>Show Chat</Text> 191 + </Pressable> 192 + )} 193 + </View> 194 + 195 + {/* Input Panel for self streams */} 196 + {isSelfAndNotLive && ( 197 + <PlayerUI.InputPanel 198 + title={title} 199 + setTitle={setTitle} 200 + ingestStarting={ingestStarting} 201 + toggleGoLive={toggleGoLive} 202 + /> 203 + )} 204 + 205 + <PlayerUI.CountdownOverlay 206 + visible={showCountdown} 207 + width={width} 208 + height={height} 209 + onDone={() => { 210 + setShowCountdown(false); 211 + }} 212 + /> 213 + 214 + <Toast 215 + open={recordSubmitted} 216 + onOpenChange={setRecordSubmitted} 217 + title="You're live!" 218 + description="We're notifying your followers that you just went live." 219 + duration={5} 220 + /> 221 + </View> 222 + </TouchableWithoutFeedback> 223 + </> 224 + ); 225 + }