stream-chat-react-native-core 4.9.0-beta.3 → 4.10.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 (150) hide show
  1. package/lib/commonjs/components/Attachment/Gallery.js +246 -190
  2. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  3. package/lib/commonjs/components/Attachment/VideoThumbnail.js +11 -5
  4. package/lib/commonjs/components/Attachment/VideoThumbnail.js.map +1 -1
  5. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  6. package/lib/commonjs/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  7. package/lib/commonjs/components/ChannelPreview/ChannelPreview.js +7 -3
  8. package/lib/commonjs/components/ChannelPreview/ChannelPreview.js.map +1 -1
  9. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +2 -1
  10. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  11. package/lib/commonjs/components/Chat/Chat.js +9 -5
  12. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  13. package/lib/commonjs/components/ImageGallery/ImageGallery.js +25 -22
  14. package/lib/commonjs/components/ImageGallery/ImageGallery.js.map +1 -1
  15. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js +15 -15
  16. package/lib/commonjs/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  17. package/lib/commonjs/components/Message/Message.js +13 -8
  18. package/lib/commonjs/components/Message/Message.js.map +1 -1
  19. package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js +7 -4
  20. package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  21. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +34 -35
  22. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  23. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  24. package/lib/commonjs/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  25. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js +15 -13
  26. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js.map +1 -1
  27. package/lib/commonjs/contexts/translationContext/TranslationContext.js +8 -4
  28. package/lib/commonjs/contexts/translationContext/TranslationContext.js.map +1 -1
  29. package/lib/commonjs/hooks/useStreami18n.js.map +1 -1
  30. package/lib/commonjs/hooks/useTranslatedMessage.js +34 -0
  31. package/lib/commonjs/hooks/useTranslatedMessage.js.map +1 -0
  32. package/lib/commonjs/mock-builders/api/getOrCreateChannel.js.map +1 -1
  33. package/lib/commonjs/mock-builders/generator/attachment.js +3 -2
  34. package/lib/commonjs/mock-builders/generator/attachment.js.map +1 -1
  35. package/lib/commonjs/mock-builders/generator/channel.js +8 -4
  36. package/lib/commonjs/mock-builders/generator/channel.js.map +1 -1
  37. package/lib/commonjs/utils/getUrlOfImageAttachment.js +1 -1
  38. package/lib/commonjs/utils/getUrlOfImageAttachment.js.map +1 -1
  39. package/lib/commonjs/version.json +1 -1
  40. package/lib/module/components/Attachment/Gallery.js +246 -190
  41. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  42. package/lib/module/components/Attachment/VideoThumbnail.js +11 -5
  43. package/lib/module/components/Attachment/VideoThumbnail.js.map +1 -1
  44. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  45. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  46. package/lib/module/components/ChannelPreview/ChannelPreview.js +7 -3
  47. package/lib/module/components/ChannelPreview/ChannelPreview.js.map +1 -1
  48. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +2 -1
  49. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  50. package/lib/module/components/Chat/Chat.js +9 -5
  51. package/lib/module/components/Chat/Chat.js.map +1 -1
  52. package/lib/module/components/ImageGallery/ImageGallery.js +25 -22
  53. package/lib/module/components/ImageGallery/ImageGallery.js.map +1 -1
  54. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js +15 -15
  55. package/lib/module/components/ImageGallery/components/ImageGalleryFooter.js.map +1 -1
  56. package/lib/module/components/Message/Message.js +13 -8
  57. package/lib/module/components/Message/Message.js.map +1 -1
  58. package/lib/module/components/Message/MessageSimple/MessageTextContainer.js +7 -4
  59. package/lib/module/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  60. package/lib/module/contexts/messageInputContext/MessageInputContext.js +34 -35
  61. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  62. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js +2 -2
  63. package/lib/module/contexts/messageOverlayContext/MessageOverlayContext.js.map +1 -1
  64. package/lib/module/contexts/overlayContext/OverlayProvider.js +15 -13
  65. package/lib/module/contexts/overlayContext/OverlayProvider.js.map +1 -1
  66. package/lib/module/contexts/translationContext/TranslationContext.js +8 -4
  67. package/lib/module/contexts/translationContext/TranslationContext.js.map +1 -1
  68. package/lib/module/hooks/useStreami18n.js.map +1 -1
  69. package/lib/module/hooks/useTranslatedMessage.js +34 -0
  70. package/lib/module/hooks/useTranslatedMessage.js.map +1 -0
  71. package/lib/module/mock-builders/api/getOrCreateChannel.js.map +1 -1
  72. package/lib/module/mock-builders/generator/attachment.js +3 -2
  73. package/lib/module/mock-builders/generator/attachment.js.map +1 -1
  74. package/lib/module/mock-builders/generator/channel.js +8 -4
  75. package/lib/module/mock-builders/generator/channel.js.map +1 -1
  76. package/lib/module/utils/getUrlOfImageAttachment.js +1 -1
  77. package/lib/module/utils/getUrlOfImageAttachment.js.map +1 -1
  78. package/lib/module/version.json +1 -1
  79. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts +3 -1
  80. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts +1 -0
  81. package/lib/typescript/components/ChannelPreview/__tests__/ChannelPreview.test.d.ts +1 -0
  82. package/lib/typescript/components/Message/MessageSimple/__tests__/MessageTextContainer.test.d.ts +1 -0
  83. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +1 -0
  84. package/lib/typescript/contexts/messageInputContext/__tests__/MessageInputContext.test.d.ts +1 -0
  85. package/lib/typescript/contexts/messageInputContext/__tests__/isValidMessage.test.d.ts +1 -0
  86. package/lib/typescript/contexts/messageInputContext/__tests__/pickFile.test.d.ts +1 -0
  87. package/lib/typescript/contexts/messageInputContext/__tests__/removeFile.test.d.ts +1 -0
  88. package/lib/typescript/contexts/messageInputContext/__tests__/removeImage.test.d.ts +1 -0
  89. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessage.test.d.ts +1 -0
  90. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessageAsync.test.d.ts +1 -0
  91. package/lib/typescript/contexts/messageInputContext/__tests__/updateMessage.test.d.ts +1 -0
  92. package/lib/typescript/contexts/messageInputContext/__tests__/uploadFile.test.d.ts +1 -0
  93. package/lib/typescript/contexts/messageInputContext/__tests__/uploadImage.test.d.ts +1 -0
  94. package/lib/typescript/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.d.ts +1 -0
  95. package/lib/typescript/contexts/messageOverlayContext/MessageOverlayContext.d.ts +2 -1
  96. package/lib/typescript/contexts/translationContext/TranslationContext.d.ts +7 -2
  97. package/lib/typescript/hooks/__tests__/useTranslatedMessage.test.d.ts +1 -0
  98. package/lib/typescript/hooks/useStreami18n.d.ts +2 -2
  99. package/lib/typescript/hooks/useTranslatedMessage.d.ts +3 -0
  100. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +13 -1
  101. package/lib/typescript/mock-builders/event/messageNew.d.ts +2 -0
  102. package/lib/typescript/mock-builders/event/messageRead.d.ts +8 -0
  103. package/lib/typescript/mock-builders/generator/attachment.d.ts +10 -0
  104. package/lib/typescript/mock-builders/generator/channel.d.ts +27 -23
  105. package/lib/typescript/mock-builders/generator/message.d.ts +38 -0
  106. package/lib/typescript/mock-builders/generator/user.d.ts +9 -9
  107. package/package.json +7 -6
  108. package/src/components/Attachment/Gallery.tsx +231 -140
  109. package/src/components/Attachment/VideoThumbnail.tsx +12 -5
  110. package/src/components/Attachment/__tests__/Attachment.test.js +12 -0
  111. package/src/components/Attachment/__tests__/Gallery.test.js +23 -1
  112. package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +1 -0
  113. package/src/components/Attachment/utils/buildGallery/types.ts +1 -0
  114. package/src/components/ChannelPreview/ChannelPreview.tsx +7 -1
  115. package/src/components/ChannelPreview/__tests__/{ChannelPreview.test.js → ChannelPreview.test.tsx} +79 -41
  116. package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +56 -21
  117. package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +4 -1
  118. package/src/components/Chat/Chat.tsx +6 -3
  119. package/src/components/ImageGallery/ImageGallery.tsx +10 -6
  120. package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +2 -1
  121. package/src/components/Message/Message.tsx +8 -1
  122. package/src/components/Message/MessageSimple/MessageTextContainer.tsx +7 -1
  123. package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx +108 -0
  124. package/src/components/Message/MessageSimple/__tests__/__snapshots__/{MessageTextContainer.test.js.snap → MessageTextContainer.test.tsx.snap} +0 -0
  125. package/src/contexts/messageInputContext/MessageInputContext.tsx +10 -4
  126. package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx +208 -0
  127. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap +113 -0
  128. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessageAsync.test.tsx.snap +33 -0
  129. package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx +71 -0
  130. package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +67 -0
  131. package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx +82 -0
  132. package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx +80 -0
  133. package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +286 -0
  134. package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx +124 -0
  135. package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx +92 -0
  136. package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx +76 -0
  137. package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx +55 -0
  138. package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx +62 -0
  139. package/src/contexts/messageOverlayContext/MessageOverlayContext.tsx +2 -1
  140. package/src/contexts/overlayContext/OverlayProvider.tsx +4 -3
  141. package/src/contexts/translationContext/TranslationContext.tsx +10 -1
  142. package/src/hooks/__tests__/useTranslatedMessage.test.tsx +106 -0
  143. package/src/hooks/useStreami18n.ts +2 -2
  144. package/src/hooks/useTranslatedMessage.ts +30 -0
  145. package/src/mock-builders/api/{getOrCreateChannel.js → getOrCreateChannel.ts} +8 -1
  146. package/src/mock-builders/generator/attachment.js +4 -2
  147. package/src/mock-builders/generator/{channel.js → channel.ts} +19 -10
  148. package/src/utils/getUrlOfImageAttachment.ts +1 -1
  149. package/src/version.json +1 -1
  150. package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.js +0 -65
