@ethora/chat-component-rn 26.6.11 → 26.6.12

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 (52) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/commonjs/components/MainComponents/MediaMessage.js +1 -1
  3. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  4. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js +1 -1
  5. package/lib/commonjs/components/MessageBubble/InteractionsOverlay.js.map +1 -1
  6. package/lib/commonjs/components/MessageBubble/Message.js +1 -1
  7. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  8. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +1 -1
  9. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  10. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +7 -4
  11. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  12. package/lib/commonjs/components/Modals/FilePreviewModal/GViewWebView.js +1 -1
  13. package/lib/commonjs/components/Modals/FilePreviewModal/GViewWebView.js.map +1 -1
  14. package/lib/commonjs/components/VideoCalls/VideoCallSession.js +6 -3
  15. package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -1
  16. package/lib/commonjs/components/styled/MediaFilePreview.js +1 -1
  17. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  18. package/lib/commonjs/components/styled/VideoMessage.js +29 -63
  19. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  20. package/lib/module/components/MainComponents/MediaMessage.js +1 -1
  21. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  22. package/lib/module/components/MessageBubble/InteractionsOverlay.js +1 -1
  23. package/lib/module/components/MessageBubble/InteractionsOverlay.js.map +1 -1
  24. package/lib/module/components/MessageBubble/Message.js +1 -1
  25. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  26. package/lib/module/components/MessageBubble/MessageInteractions.js +1 -1
  27. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  28. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +7 -4
  29. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  30. package/lib/module/components/Modals/FilePreviewModal/GViewWebView.js +1 -1
  31. package/lib/module/components/Modals/FilePreviewModal/GViewWebView.js.map +1 -1
  32. package/lib/module/components/VideoCalls/VideoCallSession.js +6 -3
  33. package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -1
  34. package/lib/module/components/styled/MediaFilePreview.js +1 -1
  35. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  36. package/lib/module/components/styled/VideoMessage.js +29 -63
  37. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  38. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  39. package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -1
  40. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  41. package/lib/typescript/hooks/useCustomTypingIndicator.d.ts +16 -4
  42. package/lib/typescript/hooks/useCustomTypingIndicator.d.ts.map +1 -1
  43. package/package.json +29 -28
  44. package/src/components/MainComponents/MediaMessage.tsx +1 -1
  45. package/src/components/MessageBubble/InteractionsOverlay.tsx +1 -1
  46. package/src/components/MessageBubble/Message.tsx +1 -1
  47. package/src/components/MessageBubble/MessageInteractions.tsx +1 -1
  48. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +6 -3
  49. package/src/components/Modals/FilePreviewModal/GViewWebView.tsx +1 -1
  50. package/src/components/VideoCalls/VideoCallSession.tsx +8 -3
  51. package/src/components/styled/MediaFilePreview.tsx +1 -1
  52. package/src/components/styled/VideoMessage.tsx +26 -59
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethora/chat-component-rn",
3
- "version": "26.6.11",
3
+ "version": "26.6.12",
4
4
  "description": "Drop-in XMPP chat component for React Native / Expo. <Chat config={...}/> and you're done.",
5
5
  "main": "index.js",
6
6
  "module": "lib/module/main.js",
@@ -139,7 +139,7 @@
139
139
  "@xmpp/client": "^0.13.1",
140
140
  "axios": "^1.7.9",
141
141
  "js-sha256": "^0.11.0",
142
- "react-native-keyboard-controller": "^1.18.5",
142
+ "react-native-keyboard-controller": "^1.22.3",
143
143
  "react-native-webview": "^13.16.1",
144
144
  "react-redux": "^9.2.0",
145
145
  "styled-components": "^6.1.13"
@@ -148,44 +148,45 @@
148
148
  "@babel/core": "^7.25.2",
149
149
  "@babel/preset-env": "^7.25.3",
150
150
  "@babel/runtime": "^7.25.0",
151
- "@livekit/react-native": "2.7.6",
152
- "@livekit/react-native-webrtc": "125.0.11",
151
+ "@livekit/react-native": "2.12.0",
152
+ "@livekit/react-native-webrtc": "144.1.2",
153
153
  "@react-native-async-storage/async-storage": "2.2.0",
154
- "@react-native-community/netinfo": "11.4.1",
154
+ "@react-native-community/netinfo": "12.0.1",
155
+ "@react-native/eslint-config": "^0.86.2",
155
156
  "@types/jest": "^29.5.12",
156
- "@types/react": "~19.1.0",
157
+ "@types/react": "~19.2.0",
157
158
  "@types/react-test-renderer": "^19.1.0",
