Live video on the AT Protocol
79
fork

Configure Feed

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

try to put it on/near the resizable

authored by

Natalie B. and committed by
Eli Mallon
ba2c5c75 5735aeed

+53 -16
+7 -1
js/components/src/components/stream-notification/stream-notification-manager.ts
··· 1 1 export type NotificationConfig = { 2 2 id?: string; 3 3 message?: string; 4 - render?: (isExiting: boolean, onDismiss: () => void) => React.ReactNode; 4 + render?: ( 5 + isExiting: boolean, 6 + onDismiss: () => void, 7 + startTime?: number, 8 + ) => React.ReactNode; 5 9 duration?: number; // seconds, 0 = manual dismiss only 6 10 actionLabel?: string; 7 11 onAction?: () => void; ··· 16 20 visible: boolean; 17 21 shouldDismiss?: boolean; 18 22 dismissReason?: "user" | "auto"; 23 + startTime?: number; 19 24 }; 20 25 21 26 type Listener = (notifications: StreamNotification[]) => void; ··· 38 43 onAutoDismiss: config.onAutoDismiss, 39 44 variant: config.variant ?? "default", 40 45 visible: true, 46 + startTime: Date.now(), 41 47 }; 42 48 43 49 // if notification with same ID exists, dismiss it first
+1 -1
js/components/src/components/stream-notification/stream-notification.tsx
··· 140 140 ]} 141 141 > 142 142 {notification.render ? ( 143 - notification.render(isExiting, handleDismiss) 143 + notification.render(isExiting, handleDismiss, notification.startTime) 144 144 ) : ( 145 145 <View style={styles.content}> 146 146 <Text style={[styles.message, { color: theme.colors.foreground }]}>
+24 -6
js/components/src/components/stream-notification/teleport-notification.tsx
··· 1 1 import { useEffect, useState } from "react"; 2 - import { View } from "react-native"; 2 + import { useWindowDimensions, View } from "react-native"; 3 3 import Animated, { 4 4 Easing, 5 5 useAnimatedStyle, ··· 12 12 export function TeleportNotification({ 13 13 targetHandle, 14 14 countdown, 15 + startTime, 15 16 onDismiss, 16 17 }: { 17 18 targetHandle: string; 18 19 countdown: number; 20 + startTime?: number; 19 21 onDismiss: () => void; 20 22 }) { 21 23 const { zero: z } = useTheme(); 22 - const [showStripes, setShowStripes] = useState(true); 23 - const [timeLeft, setTimeLeft] = useState(countdown); 24 + const w = useWindowDimensions().width; 25 + 26 + // calculate initial time left based on start time 27 + const initialTimeLeft = startTime 28 + ? Math.max(0, countdown - Math.floor((Date.now() - startTime) / 1000)) 29 + : countdown; 30 + const [timeLeft, setTimeLeft] = useState(initialTimeLeft); 31 + 32 + // if we're past 5 seconds from start, stripes should already be hidden 33 + const elapsedTime = startTime ? (Date.now() - startTime) / 1000 : 0; 34 + const [showStripes, setShowStripes] = useState(elapsedTime < 5); 24 35 25 36 const stripeX = useSharedValue(0); 26 37 const stripeOpacity = useSharedValue(1); 27 38 const progressWidth = useSharedValue(100); 28 39 29 40 useEffect(() => { 41 + // if stripes are already hidden, fade out asap and return 42 + if (!showStripes) { 43 + stripeOpacity.value = withTiming(0, { duration: 0 }); 44 + return; 45 + } 30 46 // warning stripes animation 31 47 stripeX.value = withRepeat( 32 48 withTiming(30 * 2, { ··· 47 63 // after animation, set stripes as hidden 48 64 setTimeout(() => { 49 65 setShowStripes(false); 50 - }, 500); 66 + }, 350); 51 67 }, 1500); 52 68 53 69 return () => clearTimeout(stripesTimer); ··· 99 115 zero.layout.flex.alignCenter, 100 116 zero.layout.flex.spaceBetween, 101 117 zero.px[3], 102 - zero.py[4], 118 + w > 650 ? zero.py[4] : zero.py[2], 103 119 ]} 104 120 > 105 - <Text size="xl">Teleporting to @{targetHandle}</Text> 121 + <Text size={w > 650 ? "xl" : "base"}> 122 + Teleporting to @{targetHandle} 123 + </Text> 106 124 <View 107 125 style={[ 108 126 zero.layout.flex.row,
+18 -5
js/components/src/components/ui/resizeable.tsx
··· 9 9 import Animated, { 10 10 Extrapolation, 11 11 interpolate, 12 + runOnJS, 12 13 useAnimatedStyle, 13 14 useSharedValue, 14 15 withSpring, ··· 48 49 const sheetHeight = useSharedValue(MIN_HEIGHT); 49 50 const startHeight = useSharedValue(MIN_HEIGHT); 50 51 const [isCollapsed, setIsCollapsed] = useState(true); 52 + const wasCollapsed = useSharedValue(true); 51 53 52 54 useEffect(() => { 53 55 setTimeout(() => { ··· 69 71 if (newHeight < MIN_HEIGHT) newHeight = MIN_HEIGHT; 70 72 sheetHeight.value = newHeight; 71 73 72 - if (newHeight < COLLAPSE_HEIGHT) { 74 + const nowCollapsed = newHeight < COLLAPSE_HEIGHT; 75 + if (nowCollapsed && !wasCollapsed.value) { 73 76 sheetHeight.value = withSpring(MIN_HEIGHT, SPRING_CONFIG); 74 - setIsCollapsed(true); 75 - } else { 76 - setIsCollapsed(false); 77 + wasCollapsed.value = true; 78 + runOnJS(setIsCollapsed)(true); 79 + } else if (!nowCollapsed && wasCollapsed.value) { 80 + wasCollapsed.value = false; 81 + runOnJS(setIsCollapsed)(false); 77 82 } 78 83 }); 79 84 ··· 209 214 {children} 210 215 </AnimatedView> 211 216 <Animated.View 212 - style={[aboveElementStyle, { width: "100%", pointerEvents: "none" }]} 217 + style={[ 218 + aboveElementStyle, 219 + { 220 + width: "100%", 221 + pointerEvents: "none", 222 + position: "absolute", 223 + bottom: 0, 224 + }, 225 + ]} 213 226 > 214 227 <View style={{ pointerEvents: "auto" }}> 215 228 {renderAbove?.(isCollapsed)}
+3 -3
js/components/src/lib/stream-notifications.ts
··· 11 11 }) => { 12 12 streamNotification.show({ 13 13 id: "teleport", 14 - render: (isExiting, onDismiss) => { 14 + render: (isExiting, onDismiss, startTime) => { 15 15 return React.createElement(TeleportNotification, { 16 16 targetHandle: params.targetHandle, 17 17 countdown: params.countdown, 18 + startTime: startTime, 18 19 onDismiss: () => { 19 20 params.onCancel?.(); 20 21 onDismiss(); 21 22 }, 22 23 }); 23 24 }, 24 - // allow some extra time for the countdown animation to finish 25 - duration: 30 + 7, 25 + duration: 30 + 2, // manually dismissed by countdown or user cancel 26 26 variant: "warning", 27 27 onUserDismiss: params.onCancel, 28 28 });