@@ -1,10 +1,13 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
3
 
4
+ import type { Attachment } from 'stream-chat';
5
+
4
6
  import { GalleryImage } from './GallaryImage';
5
7
  import { useLoadingImage } from './hooks/useLoadingImage';
6
8
  import { buildGallery } from './utils/buildGallery/buildGallery';
7
9
 
10
+ import type { Thumbnail } from './utils/buildGallery/types';
8
11
  import { getGalleryImageBorderRadius } from './utils/getGalleryImageBorderRadius';
9
12
 
10
13
  import { openUrlSafely } from './utils/openUrlSafely';
@@ -135,26 +138,18 @@ const GalleryWithContext = <
135
138
  VideoThumbnail,
136
139
  } = props;
137
140
 
138
- const { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError } =
139
- useLoadingImage();
140
-
141
141
  const {
142
142
  theme: {
143
- colors: { overlay },
144
143
  messageSimple: {
145
144
  gallery: {
146
145
  galleryContainer,
147
146
  galleryItemColumn,
148
147
  gridHeight,
149
148
  gridWidth,
150
- image,
151
- imageContainer,
152
149
  maxHeight,
153
150
  maxWidth,
154
151
  minHeight,
155
152
  minWidth,
156
- moreImagesContainer,
157
- moreImagesText,
158
153
  },
159
154
  },
160
155
  },
@@ -180,8 +175,6 @@ const GalleryWithContext = <
180
175
  );
