stream-chat-react-native-core 4.5.0-beta.1 → 4.6.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 (83) hide show
  1. package/lib/commonjs/components/Attachment/FileIcon.js +9 -1
  2. package/lib/commonjs/components/Attachment/FileIcon.js.map +1 -1
  3. package/lib/commonjs/components/AttachmentPicker/AttachmentPicker.js +232 -48
  4. package/lib/commonjs/components/AttachmentPicker/AttachmentPicker.js.map +1 -1
  5. package/lib/commonjs/components/Message/MessageSimple/MessageContent.js +39 -21
  6. package/lib/commonjs/components/Message/MessageSimple/MessageContent.js.map +1 -1
  7. package/lib/commonjs/components/MessageInput/FileUploadPreview.js +1 -1
  8. package/lib/commonjs/components/MessageInput/FileUploadPreview.js.map +1 -1
  9. package/lib/commonjs/components/MessageInput/MessageInput.js +129 -44
  10. package/lib/commonjs/components/MessageInput/MessageInput.js.map +1 -1
  11. package/lib/commonjs/components/Reply/Reply.js +25 -9
  12. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  13. package/lib/commonjs/contexts/attachmentPickerContext/AttachmentPickerContext.js +15 -8
  14. package/lib/commonjs/contexts/attachmentPickerContext/AttachmentPickerContext.js.map +1 -1
  15. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +10 -7
  16. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  17. package/lib/commonjs/contexts/messageInputContext/hooks/useMessageDetailsForState.js +16 -1
  18. package/lib/commonjs/contexts/messageInputContext/hooks/useMessageDetailsForState.js.map +1 -1
  19. package/lib/commonjs/contexts/themeContext/utils/theme.js +1 -0
  20. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  21. package/lib/commonjs/icons/Recorder.js +47 -0
  22. package/lib/commonjs/icons/Recorder.js.map +1 -0
  23. package/lib/commonjs/icons/Video.js +82 -0
  24. package/lib/commonjs/icons/Video.js.map +1 -0
  25. package/lib/commonjs/icons/index.js +26 -0
  26. package/lib/commonjs/icons/index.js.map +1 -1
  27. package/lib/commonjs/native.js.map +1 -1
  28. package/lib/commonjs/version.json +1 -1
  29. package/lib/module/components/Attachment/FileIcon.js +9 -1
  30. package/lib/module/components/Attachment/FileIcon.js.map +1 -1
  31. package/lib/module/components/AttachmentPicker/AttachmentPicker.js +232 -48
  32. package/lib/module/components/AttachmentPicker/AttachmentPicker.js.map +1 -1
  33. package/lib/module/components/Message/MessageSimple/MessageContent.js +39 -21
  34. package/lib/module/components/Message/MessageSimple/MessageContent.js.map +1 -1
  35. package/lib/module/components/MessageInput/FileUploadPreview.js +1 -1
  36. package/lib/module/components/MessageInput/FileUploadPreview.js.map +1 -1
  37. package/lib/module/components/MessageInput/MessageInput.js +129 -44
  38. package/lib/module/components/MessageInput/MessageInput.js.map +1 -1
  39. package/lib/module/components/Reply/Reply.js +25 -9
  40. package/lib/module/components/Reply/Reply.js.map +1 -1
  41. package/lib/module/contexts/attachmentPickerContext/AttachmentPickerContext.js +15 -8
  42. package/lib/module/contexts/attachmentPickerContext/AttachmentPickerContext.js.map +1 -1
  43. package/lib/module/contexts/messageInputContext/MessageInputContext.js +10 -7
  44. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  45. package/lib/module/contexts/messageInputContext/hooks/useMessageDetailsForState.js +16 -1
  46. package/lib/module/contexts/messageInputContext/hooks/useMessageDetailsForState.js.map +1 -1
  47. package/lib/module/contexts/themeContext/utils/theme.js +1 -0
  48. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  49. package/lib/module/icons/Recorder.js +47 -0
  50. package/lib/module/icons/Recorder.js.map +1 -0
  51. package/lib/module/icons/Video.js +82 -0
  52. package/lib/module/icons/Video.js.map +1 -0
  53. package/lib/module/icons/index.js +26 -0
  54. package/lib/module/icons/index.js.map +1 -1
  55. package/lib/module/native.js.map +1 -1
  56. package/lib/module/version.json +1 -1
  57. package/lib/typescript/components/MessageInput/MessageInput.d.ts +1 -1
  58. package/lib/typescript/contexts/attachmentPickerContext/AttachmentPickerContext.d.ts +3 -2
  59. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +4 -15
  60. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +1 -0
  61. package/lib/typescript/icons/Recorder.d.ts +3 -0
  62. package/lib/typescript/icons/Video.d.ts +3 -0
  63. package/lib/typescript/icons/index.d.ts +2 -0
  64. package/lib/typescript/native.d.ts +3 -14
  65. package/lib/typescript/types/types.d.ts +24 -0
  66. package/package.json +1 -1
  67. package/src/components/Attachment/FileIcon.tsx +45 -0
  68. package/src/components/AttachmentPicker/AttachmentPicker.tsx +204 -54
  69. package/src/components/Message/MessageSimple/MessageContent.tsx +23 -12
  70. package/src/components/MessageInput/FileUploadPreview.tsx +1 -1
  71. package/src/components/MessageInput/MessageInput.tsx +106 -19
  72. package/src/components/Reply/Reply.tsx +31 -14
  73. package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +12 -0
  74. package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx +6 -2
  75. package/src/contexts/messageInputContext/MessageInputContext.tsx +17 -24
  76. package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts +17 -1
  77. package/src/contexts/themeContext/utils/theme.ts +2 -0
  78. package/src/icons/Recorder.tsx +16 -0
  79. package/src/icons/Video.tsx +37 -0
  80. package/src/icons/index.ts +2 -0
  81. package/src/native.ts +4 -14
  82. package/src/types/types.ts +27 -0
  83. package/src/version.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { FlatList as DefaultFlatList } from 'react-native';
