stream-chat-react-native-core 4.6.1 → 4.7.0-beta.1

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 (200) hide show
  1. package/lib/commonjs/components/Attachment/Attachment.js +22 -13
  2. package/lib/commonjs/components/Attachment/Attachment.js.map +1 -1
  3. package/lib/commonjs/components/Attachment/Gallery.js +72 -37
  4. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  5. package/lib/commonjs/components/Attachment/VideoThumbnail.js +83 -0
  6. package/lib/commonjs/components/Attachment/VideoThumbnail.js.map +1 -0
  7. package/lib/commonjs/components/Attachment/hooks/useGoToURL.js +1 -1
  8. package/lib/commonjs/components/Attachment/hooks/useGoToURL.js.map +1 -1
  9. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  10. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  11. package/lib/commonjs/components/Attachment/utils/getAspectRatio.js +5 -5
  12. package/lib/commonjs/components/Attachment/utils/getAspectRatio.js.map +1 -1
  13. package/lib/commonjs/components/Channel/Channel.js +19 -14
  14. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  15. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js +4 -2
  16. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  17. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewDisplayName.js +43 -28
  18. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewDisplayName.js.map +1 -1
  19. package/lib/commonjs/components/ImageGallery/ImageGallery.js +167 -345
  20. package/lib/commonjs/components/ImageGallery/ImageGallery.js.map +1 -1
  21. package/lib/commonjs/components/ImageGallery/components/AnimatedGalleryImage.js +4 -4
  22. package/lib/commonjs/components/ImageGallery/components/AnimatedGalleryImage.js.map +1 -1
  23. package/lib/commonjs/components/ImageGallery/components/AnimatedGalleryVideo.js +225 -0
  24. package/lib/commonjs/components/ImageGallery/components/AnimatedGalleryVideo.js.map +1 -0
  25. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js +102 -40
  26. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  27. package/lib/commonjs/components/ImageGallery/components/ImageGalleryVideoControl.js +145 -0
  28. package/lib/commonjs/components/ImageGallery/components/ImageGalleryVideoControl.js.map +1 -0
  29. package/lib/commonjs/components/ImageGallery/components/ImageGrid.js +31 -34
  30. package/lib/commonjs/components/ImageGallery/components/ImageGrid.js.map +1 -1
  31. package/lib/commonjs/components/ImageGallery/components/ProgressControl.js +153 -0
  32. package/lib/commonjs/components/ImageGallery/components/ProgressControl.js.map +1 -0
  33. package/lib/commonjs/components/ImageGallery/hooks/useImageGalleryGestures.js +373 -0
  34. package/lib/commonjs/components/ImageGallery/hooks/useImageGalleryGestures.js.map +1 -0
  35. package/lib/commonjs/components/Message/Message.js +23 -13
  36. package/lib/commonjs/components/Message/Message.js.map +1 -1
  37. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js +4 -2
  38. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  39. package/lib/commonjs/components/MessageOverlay/MessageOverlay.js +25 -23
  40. package/lib/commonjs/components/MessageOverlay/MessageOverlay.js.map +1 -1
  41. package/lib/commonjs/components/Reply/Reply.js +32 -12
  42. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  43. package/lib/commonjs/components/Spinner/Spinner.js +12 -4
  44. package/lib/commonjs/components/Spinner/Spinner.js.map +1 -1
  45. package/lib/commonjs/components/index.js +26 -0
  46. package/lib/commonjs/components/index.js.map +1 -1
  47. package/lib/commonjs/contexts/messageContext/MessageContext.js +2 -2
  48. package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
  49. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  50. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  51. package/lib/commonjs/contexts/messagesContext/MessagesContext.js +2 -2
  52. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  53. package/lib/commonjs/contexts/themeContext/utils/theme.js +10 -1
  54. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  55. package/lib/commonjs/icons/Pause.js +61 -0
  56. package/lib/commonjs/icons/Pause.js.map +1 -0
  57. package/lib/commonjs/icons/Play.js +42 -0
  58. package/lib/commonjs/icons/Play.js.map +1 -0
  59. package/lib/commonjs/icons/index.js +26 -0
  60. package/lib/commonjs/icons/index.js.map +1 -1
  61. package/lib/commonjs/mock-builders/api/queryMembers.js +98 -1
  62. package/lib/commonjs/mock-builders/api/queryMembers.js.map +1 -1
  63. package/lib/commonjs/native.js +7 -1
  64. package/lib/commonjs/native.js.map +1 -1
  65. package/lib/commonjs/utils/getUrlOfImageAttachment.js +1 -1
  66. package/lib/commonjs/utils/getUrlOfImageAttachment.js.map +1 -1
  67. package/lib/commonjs/version.json +1 -1
  68. package/lib/module/components/Attachment/Attachment.js +22 -13
  69. package/lib/module/components/Attachment/Attachment.js.map +1 -1
  70. package/lib/module/components/Attachment/Gallery.js +72 -37
  71. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  72. package/lib/module/components/Attachment/VideoThumbnail.js +83 -0
  73. package/lib/module/components/Attachment/VideoThumbnail.js.map +1 -0
  74. package/lib/module/components/Attachment/hooks/useGoToURL.js +1 -1
  75. package/lib/module/components/Attachment/hooks/useGoToURL.js.map +1 -1
  76. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  77. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  78. package/lib/module/components/Attachment/utils/getAspectRatio.js +5 -5
  79. package/lib/module/components/Attachment/utils/getAspectRatio.js.map +1 -1
  80. package/lib/module/components/Channel/Channel.js +19 -14
  81. package/lib/module/components/Channel/Channel.js.map +1 -1
  82. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js +4 -2
  83. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  84. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewDisplayName.js +43 -28
  85. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewDisplayName.js.map +1 -1
  86. package/lib/module/components/ImageGallery/ImageGallery.js +167 -345
  87. package/lib/module/components/ImageGallery/ImageGallery.js.map +1 -1
  88. package/lib/module/components/ImageGallery/components/AnimatedGalleryImage.js +4 -4
  89. package/lib/module/components/ImageGallery/components/AnimatedGalleryImage.js.map +1 -1
  90. package/lib/module/components/ImageGallery/components/AnimatedGalleryVideo.js +225 -0
  91. package/lib/module/components/ImageGallery/components/AnimatedGalleryVideo.js.map +1 -0
  92. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js +102 -40
  93. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  94. package/lib/module/components/ImageGallery/components/ImageGalleryVideoControl.js +145 -0
  95. package/lib/module/components/ImageGallery/components/ImageGalleryVideoControl.js.map +1 -0
  96. package/lib/module/components/ImageGallery/components/ImageGrid.js +31 -34
  97. package/lib/module/components/ImageGallery/components/ImageGrid.js.map +1 -1
  98. package/lib/module/components/ImageGallery/components/ProgressControl.js +153 -0
  99. package/lib/module/components/ImageGallery/components/ProgressControl.js.map +1 -0
  100. package/lib/module/components/ImageGallery/hooks/useImageGalleryGestures.js +373 -0
  101. package/lib/module/components/ImageGallery/hooks/useImageGalleryGestures.js.map +1 -0
  102. package/lib/module/components/Message/Message.js +23 -13
  103. package/lib/module/components/Message/Message.js.map +1 -1
  104. package/lib/module/components/Message/hooks/useCreateMessageContext.js +4 -2
  105. package/lib/module/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  106. package/lib/module/components/MessageOverlay/MessageOverlay.js +25 -23
  107. package/lib/module/components/MessageOverlay/MessageOverlay.js.map +1 -1
  108. package/lib/module/components/Reply/Reply.js +32 -12
  109. package/lib/module/components/Reply/Reply.js.map +1 -1
  110. package/lib/module/components/Spinner/Spinner.js +12 -4
  111. package/lib/module/components/Spinner/Spinner.js.map +1 -1
  112. package/lib/module/components/index.js +26 -0
  113. package/lib/module/components/index.js.map +1 -1
  114. package/lib/module/contexts/messageContext/MessageContext.js +2 -2
  115. package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
  116. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  117. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  118. package/lib/module/contexts/messagesContext/MessagesContext.js +2 -2
  119. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  120. package/lib/module/contexts/themeContext/utils/theme.js +10 -1
  121. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  122. package/lib/module/icons/Pause.js +61 -0
  123. package/lib/module/icons/Pause.js.map +1 -0
  124. package/lib/module/icons/Play.js +42 -0
  125. package/lib/module/icons/Play.js.map +1 -0
  126. package/lib/module/icons/index.js +26 -0
  127. package/lib/module/icons/index.js.map +1 -1
  128. package/lib/module/mock-builders/api/queryMembers.js +98 -1
  129. package/lib/module/mock-builders/api/queryMembers.js.map +1 -1
  130. package/lib/module/native.js +7 -1
  131. package/lib/module/native.js.map +1 -1
  132. package/lib/module/utils/getUrlOfImageAttachment.js +1 -1
  133. package/lib/module/utils/getUrlOfImageAttachment.js.map +1 -1
  134. package/lib/module/version.json +1 -1
  135. package/lib/typescript/components/Attachment/Gallery.d.ts +1 -1
  136. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts +4 -0
  137. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts +1 -0
  138. package/lib/typescript/components/Attachment/utils/getAspectRatio.d.ts +1 -1
  139. package/lib/typescript/components/Channel/Channel.d.ts +1 -1
  140. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts +1 -1
  141. package/lib/typescript/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayName.test.d.ts +1 -0
  142. package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewDisplayName.d.ts +4 -4
  143. package/lib/typescript/components/ImageGallery/ImageGallery.d.ts +2 -0
  144. package/lib/typescript/components/ImageGallery/components/AnimatedGalleryVideo.d.ts +27 -0
  145. package/lib/typescript/components/ImageGallery/components/ImageGalleryFooter.d.ts +18 -2
  146. package/lib/typescript/components/ImageGallery/components/ImageGalleryVideoControl.d.ts +3 -0
  147. package/lib/typescript/components/ImageGallery/components/ProgressControl.d.ts +5 -0
  148. package/lib/typescript/components/ImageGallery/hooks/useImageGalleryGestures.d.ts +36 -0
  149. package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts +1 -1
  150. package/lib/typescript/components/Spinner/Spinner.d.ts +7 -1
  151. package/lib/typescript/components/index.d.ts +2 -0
  152. package/lib/typescript/contexts/messageContext/MessageContext.d.ts +3 -1
  153. package/lib/typescript/contexts/messageOverlayContext/MessageOverlayContext.d.ts +1 -0
  154. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +2 -0
  155. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +9 -0
  156. package/lib/typescript/icons/Pause.d.ts +3 -0
  157. package/lib/typescript/icons/Play.d.ts +3 -0
  158. package/lib/typescript/icons/index.d.ts +2 -0
  159. package/lib/typescript/mock-builders/api/queryMembers.d.ts +161 -0
  160. package/lib/typescript/mock-builders/api/utils.d.ts +7 -0
  161. package/lib/typescript/mock-builders/generator/user.d.ts +30 -0
  162. package/lib/typescript/mock-builders/mock.d.ts +3 -0
  163. package/lib/typescript/native.d.ts +66 -1
  164. package/package.json +1 -1
  165. package/src/components/Attachment/Attachment.tsx +11 -7
  166. package/src/components/Attachment/Gallery.tsx +75 -36
  167. package/src/components/Attachment/VideoThumbnail.tsx +45 -0
  168. package/src/components/Attachment/hooks/useGoToURL.ts +1 -1
  169. package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +1 -0
  170. package/src/components/Attachment/utils/buildGallery/types.ts +1 -0
  171. package/src/components/Attachment/utils/getAspectRatio.ts +7 -5
  172. package/src/components/Channel/Channel.tsx +4 -0
  173. package/src/components/Channel/hooks/useCreateMessagesContext.ts +2 -0
  174. package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayName.test.tsx +83 -0
  175. package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayName.ts +43 -26
  176. package/src/components/ImageGallery/ImageGallery.tsx +145 -667
  177. package/src/components/ImageGallery/components/AnimatedGalleryImage.tsx +4 -4
  178. package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx +224 -0
  179. package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +114 -19
  180. package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx +103 -0
  181. package/src/components/ImageGallery/components/ImageGrid.tsx +12 -25
  182. package/src/components/ImageGallery/components/ProgressControl.tsx +107 -0
  183. package/src/components/ImageGallery/hooks/useImageGalleryGestures.tsx +722 -0
  184. package/src/components/Message/Message.tsx +10 -3
  185. package/src/components/Message/hooks/useCreateMessageContext.ts +2 -0
  186. package/src/components/MessageOverlay/MessageOverlay.tsx +2 -0
  187. package/src/components/Reply/Reply.tsx +36 -23
  188. package/src/components/Spinner/Spinner.tsx +11 -4
  189. package/src/components/index.ts +2 -0
  190. package/src/contexts/messageContext/MessageContext.tsx +2 -0
  191. package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx +1 -0
  192. package/src/contexts/messagesContext/MessagesContext.tsx +2 -0
  193. package/src/contexts/themeContext/utils/theme.ts +18 -0
  194. package/src/icons/Pause.tsx +14 -0
  195. package/src/icons/Play.tsx +17 -0
  196. package/src/icons/index.ts +2 -0
  197. package/src/mock-builders/api/queryMembers.js +98 -0
  198. package/src/native.ts +59 -1
  199. package/src/utils/getUrlOfImageAttachment.ts +1 -1
  200. package/src/version.json +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stream-chat-react-native-core",
