@streamplace/components 0.9.12 → 0.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/components/chat/badge.d.ts.map +1 -1
  2. package/dist/components/chat/badge.js +4 -2
  3. package/dist/components/chat/badge.js.map +1 -1
  4. package/dist/components/chat/chat-box.d.ts +2 -1
  5. package/dist/components/chat/chat-box.d.ts.map +1 -1
  6. package/dist/components/chat/chat-box.js +1 -1
  7. package/dist/components/chat/chat-box.js.map +1 -1
  8. package/dist/components/chat/chat-message.d.ts.map +1 -1
  9. package/dist/components/chat/chat-message.js +3 -3
  10. package/dist/components/chat/chat-message.js.map +1 -1
  11. package/dist/components/chat/chat.d.ts +4 -2
  12. package/dist/components/chat/chat.d.ts.map +1 -1
  13. package/dist/components/chat/chat.js +18 -4
  14. package/dist/components/chat/chat.js.map +1 -1
  15. package/dist/components/chat/teleport-modal.d.ts.map +1 -1
  16. package/dist/components/chat/teleport-modal.js +5 -4
  17. package/dist/components/chat/teleport-modal.js.map +1 -1
  18. package/dist/components/chat/user-profile-card.d.ts.map +1 -1
  19. package/dist/components/chat/user-profile-card.js +11 -8
  20. package/dist/components/chat/user-profile-card.js.map +1 -1
  21. package/dist/components/error-boundary.d.ts +15 -0
  22. package/dist/components/error-boundary.d.ts.map +1 -0
  23. package/dist/components/error-boundary.js +29 -0
  24. package/dist/components/error-boundary.js.map +1 -0
  25. package/dist/components/mobile-player/ui/viewer-context-menu.d.ts.map +1 -1
  26. package/dist/components/mobile-player/ui/viewer-context-menu.js +3 -2
  27. package/dist/components/mobile-player/ui/viewer-context-menu.js.map +1 -1
  28. package/dist/components/mobile-player/ui/viewer-loading-overlay.d.ts.map +1 -1
  29. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +1 -0
  30. package/dist/components/mobile-player/ui/viewer-loading-overlay.js.map +1 -1
  31. package/dist/components/mobile-player/video-async.native.d.ts.map +1 -1
  32. package/dist/components/mobile-player/video-async.native.js +4 -1
  33. package/dist/components/mobile-player/video-async.native.js.map +1 -1
  34. package/dist/components/mobile-player/video.d.ts +3 -3
  35. package/dist/components/mobile-player/video.d.ts.map +1 -1
  36. package/dist/components/mobile-player/video.js.map +1 -1
  37. package/dist/components/ui/dropdown.native.d.ts.map +1 -1
  38. package/dist/components/ui/dropdown.native.js +3 -1
  39. package/dist/components/ui/dropdown.native.js.map +1 -1
  40. package/dist/components/ui/icons.js +1 -1
  41. package/dist/components/ui/icons.js.map +1 -1
  42. package/dist/components/ui/menu.d.ts +0 -8
  43. package/dist/components/ui/menu.d.ts.map +1 -1
  44. package/dist/components/ui/menu.js +1 -35
  45. package/dist/components/ui/menu.js.map +1 -1
  46. package/dist/components/ui/primitives/input.d.ts +4 -4
  47. package/dist/components/ui/primitives/input.d.ts.map +1 -1
  48. package/dist/components/ui/primitives/input.js +10 -3
  49. package/dist/components/ui/primitives/input.js.map +1 -1
  50. package/dist/components/ui/resizeable.d.ts +1 -1
  51. package/dist/components/ui/resizeable.d.ts.map +1 -1
  52. package/dist/components/ui/resizeable.js +8 -5
  53. package/dist/components/ui/resizeable.js.map +1 -1
  54. package/dist/components/ui/text.d.ts +1 -1
  55. package/dist/components/ui/toast.js +4 -4
  56. package/dist/components/ui/toast.js.map +1 -1
  57. package/dist/context/profile-cache.d.ts.map +1 -1
  58. package/dist/context/profile-cache.js +2 -1
  59. package/dist/context/profile-cache.js.map +1 -1
  60. package/dist/hooks/useSegmentTiming.js +1 -1
  61. package/dist/hooks/useSegmentTiming.js.map +1 -1
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +3 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/lib/facet.js +1 -1
  67. package/dist/lib/facet.js.map +1 -1
  68. package/dist/lib/theme/atoms.d.ts +252 -252
  69. package/dist/lib/theme/theme.js +2 -2
  70. package/dist/lib/theme/theme.js.map +1 -1
  71. package/dist/livestream-store/livestream-store.d.ts +2 -2
  72. package/dist/livestream-store/livestream-store.d.ts.map +1 -1
  73. package/dist/streamplace-store/index.d.ts +1 -0
  74. package/dist/streamplace-store/index.d.ts.map +1 -1
  75. package/dist/streamplace-store/index.js +1 -0
  76. package/dist/streamplace-store/index.js.map +1 -1
  77. package/dist/streamplace-store/ingest.d.ts +2 -0
  78. package/dist/streamplace-store/ingest.d.ts.map +1 -0
  79. package/dist/streamplace-store/ingest.js +31 -0
  80. package/dist/streamplace-store/ingest.js.map +1 -0
  81. package/dist/streamplace-store/stream.d.ts +1 -1
  82. package/dist/streamplace-store/stream.d.ts.map +1 -1
  83. package/dist/streamplace-store/streamplace-store.d.ts +3 -2
  84. package/dist/streamplace-store/streamplace-store.d.ts.map +1 -1
  85. package/dist/streamplace-store/streamplace-store.js +3 -4
  86. package/dist/streamplace-store/streamplace-store.js.map +1 -1
  87. package/dist/streamplace-store/user.d.ts +1 -1
  88. package/dist/streamplace-store/user.d.ts.map +1 -1
  89. package/dist/streamplace-store/xrpc.js +4 -4
  90. package/dist/streamplace-store/xrpc.js.map +1 -1
  91. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  92. package/package.json +19 -18
  93. package/src/components/chat/badge.tsx +4 -2
  94. package/src/components/chat/chat-box.tsx +2 -0
  95. package/src/components/chat/chat-message.tsx +4 -5
  96. package/src/components/chat/chat.tsx +31 -4
  97. package/src/components/chat/teleport-modal.tsx +4 -3
  98. package/src/components/chat/user-profile-card.tsx +20 -8
  99. package/src/components/error-boundary.tsx +42 -0
  100. package/src/components/mobile-player/ui/viewer-context-menu.tsx +3 -2
  101. package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +1 -0
  102. package/src/components/mobile-player/video-async.native.tsx +3 -1
  103. package/src/components/mobile-player/video.tsx +7 -5
  104. package/src/components/ui/dropdown.native.tsx +5 -1
  105. package/src/components/ui/icons.tsx +1 -1
  106. package/src/components/ui/menu.tsx +56 -62
  107. package/src/components/ui/primitives/input.tsx +18 -9
  108. package/src/components/ui/resizeable.tsx +11 -6
  109. package/src/components/ui/toast.tsx +1 -1
  110. package/src/context/profile-cache.tsx +7 -1
  111. package/src/hooks/useSegmentTiming.tsx +1 -1
  112. package/src/index.tsx +2 -0
  113. package/src/lib/facet.ts +1 -1
  114. package/src/lib/theme/theme.tsx +1 -1
  115. package/src/streamplace-store/index.tsx +1 -0
  116. package/src/streamplace-store/ingest.tsx +32 -0
  117. package/src/streamplace-store/streamplace-store.tsx +11 -4
  118. package/src/streamplace-store/xrpc.tsx +1 -1
