stream-chat-react-native-core 4.9.0-beta.2 → 4.9.0

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 (98) 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/Message/Message.js +7 -6
  8. package/lib/commonjs/components/Message/Message.js.map +1 -1
  9. package/lib/commonjs/components/MessageInput/FileUploadPreview.js +24 -18
  10. package/lib/commonjs/components/MessageInput/FileUploadPreview.js.map +1 -1
  11. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js +2 -2
  12. package/lib/commonjs/components/MessageInput/ImageUploadPreview.js.map +1 -1
  13. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  14. package/lib/commonjs/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  15. package/lib/commonjs/components/Reply/Reply.js +7 -2
  16. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  17. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +20 -27
  18. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  19. package/lib/commonjs/mock-builders/generator/attachment.js +3 -2
  20. package/lib/commonjs/mock-builders/generator/attachment.js.map +1 -1
  21. package/lib/commonjs/utils/getUrlOfImageAttachment.js +1 -1
  22. package/lib/commonjs/utils/getUrlOfImageAttachment.js.map +1 -1
  23. package/lib/commonjs/version.json +1 -1
  24. package/lib/module/components/Attachment/Gallery.js +246 -190
  25. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  26. package/lib/module/components/Attachment/VideoThumbnail.js +11 -5
  27. package/lib/module/components/Attachment/VideoThumbnail.js.map +1 -1
  28. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js +1 -0
  29. package/lib/module/components/Attachment/utils/buildGallery/buildThumbnail.js.map +1 -1
  30. package/lib/module/components/Message/Message.js +7 -6
  31. package/lib/module/components/Message/Message.js.map +1 -1
  32. package/lib/module/components/MessageInput/FileUploadPreview.js +24 -18
  33. package/lib/module/components/MessageInput/FileUploadPreview.js.map +1 -1
  34. package/lib/module/components/MessageInput/ImageUploadPreview.js +2 -2
  35. package/lib/module/components/MessageInput/ImageUploadPreview.js.map +1 -1
  36. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js +43 -0
  37. package/lib/module/components/RTLComponents/WritingDirectionAwareText.js.map +1 -0
  38. package/lib/module/components/Reply/Reply.js +7 -2
  39. package/lib/module/components/Reply/Reply.js.map +1 -1
  40. package/lib/module/contexts/messageInputContext/MessageInputContext.js +20 -27
  41. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  42. package/lib/module/mock-builders/generator/attachment.js +3 -2
  43. package/lib/module/mock-builders/generator/attachment.js.map +1 -1
  44. package/lib/module/utils/getUrlOfImageAttachment.js +1 -1
  45. package/lib/module/utils/getUrlOfImageAttachment.js.map +1 -1
  46. package/lib/module/version.json +1 -1
  47. package/lib/typescript/components/Attachment/VideoThumbnail.d.ts +3 -1
  48. package/lib/typescript/components/Attachment/utils/buildGallery/types.d.ts +1 -0
  49. package/lib/typescript/components/RTLComponents/WritingDirectionAwareText.d.ts +4 -0
  50. package/lib/typescript/components/Reply/__tests__/Reply.test.d.ts +1 -0
  51. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +1 -0
  52. package/lib/typescript/contexts/messageInputContext/__tests__/MessageInputContext.test.d.ts +1 -0
  53. package/lib/typescript/contexts/messageInputContext/__tests__/isValidMessage.test.d.ts +1 -0
  54. package/lib/typescript/contexts/messageInputContext/__tests__/pickFile.test.d.ts +1 -0
  55. package/lib/typescript/contexts/messageInputContext/__tests__/removeFile.test.d.ts +1 -0
  56. package/lib/typescript/contexts/messageInputContext/__tests__/removeImage.test.d.ts +1 -0
  57. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessage.test.d.ts +1 -0
  58. package/lib/typescript/contexts/messageInputContext/__tests__/sendMessageAsync.test.d.ts +1 -0
  59. package/lib/typescript/contexts/messageInputContext/__tests__/updateMessage.test.d.ts +1 -0
  60. package/lib/typescript/contexts/messageInputContext/__tests__/uploadFile.test.d.ts +1 -0
  61. package/lib/typescript/contexts/messageInputContext/__tests__/uploadImage.test.d.ts +1 -0
  62. package/lib/typescript/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.d.ts +1 -0
  63. package/lib/typescript/mock-builders/api/getOrCreateChannel.d.ts +7 -0
  64. package/lib/typescript/mock-builders/api/useMockedApis.d.ts +1 -0
  65. package/lib/typescript/mock-builders/generator/attachment.d.ts +10 -0
  66. package/lib/typescript/mock-builders/generator/channel.d.ts +116 -0
  67. package/lib/typescript/mock-builders/generator/message.d.ts +38 -0
  68. package/lib/typescript/mock-builders/generator/user.d.ts +9 -9
  69. package/package.json +7 -5
  70. package/src/components/Attachment/Gallery.tsx +231 -140
  71. package/src/components/Attachment/VideoThumbnail.tsx +12 -5
  72. package/src/components/Attachment/__tests__/Attachment.test.js +12 -0
  73. package/src/components/Attachment/__tests__/Gallery.test.js +23 -1
  74. package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +1 -0
  75. package/src/components/Attachment/utils/buildGallery/types.ts +1 -0
  76. package/src/components/Message/Message.tsx +5 -1
  77. package/src/components/MessageInput/FileUploadPreview.tsx +7 -5
  78. package/src/components/MessageInput/ImageUploadPreview.tsx +2 -2
  79. package/src/components/RTLComponents/WritingDirectionAwareText.tsx +17 -0
  80. package/src/components/Reply/Reply.tsx +19 -3
  81. package/src/components/Reply/__tests__/Reply.test.tsx +48 -0
  82. package/src/contexts/messageInputContext/MessageInputContext.tsx +3 -4
  83. package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx +208 -0
  84. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap +109 -0
  85. package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessageAsync.test.tsx.snap +33 -0
  86. package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx +71 -0
  87. package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +67 -0
  88. package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx +82 -0
  89. package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx +80 -0
  90. package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +286 -0
  91. package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx +124 -0
  92. package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx +92 -0
  93. package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx +76 -0
  94. package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx +55 -0
  95. package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx +62 -0
  96. package/src/mock-builders/generator/attachment.js +4 -2
  97. package/src/utils/getUrlOfImageAttachment.ts +1 -1
  98. package/src/version.json +1 -1
