Live video on the AT Protocol
79
fork

Configure Feed

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

Make UI and chat UI more streamlined

Natalie B. b624e7ef 1cff3c47

+39 -137
+4
js/app/components/mobile/chat.tsx
··· 23 23 zIndex[10], 24 24 w.percent[100], 25 25 { transform: [{ translateY: slideKeyboard }] }, 26 + { 27 + backgroundColor: "rgba(0, 0, 0, 0.4)", 28 + borderRadius: borderRadius["2xl"], 29 + }, 26 30 ]} 27 31 > 28 32 <Chat isChatVisible={true} setIsChatVisible={() => true} />
-119
js/app/components/mobile/ui-state.tsx
··· 1 - import { useNavigation } from "@react-navigation/native"; 2 - import { useLivestreamStore, usePlayerStore } from "@streamplace/components"; 3 - import { createLivestreamRecord } from "features/bluesky/blueskySlice"; 4 - import useAvatars from "hooks/useAvatars"; 5 - import { useKeyboard } from "hooks/useKeyboard"; 6 - import { useOuterAndInnerDimensions } from "hooks/useOuterAndInnerDimensions"; 7 - import { useSegmentTiming } from "hooks/useSegmentTiming"; 8 - import { useEffect, useState } from "react"; 9 - import { Dimensions, Keyboard, Platform } from "react-native"; 10 - import { useAppDispatch } from "store/hooks"; 11 - 12 - export default function useMobileUiState() { 13 - const ingest = usePlayerStore((x) => x.ingestConnectionState); 14 - const profile = useLivestreamStore((x) => x.profile); 15 - const pHeight = Number(usePlayerStore((x) => x.playerHeight)) || 0; 16 - const pWidth = Number(usePlayerStore((x) => x.playerWidth)) || 0; 17 - const ingestCamera = usePlayerStore((x) => x.ingestCamera); 18 - const setIngestCamera = usePlayerStore((x) => x.setIngestCamera); 19 - const { width, height } = Dimensions.get("window"); 20 - 21 - const [title, setTitle] = useState<string | undefined>(); 22 - const [showMetrics, setShowMetrics] = useState(false); 23 - const [showCountdown, setShowCountdown] = useState(false); 24 - const [recordSubmitted, setRecordSubmitted] = useState(false); 25 - 26 - const navigation = useNavigation(); 27 - const avatars = useAvatars(profile ? [profile?.did] : []); 28 - 29 - const isPlayerRatioGreater = pWidth / pHeight > width / height; 30 - const isSelfAndNotLive = ingest === "new"; 31 - const isLive = ingest !== null && ingest !== "new"; 32 - 33 - const ingestStarting = usePlayerStore((x) => x.ingestStarting); 34 - const setIngestStarting = usePlayerStore((x) => x.setIngestStarting); 35 - 36 - const dispatch = useAppDispatch(); 37 - 38 - const { keyboardHeight } = useKeyboard(); 39 - let { outerHeight, innerHeight } = useOuterAndInnerDimensions(); 40 - let slideKeyboard = 0; 41 - if (Platform.OS === "ios" && keyboardHeight > 0) { 42 - slideKeyboard = -keyboardHeight + (outerHeight - innerHeight); 43 - } 44 - 45 - const { segmentDeltas, mean, range, connectionQuality } = useSegmentTiming(); 46 - 47 - const handleSubmit = async () => { 48 - try { 49 - if (title) { 50 - // wait ~2 sec for the thumbnail to propogate 51 - setTimeout(() => { 52 - dispatch( 53 - createLivestreamRecord({ 54 - title, 55 - customThumbnail: undefined, // thumbnailToUse || undefined, 56 - }), 57 - ), 58 - setRecordSubmitted(true); 59 - }, 3000); 60 - } 61 - } catch (error) { 62 - console.error("Error creating livestream:", error); 63 - } finally { 64 - setRecordSubmitted(false); 65 - } 66 - }; 67 - 68 - const toggleGoLive = () => { 69 - if (!ingestStarting) { 70 - // if keyboard is open, close it 71 - if (Platform.OS === "ios" && keyboardHeight > 0) { 72 - Keyboard.dismiss(); 73 - } 74 - setShowCountdown(true); 75 - setIngestStarting(true); 76 - handleSubmit(); 77 - return; 78 - } 79 - setIngestStarting(false); 80 - }; 81 - 82 - useEffect(() => { 83 - return () => { 84 - if (ingestStarting) { 85 - setIngestStarting(false); 86 - } 87 - }; 88 - }, []); 89 - 90 - const doSetIngestCamera = () => { 91 - setIngestCamera(ingestCamera === "user" ? "environment" : "user"); 92 - }; 93 - 94 - return { 95 - ingest, 96 - profile, 97 - width, 98 - height, 99 - title, 100 - setTitle, 101 - showCountdown, 102 - setShowCountdown, 103 - recordSubmitted, 104 - setRecordSubmitted, 105 - avatars, 106 - isPlayerRatioGreater, 107 - isSelfAndNotLive, 108 - isLive, 109 - ingestStarting, 110 - slideKeyboard, 111 - segmentDeltas, 112 - mean, 113 - range, 114 - connectionQuality, 115 - toggleGoLive, 116 - doSetIngestCamera, 117 - navigation, 118 - }; 119 - }
+34 -17
js/app/components/mobile/ui.tsx
··· 1 - import { atoms, PlayerUI, Text, Toast, View } from "@streamplace/components"; 1 + import { useNavigation } from "@react-navigation/native"; 2 + import { 3 + atoms, 4 + PlayerUI, 5 + Text, 6 + Toast, 7 + useAvatars, 8 + useCameraToggle, 9 + useKeyboardSlide, 10 + useLivestreamInfo, 11 + usePlayerDimensions, 12 + useSegmentTiming as useSegmentMetrics, 13 + View, 14 + } from "@streamplace/components"; 2 15 import { ChevronLeft, SwitchCamera } from "lucide-react-native"; 16 + import { useEffect } from "react"; 3 17 import { Image, Pressable } from "react-native"; 4 18 import { ChatPanel } from "./chat"; 5 - import useMobileUiState from "./ui-state"; 6 19 7 20 const { borders, colors, gap, h, layout, position, w } = atoms; 8 - // Dropdown imports 9 21 10 22 export function MobileUi() { 23 + const navigation = useNavigation(); 11 24 const { 12 25 ingest, 13 26 profile, 14 - width, 15 - height, 16 27 title, 17 28 setTitle, 18 29 showCountdown, 19 30 setShowCountdown, 20 31 recordSubmitted, 21 32 setRecordSubmitted, 22 - avatars, 23 - isPlayerRatioGreater, 24 - isSelfAndNotLive, 25 - isLive, 26 33 ingestStarting, 27 - slideKeyboard, 28 - segmentDeltas, 29 - mean, 30 - range, 31 - connectionQuality, 34 + setIngestStarting, 32 35 toggleGoLive, 33 - doSetIngestCamera, 34 - navigation, 35 - } = useMobileUiState(); 36 + } = useLivestreamInfo(); 37 + const { width, height, isPlayerRatioGreater } = usePlayerDimensions(); 38 + const { slideKeyboard } = useKeyboardSlide(); 39 + const { connectionQuality, segmentDeltas, mean, range } = useSegmentMetrics(); 40 + const { doSetIngestCamera } = useCameraToggle(); 41 + const avatars = useAvatars(profile?.did ? [profile?.did] : []); 42 + 43 + useEffect(() => { 44 + return () => { 45 + if (ingestStarting) { 46 + setIngestStarting(false); 47 + } 48 + }; 49 + }, [ingestStarting, setIngestStarting]); 50 + 51 + const isSelfAndNotLive = ingest === "new"; 52 + const isLive = ingest !== null && ingest !== "new"; 36 53 37 54 return ( 38 55 <>
+1 -1
js/components/src/hooks/useAvatars.tsx
··· 2 2 import { useEffect, useMemo, useRef, useState } from "react"; 3 3 import { usePDSAgent } from "../streamplace-store/xrpc"; 4 4 5 - export default function useAvatars( 5 + export function useAvatars( 6 6 dids: string[], 7 7 ): Record<string, ProfileViewDetailed> { 8 8 let agent = usePDSAgent();