@@ -1,5 +1,6 @@
1
1
  import { ProfileViewBasic } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
2
2
  import { TriggerRef } from "@rn-primitives/dropdown-menu";
3
+ import { Image } from "expo-image";
3
4
  import {
4
5
  createContext,
5
6
  useCallback,
@@ -9,7 +10,7 @@ import {
9
10
  useRef,
10
11
  useState,
11
12
  } from "react";
12
- import { Image, Platform, Pressable, View } from "react-native";
13
+ import { Platform, Pressable, View } from "react-native";
13
14
  import { ChatMessageViewHydrated } from "streamplace";
14
15
  import { useAvatars } from "../../hooks/useAvatars";
15
16
  import { useLivestreamStore } from "../../livestream-store";
@@ -62,8 +63,9 @@ export const ProfileCardProvider = ({
62
63
  children: React.ReactNode;
63
64
  }) => {
64
65
  const [openUri, setOpenUri] = useState<string | null>(null);
66
+ const value = useMemo(() => ({ openUri, setOpenUri }), [openUri]);
65
67
  return (
66
- <OpenCardContext.Provider value={{ openUri, setOpenUri }}>
68
+ <OpenCardContext.Provider value={value}>
67
69
  {children}
68
70
  </OpenCardContext.Provider>
69
71
  );
@@ -73,17 +75,14 @@ const BadgeRow = ({
73
75
  streamer,
74
76
  badge,
75
77
  serviceDid,
78
+ issuerProfiles,
76
79
  }: {
77
80
  badge: NonNullable<ChatMessageViewHydrated["badges"]>[number];
78
81
  serviceDid: string;
79
82
  streamer?: ProfileViewBasic;
83
+ issuerProfiles: ReturnType<typeof useAvatars>;
80
84
  }) => {
81
85
  const isServiceIssued = badge.issuer === serviceDid;
82
- const issuerDids = useMemo(
83
- () => (isServiceIssued ? [] : [badge.issuer]),
84
- [isServiceIssued, badge.issuer],
85
- );
86
- const issuerProfiles = useAvatars(issuerDids);
87
86
  const meta = BADGE_META[badge.badgeType];
88
87
 
89
88
  if (!meta) return null;
@@ -149,7 +148,19 @@ export const UserProfileCard = ({
149
148
  const thisRef = useRef<TriggerRef>(null);
150
149
  const [hovered, setHovered] = useState(false);
151
150
 
152
- const profiles = useAvatars(author.did ? [author.did] : []);
151
+ const issuerDids = useMemo(
152
+ () =>
153
+ badges?.map((b) => b.issuer).filter((did) => did && did !== serviceDid) ??
154
+ [],
155
+ [badges, serviceDid],
156
+ );
157
+
158
+ const allDids = useMemo(
159
+ () => (author.did ? [author.did, ...issuerDids] : issuerDids),
160
+ [author.did, issuerDids],
161
+ );
162
+
163
+ const profiles = useAvatars(allDids);
153
164
  const profile = profiles[author.did];
154
165
 
155
166
  useEffect(() => {
@@ -273,6 +284,7 @@ export const UserProfileCard = ({
273
284
  badge={badge}
274
285
  serviceDid={serviceDid}
275
286
  streamer={streamer}
287
+ issuerProfiles={profiles}
276
288
  />
277
289
  ))}
278
290
  </View>
@@ -0,0 +1,42 @@
1
+ import * as React from "react";
2
+ import { Text, View } from "react-native";
3
+
4
+ type ErrorBoundaryProps = {
5
+ children: React.ReactNode;
6
+ };
7
+
8
+ type ErrorBoundaryState = {
9
+ hasError: boolean;
10
+ };
11
+
12
+ export class ErrorBoundary extends React.Component<
13
+ ErrorBoundaryProps,
14
+ ErrorBoundaryState
15
+ > {
16
+ constructor(props: ErrorBoundaryProps) {
17
+ super(props);
18
+ this.state = { hasError: false };
19
+ }
20
+
21
+ static getDerivedStateFromError(error: any): ErrorBoundaryState {
22
+ // Update state so the next render will show the fallback UI.
23
+ return { hasError: true };
24
+ }
25
+
26
+ componentDidCatch(error: any, info: any) {
27
+ console.error("<ErrorBoundary> caught:", error);
28
+ }
29
+
30
+ render() {
31
+ if (this.state.hasError) {
32
+ // You can render any custom fallback UI
33
+ return (
34
+ <View>
35
+ <Text>Error</Text>
36
+ </View>
37
+ );
38
+ }
39
+
40
+ return this.props.children;
41
+ }
42
+ }
@@ -1,7 +1,8 @@
1
1
  import { useRootContext } from "@rn-primitives/dropdown-menu";
2
+ import { Image } from "expo-image";
2
3
  import { Cog } from "lucide-react-native";
3
4
  import { useState } from "react";
4
- import { Image, Linking, Platform, Pressable, View } from "react-native";
5
+ import { Linking, Platform, Pressable, View } from "react-native";
5
6
  import Animated, {
6
7
  Easing,
7
8
  useAnimatedStyle,
@@ -161,7 +162,7 @@ export function ContextMenu({
161
162
  uri: avatars[profile?.did]?.avatar,
162
163
  }}
163
164
  style={{ width: 42, height: 42, borderRadius: 999 }}
164
- resizeMode="cover"
165
+ contentFit="cover"
165
166
  />
166
167
  )}
167
168
  <View style={{ flex: 1, minWidth: 0 }}>
@@ -55,6 +55,7 @@ export function ViewerLoadingOverlay() {
55
55
  alignItems: "center",
56
56
  justifyContent: "center",
57
57
  backgroundColor: "rgba(0,0,0,0.3)",
58
+ pointerEvents: "none",
58
59
  },
59
60
  animatedStyle,
60
61
  ]}
@@ -91,6 +91,9 @@ export function NativeVideo(props?: {
91
91
 
92
92
  const handleLayout = useCallback((event: LayoutChangeEvent) => {
93
93
  const { width, height } = event.nativeEvent.layout;
94
+ if (dimensions.width === width && dimensions.height === height) {
95
+ return;
96
+ }
94
97
  setDimensions({ width, height });
95
98
  setPlayerWidth(width);
96
99
  setPlayerHeight(height);
@@ -165,7 +168,6 @@ export function NativeVideo(props?: {
165
168
  <VideoView
166
169
  ref={videoRef}
167
170
  player={player}
168
- allowsFullscreen
169
171
  nativeControls={fullscreen}
170
172
  onFullscreenEnter={() => {
171
173
  setFullscreen(true);
@@ -36,12 +36,14 @@ function assignVideoRef(
36
36
 
37
37
  type VideoProps = {
38
38
  url: string;
39
- videoRef?: React.RefObject<HTMLVideoElement>;
39
+ videoRef?: React.RefObject<HTMLVideoElement | null>;
40
40
  objectFit?: "contain" | "cover";
41
41
  pictureInPictureEnabled?: boolean;
42
42
  };
43
43
 
44
- function useVideoDimensions(videoRef: React.RefObject<HTMLVideoElement>) {
44
+ function useVideoDimensions(
45
+ videoRef: React.RefObject<HTMLVideoElement | null>,
46
+ ) {
45
47
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
46
48
 
47
49
  useEffect(() => {
@@ -133,7 +135,7 @@ const updateEvents = {
133
135
 
134
136
  const VideoElement = forwardRef<
135
137
  HTMLVideoElement,
136
- VideoProps & { videoRef?: React.RefObject<HTMLVideoElement> }
138
+ VideoProps & { videoRef?: React.RefObject<HTMLVideoElement | null> }
137
139
  >((props, ref) => {
138
140
  const x = usePlayerStore((x) => x);
139
141
  const url = useStreamplaceStore((x) => x.url);
@@ -359,7 +361,7 @@ export function HLSPlayer(props: VideoProps) {
359
361
  }
360
362
 
361
363
  export function WebRTCPlayer(
362
- props: VideoProps & { videoRef?: React.RefObject<HTMLVideoElement> },
364
+ props: VideoProps & { videoRef?: React.RefObject<HTMLVideoElement | null> },
363
365
  ) {
364
366
  const [webrtcError, setWebrtcError] = useState<string | null>(null);
365
367
  const setStatus = usePlayerStore((x) => x.setStatus);
@@ -432,7 +434,7 @@ export function WebRTCPlayerInner({
432
434
  width,
433
435
  height,
434
436
  }: {
435
- videoRef?: React.RefObject<HTMLVideoElement>;
437
+ videoRef?: React.RefObject<HTMLVideoElement | null>;
436
438
  url: string;
437
439
  width?: string | number;
438
440
  height?: string | number;
@@ -358,7 +358,11 @@ export const DropdownMenuSubTrigger = forwardRef<
358
358
  const { icons } = useTheme();
359
359
 
360
360
  React.useEffect(() => {
361
- if (subMenuContext && subMenuTitle) {
361
+ if (
362
+ subMenuContext &&
363
+ subMenuTitle &&
364
+ subMenuContext.title !== subMenuTitle
365
+ ) {
362
366
  subMenuContext.setTitle(subMenuTitle);
363
367
  }
364
368
  }, [subMenuContext, subMenuTitle]);
@@ -1,6 +1,6 @@
1
1
  import { type LucideProps } from "lucide-react-native";
2
2
  import React from "react";
3
- import { useTheme } from "../../lib/theme";
3
+ import { useTheme } from "../../lib/theme/theme";
4
4
 
5
5
  // Simple icon wrapper that integrates with theme
6
6
  export interface IconProps {
@@ -1,10 +1,4 @@
1
- import {
2
- Children,
3
- cloneElement,
4
- forwardRef,
5
- isValidElement,
6
- ReactNode,
7
- } from "react";
1
+ import { forwardRef, ReactNode } from "react";
8
2
  import { Animated, Platform, View, ViewStyle } from "react-native";
9
3
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
10
4
  import {
@@ -285,63 +279,63 @@ export const MenuInfo = forwardRef<View, MenuInfoProps>(
285
279
  },
286
280
  );
287
281
 
288
- export interface MenuDraggableGroupProps {
289
- children: ReactNode;
290
- onMove: (fromIndex: number, toIndex: number) => void;
291
- onDragEnd: (fromIndex: number, toIndex: number) => void;
292
- dragHandle?: ReactNode;
293
- style?: ViewStyle;
294
- }
282
+ // export interface MenuDraggableGroupProps {
283
+ // children: ReactNode;
284
+ // onMove: (fromIndex: number, toIndex: number) => void;
285
+ // onDragEnd: (fromIndex: number, toIndex: number) => void;
286
+ // dragHandle?: ReactNode;
287
+ // style?: ViewStyle;
288
+ // }
295
289
 
296
- export const MenuDraggableGroup = forwardRef<View, MenuDraggableGroupProps>(
297
- ({ children, onMove, onDragEnd, dragHandle, style }, ref) => {
298
- const { theme } = useTheme();
290
+ // export const MenuDraggableGroup = forwardRef<View, MenuDraggableGroupProps>(
291
+ // ({ children, onMove, onDragEnd, dragHandle, style }, ref) => {
292
+ // const { theme } = useTheme();
299
293
 
300
- const childrenArray = Children.toArray(children);
301
- const draggableItems = childrenArray.filter(
302
- (child) =>
303
- isValidElement(child) &&
304
- (child.type === MenuItem || child.type === MenuSeparator),
305
- );
294
+ // const childrenArray = Children.toArray(children);
295
+ // const draggableItems = childrenArray.filter(
296
+ // (child) =>
297
+ // isValidElement(child) &&
298
+ // (child.type === MenuItem || child.type === MenuSeparator),
299
+ // );
306
300
 
307
- let itemIndex = 0;
308
- const enhancedChildren = Children.map(children, (child) => {
309
- if (isValidElement(child)) {
310
- if (child.type === MenuItem) {
311
- const currentIndex = itemIndex;
312
- itemIndex++;
301
+ // let itemIndex = 0;
302
+ // const enhancedChildren = Children.map(children, (child) => {
303
+ // if (isValidElement(child)) {
304
+ // if (child.type === MenuItem) {
305
+ // const currentIndex = itemIndex;
306
+ // itemIndex++;
313
307
 
314
- return cloneElement(child, {
315
- draggable: true,
316
- dragHandle: dragHandle || child.props.dragHandle,
317
- _dragIndex: currentIndex,
318
- _dragTotalItems: draggableItems.filter(
319
- (c) => isValidElement(c) && c.type === MenuItem,
320
- ).length,
321
- _onDragMove: onMove,
322
- _onDragEnd: onDragEnd,
323
- } as any);
324
- }
325
- if (child.type === MenuSeparator) {
326
- return child;
327
- }
328
- }
329
- return child;
330
- });
308
+ // return cloneElement(child, {
309
+ // draggable: true,
310
+ // dragHandle: dragHandle || (child && child.props.dragHandle),
311
+ // _dragIndex: currentIndex,
312
+ // _dragTotalItems: draggableItems.filter(
313
+ // (c) => isValidElement(c) && c.type === MenuItem,
314
+ // ).length,
315
+ // _onDragMove: onMove,
316
+ // _onDragEnd: onDragEnd,
317
+ // } as any);
318
+ // }
319
+ // if (child.type === MenuSeparator) {
320
+ // return child;
321
+ // }
322
+ // }
323
+ // return child;
324
+ // });
331
325
 
332
- return (
333
- <View
334
- ref={ref}
335
- style={[
336
- { backgroundColor: theme.colors.muted + "c0" },
337
- Platform.OS === "web" ? [px[1], py[1]] : p[1],
338
- gap.all[1],
339
- { borderRadius: borderRadius.lg },
340
- style,
341
- ]}
342
- >
343
- {enhancedChildren}
344
- </View>
345
- );
346
- },
347
- );
326
+ // return (
327
+ // <View
328
+ // ref={ref}
329
+ // style={[
330
+ // { backgroundColor: theme.colors.muted + "c0" },
331
+ // Platform.OS === "web" ? [px[1], py[1]] : p[1],
332
+ // gap.all[1],
333
+ // { borderRadius: borderRadius.lg },
334
+ // style,
335
+ // ]}
336
+ // >
337
+ // {enhancedChildren}
338
+ // </View>
339
+ // );
340
+ // },
341
+ // );
@@ -4,28 +4,29 @@ import {
4
4
  } from "@gorhom/bottom-sheet";
5
5
  import React, { forwardRef } from "react";
6
6
  import {
7
- NativeSyntheticEvent,
7
+ BlurEvent,
8
+ FocusEvent,
8
9
  Platform,
9
10
  StyleSheet,
10
11
  Text,
11
12
  TextInput,
12
- TextInputFocusEventData,
13
13
  TextInputProps,
14
14
  TextProps,
15
15
  TouchableOpacity,
16
16
  View,
17
17
  ViewProps,
18
18
  } from "react-native";
19
- import { tokens } from "../../../ui";
19
+ import * as tokens from "../../../lib/theme/tokens";
20
20
 
21
21
  // Base input primitive interface
22
- export interface InputPrimitiveProps extends Omit<TextInputProps, "onChange"> {
22
+ export interface InputPrimitiveProps
23
+ extends Omit<TextInputProps, "onChange" | "onFocus" | "onBlur"> {
23
24
  error?: boolean;
24
25
  disabled?: boolean;
25
26
  loading?: boolean;
26
27
  onChange?: (text: string) => void;
27
- onFocus?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
28
- onBlur?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
28
+ onFocus?: (event: FocusEvent) => void;
29
+ onBlur?: (event: BlurEvent) => void;
29
30
  }
30
31
 
31
32
  // Input root primitive - the main TextInput component
@@ -75,7 +76,7 @@ export const InputRoot = forwardRef<any, InputPrimitiveProps>(
75
76
  );
76
77
 
77
78
  const handleFocus = React.useCallback(
78
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
79
+ (event: FocusEvent) => {
79
80
  setIsFocused(true);
80
81
  if (onFocus) {
81
82
  onFocus(event);
@@ -85,7 +86,7 @@ export const InputRoot = forwardRef<any, InputPrimitiveProps>(
85
86
  );
86
87
 
87
88
  const handleBlur = React.useCallback(
88
- (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
89
+ (event: BlurEvent) => {
89
90
  setIsFocused(false);
90
91
  if (onBlur) {
91
92
  onBlur(event);
@@ -272,13 +273,21 @@ export const InputAddon = forwardRef<
272
273
  style,
273
274
  ];
274
275
 
276
+ // Filter out null event handlers for TouchableOpacity compatibility
277
+ const { onBlur, onFocus, ...restProps } = props;
278
+ const touchableProps = {
279
+ ...restProps,
280
+ ...(onBlur !== null && onBlur !== undefined && { onBlur }),
281
+ ...(onFocus !== null && onFocus !== undefined && { onFocus }),
282
+ };
283
+
275
284
  if (touchable && onPress) {
276
285
  return (
277
286
  <TouchableOpacity
278
287
  ref={ref as React.Ref<React.ComponentRef<typeof TouchableOpacity>>}
279
288
  style={addonStyle as any}
280
289
  onPress={onPress}
281
- {...props}
290
+ {...touchableProps}
282
291
  >
283
292
  {children}
284
293
  </TouchableOpacity>
@@ -7,12 +7,14 @@ import {
7
7
  Pressable,
8
8
  } from "react-native-gesture-handler";
9
9
  import Animated, {
10
+ Easing,
10
11
  Extrapolation,
11
12
  interpolate,
12
13
  runOnJS,
13
14
  useAnimatedStyle,
14
15
  useSharedValue,
15
16
  withSpring,
17
+ withTiming,
16
18
  } from "react-native-reanimated";
17
19
  import { useSafeAreaInsets } from "react-native-safe-area-context";
18
20
  import { useKeyboardSlide } from "../../hooks";
@@ -23,6 +25,11 @@ const AnimatedView = Animated.createAnimatedComponent(View);
23
25
 
24
26
  const { height: SCREEN_HEIGHT } = Dimensions.get("window");
25
27
 
28
+ const TIMING_CONFIG = {
29
+ duration: 300,
30
+ easing: Easing.inOut(Easing.quad),
31
+ };
32
+
26
33
  type ResizableChatSheetProps = {
27
34
  startingPercentage?: number;
28
35
  isPlayerRatioGreater: boolean;
@@ -31,8 +38,6 @@ type ResizableChatSheetProps = {
31
38
  renderAbove?: (isCollapsed: boolean) => React.ReactNode;
32
39
  };
33
40
 
34
- const SPRING_CONFIG = { damping: 20, stiffness: 100 };
35
-
36
41
  export function Resizable({
37
42
  startingPercentage,
38
43
  isPlayerRatioGreater,
@@ -56,7 +61,7 @@ export function Resizable({
56
61
  const targetHeight = startingPercentage
57
62
  ? startingPercentage * SCREEN_HEIGHT
58
63
  : MIN_HEIGHT;
59
- sheetHeight.value = withSpring(targetHeight, SPRING_CONFIG);
64
+ sheetHeight.value = withTiming(targetHeight, TIMING_CONFIG);
60
65
  setIsCollapsed(targetHeight < COLLAPSE_HEIGHT);
61
66
  }, 1000);
62
67
  }, []);
@@ -73,7 +78,7 @@ export function Resizable({
73
78
 
74
79
  const nowCollapsed = newHeight < COLLAPSE_HEIGHT;
75
80
  if (nowCollapsed && !wasCollapsed.value) {
76
- sheetHeight.value = withSpring(MIN_HEIGHT, SPRING_CONFIG);
81
+ sheetHeight.value = withTiming(MIN_HEIGHT, TIMING_CONFIG);
77
82
  wasCollapsed.value = true;
78
83
  runOnJS(setIsCollapsed)(true);
79
84
  } else if (!nowCollapsed && wasCollapsed.value) {
@@ -139,8 +144,8 @@ export function Resizable({
139
144
  onPress={() => {
140
145
  const isCurrentlyCollapsed = sheetHeight.value === MIN_HEIGHT;
141
146
  sheetHeight.value = isCurrentlyCollapsed
142
- ? withSpring(MAX_HEIGHT, SPRING_CONFIG)
143
- : withSpring(MIN_HEIGHT, SPRING_CONFIG);
147
+ ? withTiming(MAX_HEIGHT, TIMING_CONFIG)
148
+ : withTiming(MIN_HEIGHT, TIMING_CONFIG);
144
149
  setIsCollapsed(!isCurrentlyCollapsed);
145
150
  }}
146
151
  >
@@ -21,7 +21,7 @@ import Animated, {
21
21
  } from "react-native-reanimated";
22
22
  import { useSafeAreaInsets } from "react-native-safe-area-context";
23
23
  import { Circle, Svg } from "react-native-svg";
24
- import { useTheme } from "../../ui";
24
+ import { useTheme } from "../../lib/theme/theme";
25
25
  import { Button } from "./button";
26
26
  import { Text } from "./text";
27
27
 
@@ -3,6 +3,7 @@ import {
3
3
  createContext,
4
4
  useCallback,
5
5
  useContext,
6
+ useMemo,
6
7
  useRef,
7
8
  useState,
8
9
  } from "react";
@@ -85,8 +86,13 @@ export function ProfileCacheProvider({
85
86
  [flush],
86
87
  );
87
88
 
89
+ const value = useMemo(
90
+ () => ({ profiles, requestProfiles }),
91
+ [profiles, requestProfiles],
92
+ );
93
+
88
94
  return (
89
- <ProfileCacheContext.Provider value={{ profiles, requestProfiles }}>
95
+ <ProfileCacheContext.Provider value={value}>
90
96
  {children}
91
97
  </ProfileCacheContext.Provider>
92
98
  );
@@ -22,7 +22,7 @@ function getLiveConnectionQuality(
22
22
  export function useSegmentTiming() {
23
23
  const latestSegment = useLivestreamStore((x) => x.segment);
24
24
  const [segmentDeltas, setSegmentDeltas] = useState<number[]>([]);
25
- const prevSegmentRef = useRef<any>();
25
+ const prevSegmentRef = useRef(latestSegment);
26
26
  const prevTimestampRef = useRef<number | null>(null);
27
27
  const ls = useLivestream();
28
28
 
package/src/index.tsx CHANGED
@@ -7,6 +7,8 @@ export * from "./player-store";
7
7
  export * from "./streamplace-provider";
8
8
  export * from "./streamplace-store";
9
9
 
10
+ export { ErrorBoundary } from "./components/error-boundary";
11
+
10
12
  export {
11
13
  PlayerProvider,
12
14
  withPlayerProvider,
package/src/lib/facet.ts CHANGED
@@ -95,7 +95,7 @@ export const segmentize = (
95
95
  const { byteStart, byteEnd } = facet.index;
96
96
  const features = facet.features;
97
97
 
98
- if (byteStart > byteEnd || features.length === 0) {
98
+ if (byteStart > byteEnd || !features || features.length === 0) {
99
99
  continue;
100
100
  }
101
101
 
@@ -18,7 +18,7 @@ import {
18
18
  } from "./tokens";
19
19
 
20
20
  import { GestureHandlerRootView } from "react-native-gesture-handler";
21
- import { ToastProvider } from "../../components/ui";
21
+ import { ToastProvider } from "../../components/ui/toast";
22
22
 
23
23
  // Import pairify function for generating theme tokens
24
24
  function pairify<T extends Record<string, any>>(
@@ -1,5 +1,6 @@
1
1
  export * from "./block";
2
2
  export * from "./branding";
3
+ export * from "./ingest";
3
4
  export * from "./moderation";
4
5
  export * from "./moderator-management";
5
6
  export * from "./stream";
@@ -0,0 +1,32 @@
1
+ import { PlaceStreamIngestDefs } from "streamplace";
2
+ import { useDID, useStreamplaceStore } from "./streamplace-store";
3
+ import { usePDSAgent } from "./xrpc";
4
+
5
+ export default function useGetIngests() {
6
+ const pdsAgent = usePDSAgent();
7
+ const did = useDID();
8
+ const setIngests = useStreamplaceStore((state) => state.setIngests);
9
+
10
+ return async () => {
11
+ if (!pdsAgent || !did) {
12
+ throw new Error("No PDS agent or DID available");
13
+ }
14
+
15
+ const result = await pdsAgent.place.stream.ingest.getIngestUrls();
16
+ if (!result.success) {
17
+ throw new Error("Failed to get ingests");
18
+ }
19
+
20
+ const ingests = result.data.ingests
21
+ .map((ingest) => {
22
+ if (PlaceStreamIngestDefs.isIngest(ingest)) {
23
+ return ingest;
24
+ }
25
+ console.error("Invalid ingest", ingest);
26
+ return null;
27
+ })
28
+ .filter((ingest) => ingest !== null);
29
+
30
+ setIngests(ingests);
31
+ };
32
+ }
@@ -1,6 +1,10 @@
1
1
  import { SessionManager } from "@atproto/api/dist/session-manager";
2
2
  import { useContext } from "react";
3
- import { PlaceStreamChatProfile, PlaceStreamLivestream } from "streamplace";
3
+ import {
4
+ PlaceStreamChatProfile,
5
+ PlaceStreamIngestDefs,
6
+ PlaceStreamLivestream,
7
+ } from "streamplace";
4
8
  import { createStore, StoreApi, useStore } from "zustand";
5
9
  import storage from "../storage";
6
10
  import { StreamplaceContext } from "../streamplace-provider/context";
@@ -41,6 +45,9 @@ export interface StreamplaceState {
41
45
  handle: string | null;
42
46
  chatProfile: PlaceStreamChatProfile.Record | null;
43
47
 
48
+ ingests: PlaceStreamIngestDefs.Ingest[] | null;
49
+ setIngests: (ingests: PlaceStreamIngestDefs.Ingest[] | null) => void;
50
+
44
51
  // Content metadata state
45
52
  contentMetadata: ContentMetadataResult | null;
46
53
  setContentMetadata: (metadata: ContentMetadataResult | null) => void;
@@ -113,7 +120,9 @@ export const makeStreamplaceStore = ({
113
120
  oauthSession: null,
114
121
  handle: null,
115
122
  chatProfile: null,
116
-
123
+ ingests: null,
124
+ setIngests: (ingests: PlaceStreamIngestDefs.Ingest[] | null) =>
125
+ set({ ingests: ingests }),
117
126
  broadcasterDID: null,
118
127
  setBroadcasterDID: (broadcasterDID: string | null) =>
119
128
  set({ broadcasterDID }),
@@ -409,5 +418,3 @@ export const useDanmuSettings = () => {
409
418
  setDanmuMaxMessages,
410
419
  };
411
420
  };
412
-
413
- export { useCreateStreamRecord, useUpdateStreamRecord } from "./stream";
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from "react";
2
2
  import { StreamplaceAgent } from "streamplace";
3
- import { useStreamplaceStore, useUrl } from ".";
3
+ import { useStreamplaceStore, useUrl } from "./streamplace-store";
4
4
 
5
5
  export function usePDSAgent(): StreamplaceAgent | null {
6
6
  const oauthSession = useStreamplaceStore((state) => state.oauthSession);