Live video on the AT Protocol

Merge pull request #724 from streamplace/natb/make-dashboard-scrollable-native

fix: make dashboard properly scrollable on native

authored by Eli Mallon and committed by GitHub 8a5859ca ee616eb7

+51 -49
+26 -29
js/app/components/live-dashboard/bento-grid.tsx
··· 7 useProfile, 8 useSegment, 9 useSegmentTiming, 10 zero, 11 } from "@streamplace/components"; 12 import { 13 ProblemsWrapper, 14 ProblemsWrapperRef, 15 } from "@streamplace/components/src/components/dashboard/problems"; 16 import { useCallback, useEffect, useMemo, useRef, useState } from "react"; 17 import { Dimensions, Platform, ScrollView, View } from "react-native"; 18 import LivestreamPanel from "./livestream-panel"; 19 import StreamMonitor from "./stream-monitor"; 20 ··· 61 }, [isWeb]); 62 63 const isDesktop = screenWidth >= 1200; 64 65 // Get data from hooks for Dashboard components 66 const profile = useProfile(); ··· 208 209 return ( 210 <ProblemsWrapper ref={problemsRef}> 211 - <ScrollView style={[flex.values[1], bg.black]}> 212 {/* Header always at top */} 213 <View style={[p[4]]}> 214 <Dashboard.Header ··· 225 onProblemsPress={handleProblemsPress} 226 /> 227 </View> 228 - 229 - {/* Fixed layout with flex */} 230 - <View 231 - style={[ 232 - flex.values[1], 233 - layout.flex.column, 234 - gap.all[4], 235 - p[4], 236 - { paddingTop: 0 }, 237 - ]} 238 > 239 - {/* Stream Monitor Panel */} 240 - <View style={[{ maxHeight: screenHeight * 0.35, height: "100%" }]}> 241 - <StreamMonitor 242 - isLive={isLive} 243 - userProfile={profile} 244 - videoRef={videoRef} 245 - /> 246 - </View> 247 - 248 - {/* Chat Panel - takes remaining space */} 249 - <View style={[flex.values[1], { maxHeight: screenHeight * 0.65 }]}> 250 <Button 251 disabled={!profile} 252 onPress={() => 253 navigation.navigate("PopoutChat", { user: profile!.did }) 254 } 255 > 256 Go to chat 257 </Button> 258 - </View> 259 - 260 - {/* Livestream Panel */} 261 - <View style={[{ height: "auto" }]}> 262 - <LivestreamPanel /> 263 </View> 264 - </View> 265 - </ScrollView> 266 </ProblemsWrapper> 267 ); 268 }
··· 7 useProfile, 8 useSegment, 9 useSegmentTiming, 10 + useTheme, 11 zero, 12 } from "@streamplace/components"; 13 import { 14 ProblemsWrapper, 15 ProblemsWrapperRef, 16 } from "@streamplace/components/src/components/dashboard/problems"; 17 + import { ArrowRight } from "lucide-react-native"; 18 import { useCallback, useEffect, useMemo, useRef, useState } from "react"; 19 import { Dimensions, Platform, ScrollView, View } from "react-native"; 20 + import { useSafeAreaInsets } from "react-native-safe-area-context"; 21 import LivestreamPanel from "./livestream-panel"; 22 import StreamMonitor from "./stream-monitor"; 23 ··· 64 }, [isWeb]); 65 66 const isDesktop = screenWidth >= 1200; 67 + 68 + const insets = useSafeAreaInsets(); 69 + const { theme } = useTheme(); 70 71 // Get data from hooks for Dashboard components 72 const profile = useProfile(); ··· 214 215 return ( 216 <ProblemsWrapper ref={problemsRef}> 217 + <> 218 {/* Header always at top */} 219 <View style={[p[4]]}> 220 <Dashboard.Header ··· 231 onProblemsPress={handleProblemsPress} 232 /> 233 </View> 234 + <ScrollView 235 + contentContainerStyle={{ paddingBottom: insets.bottom }} 236 + style={[flex.values[1], bg.black]} 237 > 238 + <View style={[gap.all[4], p[4], { paddingTop: 0 }]}> 239 + {/* Stream Monitor Panel */} 240 + <View style={[{ height: screenHeight * 0.35 }]}> 241 + <StreamMonitor 242 + isLive={isLive} 243 + userProfile={profile} 244 + videoRef={videoRef} 245 + /> 246 + </View> 247 <Button 248 disabled={!profile} 249 onPress={() => 250 navigation.navigate("PopoutChat", { user: profile!.did }) 251 } 252 + size="lg" 253 + rightIcon={<ArrowRight size="18" color={theme.colors.text} />} 254 > 255 Go to chat 256 </Button> 257 + <View> 258 + <LivestreamPanel scrollable={false} /> 259 + </View> 260 </View> 261 + </ScrollView> 262 + </> 263 </ProblemsWrapper> 264 ); 265 }
+13 -8
js/app/components/live-dashboard/livestream-panel.tsx
··· 160 ); 161 }; 162 163 - function LivestreamPanel() { 164 const toast = useToast(); 165 const userIsLive = useLiveUser(); 166 const captureFrame = useCaptureVideoFrame(); ··· 326 return mode === "create" ? "Announce Livestream!" : "Update Livestream!"; 327 }, [loading, userIsLive, mode]); 328 329 return ( 330 <> 331 - <ScrollView 332 - contentContainerStyle={{ 333 - flexGrow: 1, 334 - }} 335 - showsVerticalScrollIndicator={false} 336 - > 337 <View 338 style={[ 339 flex.values[1], ··· 573 </View> 574 )} 575 </View> 576 - </ScrollView> 577 </> 578 ); 579 }
··· 160 ); 161 }; 162 163 + function LivestreamPanel({ scrollable = true }: { scrollable?: boolean }) { 164 const toast = useToast(); 165 const userIsLive = useLiveUser(); 166 const captureFrame = useCaptureVideoFrame(); ··· 326 return mode === "create" ? "Announce Livestream!" : "Update Livestream!"; 327 }, [loading, userIsLive, mode]); 328 329 + const Wrapper = scrollable ? ScrollView : View; 330 + const wrapperProps = scrollable 331 + ? { 332 + contentContainerStyle: { 333 + flexGrow: 1, 334 + }, 335 + showsVerticalScrollIndicator: false, 336 + } 337 + : {}; 338 + 339 return ( 340 <> 341 + <Wrapper {...wrapperProps}> 342 <View 343 style={[ 344 flex.values[1], ··· 578 </View> 579 )} 580 </View> 581 + </Wrapper> 582 </> 583 ); 584 }
+7 -6
js/app/components/live-dashboard/stream-key.tsx
··· 3 Button, 4 Code, 5 Row, 6 - View, 7 useTheme, 8 useToast, 9 } from "@streamplace/components"; 10 import { Redirect } from "components/aqlink"; 11 import Loading from "components/loading/loading"; ··· 85 <FormRow> 86 <Label>Output Settings</Label> 87 <Content> 88 - <Body> 89 - Output mode: Advanced 90 - <br /> 91 Keyframe Interval: <Code>1s</Code> 92 - <br /> 93 x264 Options: <Code>bframes=0</Code> 94 - </Body> 95 </Content> 96 </FormRow> 97 </View>
··· 3 Button, 4 Code, 5 Row, 6 + Text, 7 useTheme, 8 useToast, 9 + View, 10 } from "@streamplace/components"; 11 import { Redirect } from "components/aqlink"; 12 import Loading from "components/loading/loading"; ··· 86 <FormRow> 87 <Label>Output Settings</Label> 88 <Content> 89 + <Text>Output mode: Advanced</Text> 90 + <Text> 91 Keyframe Interval: <Code>1s</Code> 92 + </Text> 93 + <Text> 94 x264 Options: <Code>bframes=0</Code> 95 + </Text> 96 </Content> 97 </FormRow> 98 </View>
+1 -1
js/app/components/settings/settings.tsx
··· 217 const isReady = useIsReady(); 218 const serverSettings = useServerSettings(); 219 const { url } = useStreamplaceNode(); 220 - const { t } = useTranslation(); 221 const debugRecordingOn = serverSettings?.debugRecording === true; 222 223 useEffect(() => {
··· 217 const isReady = useIsReady(); 218 const serverSettings = useServerSettings(); 219 const { url } = useStreamplaceNode(); 220 + const { t } = useTranslation("settings"); 221 const debugRecordingOn = serverSettings?.debugRecording === true; 222 223 useEffect(() => {
+1 -4
js/app/src/screens/live-dashboard.tsx
··· 9 import { VideoElementProvider } from "contexts/VideoElementContext"; 10 import { useLiveUser } from "hooks/useLiveUser"; 11 import { useCallback, useState } from "react"; 12 - import { View } from "react-native"; 13 import { useIsReady, useUserProfile } from "store/hooks"; 14 15 const { flex, bg } = zero; ··· 40 <LivestreamProvider src={userProfile.did}> 41 <VideoElementProvider videoElement={videoElement}> 42 <PlayerProvider> 43 - <View style={[flex.values[1], bg.gray[900]]}> 44 - <BentoGrid isLive={isLive} videoRef={videoRef} /> 45 - </View> 46 </PlayerProvider> 47 </VideoElementProvider> 48 </LivestreamProvider>
··· 9 import { VideoElementProvider } from "contexts/VideoElementContext"; 10 import { useLiveUser } from "hooks/useLiveUser"; 11 import { useCallback, useState } from "react"; 12 import { useIsReady, useUserProfile } from "store/hooks"; 13 14 const { flex, bg } = zero; ··· 39 <LivestreamProvider src={userProfile.did}> 40 <VideoElementProvider videoElement={videoElement}> 41 <PlayerProvider> 42 + <BentoGrid isLive={isLive} videoRef={videoRef} /> 43 </PlayerProvider> 44 </VideoElementProvider> 45 </LivestreamProvider>
+3 -1
js/components/src/components/content-metadata/content-metadata-form.tsx
··· 684 ]} 685 > 686 <Text 687 style={[ 688 text.neutral[300], 689 { 690 minWidth: 100, 691 textAlign: "left", 692 paddingBottom: 8, 693 fontSize: 14, 694 }, 695 ]} 696 > 697 - Allowed<br></br>Broadcasters 698 </Text> 699 <View style={[flex.values[1]]}> 700 <Text
··· 684 ]} 685 > 686 <Text 687 + leading="tight" 688 style={[ 689 text.neutral[300], 690 { 691 minWidth: 100, 692 + maxWidth: 100, 693 textAlign: "left", 694 paddingBottom: 8, 695 fontSize: 14, 696 }, 697 ]} 698 > 699 + Allowed Broadcasters 700 </Text> 701 <View style={[flex.values[1]]}> 702 <Text