181
176
 
182
177
  if (!imagesAndVideos?.length) return null;
183
- const messageText = message?.text;
184
- const messageId = message?.id;
185
178
  const numOfColumns = thumbnailGrid.length;
186
179
 
187
180
  return (
@@ -212,31 +205,7 @@ const GalleryWithContext = <
212
205
  ]}
213
206
  testID={`gallery-${invertedDirections ? 'row' : 'column'}-${colIndex}`}
214
207
  >
215
- {rows.map(({ height, resizeMode, type, url, width }, rowIndex) => {
216
- const openImageViewer = () => {
217
- if (!legacyImageViewerSwipeBehaviour && message) {
218
- // Added if-else to keep the logic readable, instead of DRY.
219
- // if - legacyImageViewerSwipeBehaviour is disabled
220
- // else - legacyImageViewerSwipeBehaviour is enabled
221
- setImages([message]);
222
- setImage({ messageId: message.id, url });
223
- setOverlay('gallery');
224
- } else if (legacyImageViewerSwipeBehaviour) {
225
- setImage({ messageId: message?.id, url });
226
- setOverlay('gallery');
227
- }
228
- };
229
-
230
- const defaultOnPress = () => {
231
- if (type === 'video' && !isVideoPackageAvailable()) {
232
- // This condition is kinda unreachable, since we render videos as file attachment if the video
233
- // library is not installed. But doesn't hurt to have extra safeguard, in case of some customizations.
234
- openUrlSafely(url);
235
- } else {
236
- openImageViewer();
237
- }
238
- };
239
-
208
+ {rows.map((thumbnail, rowIndex) => {
240
209
  const borderRadius = getGalleryImageBorderRadius({
241
210
  alignment,
242
211
  colIndex,
@@ -244,7 +213,7 @@ const GalleryWithContext = <
244
213
  hasThreadReplies,
245
214
  height,
246
215
  invertedDirections,
247
- messageText,
216
+ messageText: message?.text,
248
217
  numOfColumns,
249
218
  numOfRows,
250
219
  rowIndex,
@@ -253,111 +222,35 @@ const GalleryWithContext = <
253
222
  width,
254
223
  });
255
224
 
225
+ if (message === undefined) {
226
+ return null;
227
+ }
228
+
256
229
  return (
257
- <TouchableOpacity
258
- activeOpacity={0.8}
259
- disabled={preventPress}
260
- key={`gallery-item-${messageId}/${colIndex}/${rowIndex}/${imagesAndVideos.length}`}
261
- onLongPress={(event) => {
262
- if (onLongPress) {
263
- onLongPress({
264
- emitter: 'gallery',
265
- event,
266
- });
267
- }
268
- }}
269
- onPress={(event) => {
270
- if (onPress) {
271
- onPress({
272
- defaultHandler: defaultOnPress,
273
- emitter: 'gallery',
274
- event,
275
- });
276
- }
277
- }}
278
- onPressIn={(event) => {
279
- if (onPressIn) {
280
- onPressIn({
281
- defaultHandler: defaultOnPress,
282
- emitter: 'gallery',
283
- event,
284
- });
285
- }
286
- }}
287
- style={[
288
- styles.imageContainer,
289
- {
290
- height,
291
- width,
292
- },
293
- imageContainer,
294
- ]}
295
- testID={`gallery-${
296
- invertedDirections ? 'row' : 'column'
297
- }-${colIndex}-item-${rowIndex}`}
298
- {...additionalTouchableProps}
299
- >
300
- {type === 'video' ? (
301
- <VideoThumbnail
302
- style={[
303
- borderRadius,
304
- image,
305
- {
306
- height: height - 1,
307
- width: width - 1,
308
- },
309
- ]}
310
- />
311
- ) : (
312
- <View style={styles.imageContainerStyle}>
313
- <MemoizedGalleryImage
314
- onError={(error) => {
315
- console.warn(error);
316
- setLoadingImage(false);
317
- setLoadingImageError(true);
318
- }}
319
- onLoadEnd={() => setLoadingImage(false)}
320
- onLoadStart={() => setLoadingImage(true)}
321
- resizeMode={resizeMode}
322
- style={[
323
- borderRadius,
324
- image,
325
- {
326
- height: height - 1,
327
- width: width - 1,
328
- },
329
- ]}
330
- uri={url}
331
- />
332
- {isLoadingImage && (
333
- <View style={{ position: 'absolute' }}>
334
- <ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
335
- </View>
336
- )}
337
- {isLoadingImageError && (
338
- <View style={{ position: 'absolute' }}>
339
- <ImageLoadingFailedIndicator style={styles.imageLoadingIndicatorStyle} />
340
- </View>
341
- )}
342
- </View>
343
- )}
344
- {colIndex === numOfColumns - 1 &&
345
- rowIndex === numOfRows - 1 &&
346
- imagesAndVideos.length > 4 ? (
347
- <View
348
- style={[
349
- StyleSheet.absoluteFillObject,
350
- styles.moreImagesContainer,
351
- { backgroundColor: overlay },
352
- moreImagesContainer,
353
- ]}
354
- >
355
- <Text style={[styles.moreImagesText, moreImagesText]}>
356
- {`+${imagesAndVideos.length - 4}`}
357
- </Text>
358
- </View>
359
- ) : null}
360
- </TouchableOpacity>
230
+ <GalleryThumbnail
231
+ additionalTouchableProps={additionalTouchableProps}
232
+ borderRadius={borderRadius}
233
+ colIndex={colIndex}
234
+ ImageLoadingFailedIndicator={ImageLoadingFailedIndicator}
235
+ ImageLoadingIndicator={ImageLoadingIndicator}
236
+ imagesAndVideos={imagesAndVideos}
237
+ invertedDirections={invertedDirections || false}
238
+ key={rowIndex}
239
+ legacyImageViewerSwipeBehaviour={legacyImageViewerSwipeBehaviour}
240
+ message={message}
241
+ numOfColumns={numOfColumns}
242
+ numOfRows={numOfRows}
243
+ onLongPress={onLongPress}
244
+ onPress={onPress}
245
+ onPressIn={onPressIn}
246
+ preventPress={preventPress}
247
+ rowIndex={rowIndex}
248
+ setImage={setImage}
249
+ setImages={setImages}
250
+ setOverlay={setOverlay}
251
+ thumbnail={thumbnail}
252
+ VideoThumbnail={VideoThumbnail}
253
+ />
361
254
  );
362
255
  })}
363
256
  </View>
@@ -367,6 +260,204 @@ const GalleryWithContext = <
367
260
  );
368
261
  };
369
262
 
263
+ type GalleryThumbnailProps<
264
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
265
+ > = {
266
+ borderRadius: {
267
+ borderBottomLeftRadius: number;
268
+ borderBottomRightRadius: number;
269
+ borderTopLeftRadius: number;
270
+ borderTopRightRadius: number;
271
+ };
272
+ colIndex: number;
273
+ imagesAndVideos: Attachment<StreamChatGenerics>[];
274
+ invertedDirections: boolean;
275
+ message: MessageType<StreamChatGenerics>;
276
+ numOfColumns: number;
277
+ numOfRows: number;
278
+ rowIndex: number;
279
+ thumbnail: Thumbnail;
280
+ } & Pick<
281
+ MessagesContextValue<StreamChatGenerics>,
282
+ | 'additionalTouchableProps'
283
+ | 'legacyImageViewerSwipeBehaviour'
284
+ | 'VideoThumbnail'
285
+ | 'ImageLoadingIndicator'
286
+ | 'ImageLoadingFailedIndicator'
287
+ > &
288
+ Pick<ImageGalleryContextValue<StreamChatGenerics>, 'setImage' | 'setImages'> &
289
+ Pick<
290
+ MessageContextValue<StreamChatGenerics>,
291
+ 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'
292
+ > &
293
+ Pick<OverlayContextValue, 'setOverlay'>;
294
+
295
+ const GalleryThumbnail = <
296
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
297
+ >({
298
+ additionalTouchableProps,
299
+ borderRadius,
300
+ colIndex,
301
+ ImageLoadingFailedIndicator,
302
+ ImageLoadingIndicator,
303
+ imagesAndVideos,
304
+ invertedDirections,
305
+ legacyImageViewerSwipeBehaviour,
306
+ message,
307
+ numOfColumns,
308
+ numOfRows,
309
+ onLongPress,
310
+ onPress,
311
+ onPressIn,
312
+ preventPress,
313
+ rowIndex,
314
+ setImage,
315
+ setImages,
316
+ setOverlay,
317
+ thumbnail,
318
+ VideoThumbnail,
319
+ }: GalleryThumbnailProps<StreamChatGenerics>) => {
320
+ const { isLoadingImage, isLoadingImageError, setLoadingImage, setLoadingImageError } =
321
+ useLoadingImage();
322
+
323
+ const {
324
+ theme: {
325
+ colors: { overlay },
326
+ messageSimple: {
327
+ gallery: { image, imageContainer, moreImagesContainer, moreImagesText },
328
+ },
329
+ },
330
+ } = useTheme();
331
+
332
+ const openImageViewer = () => {
333
+ if (!legacyImageViewerSwipeBehaviour && message) {
334
+ // Added if-else to keep the logic readable, instead of DRY.
335
+ // if - legacyImageViewerSwipeBehaviour is disabled
336
+ // else - legacyImageViewerSwipeBehaviour is enabled
337
+ setImages([message]);
338
+ setImage({ messageId: message.id, url: thumbnail.url });
339
+ setOverlay('gallery');
340
+ } else if (legacyImageViewerSwipeBehaviour) {
341
+ setImage({ messageId: message?.id, url: thumbnail.url });
342
+ setOverlay('gallery');
343
+ }
344
+ };
345
+
346
+ const defaultOnPress = () => {
347
+ if (thumbnail.type === 'video' && !isVideoPackageAvailable()) {
348
+ // This condition is kinda unreachable, since we render videos as file attachment if the video
349
+ // library is not installed. But doesn't hurt to have extra safeguard, in case of some customizations.
350
+ openUrlSafely(thumbnail.url);
351
+ } else {
352
+ openImageViewer();
353
+ }
354
+ };
355
+
356
+ return (
357
+ <TouchableOpacity
358
+ activeOpacity={0.8}
359
+ disabled={preventPress}
360
+ key={`gallery-item-${message.id}/${colIndex}/${rowIndex}/${imagesAndVideos.length}`}
361
+ onLongPress={(event) => {
362
+ if (onLongPress) {
363
+ onLongPress({
364
+ emitter: 'gallery',
365
+ event,
366
+ });
367
+ }
368
+ }}
369
+ onPress={(event) => {
370
+ if (onPress) {
371
+ onPress({
372
+ defaultHandler: defaultOnPress,
373
+ emitter: 'gallery',
374
+ event,
375
+ });
376
+ }
377
+ }}
378
+ onPressIn={(event) => {
379
+ if (onPressIn) {
380
+ onPressIn({
381
+ defaultHandler: defaultOnPress,
382
+ emitter: 'gallery',
383
+ event,
384
+ });
385
+ }
386
+ }}
387
+ style={[
388
+ styles.imageContainer,
389
+ {
390
+ height: thumbnail.height,
391
+ width: thumbnail.width,
392
+ },
393
+ imageContainer,
394
+ ]}
395
+ testID={`gallery-${invertedDirections ? 'row' : 'column'}-${colIndex}-item-${rowIndex}`}
396
+ {...additionalTouchableProps}
397
+ >
398
+ {thumbnail.type === 'video' ? (
399
+ <VideoThumbnail
400
+ style={[
401
+ borderRadius,
402
+ image,
403
+ {
404
+ height: thumbnail.height - 1,
405
+ width: thumbnail.width - 1,
406
+ },
407
+ ]}
408
+ thumb_url={thumbnail.thumb_url}
409
+ />
410
+ ) : (
411
+ <View style={styles.imageContainerStyle}>
412
+ <MemoizedGalleryImage
413
+ onError={(error) => {
414
+ console.warn(error);
415
+ setLoadingImage(false);
416
+ setLoadingImageError(true);
417
+ }}
418
+ onLoadEnd={() => setLoadingImage(false)}
419
+ onLoadStart={() => setLoadingImage(true)}
420
+ resizeMode={thumbnail.resizeMode}
421
+ style={[
422
+ borderRadius,
423
+ image,
424
+ {
425
+ height: thumbnail.height - 1,
426
+ width: thumbnail.width - 1,
427
+ },
428
+ ]}
429
+ uri={thumbnail.url}
430
+ />
431
+ {isLoadingImage && (
432
+ <View style={{ position: 'absolute' }}>
433
+ <ImageLoadingIndicator style={styles.imageLoadingIndicatorStyle} />
434
+ </View>
435
+ )}
436
+ {isLoadingImageError && (
437
+ <View style={{ position: 'absolute' }}>
438
+ <ImageLoadingFailedIndicator style={styles.imageLoadingIndicatorStyle} />
439
+ </View>
440
+ )}
441
+ </View>
442
+ )}
443
+ {colIndex === numOfColumns - 1 && rowIndex === numOfRows - 1 && imagesAndVideos.length > 4 ? (
444
+ <View
445
+ style={[
446
+ StyleSheet.absoluteFillObject,
447
+ styles.moreImagesContainer,
448
+ { backgroundColor: overlay },
449
+ moreImagesContainer,
450
+ ]}
451
+ >
452
+ <Text style={[styles.moreImagesText, moreImagesText]}>
453
+ {`+${imagesAndVideos.length - 4}`}
454
+ </Text>
455
+ </View>
456
+ ) : null}
457
+ </TouchableOpacity>
458
+ );
459
+ };
460
+
370
461
  const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(
371
462
  prevProps: GalleryPropsWithContext<StreamChatGenerics>,
372
463
  nextProps: GalleryPropsWithContext<StreamChatGenerics>,
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { StyleSheet, View, ViewProps } from 'react-native';
2
+ import { ImageBackground, StyleSheet, View, ViewProps } from 'react-native';
3
3
 
4
4
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
5
5
  import { Play } from '../../icons';
@@ -24,7 +24,9 @@ const styles = StyleSheet.create({
24
24
  },
25
25
  });
26
26
 
27
- export type VideoThumbnailProps = ViewProps;
27
+ export type VideoThumbnailProps = ViewProps & {
28
+ thumb_url?: string;
29
+ };
28
30
 
29
31
  export const VideoThumbnail: React.FC<VideoThumbnailProps> = (props) => {
30
32
  const {
@@ -34,12 +36,17 @@ export const VideoThumbnail: React.FC<VideoThumbnailProps> = (props) => {
34
36
  },
35
37
  },
36
38
  } = useTheme();
37
- const { style, ...rest } = props;
39
+ const { style, thumb_url, ...rest } = props;
38
40
  return (
39
- <View {...rest} style={[styles.container, container, style]}>
41
+ <ImageBackground
42
+ accessibilityLabel='video-thumbnail'
43
+ source={{ uri: thumb_url }}
44
+ {...rest}
45
+ style={[styles.container, container, style]}
46
+ >
40
47
  <View style={[styles.roundedView, roundedView]}>
41
48
  <Play height={24} pathFill={'#000'} width={24} />
42
49
  </View>
43
- </View>
50
+ </ImageBackground>
44
51
  );
45
52
  };