2
2
  import type { NetInfoSubscription } from '@react-native-community/netinfo';
3
+ import type { Asset, File } from './types/types';
3
4
  declare type CompressImage = ({ compressImageQuality, height, uri, width, }: {
4
5
  compressImageQuality: number;
5
6
  height: number;
@@ -11,15 +12,8 @@ declare type DeleteFile = ({ uri }: {
11
12
  uri: string;
12
13
  }) => Promise<boolean> | never;
13
14
  export declare let deleteFile: DeleteFile;
14
- declare type GetLocalAssetUri = (uriOrAssetId: string) => Promise<string> | never;
15
+ declare type GetLocalAssetUri = (uriOrAssetId: string) => never;
15
16
  export declare let getLocalAssetUri: GetLocalAssetUri;
16
- export declare type Asset = {
17
- height: number;
18
- source: 'camera' | 'picker';
19
- uri: string;
20
- width: number;
21
- id?: string;
22
- };
23
17
  declare type GetPhotos = ({ after, first }: {
24
18
  first: number;
25
19
  after?: string;
@@ -41,12 +35,7 @@ declare type PickDocument = ({ maxNumberOfFiles }: {
41
35
  maxNumberOfFiles?: number;
42
36
  }) => Promise<{
43
37
  cancelled: boolean;
44
- docs?: {
45
- name: string;
46
- size?: number | string;
47
- type?: string;
48
- uri?: string;
49
- }[];
38
+ docs?: File[];
50
39
  }> | never;
51
40
  export declare let pickDocument: PickDocument;
52
41
  declare type SaveFileOptions = {
@@ -1,4 +1,28 @@
1
1
  import type { ExtendableGenerics, LiteralStringForUnion } from 'stream-chat';
2
+ export declare type Asset = {
3
+ duration: number | null;
4
+ filename: string;
5
+ fileSize: string;
6
+ height: number;
7
+ playableDuration: number | null;
8
+ source: 'camera' | 'picker';
9
+ type: string;
10
+ uri: string;
11
+ width: number;
12
+ id?: string;
13
+ size?: number | string;
14
+ };
15
+ export declare type File = {
16
+ name: string;
17
+ duration?: string | null;
18
+ size?: number | string;
19
+ type?: string;
20
+ uri?: string;
21
+ };
22
+ export declare type DefaultAttachmentType = UnknownType & {
23
+ file_size?: number | string;
24
+ mime_type?: string;
25
+ };
2
26
  interface DefaultUserType extends UnknownType {
3
27
  image?: string;
4
28
  }
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.5.0-beta.1",
4
+ "version": "4.6.0-beta.1",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -19,6 +19,7 @@ import { SEVEN_Z } from '../../icons/SEVEN_Z';
19
19
  import { TAR } from '../../icons/TAR';
20
20
  import { TXT } from '../../icons/TXT';
21
21
  import type { IconProps } from '../../icons/utils/base';
22
+ import { Video } from '../../icons/Video';
22
23
  import { XLS } from '../../icons/XLS';
23
24
  import { XLSX } from '../../icons/XLSX';
24
25
  import { ZIP } from '../../icons/ZIP';
@@ -320,6 +321,46 @@ const zipFileTypes = [
320
321
  'application/zip',
321
322
  ];
322
323
 
324
+ const videoFileTypes = [
325
+ 'video/3g2',
326
+ 'video/3gp',
327
+ 'video/aaf',
328
+ 'video/asf',
329
+ 'video/avchd',
330
+ 'video/avi',
331
+ 'video/drc',
332
+ 'video/flv',
333
+ 'video/m2v',
334
+ 'video/m3u8',
335
+ 'video/m4p',
336
+ 'video/m4v',
337
+ 'video/mkv',
338
+ 'video/mng',
339
+ 'video/mov',
340
+ 'video/mp2',
341
+ 'video/mp4',
342
+ 'video/mpe',
343
+ 'video/mpeg',
344
+ 'video/mpg',
345
+ 'video/mpv',
346
+ 'video/mxf',
347
+ 'video/nsv',
348
+ 'video/ogg',
349
+ 'video/ogv',
350
+ 'video/qt',
351
+ 'video/rm',
352
+ 'video/rmvb',
353
+ 'video/roq',
354
+ 'video/svi',
355
+ 'video/vob',
356
+ 'video/webm',
357
+ 'video/wmv',
358
+ 'video/yuv',
359
+ 'video/quicktime',
360
+ 'video/webm',
361
+ 'video/x-flv',
362
+ ];
363
+
323
364
  const mimeTypeToIconMap: Record<string, React.FC<IconProps>> = {
324
365
  'application/pdf': PDF, // .pdf
325
366
  'application/rtf': RTF, // .rtf
@@ -371,6 +412,10 @@ for (const type of zipFileTypes) {
371
412
  mimeTypeToIconMap[type] = ZIP;
372
413
  }
373
414
 
415
+ for (const type of videoFileTypes) {
416
+ mimeTypeToIconMap[type] = Video;
417
+ }
418
+
374
419
  function mimeTypeToIcon(mimeType?: string): React.FC<IconProps> {
375
420
  if (!mimeType) return GenericFile;
376
421
 
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
3
  BackHandler,
4
4
  Dimensions,
@@ -7,6 +7,7 @@ import {
7
7
  Platform,
8
8
  StatusBar,
9
9
  StyleSheet,
10
+ Text,
10
11
  View,
11
12
  } from 'react-native';
12
13
 
@@ -15,42 +16,161 @@ import BottomSheet, {
15
16
  BottomSheetHandleProps,
16
17
  TouchableOpacity,
17
18
  } from '@gorhom/bottom-sheet';
19
+ import dayjs from 'dayjs';
20
+ import duration from 'dayjs/plugin/duration';
21
+ import { lookup } from 'mime-types';
18
22
 
19
23
  import type { AttachmentPickerErrorProps } from './components/AttachmentPickerError';
20
24
 
21
25
  import { useAttachmentPickerContext } from '../../contexts/attachmentPickerContext/AttachmentPickerContext';
22
26
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
23
- import { Asset, getPhotos } from '../../native';
27
+ import { Recorder } from '../../icons';
28
+ import { getPhotos } from '../../native';
29
+ import type { Asset, File } from '../../types/types';
24
30
  import { vh, vw } from '../../utils/utils';
25
31
 
32
+ dayjs.extend(duration);
33
+
26
34
  const styles = StyleSheet.create({
27
35
  container: {
28
36
  flexGrow: 1,
29
37
  },
38
+ durationText: {
39
+ fontWeight: 'bold',
40
+ },
30
41
  overlay: {
31
42
  alignItems: 'flex-end',
32
43
  flex: 1,
33
44
  },
45
+ videoView: {
46
+ bottom: 5,
47
+ display: 'flex',
48
+ flexDirection: 'row',
49
+ justifyContent: 'space-between',
50
+ paddingHorizontal: 5,
51
+ position: 'absolute',
52
+ width: '100%',
53
+ },
34
54
  });
35
55
 
36
56
  const screenHeight = vh(100);
37
57
  const fullScreenHeight = Dimensions.get('window').height;
38
58
 
39
59
  type AttachmentImageProps = {
60
+ asset: Asset;
40
61
  ImageOverlaySelectedComponent: React.ComponentType;
41
- onPress: () => void;
62
+ maxNumberOfFiles: number;
42
63
  selected: boolean;
43
- uri: string;
64
+ setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
44
65
  numberOfAttachmentPickerImageColumns?: number;
45
66
  };
46
67
 
68
+ type AttachmentVideoProps = {
69
+ asset: Asset;
70
+ ImageOverlaySelectedComponent: React.ComponentType;
71
+ maxNumberOfFiles: number;
72
+ selected: boolean;
73
+ setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
74
+ numberOfAttachmentPickerImageColumns?: number;
75
+ };
76
+
77
+ const AttachmentVideo: React.FC<AttachmentVideoProps> = (props) => {
78
+ const {
79
+ asset,
80
+ ImageOverlaySelectedComponent,
81
+ maxNumberOfFiles,
82
+ numberOfAttachmentPickerImageColumns,
83
+ selected,
84
+ setSelectedFiles,
85
+ } = props;
86
+
87
+ const {
88
+ theme: {
89
+ attachmentPicker: { durationText, image, imageOverlay },
90
+ colors: { overlay, white },
91
+ },
92
+ } = useTheme();
93
+
94
+ const { duration, playableDuration, uri } = asset;
95
+
96
+ const videoDuration = duration ? duration : playableDuration;
97
+
98
+ const ONE_HOUR_IN_SECONDS = 3600;
99
+
100
+ let durationLabel = '00:00';
101
+
102
+ if (videoDuration) {
103
+ const isDurationLongerThanHour = videoDuration / ONE_HOUR_IN_SECONDS >= 1;
104
+ const formattedDurationParam = isDurationLongerThanHour ? 'HH:mm:ss' : 'mm:ss';
105
+ const formattedVideoDuration = dayjs
106
+ .duration(videoDuration, 'second')
107
+ .format(formattedDurationParam);
108
+ durationLabel = formattedVideoDuration;
109
+ }
110
+
111
+ const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2;
112
+
113
+ const onPressVideo = () => {
114
+ if (selected) {
115
+ setSelectedFiles((files) => files.filter((file) => file.uri !== asset.uri));
116
+ } else {
117
+ setSelectedFiles((files) => {
118
+ if (files.length >= maxNumberOfFiles) {
119
+ return files;
120
+ }
121
+ return [
122
+ ...files,
123
+ {
124
+ duration: durationLabel,
125
+ name: asset.filename,
126
+ size: asset.fileSize,
127
+ type: 'video',
128
+ uri: asset.uri,
129
+ },
130
+ ];
131
+ });
132
+ }
133
+ };
134
+
135
+ return (
136
+ <TouchableOpacity onPress={onPressVideo}>
137
+ <ImageBackground
138
+ source={{ uri }}
139
+ style={[
140
+ {
141
+ height: size,
142
+ margin: 1,
143
+ width: size,
144
+ },
145
+ image,
146
+ ]}
147
+ >
148
+ {selected && (
149
+ <View style={[styles.overlay, { backgroundColor: overlay }, imageOverlay]}>
150
+ <ImageOverlaySelectedComponent />
151
+ </View>
152
+ )}
153
+ <View style={styles.videoView}>
154
+ <Recorder height={20} pathFill={white} width={25} />
155
+ {videoDuration ? (
156
+ <Text style={[styles.durationText, durationText, { color: white }]}>
157
+ {durationLabel}
158
+ </Text>
159
+ ) : null}
160
+ </View>
161
+ </ImageBackground>
162
+ </TouchableOpacity>
163
+ );
164
+ };
165
+
47
166
  const AttachmentImage: React.FC<AttachmentImageProps> = (props) => {
48
167
  const {
168
+ asset,
49
169
  ImageOverlaySelectedComponent,
170
+ maxNumberOfFiles,
50
171
  numberOfAttachmentPickerImageColumns,
51
- onPress,
52
172
  selected,
53
- uri,
173
+ setSelectedImages,
54
174
  } = props;
55
175
  const {
56
176
  theme: {
@@ -61,8 +181,23 @@ const AttachmentImage: React.FC<AttachmentImageProps> = (props) => {
61
181
 
62
182
  const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2;
63
183
 
184
+ const { uri } = asset;
185
+
186
+ const onPressImage = () => {
187
+ if (selected) {
188
+ setSelectedImages((images) => images.filter((image) => image.uri !== asset.uri));
189
+ } else {
190
+ setSelectedImages((images) => {
191
+ if (images.length >= maxNumberOfFiles) {
192
+ return images;
193
+ }
194
+ return [...images, asset];
195
+ });
196
+ }
197
+ };
198
+
64
199
  return (
65
- <TouchableOpacity onPress={onPress}>
200
+ <TouchableOpacity onPress={onPressImage}>
66
201
  <ImageBackground
67
202
  source={{ uri }}
68
203
  style={[
@@ -92,6 +227,7 @@ const renderImage = ({
92
227
  ImageOverlaySelectedComponent: React.ComponentType;
93
228
  maxNumberOfFiles: number;
94
229
  selected: boolean;
230
+ setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
95
231
  setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
96
232
  numberOfAttachmentPickerImageColumns?: number;
97
233
  };
@@ -102,28 +238,37 @@ const renderImage = ({
102
238
  maxNumberOfFiles,
103
239
  numberOfAttachmentPickerImageColumns,
104
240
  selected,
241
+ setSelectedFiles,
105
242
  setSelectedImages,
106
243
  } = item;
107
- const onPress = () => {
108
- if (selected) {
109
- setSelectedImages((images) => images.filter((image) => image.uri !== asset.uri));
110
- } else {
111
- setSelectedImages((images) => {
112
- if (images.length >= maxNumberOfFiles) {
113
- return images;
114
- }
115
- return [...images, asset];
116
- });
117
- }
118
- };
119
244
 
120
- return (
245
+ const contentType = lookup(asset.filename) || 'multipart/form-data';
246
+
247
+ const fileType = asset.filename
248
+ ? contentType.startsWith('image/')
249
+ ? 'image'
250
+ : 'video'
251
+ : asset.type === 'video'
252
+ ? 'video'
253
+ : 'image';
254
+
255
+ return fileType === 'image' ? (
121
256
  <AttachmentImage
257
+ asset={asset}
258
+ ImageOverlaySelectedComponent={ImageOverlaySelectedComponent}
259
+ maxNumberOfFiles={maxNumberOfFiles}
260
+ numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
261
+ selected={selected}
262
+ setSelectedImages={setSelectedImages}
263
+ />
264
+ ) : (
265
+ <AttachmentVideo
266
+ asset={asset}
122
267
  ImageOverlaySelectedComponent={ImageOverlaySelectedComponent}
268
+ maxNumberOfFiles={maxNumberOfFiles}
123
269
  numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
124
- onPress={onPress}
125
270
  selected={selected}
126
- uri={asset.uri}
271
+ setSelectedFiles={setSelectedFiles}
127
272
  />
128
273
  );
129
274
  };
@@ -187,8 +332,10 @@ export const AttachmentPicker = React.forwardRef(
187
332
  const {
188
333
  closePicker,
189
334
  maxNumberOfFiles,
335
+ selectedFiles,
190
336
  selectedImages,
191
337
  selectedPicker,
338
+ setSelectedFiles,
192
339
  setSelectedImages,
193
340
  setSelectedPicker,
194
341
  topInset,
@@ -200,13 +347,7 @@ export const AttachmentPicker = React.forwardRef(
200
347
  const [hasNextPage, setHasNextPage] = useState(true);
201
348
  const [loadingPhotos, setLoadingPhotos] = useState(false);
202
349
  const [photos, setPhotos] = useState<Asset[]>([]);
203
-
204
- const hideAttachmentPicker = () => {
205
- setSelectedPicker(undefined);
206
- if ((ref as React.MutableRefObject<BottomSheet>)?.current) {
207
- (ref as React.MutableRefObject<BottomSheet>).current.close();
208
- }
209
- };
350
+ const bottomSheetCloseOnKeyboardShowTimeout = useRef<NodeJS.Timeout>();
210
351
 
211
352
  const getMorePhotos = async () => {
212
353
  if (hasNextPage && !loadingPhotos && currentIndex > -1 && selectedPicker === 'images') {
@@ -248,6 +389,20 @@ export const AttachmentPicker = React.forwardRef(
248
389
  }, [selectedPicker]);
249
390
 
250
391
  useEffect(() => {
392
+ const hideAttachmentPicker = () => {
393
+ if (bottomSheetCloseOnKeyboardShowTimeout.current) {
394
+ clearTimeout(bottomSheetCloseOnKeyboardShowTimeout.current);
395
+ }
396
+ setSelectedPicker(undefined);
397
+ // This short timeout is to prevent a race condition
398
+ // where the close function is called during the point when a internal container layout happens within the bottomsheet due to keyboard affecting the layout
399
+ // If the container layout measures a shorter height than previous but if the close snapped to the previous height's position, the bottom sheet will show up
400
+ // this short delay ensures that close function is always called after a container layout due to keyboard change
401
+ bottomSheetCloseOnKeyboardShowTimeout.current = setTimeout(
402
+ () => (ref as React.MutableRefObject<BottomSheet | undefined>).current?.close(),
403
+ 150,
404
+ );
405
+ };
251
406
  const keyboardSubscription =
252
407
  Platform.OS === 'ios'
253
408
  ? Keyboard.addListener('keyboardWillShow', hideAttachmentPicker)
@@ -265,6 +420,9 @@ export const AttachmentPicker = React.forwardRef(
265
420
  } else {
266
421
  Keyboard.removeListener('keyboardDidShow', hideAttachmentPicker);
267
422
  }
423
+ if (bottomSheetCloseOnKeyboardShowTimeout.current) {
424
+ clearTimeout(bottomSheetCloseOnKeyboardShowTimeout.current);
425
+ }
268
426
  };
269
427
  }, []);
270
428
 
@@ -295,7 +453,10 @@ export const AttachmentPicker = React.forwardRef(
295
453
  ImageOverlaySelectedComponent,
296
454
  maxNumberOfFiles,
297
455
  numberOfAttachmentPickerImageColumns,
298
- selected: selectedImages.some((image) => image.uri === asset.uri),
456
+ selected:
457
+ selectedImages.some((image) => image.uri === asset.uri) ||
458
+ selectedFiles.some((file) => file.uri === asset.uri),
459
+ setSelectedFiles,
299
460
  setSelectedImages,
300
461
  }));
301
462
 
@@ -326,35 +487,24 @@ export const AttachmentPicker = React.forwardRef(
326
487
  : statusBarHeight
327
488
  : 0;
328
489
 
329
- const initialSnapPoint = useMemo(
330
- () =>
331
- attachmentPickerBottomSheetHeight ?? Platform.OS === 'android'
332
- ? 308 +
333
- (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment) -
334
- handleHeight
335
- : 308 + (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment),
336
- [
337
- attachmentPickerBottomSheetHeight,
338
- androidBottomBarHeightAdjustment,
339
- fullScreenHeight,
340
- handleHeight,
341
- screenHeight,
342
- ],
343
- );
490
+ const initialSnapPoint =
491
+ attachmentPickerBottomSheetHeight ?? Platform.OS === 'android'
492
+ ? 308 + (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment) - handleHeight
493
+ : 308 + (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment);
344
494
 
345
- const finalSnapPoint = useMemo(
346
- () =>
347
- Platform.OS === 'android'
348
- ? fullScreenHeight - topInset - handleHeight
349
- : fullScreenHeight - topInset,
350
- [fullScreenHeight, handleHeight, topInset],
351
- );
495
+ const finalSnapPoint =
496
+ Platform.OS === 'android'
497
+ ? fullScreenHeight - topInset - handleHeight
498
+ : fullScreenHeight - topInset;
352
499
 
353
500
  /**
354
501
  * Snap points changing cause a rerender of the position,
355
502
  * this is an issue if you are calling close on the bottom sheet.
356
503
  */
357
- const snapPoints = [initialSnapPoint, finalSnapPoint];
504
+ const snapPoints = useMemo(
505
+ () => [initialSnapPoint, finalSnapPoint],
506
+ [initialSnapPoint, finalSnapPoint],
507
+ );
358
508
 
359
509
  return (
360
510
  <>
@@ -370,7 +520,7 @@ export const AttachmentPicker = React.forwardRef(
370
520
  }
371
521
  handleHeight={handleHeight}
372
522
  index={-1}
373
- onChange={(index: number) => setCurrentIndex(index)}
523
+ onChange={setCurrentIndex}
374
524
  ref={ref}
375
525
  snapPoints={snapPoints}
376
526
  >
@@ -143,7 +143,7 @@ const MessageContentWithContext = <
143
143
  colors: { accent_red, blue_alice, grey_gainsboro, grey_whisper, transparent, white },
144
144
  messageSimple: {
145
145
  content: {
146
- container: { borderRadiusL, borderRadiusS, ...container },
146
+ container: { borderRadius, borderRadiusL, borderRadiusS, ...container },
147
147
  containerInner,
148
148
  errorContainer,
149
149
  errorIcon,
@@ -223,6 +223,22 @@ const MessageContentWithContext = <
223
223
 
224
224
  const repliesCurveColor = isMyMessage && !error ? backgroundColor : grey_whisper;
225
225
 
226
+ const isBorderColor = isMyMessage && !error;
227
+
228
+ const shouldApplyBorderRadius = (firstGroupStyle: string, secondGroupStyle: string): boolean =>
229
+ (groupStyle === firstGroupStyle || groupStyle === secondGroupStyle) &&
230
+ (!hasThreadReplies || threadList);
231
+
232
+ const applyBorderRadius = (
233
+ firstGroupStyle: string,
234
+ secondGroupStyle: string,
235
+ ): number | undefined => {
236
+ if (shouldApplyBorderRadius(firstGroupStyle, secondGroupStyle)) {
237
+ return borderRadiusS;
238
+ }
239
+ return borderRadiusL;
240
+ };
241
+
226
242
  return (
227
243
  <TouchableOpacity
228
244
  activeOpacity={0.7}
@@ -297,17 +313,12 @@ const MessageContentWithContext = <
297
313
  styles.containerInner,
298
314
  {
299
315
  backgroundColor,
300
- borderBottomLeftRadius:
301
- (groupStyle === 'left_bottom' || groupStyle === 'left_single') &&
302
- (!hasThreadReplies || threadList)
303
- ? borderRadiusS
304
- : borderRadiusL,
305
- borderBottomRightRadius:
306
- (groupStyle === 'right_bottom' || groupStyle === 'right_single') &&
307
- (!hasThreadReplies || threadList)
308
- ? borderRadiusS
309
- : borderRadiusL,
310
- borderColor: isMyMessage && !error ? backgroundColor : grey_whisper,
316
+ borderBottomLeftRadius: applyBorderRadius('left_bottom', 'left_single'),
317
+ borderBottomRightRadius: applyBorderRadius('right_bottom', 'right_single'),
318
+ borderColor: isBorderColor ? backgroundColor : grey_whisper,
319
+ borderRadius,
320
+ borderTopLeftRadius: applyBorderRadius('left_top', 'right_single'),
321
+ borderTopRightRadius: applyBorderRadius('right_top', 'right_single'),
311
322
  },
312
323
  noBorder ? { borderWidth: 0 } : {},
313
324
  containerInner,
@@ -105,7 +105,7 @@ const UnsupportedFileTypeOrFileSizeIndicator = ({
105
105
  </View>
106
106
  ) : (
107
107
  <Text style={[styles.fileSizeText, { color: grey }, fileSizeText]}>
108
- {getFileSizeDisplayText(item.file.size)}
108
+ {item.file.duration ? item.file.duration : getFileSizeDisplayText(item.file.size)}
109
109
  </Text>
110
110
  );
111
111
  };