3
3
  "description": "The official React Native and Expo components for Stream Chat, a service for building chat applications",
4
- "version": "4.6.1",
4
+ "version": "4.7.0-beta.1",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -75,17 +75,21 @@ const AttachmentWithContext = <
75
75
  );
76
76
  }
77
77
 
78
- if (attachment.type === 'file' || attachment.type === 'audio') {
79
- return <FileAttachment attachment={attachment} />;
80
- }
81
-
82
- if (attachment.type === 'video' && attachment.asset_url) {
78
+ if (attachment.type === 'video' && !attachment.og_scrape_url) {
83
79
  return (
84
- // TODO: Put in video component
85
- <FileAttachment attachment={attachment} />
80
+ <>
81
+ <Gallery videos={[attachment]} />
82
+ {hasAttachmentActions && (
83
+ <AttachmentActions key={`key-actions-${attachment.id}`} {...attachment} />
84
+ )}
85
+ </>
86
86
  );
87
87
  }
88
88
 
89
+ if (attachment.type === 'file' || attachment.type === 'audio') {
90
+ return <FileAttachment attachment={attachment} />;
91
+ }
92
+
89
93
  if (hasAttachmentActions) {
90
94
  return (
91
95
  <>
@@ -59,7 +59,7 @@ const styles = StyleSheet.create({
59
59
  flexWrap: 'wrap',
60
60
  overflow: 'hidden',
61
61
  },
62
- imageContainer: { padding: 1 },
62
+ imageContainer: { display: 'flex', flexDirection: 'row', justifyContent: 'center', padding: 1 },
63
63
  moreImagesContainer: {
64
64
  alignItems: 'center',
65
65
  justifyContent: 'center',
@@ -76,6 +76,7 @@ export type GalleryPropsWithContext<
76
76
  | 'alignment'
77
77
  | 'groupStyles'
78
78
  | 'images'
79
+ | 'videos'
79
80
  | 'onLongPress'
80
81
  | 'onPress'
81
82
  | 'onPressIn'
@@ -84,7 +85,7 @@ export type GalleryPropsWithContext<
84
85
  > &
85
86
  Pick<
86
87
  MessagesContextValue<StreamChatGenerics>,
87
- 'additionalTouchableProps' | 'legacyImageViewerSwipeBehaviour'
88
+ 'additionalTouchableProps' | 'legacyImageViewerSwipeBehaviour' | 'VideoThumbnail'
88
89
  > &
89
90
  Pick<OverlayContextValue, 'setOverlay'> & {
90
91
  channelId: string | undefined;
@@ -125,6 +126,8 @@ const GalleryWithContext = <
125
126
  setImages,
126
127
  setOverlay,
127
128
  threadList,
129
+ videos,
130
+ VideoThumbnail,
128
131
  } = props;
129
132
 
130
133
  const {
@@ -158,16 +161,17 @@ const GalleryWithContext = <
158
161
  minWidth,
159
162
  };
160
163
 
164
+ const imagesAndVideos = [...(images || []), ...(videos || [])];
161
165
  const { height, invertedDirections, thumbnailGrid, width } = useMemo(
162
166
  () =>
163
167
  buildGallery({
164
- images,
168
+ images: imagesAndVideos,
165
169
  sizeConfig,
166
170
  }),
167
- [images.length],
171
+ [imagesAndVideos.length],
168
172
  );
169
173
 
170
- if (!images?.length) return null;
174
+ if (!imagesAndVideos?.length) return null;
171
175
  const messageText = message?.text;
172
176
  const messageId = message?.id;
173
177
  const numOfColumns = thumbnailGrid.length;
@@ -200,7 +204,7 @@ const GalleryWithContext = <
200
204
  ]}
201
205
  testID={`gallery-${invertedDirections ? 'row' : 'column'}-${colIndex}`}
202
206
  >
203
- {rows.map(({ height, resizeMode, url, width }, rowIndex) => {
207
+ {rows.map(({ height, resizeMode, type, url, width }, rowIndex) => {
204
208
  const defaultOnPress = () => {
205
209
  // Added if-else to keep the logic readable, instead of DRY.
206
210
  // if - legacyImageViewerSwipeBehaviour is disabled
@@ -214,11 +218,28 @@ const GalleryWithContext = <
214
218
  setOverlay('gallery');
215
219
  }
216
220
  };
221
+
222
+ const borderRadius = getGalleryImageBorderRadius({
223
+ alignment,
224
+ colIndex,
225
+ groupStyles,
226
+ hasThreadReplies,
227
+ height,
228
+ invertedDirections,
229
+ messageText,
230
+ numOfColumns,
231
+ numOfRows,
232
+ rowIndex,
233
+ sizeConfig,
234
+ threadList,
235
+ width,
236
+ });
237
+
217
238
  return (
218
239
  <TouchableOpacity
219
240
  activeOpacity={0.8}
220
241
  disabled={preventPress}
221
- key={`gallery-item-${messageId}/${colIndex}/${rowIndex}/${images.length}`}
242
+ key={`gallery-item-${messageId}/${colIndex}/${rowIndex}/${imagesAndVideos.length}`}
222
243
  onLongPress={(event) => {
223
244
  if (onLongPress) {
224
245
  onLongPress({
@@ -258,35 +279,34 @@ const GalleryWithContext = <
258
279
  }-${colIndex}-item-${rowIndex}`}
259
280
  {...additionalTouchableProps}
260
281
  >
261
- <MemoizedGalleryImage
262
- resizeMode={resizeMode}
263
- style={[
264
- getGalleryImageBorderRadius({
265
- alignment,
266
- colIndex,
267
- groupStyles,
268
- hasThreadReplies,
269
- height,
270
- invertedDirections,
271
- messageText,
272
- numOfColumns,
273
- numOfRows,
274
- rowIndex,
275
- sizeConfig,
276
- threadList,
277
- width,
278
- }),
279
- image,
280
- {
281
- height: height - 1,
282
- width: width - 1,
283
- },
284
- ]}
285
- uri={url}
286
- />
282
+ {type === 'video' ? (
283
+ <VideoThumbnail
284
+ style={[
285
+ borderRadius,
286
+ image,
287
+ {
288
+ height: height - 1,
289
+ width: width - 1,
290
+ },
291
+ ]}
292
+ />
293
+ ) : (
294
+ <MemoizedGalleryImage
295
+ resizeMode={resizeMode}
296
+ style={[
297
+ borderRadius,
298
+ image,
299
+ {
300
+ height: height - 1,
301
+ width: width - 1,
302
+ },
303
+ ]}
304
+ uri={url}
305
+ />
306
+ )}
287
307
  {colIndex === numOfColumns - 1 &&
288
308
  rowIndex === numOfRows - 1 &&
289
- images.length > 4 ? (
309
+ imagesAndVideos.length > 4 ? (
290
310
  <View
291
311
  style={[
292
312
  StyleSheet.absoluteFillObject,
@@ -296,7 +316,7 @@ const GalleryWithContext = <
296
316
  ]}
297
317
  >
298
318
  <Text style={[styles.moreImagesText, moreImagesText]}>
299
- {`+${images.length - 4}`}
319
+ {`+${imagesAndVideos.length - 4}`}
300
320
  </Text>
301
321
  </View>
302
322
  ) : null}
@@ -319,12 +339,14 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
319
339
  hasThreadReplies: prevHasThreadReplies,
320
340
  images: prevImages,
321
341
  message: prevMessage,
342
+ videos: prevVideos,
322
343
  } = prevProps;
323
344
  const {
324
345
  groupStyles: nextGroupStyles,
325
346
  hasThreadReplies: nextHasThreadReplies,
326
347
  images: nextImages,
327
348
  message: nextMessage,
349
+ videos: nextVideos,
328
350
  } = nextProps;
329
351
 
330
352
  const messageEqual = prevMessage?.id === nextMessage?.id;
@@ -346,6 +368,15 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
346
368
  );
347
369
  if (!imagesEqual) return false;
348
370
 
371
+ const videosEqual =
372
+ prevVideos.length === nextVideos.length &&
373
+ prevVideos.every(
374
+ (image, index) =>
375
+ getUrlWithoutParams(image.image_url) === getUrlWithoutParams(nextVideos[index].image_url) &&
376
+ getUrlWithoutParams(image.thumb_url) === getUrlWithoutParams(nextVideos[index].thumb_url),
377
+ );
378
+ if (!videosEqual) return false;
379
+
349
380
  return true;
350
381
  };
351
382
 
@@ -376,6 +407,8 @@ export const Gallery = <
376
407
  setImage: propSetImage,
377
408
  setOverlay: propSetOverlay,
378
409
  threadList: propThreadList,
410
+ videos: propVideos,
411
+ VideoThumbnail: PropVideoThumbnail,
379
412
  } = props;
380
413
 
381
414
  const { setImage: contextSetImage, setImages } = useImageGalleryContext<StreamChatGenerics>();
@@ -389,16 +422,19 @@ export const Gallery = <
389
422
  onPressIn: contextOnPressIn,
390
423
  preventPress: contextPreventPress,
391
424
  threadList: contextThreadList,
425
+ videos: contextVideos,
392
426
  } = useMessageContext<StreamChatGenerics>();
393
427
  const {
394
428
  additionalTouchableProps: contextAdditionalTouchableProps,
395
429
  legacyImageViewerSwipeBehaviour,
430
+ VideoThumbnail: ContextVideoThumnbnail,
396
431
  } = useMessagesContext<StreamChatGenerics>();
397
432
  const { setOverlay: contextSetOverlay } = useOverlayContext();
398
433
 
399
434
  const images = propImages || contextImages;
435
+ const videos = propVideos || contextVideos;
400
436
 
401
- if (!images.length) return null;
437
+ if (!images.length && !videos.length) return null;
402
438
 
403
439
  const additionalTouchableProps = propAdditionalTouchableProps || contextAdditionalTouchableProps;
404
440
  const alignment = propAlignment || contextAlignment;
@@ -411,6 +447,7 @@ export const Gallery = <
411
447
  const setImage = propSetImage || contextSetImage;
412
448
  const setOverlay = propSetOverlay || contextSetOverlay;
413
449
  const threadList = propThreadList || contextThreadList;
450
+ const VideoThumbnail = PropVideoThumbnail || ContextVideoThumnbnail;
414
451
 
415
452
  return (
416
453
  <MemoizedGallery
@@ -431,6 +468,8 @@ export const Gallery = <
431
468
  setImages,
432
469
  setOverlay,
433
470
  threadList,
471
+ videos,
472
+ VideoThumbnail,
434
473
  }}
435
474
  />
436
475
  );
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { StyleSheet, View, ViewProps } from 'react-native';
3
+
4
+ import { useTheme } from '../../contexts/themeContext/ThemeContext';
5
+ import { Play } from '../../icons';
6
+
7
+ const styles = StyleSheet.create({
8
+ container: {
9
+ alignItems: 'center',
10
+ backgroundColor: 'black',
11
+ display: 'flex',
12
+ height: '100%',
13
+ justifyContent: 'center',
14
+ width: '100%',
15
+ },
16
+ roundedView: {
17
+ alignItems: 'center',
18
+ backgroundColor: '#fff',
19
+ borderRadius: 50,
20
+ display: 'flex',
21
+ height: 36,
22
+ justifyContent: 'center',
23
+ width: 36,
24
+ },
25
+ });
26
+
27
+ export type VideoThumbnailProps = ViewProps;
28
+
29
+ export const VideoThumbnail: React.FC<VideoThumbnailProps> = (props) => {
30
+ const {
31
+ theme: {
32
+ messageSimple: {
33
+ videoThumbnail: { container, roundedView },
34
+ },
35
+ },
36
+ } = useTheme();
37
+ const { style, ...rest } = props;
38
+ return (
39
+ <View {...rest} style={[styles.container, container, style]}>
40
+ <View style={[styles.roundedView, roundedView]}>
41
+ <Play height={24} pathFill={'#000'} width={24} />
42
+ </View>
43
+ </View>
44
+ );
45
+ };
@@ -19,7 +19,7 @@ export const useGoToURL: UseGoToURLType = (url?: string) => {
19
19
  Linking.openURL(finalUrl);
20
20
  } else {
21
21
  setError(true);
22
- console.log(`Don't know how to open URI: ${finalUrl}`);
22
+ console.warn(`Don't know how to open URI: ${finalUrl}`);
23
23
  }
24
24
  });
25
25
 
@@ -36,6 +36,7 @@ export function buildThumbnail<
36
36
  resizeMode: resizeMode
37
37
  ? resizeMode
38
38
  : ((image.original_height && image.original_width ? 'contain' : 'cover') as ImageResizeMode),
39
+ type: image.type,
39
40
  url: shouldResize
40
41
  ? getResizedImageUrl({
41
42
  height,
@@ -6,6 +6,7 @@ export type Thumbnail = {
6
6
  url: string;
7
7
  width: number;
8
8
  id?: string;
9
+ type?: string;
9
10
  };
10
11
 
11
12
  export type GallerySizeConfig = {
@@ -10,12 +10,14 @@ import type { DefaultStreamChatGenerics } from '../../../types/types';
10
10
  */
11
11
  export function getAspectRatio<
12
12
  StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
13
- >(image: Attachment<StreamChatGenerics>) {
14
- if (image.type !== 'image') {
15
- throw new Error('getAspectRatio() can only be called on an image attachment');
13
+ >(attachment: Attachment<StreamChatGenerics>) {
14
+ if (!(attachment.type === 'image' || attachment.type === 'video')) {
15
+ throw new Error(
16
+ 'getAspectRatio() can only be called on an image attachment or video thumbnail',
17
+ );
16
18
  }
17
19
 
18
- if (!image.original_width || !image.original_height) return 1;
20
+ if (!attachment.original_width || !attachment.original_height) return 1;
19
21
 
20
- return image.original_width / image.original_height;
22
+ return attachment.original_width / attachment.original_height;
21
23
  }
@@ -82,6 +82,7 @@ import { FileAttachmentGroup as FileAttachmentGroupDefault } from '../Attachment
82
82
  import { FileIcon as FileIconDefault } from '../Attachment/FileIcon';
83
83
  import { Gallery as GalleryDefault } from '../Attachment/Gallery';
84
84
  import { Giphy as GiphyDefault } from '../Attachment/Giphy';
85
+ import { VideoThumbnail as VideoThumbnailDefault } from '../Attachment/VideoThumbnail';
85
86
  import { AutoCompleteSuggestionHeader as AutoCompleteSuggestionHeaderDefault } from '../AutoCompleteInput/AutoCompleteSuggestionHeader';
86
87
  import { AutoCompleteSuggestionItem as AutoCompleteSuggestionItemDefault } from '../AutoCompleteInput/AutoCompleteSuggestionItem';
87
88
  import { AutoCompleteSuggestionList as AutoCompleteSuggestionListDefault } from '../AutoCompleteInput/AutoCompleteSuggestionList';
@@ -287,6 +288,7 @@ export type ChannelPropsWithContext<
287
288
  | 'TypingIndicator'
288
289
  | 'TypingIndicatorContainer'
289
290
  | 'UrlPreview'
291
+ | 'VideoThumbnail'
290
292
  >
291
293
  > &
292
294
  Partial<
@@ -534,6 +536,7 @@ const ChannelWithContext = <
534
536
  TypingIndicatorContainer = TypingIndicatorContainerDefault,
535
537
  UploadProgressIndicator = UploadProgressIndicatorDefault,
536
538
  UrlPreview = CardDefault,
539
+ VideoThumbnail = VideoThumbnailDefault,
537
540
  watcherCount,
538
541
  watchers,
539
542
  } = props;
@@ -1738,6 +1741,7 @@ const ChannelWithContext = <
1738
1741
  TypingIndicatorContainer,
1739
1742
  updateMessage,
1740
1743
  UrlPreview,
1744
+ VideoThumbnail,
1741
1745
  });
1742
1746
 
1743
1747
  const suggestionsContext = {
@@ -80,6 +80,7 @@ export const useCreateMessagesContext = <
80
80
  TypingIndicatorContainer,
81
81
  updateMessage,
82
82
  UrlPreview,
83
+ VideoThumbnail,
83
84
  }: MessagesContextValue<StreamChatGenerics> & {
84
85
  /**
85
86
  * To ensure we allow re-render, when channel is changed
@@ -166,6 +167,7 @@ export const useCreateMessagesContext = <
166
167
  TypingIndicatorContainer,
167
168
  updateMessage,
168
169
  UrlPreview,
170
+ VideoThumbnail,
169
171
  }),
170
172
  [
171
173
  additionalTouchablePropsLength,
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { Text } from 'react-native';
3
+
4
+ import { render, waitFor } from '@testing-library/react-native';
5
+
6
+ import type { DefaultStreamChatGenerics } from 'src/types/types';
7
+ import type { Channel, ChannelMemberResponse, DefaultGenerics, StreamChat } from 'stream-chat';
8
+
9
+ import {
10
+ GROUP_CHANNEL_MEMBERS_MOCK,
11
+ ONE_CHANNEL_MEMBER_MOCK,
12
+ ONE_MEMBER_WITH_EMPTY_USER_MOCK,
13
+ } from '../../../../mock-builders/api/queryMembers';
14
+ import { generateUser } from '../../../../mock-builders/generator/user';
15
+ import { getTestClientWithUser } from '../../../../mock-builders/mock';
16
+ import {
17
+ getChannelPreviewDisplayName,
18
+ useChannelPreviewDisplayName,
19
+ } from '../useChannelPreviewDisplayName';
20
+
21
+ describe('useChannelPreviewDisplayName', () => {
22
+ const clientUser = generateUser();
23
+ let chatClient: StreamChat<DefaultGenerics> | StreamChat<DefaultStreamChatGenerics>;
24
+ const CHARACTER_LENGTH = 15;
25
+
26
+ beforeEach(async () => {
27
+ chatClient = await getTestClientWithUser(clientUser);
28
+ });
29
+
30
+ it('should return a channel display name', async () => {
31
+ const channelName = 'okechukwu';
32
+
33
+ const TestComponent = () => {
34
+ const channelDisplayName = useChannelPreviewDisplayName(
35
+ {
36
+ data: { name: channelName },
37
+ } as unknown as Channel<DefaultGenerics>,
38
+ CHARACTER_LENGTH,
39
+ );
40
+ return <Text>{channelDisplayName}</Text>;
41
+ };
42
+
43
+ const { getByText } = render(<TestComponent />);
44
+
45
+ await waitFor(() => {
46
+ expect(getByText(channelName)).toBeTruthy();
47
+ });
48
+ });
49
+
50
+ it('should return the full channelName when channelName length is less than characterLength', () => {
51
+ const channelName = 'okechukwu';
52
+ const currentUserId = chatClient.userID;
53
+
54
+ const displayName = getChannelPreviewDisplayName({
55
+ channelName,
56
+ characterLimit: CHARACTER_LENGTH,
57
+ currentUserId,
58
+ members: ONE_CHANNEL_MEMBER_MOCK,
59
+ });
60
+
61
+ expect(displayName).toEqual(channelName);
62
+ });
63
+
64
+ it.each([
65
+ [CHARACTER_LENGTH, GROUP_CHANNEL_MEMBERS_MOCK, 'okechukwu nwagba', 'ben, nick, q,...+2'],
66
+ [CHARACTER_LENGTH, ONE_CHANNEL_MEMBER_MOCK, 'okechukwu nwagba', 'okechukwu nw...'],
67
+ [CHARACTER_LENGTH, ONE_MEMBER_WITH_EMPTY_USER_MOCK, 'okechukwu nwagba', 'Unknown User...'],
68
+ ])(
69
+ 'getChannelPreviewDisplayName(%i, %p, %s) result in %s',
70
+ (characterLength, members, currentUserId, expected) => {
71
+ const displayName = getChannelPreviewDisplayName({
72
+ characterLimit: characterLength,
73
+ currentUserId,
74
+ members: members as unknown as Record<
75
+ string,
76
+ ChannelMemberResponse<DefaultStreamChatGenerics>
77
+ >,
78
+ });
79
+
80
+ expect(displayName).toEqual(expected);
81
+ },
82
+ );
83
+ });
@@ -1,48 +1,66 @@
1
1
  import { useEffect, useState } from 'react';
2
2
 
3
- import type { Channel } from 'stream-chat';
3
+ import type { Channel, ChannelMemberResponse } from 'stream-chat';
4
4
 
5
5
  import { useChatContext } from '../../../contexts/chatContext/ChatContext';
6
6
 
7
7
  import type { DefaultStreamChatGenerics } from '../../../types/types';
8
8
  import { vw } from '../../../utils/utils';
9
9
 
10
- const maxCharacterLengthDefault = (vw(100) - 16) / 6;
10
+ const DEFAULT_MAX_CHARACTER_LENGTH = (vw(100) - 16) / 6;
11
+
12
+ const ELLIPSIS = `...`;
13
+
14
+ const getMemberName = (member: ChannelMemberResponse) =>
15
+ member.user?.name || member.user?.id || 'Unknown User';
11
16
 
12
17
  export const getChannelPreviewDisplayName = <
13
18
  StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
14
19
  >({
15
20
  channelName,
21
+ characterLimit,
16
22
  currentUserId,
17
- maxCharacterLength,
18
23
  members,
19
24
  }: {
20
- maxCharacterLength: number;
25
+ characterLimit: number;
21
26
  channelName?: string;
22
27
  currentUserId?: string;
23
28
  members?: Channel<StreamChatGenerics>['state']['members'];
24
- }) => {
29
+ }): string => {
25
30
  if (channelName) return channelName;
26
31
 
27
32
  const channelMembers = Object.values(members || {});
33
+
28
34
  const otherMembers = channelMembers.filter((member) => member.user?.id !== currentUserId);
35
+ otherMembers.sort((prevUser, nextUser) =>
36
+ (prevUser?.user?.name ?? '')
37
+ .toLowerCase()
38
+ .localeCompare((nextUser?.user?.name ?? '').toLocaleUpperCase()),
39
+ );
40
+
41
+ const createChannelNameSuffix = (remainingNumberOfMembers: number) =>
42
+ remainingNumberOfMembers <= 1 ? `${ELLIPSIS}` : `,${ELLIPSIS}+${remainingNumberOfMembers}`;
29
43
 
30
- const name = otherMembers.slice(0).reduce((returnString, currentMember, index, originalArray) => {
31
- const returnStringLength = returnString.length;
32
- const currentMemberName = currentMember.user?.name || currentMember.user?.id || 'Unknown User';
33
- // a rough approximation of when the +Number shows up
34
- if (returnStringLength + (currentMemberName.length + 2) < maxCharacterLength) {
35
- if (returnStringLength) {
36
- returnString += `, ${currentMemberName}`;
37
- } else {
38
- returnString = currentMemberName;
39
- }
44
+ const name = otherMembers.reduce((result, currentMember, index, originalArray) => {
45
+ if (result.length >= characterLimit) {
46
+ return result;
47
+ }
48
+
49
+ const currentMemberName = getMemberName(currentMember);
50
+
51
+ const resultHasSpaceForCurrentMemberName =
52
+ result.length + (currentMemberName.length + ELLIPSIS.length) < characterLimit;
53
+ if (resultHasSpaceForCurrentMemberName) {
54
+ return result.length > 0 ? `${result}, ${currentMemberName}, ` : currentMemberName;
40
55
  } else {
41
- const remainingMembers = originalArray.length - index;
42
- returnString += `, +${remainingMembers}`;
43
- originalArray.splice(1); // exit early
56
+ const remainingNumberOfMembers = originalArray.length - index;
57
+ const truncateLimit = characterLimit - (ELLIPSIS.length + result.length);
58
+ const tuncatedCurrentMemberName = `${currentMemberName.slice(0, truncateLimit)}`;
59
+
60
+ const channelNameSuffix = createChannelNameSuffix(remainingNumberOfMembers);
61
+
62
+ return `${result}${tuncatedCurrentMemberName}${channelNameSuffix}`;
44
63
  }
45
- return returnString;
46
64
  }, '');
47
65
 
48
66
  return name;
@@ -56,17 +74,16 @@ export const useChannelPreviewDisplayName = <
56
74
  ) => {
57
75
  const { client } = useChatContext<StreamChatGenerics>();
58
76
 
59
- const currentUserId = client.userID;
60
- const members = channel?.state.members;
77
+ const currentUserId = client?.userID;
78
+ const members = channel?.state?.members;
61
79
  const numOfMembers = Object.keys(members || {}).length;
62
80
  const channelName = channel?.data?.name;
63
- const maxCharacterLength = characterLength || maxCharacterLengthDefault;
64
-
81
+ const characterLimit = characterLength || DEFAULT_MAX_CHARACTER_LENGTH;
65
82
  const [displayName, setDisplayName] = useState(
66
83
  getChannelPreviewDisplayName({
67
84
  channelName,
85
+ characterLimit,
68
86
  currentUserId,
69
- maxCharacterLength,
70
87
  members,
71
88
  }),
72
89
  );
@@ -75,12 +92,12 @@ export const useChannelPreviewDisplayName = <
75
92
  setDisplayName(
76
93
  getChannelPreviewDisplayName({
77
94
  channelName,
95
+ characterLimit,
78
96
  currentUserId,
79
- maxCharacterLength,
80
97
  members,
81
98
  }),
82
99
  );
83
- }, [channelName, currentUserId, maxCharacterLength, numOfMembers]);
100
+ }, [channelName, currentUserId, characterLimit, numOfMembers]);
84
101
 
85
102
  return displayName;
86
103
  };