@@ -11,8 +11,10 @@ import {
11
11
  generateAudioAttachment,
12
12
  generateFileAttachment,
13
13
  generateImageAttachment,
14
+ generateVideoAttachment,
14
15
  } from '../../../mock-builders/generator/attachment';
15
16
  import { generateMessage } from '../../../mock-builders/generator/message';
17
+ import * as NativeUtils from '../../../native';
16
18
 
17
19
  import { ImageLoadingFailedIndicator } from '../../Attachment/ImageLoadingFailedIndicator';
18
20
  import { ImageLoadingIndicator } from '../../Attachment/ImageLoadingIndicator';
@@ -47,6 +49,16 @@ describe('Attachment', () => {
47
49
  });
48
50
  });
49
51
 
52
+ it('should render File component for "video" type attachment', async () => {
53
+ jest.spyOn(NativeUtils, 'isVideoPackageAvailable').mockImplementation(jest.fn(() => false));
54
+ const attachment = generateVideoAttachment();
55
+ const { getByTestId } = render(getAttachmentComponent({ attachment }));
56
+
57
+ await waitFor(() => {
58
+ expect(getByTestId('file-attachment')).toBeTruthy();
59
+ });
60
+ });
61
+
50
62
  it('should render File component for "file" type attachment', async () => {
51
63
  const attachment = generateFileAttachment();
52
64
  const { getByTestId } = render(getAttachmentComponent({ attachment }));
@@ -11,12 +11,16 @@ import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvide
11
11
 
12
12
  import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel';
13
13
  import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
14
- import { generateImageAttachment } from '../../../mock-builders/generator/attachment';
14
+ import {
15
+ generateImageAttachment,
16
+ generateVideoAttachment,
17
+ } from '../../../mock-builders/generator/attachment';
15
18
  import { generateChannelResponse } from '../../../mock-builders/generator/channel';
16
19
  import { generateMember } from '../../../mock-builders/generator/member';
17
20
  import { generateMessage } from '../../../mock-builders/generator/message';
18
21
  import { generateUser } from '../../../mock-builders/generator/user';
19
22
  import { getTestClientWithUser } from '../../../mock-builders/mock';
23
+ import * as NativeUtils from '../../../native';
20
24
  import { Channel } from '../../Channel/Channel';
21
25
  import { Chat } from '../../Chat/Chat';
22
26
  import { MessageList } from '../../MessageList/MessageList';
@@ -75,6 +79,24 @@ describe('Gallery', () => {
75
79
  });
76
80
  });