@@ -0,0 +1,116 @@
1
+ export function generateChannel(customValues: any): {
2
+ _client: {};
3
+ data: {
4
+ cid: string;
5
+ config: {
6
+ name: string;
7
+ type: string;
8
+ automod: string;
9
+ automod_behavior: string;
10
+ commands: {
11
+ args: string;
12
+ description: string;
13
+ name: string;
14
+ set: string;
15
+ }[];
16
+ connect_events: boolean;
17
+ created_at: string;
18
+ max_message_length: number;
19
+ message_retention: string;
20
+ mutes: boolean;
21
+ reactions: boolean;
22
+ read_events: boolean;
23
+ replies: boolean;
24
+ search: boolean;
25
+ typing_events: boolean;
26
+ updated_at: string;
27
+ uploads: boolean;
28
+ url_enrichment: boolean;
29
+ };
30
+ created_at: string;
31
+ created_by: {
32
+ banned: boolean;
33
+ created_at: string;
34
+ id: string;
35
+ image: string;
36
+ name: string;
37
+ online: boolean;
38
+ role: string;
39
+ updated_at: string;
40
+ };
41
+ frozen: boolean;
42
+ id: string;
43
+ own_capabilities: string[];
44
+ type: string;
45
+ updated_at: string;
46
+ };
47
+ id: string;
48
+ state: {
49
+ members: {};
50
+ messages: never[];
51
+ setIsUpToDate: jest.Mock<any, any>;
52
+ };
53
+ type: string;
54
+ };
55
+ export function generateChannelResponse(customValues?: {
56
+ channel: {};
57
+ id: string;
58
+ type: string;
59
+ }): {
60
+ channel: {
61
+ id: string;
62
+ type: string;
63
+ user: {
64
+ banned: boolean;
65
+ created_at: string;
66
+ id: string;
67
+ image: string;
68
+ name: string;
69
+ online: boolean;
70
+ role: string;
71
+ updated_at: string;
72
+ };
73
+ cid: string;
74
+ config: {
75
+ name: string;
76
+ type: string;
77
+ automod: string;
78
+ automod_behavior: string;
79
+ commands: {
80
+ args: string;
81
+ description: string;
82
+ name: string;
83
+ set: string;
84
+ }[];
85
+ connect_events: boolean;
86
+ created_at: string;
87
+ max_message_length: number;
88
+ message_retention: string;
89
+ mutes: boolean;
90
+ reactions: boolean;
91
+ read_events: boolean;
92
+ replies: boolean;
93
+ search: boolean;
94
+ typing_events: boolean;
95
+ updated_at: string;
96
+ uploads: boolean;
97
+ url_enrichment: boolean;
98
+ };
99
+ created_at: string;
100
+ created_by: {
101
+ banned: boolean;
102
+ created_at: string;
103
+ id: string;
104
+ image: string;
105
+ name: string;
106
+ online: boolean;
107
+ role: string;
108
+ updated_at: string;
109
+ };
110
+ frozen: boolean;
111
+ own_capabilities: string[];
112
+ updated_at: string;
113
+ };
114
+ members: never[];
115
+ messages: never[];
116
+ };
@@ -0,0 +1,38 @@
1
+ export function generateMessage(options?: {}): {
2
+ attachments: never[];
3
+ created_at: any;
4
+ html: string;
5
+ id: string;
6
+ text: string;
7
+ type: string;
8
+ updated_at: any;
9
+ user: {
10
+ banned: boolean;
11
+ created_at: string;
12
+ id: string;
13
+ image: string;
14
+ name: string;
15
+ online: boolean;
16
+ role: string;
17
+ updated_at: string;
18
+ };
19
+ };
20
+ export function generateStaticMessage(seed: any, options: any, date: any): {
21
+ attachments: never[];
22
+ created_at: any;
23
+ html: string;
24
+ id: string;
25
+ text: string;
26
+ type: string;
27
+ updated_at: any;
28
+ user: {
29
+ banned: boolean;
30
+ created_at: string;
31
+ id: string;
32
+ image: string;
33
+ name: string;
34
+ online: boolean;
35
+ role: string;
36
+ updated_at: string;
37
+ };
38
+ };
@@ -1,9 +1,9 @@
1
1
  export function getUserDefaults(): {
2
2
  banned: boolean;
3
3
  created_at: string;
4
- id: any;
5
- image: any;
6
- name: any;
4
+ id: string;
5
+ image: string;
6
+ name: string;
7
7
  online: boolean;
8
8
  role: string;
9
9
  updated_at: string;
@@ -11,9 +11,9 @@ export function getUserDefaults(): {
11
11
  export function generateUser(options?: {}): {
12
12
  banned: boolean;
13
13
  created_at: string;
14
- id: any;
15
- image: any;
16
- name: any;
14
+ id: string;
15
+ image: string;
16
+ name: string;
17
17
  online: boolean;
18
18
  role: string;
19
19
  updated_at: string;
@@ -21,9 +21,9 @@ export function generateUser(options?: {}): {
21
21
  export function generateStaticUser(userNumber: any): {
22
22
  banned: boolean;
23
23
  created_at: string;
24
- id: any;
25
- image: any;
26
- name: any;
24
+ id: string;
25
+ image: string;
26
+ name: string;
27
27
  online: boolean;
28
28
  role: string;
29
29
  updated_at: string;
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.9.0-beta.2",
4
+ "version": "4.9.0",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -34,7 +34,8 @@
34
34
  "prepare-distribution": "cd examples/SampleApp && npm version patch && react-native-version && git add ./ && git commit -m 'Bump build version' --no-verify",
35
35
  "prettier": "prettier --list-different '**/*.{js,ts,tsx,md,json}' .eslintrc.json ../.prettierrc .babelrc",
36
36
  "prettier-fix": "prettier --write '**/*.{js,ts,tsx,md,json}' .eslintrc.json ../.prettierrc .babelrc",
37
- "test:unit": "TZ=UTC jest --coverage",
37
+ "test:coverage": "yarn test:unit --coverage",
38
+ "test:unit": "TZ=UTC jest",
38
39
  "validate-translations": "node bin/validate-translations.js",
39
40
  "get-version": "echo $npm_package_version",
40
41
  "version": "bash ./bin/before-tag.sh",
@@ -79,7 +80,7 @@
79
80
  "react-art": "^17.0.2",
80
81
  "react-native-markdown-package": "1.8.1",
81
82
  "react-native-url-polyfill": "^1.3.0",
82
- "stream-chat": "6.2.0"
83
+ "stream-chat": "6.7.0"
83
84
  },
84
85
  "peerDependencies": {
85
86
  "react-native-svg": "^12.1.0"
@@ -87,18 +88,18 @@
87
88
  "devDependencies": {
88
89
  "@babel/cli": "^7.12.17",
89
90
  "@babel/core": "^7.12.9",
90
- "@babel/runtime": "^7.12.5",
91
91
  "@babel/plugin-proposal-object-rest-spread": "7.13.8",
92
92
  "@babel/plugin-transform-runtime": "7.13.10",
93
93
  "@babel/polyfill": "7.12.1",
94
94
  "@babel/preset-env": "7.13.12",
95
95
  "@babel/preset-typescript": "7.13.0",
96
+ "@babel/runtime": "^7.12.5",
96
97
  "@react-native-community/eslint-config": "2.0.0",
97
98
  "@react-native-community/eslint-plugin": "1.1.0",
98
99
  "@react-native-community/netinfo": "6.0.0",
99
100
  "@testing-library/jest-native": "4.0.4",
100
- "@testing-library/react-native": "9.0.0",
101
101
  "@testing-library/react-hooks": "7.0.2",
102
+ "@testing-library/react-native": "9.0.0",
102
103
  "@types/eslint": "7.2.10",
103
104
  "@types/jest": "26.0.23",
104
105
  "@types/lodash": "4.14.169",
@@ -106,6 +107,7 @@
106
107
  "@types/react": "17.0.5",
107
108
  "@types/react-native": "0.67.3",
108
109
  "@types/react-test-renderer": "17.0.1",
110
+ "@types/uuid": "^8.3.4",
109
111
  "@typescript-eslint/eslint-plugin": "^5.7.0",
110
112
  "@typescript-eslint/parser": "^5.7.0",
111
113
  "babel-eslint": "10.1.0",
@@ -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>,