158
159
  "babel-jest": "^29.6.3",
159
160
  "eslint": "^8.19.0",
160
- "eslint-config-expo": "~9.2.0",
161
- "expo": "~54.0.0",
162
- "expo-audio": "~1.1.1",
163
- "expo-build-properties": "^55.0.14",
164
- "expo-clipboard": "~8.0.8",
165
- "expo-document-picker": "~14.0.0",
166
- "expo-image-manipulator": "~14.0.0",
167
- "expo-image-picker": "~17.0.0",
168
- "expo-media-library": "~18.2.1",
169
- "expo-status-bar": "~3.0.0",
170
- "expo-video": "~3.0.16",
161
+ "eslint-config-expo": "~57.0.1",
162
+ "expo": "~57.0.0",
163
+ "expo-audio": "~57.0.3",
164
+ "expo-build-properties": "~57.0.10",
165
+ "expo-clipboard": "~57.0.1",
166
+ "expo-document-picker": "~57.0.1",
167
+ "expo-image-manipulator": "~57.0.9",
168
+ "expo-image-picker": "~57.0.9",
169
+ "expo-media-library": "~57.0.3",
170
+ "expo-status-bar": "~57.0.1",
171
+ "expo-video": "~57.0.2",
171
172
  "jest": "^29.6.3",
172
- "jest-expo": "~54.0.0",
173
- "livekit-client": "2.9.0",
173
+ "jest-expo": "~57.0.4",
174
+ "livekit-client": "2.21.0",
174
175
  "patch-package": "^8.0.1",
175
176
  "prettier": "2.8.8",
176
- "react": "19.1.0",
177
- "react-native": "0.81.5",
177
+ "react": "19.2.8",
178
+ "react-native": "0.86.2",
178
179
  "react-native-builder-bob": "^0.41.0",
179
180
  "react-native-callkeep": "4.3.16",
180
- "react-native-gesture-handler": "~2.28.0",
181
+ "react-native-gesture-handler": "~2.32.0",
181
182
  "react-native-get-random-values": "~1.11.0",
182
- "react-native-reanimated": "~4.1.0",
183
- "react-native-safe-area-context": "~5.6.0",
184
- "react-native-screens": "~4.16.0",
185
- "react-native-svg": "15.12.1",
186
- "react-native-worklets": "~0.5.0",
183
+ "react-native-reanimated": "~4.5.1",
184
+ "react-native-safe-area-context": "~5.7.0",
185
+ "react-native-screens": "~4.26.0",
186
+ "react-native-svg": "15.15.4",
187
+ "react-native-worklets": "~0.10.1",
187
188
  "react-qr-code": "^2.0.15",
188
- "react-test-renderer": "19.1.0",
189
+ "react-test-renderer": "19.2.8",
189
190
  "typescript": "~5.9.0"
190
191
  },