77
81
 
82
+ it('should render one image and one video attachment', async () => {
83
+ jest.spyOn(NativeUtils, 'isVideoPackageAvailable').mockImplementation(jest.fn(() => true));
84
+ const attachment1 = generateImageAttachment({
85
+ original_height: 600,
86
+ original_width: 400,
87
+ });
88
+ const attachment2 = generateVideoAttachment();
89
+ const component = await getComponent([attachment1, attachment2]);
90
+ const { getAllByA11yLabel, queryAllByTestId } = render(component);
91
+
92
+ await waitFor(() => {
93
+ expect(queryAllByTestId('gallery-row-0').length).toBe(1);
94
+
95
+ expect(queryAllByTestId('gallery-row-0-item-0').length).toBe(1);
96
+ expect(getAllByA11yLabel('video-thumbnail').length).toBe(1);
97
+ });
98
+ });
99
+
78
100
  it('should render portrait and landscape image in two rows', async () => {
79
101
  const attachment1 = generateImageAttachment({
80
102
  original_height: 600,
@@ -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
+ thumb_url: image.thumb_url,
39
40
  type: image.type,
40
41
  url: shouldResize
41
42
  ? getResizedImageUrl({
@@ -6,6 +6,7 @@ export type Thumbnail = {
6
6
  url: string;
7
7
  width: number;
8
8
  id?: string;
9
+ thumb_url?: string;
9
10
  type?: string;
10
11
  };
11
12
 
@@ -10,6 +10,7 @@ import {
10
10
  } from '../../contexts/channelsContext/ChannelsContext';
11
11
  import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext';
12
12
 
13
+ import { useTranslatedMessage } from '../../hooks/useTranslatedMessage';
13
14
  import type { DefaultStreamChatGenerics } from '../../types/types';
14
15
 
15
16
  export type ChannelPreviewPropsWithContext<
@@ -38,10 +39,15 @@ const ChannelPreviewWithContext = <
38
39
  | MessageResponse<StreamChatGenerics>
39
40
  | undefined
40
41
  >(channel.state.messages[channel.state.messages.length - 1]);
42
+
43
+ const translatedLastMessage = useTranslatedMessage<StreamChatGenerics>(
44
+ lastMessage || ({} as MessageResponse<StreamChatGenerics>),
45
+ );
46
+
41
47
  const [forceUpdate, setForceUpdate] = useState(0);
42
48
  const [unread, setUnread] = useState(channel.countUnread());
43
49
 
44
- const latestMessagePreview = useLatestMessagePreview(channel, forceUpdate, lastMessage);
50
+ const latestMessagePreview = useLatestMessagePreview(channel, forceUpdate, translatedLastMessage);
45
51
 
46
52
  const channelLastMessage = channel.lastMessage();
47
53
  const channelLastMessageString = `${channelLastMessage?.id}${channelLastMessage?.updated_at}`;