191
192
  "engines": {
@@ -268,7 +268,7 @@ const styles = StyleSheet.create({
268
268
  height: '100%',
269
269
  },
270
270
  pendingImageOverlay: {
271
- ...StyleSheet.absoluteFillObject,
271
+ ...StyleSheet.absoluteFill,
272
272
  alignItems: 'center',
273
273
  justifyContent: 'center',
274
274
  gap: 8,
@@ -115,7 +115,7 @@ export const InteractionsOverlayProvider: React.FC<{
115
115
 
116
116
  const styles = StyleSheet.create({
117
117
  host: { flex: 1 },
118
- overlay: { ...StyleSheet.absoluteFillObject, zIndex: 9999, elevation: 9999 },
118
+ overlay: { ...StyleSheet.absoluteFill, zIndex: 9999, elevation: 9999 },
119
119
  });
120
120
 
121
121
  export default InteractionsOverlayProvider;
@@ -605,7 +605,7 @@ const styles = StyleSheet.create({
605
605
  position: 'relative',
606
606
  },
607
607
  overlay: {
608
- ...StyleSheet.absoluteFillObject,
608
+ ...StyleSheet.absoluteFill,
609
609
  position: 'absolute',
610
610
  top: 0,
611
611
  left: 0,
@@ -235,7 +235,7 @@ export default MessageInteractions;
235
235
 
236
236
  const styles = StyleSheet.create({
237
237
  overlayFill: {
238
- ...StyleSheet.absoluteFillObject,
238
+ ...StyleSheet.absoluteFill,
239
239
  backgroundColor: 'transparent',
240
240
  },
241
241
  contextMenu: {
@@ -82,7 +82,10 @@ const ModalVideo: React.FC<{ uri: string }> = ({ uri }) => {
82
82
  contentFit="contain"
83
83
  nativeControls
84
84
  surfaceType="textureView"
85
- allowsFullscreen
85
+ // Fullscreen stays available by default on both expo-video lines:
86
+ // `allowsFullscreen` (SDK 54, default true) was replaced by
87
+ // `fullscreenOptions.enable` (SDK 57, default true). Passing neither
88
+ // keeps this file compiling against both.
86
89
  />
87
90
  {/* Play affordance shown immediately on open; tapping starts
88
91
  playback and the native controls take over. */}
@@ -90,7 +93,7 @@ const ModalVideo: React.FC<{ uri: string }> = ({ uri }) => {
90
93
  <TouchableOpacity
91
94
  activeOpacity={0.8}
92
95
  onPress={handlePlay}
93
- style={StyleSheet.absoluteFillObject}
96
+ style={StyleSheet.absoluteFill}
94
97
  >
95
98
  <View style={styles.playOverlay}>
96
99
  <View style={styles.playButton}>
@@ -426,7 +429,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
426
429
 
427
430
  const styles = StyleSheet.create({
428
431
  playOverlay: {
429
- ...StyleSheet.absoluteFillObject,
432
+ ...StyleSheet.absoluteFill,
430
433
  justifyContent: 'center',
431
434
  alignItems: 'center',
432
435
  },
@@ -134,7 +134,7 @@ const styles = StyleSheet.create({
134
134
  container: { flex: 1, width: '100%', backgroundColor: '#fff' },
135
135
  webview: { flex: 1, backgroundColor: '#fff' },
136
136
  loadingOverlay: {
137
- ...StyleSheet.absoluteFillObject,
137
+ ...StyleSheet.absoluteFill,
138
138
  justifyContent: 'center',
139
139
  alignItems: 'center',
140
140
  backgroundColor: '#ffffff',
@@ -345,9 +345,14 @@ export const VideoCallSession: FC<VideoCallSessionProps> = ({
345
345
  if (!runtime) {return;}
346
346
  try {
347
347
  if (Platform.OS === 'android') {
348
- await runtime.AudioSession.setAndroidAudioConfiguration?.({
349
- preferredOutputList: next ? ['speaker'] : ['earpiece'],
350
- });
348
+ // NB: not `setAndroidAudioConfiguration` — that method has never
349
+ // existed on AudioSession (the old `?.` call silently no-oped, so
350
+ // the speaker icon toggled while audio stayed on the earpiece).
351
+ // `selectAudioOutput` routes directly; on Android its device ids
352
+ // are the literal strings 'speaker' / 'earpiece'.
353
+ await runtime.AudioSession.selectAudioOutput(
354
+ next ? 'speaker' : 'earpiece'
355
+ );
351
356
  } else {
352
357
  await runtime.AudioSession.setAppleAudioConfiguration?.({
353
358
  audioCategory: 'playAndRecord',
@@ -110,7 +110,7 @@ const styles = StyleSheet.create({
110
110
  backgroundColor: '#1a1a1a',
111
111
  },
112
112
  playOverlay: {
113
- ...StyleSheet.absoluteFillObject,
113
+ ...StyleSheet.absoluteFill,
114
114
  justifyContent: 'center',
115
115
  alignItems: 'center',
116
116
  backgroundColor: 'rgba(0,0,0,0.3)',
@@ -1,6 +1,5 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import { Image, Pressable, StyleSheet, View } from 'react-native';
3
- import { useVideoPlayer, VideoView } from 'expo-video';
4
3
  import { useDispatch } from 'react-redux';
5
4
  import {
6
5
  setActiveFile,
@@ -37,12 +36,6 @@ const PlayBadge: React.FC = () => (
37
36
  * playback happens in the full-screen preview. Drawing that still with a
38
37
  * plain <Image> keeps the whole bubble inside the RN view hierarchy, so
39
38
  * the play badge is guaranteed to composite on top of it.
40
- *
41
- * The VideoView path below cannot make that promise: expo-video draws
42
- * into a native surface, and depending on platform, expo-video version
43
- * and surfaceType that surface can end up above its sibling RN views —
44
- * swallowing the badge and leaving the video looking like a plain photo.
45
- * It is also cheaper: no video player instance per row in a long list.
46
39
  */
47
40
  const PosterVideo: React.FC<{
48
41
  previewURL: string;
@@ -85,8 +78,8 @@ const PosterVideo: React.FC<{
85
78
  resizeMode="cover"
86
79
  // Not every backend returns a real still — some set
87
80
  // locationPreview to the video URL itself, which decodes to
88
- // nothing. Rather than show an empty box, hand the row back to
89
- // the player so it can paint a first frame.
81
+ // nothing. Rather than show an empty box, fall back to the
82
+ // static placeholder branch.
90
83
  onError={onPosterError}
91
84
  />
92
85
  <PlayBadge />
@@ -95,61 +88,35 @@ const PosterVideo: React.FC<{
95
88
  };
96
89
 
97
90
  /**
98
- * Fallback for messages with no usable poster frame: mount the player
99
- * purely to paint its first frame.
91
+ * Fallback for messages with no usable poster frame: a static dark
92
+ * placeholder behind the play badge.
93
+ *
94
+ * This branch used to mount an expo-video player purely to paint the
95
+ * first frame (`VideoView` + `surfaceType="textureView"`). That never
96
+ * composited reliably: on Android (Pixel, Android 16, New Architecture)
97
+ * the native video surface draws ABOVE sibling RN views, swallowing the
98
+ * play badge — the video looked like a plain photo. There is no
99
+ * surfaceType/zIndex combination that guarantees ordering across SDK 54
100
+ * and 57, so we don't fight it: no live surface in the bubble at all.
101
+ * The badge is plain RN views over a plain RN background, which cannot
102
+ * be occluded, and we drop the per-row player instance as a bonus.
103
+ * Actual playback (and the real first frame) happens in the full-screen
104
+ * preview modal.
100
105
  */
101
- const VideoSurfaceVideo: React.FC<{
102
- fileURL: string;
106
+ const PlaceholderVideo: React.FC<{
103
107
  onOpen: () => void;
104
- }> = ({ fileURL, onOpen }) => {
105
- const [dims, setDims] = useState<MediaDims>(defaultMediaDims());
106
-
107
- const player = useVideoPlayer(fileURL, (p) => {
108
- p.muted = true;
109
- });
110
-
111
- useEffect(() => {
112
- const sub = player.addListener('statusChange', () => {
113
- const size = player.videoTrack?.size;
114
- if (size?.width && size?.height) {
115
- setDims(fitMediaDimensions(size.width, size.height));
116
- }
117
- });
118
- return () => sub.remove();
119
- }, [player]);
108
+ }> = ({ onOpen }) => {
109
+ const dims = defaultMediaDims();
120
110
 
121
111
  return (
122
- // Capture-overlay pattern: VideoView sits underneath without ANY
123
- // touch participation; a transparent absolute-fill <Pressable> on
124
- // top owns the tap. Previously we wrapped VideoView in a
125
- // TouchableOpacity and set `pointerEvents="none"` on the inner
126
- // VideoView — but on some expo-video versions / iOS the native
127
- // VideoView still intercepted gestures despite that hint, so
128
- // tapping the poster did nothing. Customer-reported #9 (video
129
- // preview unresponsive). The Pressable now guarantees the tap
130
- // reaches `onOpen`.
131
- <View
112
+ <Pressable
113
+ onPress={onOpen}
132
114
  style={[styles.wrapper, { width: dims.width, height: dims.height }]}
115
+ accessibilityRole="button"
116
+ accessibilityLabel="Play video"
133
117
  >
134
- <VideoView
135
- player={player}
136
- style={styles.video}
137
- contentFit="cover"
138
- nativeControls={false}
139
- surfaceType="textureView"
140
- pointerEvents="none"
141
- />
142
118
  <PlayBadge />
143
- <Pressable
144
- onPress={onOpen}
145
- // Sits ON TOP of VideoView + the play-button overlay; transparent
146
- // so the poster + play icon show through. Captures the tap and
147
- // routes to onOpen → setActiveModal(FILE_PREVIEW).
148
- style={StyleSheet.absoluteFill}
149
- accessibilityRole="button"
150
- accessibilityLabel="Play video"
151
- />
152
- </View>
119
+ </Pressable>
153
120
  );
154
121
  };
155
122
 
@@ -177,7 +144,7 @@ const CustomMessageVideo: React.FC<CustomMessageVideoProps> = ({
177
144
  );
178
145
  }
179
146
 
180
- return <VideoSurfaceVideo fileURL={fileURL} onOpen={handleOpen} />;
147
+ return <PlaceholderVideo onOpen={handleOpen} />;
181
148
  };
182
149
 
183
150
  export default CustomMessageVideo;
@@ -193,7 +160,7 @@ const styles = StyleSheet.create({
193
160
  height: '100%',
194
161
  },
195
162
  overlay: {
196
- ...StyleSheet.absoluteFillObject,
163
+ ...StyleSheet.absoluteFill,
197
164
  justifyContent: 'center',
198
165
  alignItems: 'center',
199
166
  },