stream-chat-react-native-core 5.4.2-beta.1 → 5.4.2-beta.3

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 (45) hide show
  1. package/lib/commonjs/components/AttachmentPicker/AttachmentPicker.js +122 -433
  2. package/lib/commonjs/components/AttachmentPicker/AttachmentPicker.js.map +1 -1
  3. package/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerItem.js +358 -0
  4. package/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerItem.js.map +1 -0
  5. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteInput.js +1 -1
  6. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  7. package/lib/commonjs/components/MessageList/NetworkDownIndicator.js +14 -6
  8. package/lib/commonjs/components/MessageList/NetworkDownIndicator.js.map +1 -1
  9. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +134 -85
  10. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  11. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js +39 -17
  12. package/lib/commonjs/contexts/overlayContext/OverlayProvider.js.map +1 -1
  13. package/lib/commonjs/hooks/useAppStateListener.js +9 -15
  14. package/lib/commonjs/hooks/useAppStateListener.js.map +1 -1
  15. package/lib/commonjs/version.json +1 -1
  16. package/lib/module/components/AttachmentPicker/AttachmentPicker.js +122 -433
  17. package/lib/module/components/AttachmentPicker/AttachmentPicker.js.map +1 -1
  18. package/lib/module/components/AttachmentPicker/components/AttachmentPickerItem.js +358 -0
  19. package/lib/module/components/AttachmentPicker/components/AttachmentPickerItem.js.map +1 -0
  20. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js +1 -1
  21. package/lib/module/components/AutoCompleteInput/AutoCompleteInput.js.map +1 -1
  22. package/lib/module/components/MessageList/NetworkDownIndicator.js +14 -6
  23. package/lib/module/components/MessageList/NetworkDownIndicator.js.map +1 -1
  24. package/lib/module/contexts/messageInputContext/MessageInputContext.js +134 -85
  25. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  26. package/lib/module/contexts/overlayContext/OverlayProvider.js +39 -17
  27. package/lib/module/contexts/overlayContext/OverlayProvider.js.map +1 -1
  28. package/lib/module/hooks/useAppStateListener.js +9 -15
  29. package/lib/module/hooks/useAppStateListener.js.map +1 -1
  30. package/lib/module/version.json +1 -1
  31. package/lib/typescript/components/AttachmentPicker/AttachmentPicker.d.ts +0 -11
  32. package/lib/typescript/components/AttachmentPicker/components/AttachmentPickerItem.d.ts +16 -0
  33. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +1 -1
  34. package/lib/typescript/hooks/__tests__/useAppStateListener.test.d.ts +1 -0
  35. package/package.json +2 -2
  36. package/src/components/AttachmentPicker/AttachmentPicker.tsx +79 -322
  37. package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx +245 -0
  38. package/src/components/AutoCompleteInput/AutoCompleteInput.tsx +1 -1
  39. package/src/components/MessageInput/__tests__/__snapshots__/MessageInput.test.js.snap +1 -1
  40. package/src/components/MessageList/NetworkDownIndicator.tsx +13 -10
  41. package/src/contexts/messageInputContext/MessageInputContext.tsx +27 -25
  42. package/src/contexts/overlayContext/OverlayProvider.tsx +28 -4
  43. package/src/hooks/__tests__/useAppStateListener.test.tsx +41 -0
  44. package/src/hooks/useAppStateListener.ts +9 -12
  45. package/src/version.json +1 -1
@@ -1,31 +1,25 @@
1
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
2
-
3
1
  Object.defineProperty(exports, "__esModule", {
4
2
  value: true
5
3
  });
6
4
  exports.useAppStateListener = void 0;
7
5
 
8
- var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
-
10
6
  var _react = require("react");
11
7
 
12
8
  var _reactNative = require("react-native");
13
9
 
14
10
  var useAppStateListener = function useAppStateListener(onForeground, onBackground) {
15
- var _useState = (0, _react.useState)(_reactNative.AppState.currentState),
16
- _useState2 = (0, _slicedToArray2["default"])(_useState, 2),
17
- appState = _useState2[0],
18
- setAppState = _useState2[1];
19
-
11
+ var appStateRef = (0, _react.useRef)(_reactNative.AppState.currentState);
20
12
  var handleAppStateChange = (0, _react.useCallback)(function (nextAppState) {
21
- if (appState === 'background' && nextAppState === 'active' && onForeground) {
22
- onForeground();
23
- } else if (appState.match(/active|inactive/) && nextAppState === 'background' && onBackground) {
24
- onBackground();
13
+ var prevAppState = appStateRef.current;
14
+
15
+ if (prevAppState.match(/inactive|background/) && nextAppState === 'active') {
16
+ onForeground == null ? void 0 : onForeground();
17
+ } else if (prevAppState === 'active' && nextAppState.match(/inactive|background/)) {
18
+ onBackground == null ? void 0 : onBackground();
25
19
  }
26
20
 
27
- setAppState(nextAppState);
28
- }, [onBackground, onForeground, appState]);
21
+ appStateRef.current = nextAppState;
22
+ }, [onBackground, onForeground]);
29
23
  (0, _react.useEffect)(function () {
30
24
  var subscription = _reactNative.AppState.addEventListener('change', handleAppStateChange);
31
25
 
@@ -1 +1 @@
1
- {"version":3,"sources":["useAppStateListener.ts"],"names":["useAppStateListener","onForeground","onBackground","AppState","currentState","appState","setAppState","handleAppStateChange","nextAppState","match","subscription","addEventListener","remove","removeEventListener"],"mappings":";;;;;;;;;AAAA;;AACA;;AAEO,IAAMA,mBAAmB,GAAG,SAAtBA,mBAAsB,CAACC,YAAD,EAA4BC,YAA5B,EAA0D;AAC3F,kBAAgC,qBAASC,sBAASC,YAAlB,CAAhC;AAAA;AAAA,MAAOC,QAAP;AAAA,MAAiBC,WAAjB;;AACA,MAAMC,oBAAoB,GAAG,wBAC3B,UAACC,YAAD,EAAkC;AAChC,QAAIH,QAAQ,KAAK,YAAb,IAA6BG,YAAY,KAAK,QAA9C,IAA0DP,YAA9D,EAA4E;AAC1EA,MAAAA,YAAY;AACb,KAFD,MAEO,IACLI,QAAQ,CAACI,KAAT,CAAe,iBAAf,KACAD,YAAY,KAAK,YADjB,IAEAN,YAHK,EAIL;AACAA,MAAAA,YAAY;AACb;;AACDI,IAAAA,WAAW,CAACE,YAAD,CAAX;AACD,GAZ0B,EAa3B,CAACN,YAAD,EAAeD,YAAf,EAA6BI,QAA7B,CAb2B,CAA7B;AAgBA,wBAAU,YAAM;AACd,QAAMK,YAAY,GAAGP,sBAASQ,gBAAT,CAA0B,QAA1B,EAAoCJ,oBAApC,CAArB;;AAEA,WAAO,YAAM;AAGX,UAAIG,YAAJ,YAAIA,YAAY,CAAEE,MAAlB,EAA0B;AACxBF,QAAAA,YAAY,CAACE,MAAb;AACD,OAFD,MAEO;AACLT,8BAASU,mBAAT,CAA6B,QAA7B,EAAuCN,oBAAvC;AACD;AACF,KARD;AASD,GAZD,EAYG,CAACA,oBAAD,CAZH;AAaD,CA/BM","sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { AppState, AppStateStatus } from 'react-native';\n\nexport const useAppStateListener = (onForeground?: () => void, onBackground?: () => void) => {\n const [appState, setAppState] = useState(AppState.currentState);\n const handleAppStateChange = useCallback(\n (nextAppState: AppStateStatus) => {\n if (appState === 'background' && nextAppState === 'active' && onForeground) {\n onForeground();\n } else if (\n appState.match(/active|inactive/) &&\n nextAppState === 'background' &&\n onBackground\n ) {\n onBackground();\n }\n setAppState(nextAppState);\n },\n [onBackground, onForeground, appState],\n );\n\n useEffect(() => {\n const subscription = AppState.addEventListener('change', handleAppStateChange);\n\n return () => {\n // Following if-else logic is to support RN >= 0.65 and RN < 0.65 versions.\n // https://github.com/react-native-community/releases/blob/master/CHANGELOG.md#:~:text=EventEmitter%23removeSubscription%20is%20now%20deprecated.%20(cb6cbd12f8%20by%20%40yungsters)\n if (subscription?.remove) {\n subscription.remove();\n } else {\n AppState.removeEventListener('change', handleAppStateChange);\n }\n };\n }, [handleAppStateChange]);\n};\n"]}
1
+ {"version":3,"sources":["useAppStateListener.ts"],"names":["useAppStateListener","onForeground","onBackground","appStateRef","AppState","currentState","handleAppStateChange","nextAppState","prevAppState","current","match","subscription","addEventListener","remove","removeEventListener"],"mappings":";;;;;AAAA;;AACA;;AAEO,IAAMA,mBAAmB,GAAG,SAAtBA,mBAAsB,CAACC,YAAD,EAA4BC,YAA5B,EAA0D;AAC3F,MAAMC,WAAW,GAAG,mBAAOC,sBAASC,YAAhB,CAApB;AACA,MAAMC,oBAAoB,GAAG,wBAC3B,UAACC,YAAD,EAAkC;AAChC,QAAMC,YAAY,GAAGL,WAAW,CAACM,OAAjC;;AACA,QAAID,YAAY,CAACE,KAAb,CAAmB,qBAAnB,KAA6CH,YAAY,KAAK,QAAlE,EAA4E;AAC1EN,MAAAA,YAAY,QAAZ,YAAAA,YAAY;AACb,KAFD,MAEO,IAAIO,YAAY,KAAK,QAAjB,IAA6BD,YAAY,CAACG,KAAb,CAAmB,qBAAnB,CAAjC,EAA4E;AACjFR,MAAAA,YAAY,QAAZ,YAAAA,YAAY;AACb;;AACDC,IAAAA,WAAW,CAACM,OAAZ,GAAsBF,YAAtB;AACD,GAT0B,EAU3B,CAACL,YAAD,EAAeD,YAAf,CAV2B,CAA7B;AAaA,wBAAU,YAAM;AACd,QAAMU,YAAY,GAAGP,sBAASQ,gBAAT,CAA0B,QAA1B,EAAoCN,oBAApC,CAArB;;AAEA,WAAO,YAAM;AAGX,UAAIK,YAAJ,YAAIA,YAAY,CAAEE,MAAlB,EAA0B;AACxBF,QAAAA,YAAY,CAACE,MAAb;AACD,OAFD,MAEO;AACLT,8BAASU,mBAAT,CAA6B,QAA7B,EAAuCR,oBAAvC;AACD;AACF,KARD;AASD,GAZD,EAYG,CAACA,oBAAD,CAZH;AAaD,CA5BM","sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { AppState, AppStateStatus } from 'react-native';\n\nexport const useAppStateListener = (onForeground?: () => void, onBackground?: () => void) => {\n const appStateRef = useRef(AppState.currentState);\n const handleAppStateChange = useCallback(\n (nextAppState: AppStateStatus) => {\n const prevAppState = appStateRef.current;\n if (prevAppState.match(/inactive|background/) && nextAppState === 'active') {\n onForeground?.();\n } else if (prevAppState === 'active' && nextAppState.match(/inactive|background/)) {\n onBackground?.();\n }\n appStateRef.current = nextAppState;\n },\n [onBackground, onForeground],\n );\n\n useEffect(() => {\n const subscription = AppState.addEventListener('change', handleAppStateChange);\n\n return () => {\n // Following if-else logic is to support RN >= 0.65 and RN < 0.65 versions.\n // https://github.com/react-native-community/releases/blob/master/CHANGELOG.md#:~:text=EventEmitter%23removeSubscription%20is%20now%20deprecated.%20(cb6cbd12f8%20by%20%40yungsters)\n if (subscription?.remove) {\n subscription.remove();\n } else {\n AppState.removeEventListener('change', handleAppStateChange);\n }\n };\n }, [handleAppStateChange]);\n};\n"]}
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "5.4.2-beta.1"
2
+ "version": "5.4.2-beta.3"
3
3
  }
@@ -1,17 +1,6 @@
1
1
  import React from 'react';
2
2
  import { BottomSheetHandleProps } from '@gorhom/bottom-sheet';
3
3
  import type { AttachmentPickerErrorProps } from './components/AttachmentPickerError';
4
- import type { Asset, File } from '../../types/types';
5
- export declare type AttachmentPickerItemType = {
6
- asset: Asset;
7
- ImageOverlaySelectedComponent: React.ComponentType;
8
- maxNumberOfFiles: number;
9
- numberOfUploads: number;
10
- selected: boolean;
11
- setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
12
- setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
13
- numberOfAttachmentPickerImageColumns?: number;
14
- };
15
4
  export declare type AttachmentPickerProps = {
16
5
  /**
17
6
  * Custom UI component to render [draggable handle](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) of attachment picker.
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import type { Asset, File } from '../../../types/types';
3
+ declare type AttachmentPickerItemType = {
4
+ asset: Asset;
5
+ ImageOverlaySelectedComponent: React.ComponentType;
6
+ maxNumberOfFiles: number;
7
+ numberOfUploads: number;
8
+ selected: boolean;
9
+ setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
10
+ setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
11
+ numberOfAttachmentPickerImageColumns?: number;
12
+ };
13
+ export declare const renderAttachmentPickerItem: ({ item }: {
14
+ item: AttachmentPickerItemType;
15
+ }) => JSX.Element;
16
+ export {};
@@ -166,7 +166,7 @@ export declare type LocalMessageInputContext<StreamChatGenerics extends DefaultS
166
166
  newImage: ImageUpload;
167
167
  }) => Promise<void>;
168
168
  uploadNewFile: (file: File) => Promise<void>;
169
- uploadNewImage: (image: Asset) => Promise<void>;
169
+ uploadNewImage: (image: Partial<Asset>) => Promise<void>;
170
170
  };
171
171
  export declare type InputMessageInputContextValue<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
172
172
  /**
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": "5.4.2-beta.1",
4
+ "version": "5.4.2-beta.3",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "@babel/runtime": "^7.12.5",
72
- "@gorhom/bottom-sheet": "^4.1.6",
72
+ "@gorhom/bottom-sheet": "4.4.5",
73
73
  "dayjs": "1.10.5",
74
74
  "file-loader": "6.2.0",
75
75
  "i18next": "20.2.4",
@@ -1,34 +1,19 @@
1
- import React, { useEffect, useMemo, useRef, useState } from 'react';
2
- import {
3
- Alert,
4
- BackHandler,
5
- Dimensions,
6
- ImageBackground,
7
- Keyboard,
8
- Platform,
9
- StatusBar,
10
- StyleSheet,
11
- Text,
12
- View,
13
- } from 'react-native';
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { BackHandler, Dimensions, Keyboard, Platform, StatusBar, StyleSheet } from 'react-native';
14
3
 
15
- import BottomSheet, {
16
- BottomSheetFlatList,
17
- BottomSheetHandleProps,
18
- TouchableOpacity,
19
- } from '@gorhom/bottom-sheet';
4
+ import BottomSheet, { BottomSheetFlatList, BottomSheetHandleProps } from '@gorhom/bottom-sheet';
20
5
  import dayjs from 'dayjs';
21
6
  import duration from 'dayjs/plugin/duration';
22
- import { lookup } from 'mime-types';
23
7
 
24
8
  import type { AttachmentPickerErrorProps } from './components/AttachmentPickerError';
25
9
 
10
+ import { renderAttachmentPickerItem } from './components/AttachmentPickerItem';
11
+
26
12
  import { useAttachmentPickerContext } from '../../contexts/attachmentPickerContext/AttachmentPickerContext';
27
13
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
28
- import { Recorder } from '../../icons';
29
- import { getLocalAssetUri, getPhotos } from '../../native';
30
- import type { Asset, File } from '../../types/types';
31
- import { vh, vw } from '../../utils/utils';
14
+ import { getPhotos } from '../../native';
15
+ import type { Asset } from '../../types/types';
16
+ import { vh } from '../../utils/utils';
32
17
 
33
18
  dayjs.extend(duration);
34
19
 
@@ -36,238 +21,11 @@ const styles = StyleSheet.create({
36
21
  container: {
37
22
  flexGrow: 1,
38
23
  },
39
- durationText: {
40
- fontWeight: 'bold',
41
- },
42
- overlay: {
43
- alignItems: 'flex-end',
44
- flex: 1,
45
- },
46
- videoView: {
47
- bottom: 5,
48
- display: 'flex',
49
- flexDirection: 'row',
50
- justifyContent: 'space-between',
51
- paddingHorizontal: 5,
52
- position: 'absolute',
53
- width: '100%',
54
- },
55
24
  });
56
25
 
57
26
  const screenHeight = vh(100);
58
27
  const fullScreenHeight = Dimensions.get('window').height;
59
28
 
60
- export type AttachmentPickerItemType = {
61
- asset: Asset;
62
- ImageOverlaySelectedComponent: React.ComponentType;
63
- maxNumberOfFiles: number;
64
- numberOfUploads: number;
65
- selected: boolean;
66
- setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
67
- setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
68
- numberOfAttachmentPickerImageColumns?: number;
69
- };
70
-
71
- type AttachmentImageProps = Omit<AttachmentPickerItemType, 'setSelectedFiles'>;
72
-
73
- type AttachmentVideoProps = Omit<AttachmentPickerItemType, 'setSelectedImages'>;
74
-
75
- const AttachmentVideo: React.FC<AttachmentVideoProps> = (props) => {
76
- const {
77
- asset,
78
- ImageOverlaySelectedComponent,
79
- maxNumberOfFiles,
80
- numberOfAttachmentPickerImageColumns,
81
- numberOfUploads,
82
- selected,
83
- setSelectedFiles,
84
- } = props;
85
-
86
- const {
87
- theme: {
88
- attachmentPicker: { durationText, image, imageOverlay },
89
- colors: { overlay, white },
90
- },
91
- } = useTheme();
92
-
93
- const { duration, playableDuration, uri } = asset;
94
-
95
- const videoDuration = duration ? duration : playableDuration;
96
-
97
- const ONE_HOUR_IN_SECONDS = 3600;
98
-
99
- let durationLabel = '00:00';
100
-
101
- if (videoDuration) {
102
- const isDurationLongerThanHour = videoDuration / ONE_HOUR_IN_SECONDS >= 1;
103
- const formattedDurationParam = isDurationLongerThanHour ? 'HH:mm:ss' : 'mm:ss';
104
- const formattedVideoDuration = dayjs
105
- .duration(videoDuration, 'second')
106
- .format(formattedDurationParam);
107
- durationLabel = formattedVideoDuration;
108
- }
109
-
110
- const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2;
111
-
112
- const onPressVideo = async () => {
113
- // For the case of expo messaging app where you need to fetch the asset uri from asset id
114
- const localAssetURI = asset.id && (await getLocalAssetUri(asset.id));
115
- if (selected) {
116
- setSelectedFiles((files) => files.filter((file) => file.uri !== asset.uri));
117
- } else {
118
- setSelectedFiles((files) => {
119
- if (numberOfUploads >= maxNumberOfFiles) {
120
- Alert.alert('Maximum number of files reached');
121
- return files;
122
- }
123
- return [
124
- ...files,
125
- {
126
- duration: durationLabel,
127
- name: asset.filename,
128
- size: asset.fileSize,
129
- type: 'video',
130
- uri: localAssetURI || asset.uri,
131
- },
132
- ];
133
- });
134
- }
135
- };
136
-
137
- return (
138
- <TouchableOpacity onPress={onPressVideo}>
139
- <ImageBackground
140
- source={{ uri }}
141
- style={[
142
- {
143
- height: size,
144
- margin: 1,
145
- width: size,
146
- },
147
- image,
148
- ]}
149
- >
150
- {selected && (
151
- <View style={[styles.overlay, { backgroundColor: overlay }, imageOverlay]}>
152
- <ImageOverlaySelectedComponent />
153
- </View>
154
- )}
155
- <View style={styles.videoView}>
156
- <Recorder height={20} pathFill={white} width={25} />
157
- {videoDuration ? (
158
- <Text style={[styles.durationText, durationText, { color: white }]}>
159
- {durationLabel}
160
- </Text>
161
- ) : null}
162
- </View>
163
- </ImageBackground>
164
- </TouchableOpacity>
165
- );
166
- };
167
-
168
- const AttachmentImage: React.FC<AttachmentImageProps> = (props) => {
169
- const {
170
- asset,
171
- ImageOverlaySelectedComponent,
172
- maxNumberOfFiles,
173
- numberOfAttachmentPickerImageColumns,
174
- numberOfUploads,
175
- selected,
176
- setSelectedImages,
177
- } = props;
178
- const {
179
- theme: {
180
- attachmentPicker: { image, imageOverlay },
181
- colors: { overlay },
182
- },
183
- } = useTheme();
184
-
185
- const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2;
186
-
187
- const { uri } = asset;
188
-
189
- const onPressImage = () => {
190
- if (selected) {
191
- setSelectedImages((images) => images.filter((image) => image.uri !== asset.uri));
192
- } else {
193
- setSelectedImages((images) => {
194
- if (numberOfUploads >= maxNumberOfFiles) {
195
- Alert.alert('Maximum number of files reached');
196
- return images;
197
- }
198
- return [...images, asset];
199
- });
200
- }
201
- };
202
-
203
- return (
204
- <TouchableOpacity onPress={onPressImage}>
205
- <ImageBackground
206
- source={{ uri }}
207
- style={[
208
- {
209
- height: size,
210
- margin: 1,
211
- width: size,
212
- },
213
- image,
214
- ]}
215
- >
216
- {selected && (
217
- <View style={[styles.overlay, { backgroundColor: overlay }, imageOverlay]}>
218
- <ImageOverlaySelectedComponent />
219
- </View>
220
- )}
221
- </ImageBackground>
222
- </TouchableOpacity>
223
- );
224
- };
225
-
226
- const renderItem = ({ item }: { item: AttachmentPickerItemType }) => {
227
- const {
228
- asset,
229
- ImageOverlaySelectedComponent,
230
- maxNumberOfFiles,
231
- numberOfAttachmentPickerImageColumns,
232
- numberOfUploads,
233
- selected,
234
- setSelectedFiles,
235
- setSelectedImages,
236
- } = item;
237
-
238
- const contentType = lookup(asset.filename) || 'multipart/form-data';
239
-
240
- const fileType = asset.filename
241
- ? contentType.startsWith('image/')
242
- ? 'image'
243
- : 'video'
244
- : asset.type === 'video'
245
- ? 'video'
246
- : 'image';
247
-
248
- return fileType === 'image' ? (
249
- <AttachmentImage
250
- asset={asset}
251
- ImageOverlaySelectedComponent={ImageOverlaySelectedComponent}
252
- maxNumberOfFiles={maxNumberOfFiles}
253
- numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
254
- numberOfUploads={numberOfUploads}
255
- selected={selected}
256
- setSelectedImages={setSelectedImages}
257
- />
258
- ) : (
259
- <AttachmentVideo
260
- asset={asset}
261
- ImageOverlaySelectedComponent={ImageOverlaySelectedComponent}
262
- maxNumberOfFiles={maxNumberOfFiles}
263
- numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
264
- numberOfUploads={numberOfUploads}
265
- selected={selected}
266
- setSelectedFiles={setSelectedFiles}
267
- />
268
- );
269
- };
270
-
271
29
  export type AttachmentPickerProps = {
272
30
  /**
273
31
  * Custom UI component to render [draggable handle](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) of attachment picker.
@@ -337,35 +95,39 @@ export const AttachmentPicker = React.forwardRef(
337
95
  } = useAttachmentPickerContext();
338
96
 
339
97
  const [currentIndex, setCurrentIndex] = useState(-1);
340
- const [endCursor, setEndCursor] = useState<string>();
98
+ const endCursorRef = useRef<string>();
341
99
  const [photoError, setPhotoError] = useState(false);
342
- const [hasNextPage, setHasNextPage] = useState(true);
100
+ const hasNextPageRef = useRef(true);
343
101
  const [loadingPhotos, setLoadingPhotos] = useState(false);
344
102
  const [photos, setPhotos] = useState<Asset[]>([]);
345
- const bottomSheetCloseOnKeyboardShowTimeout = useRef<NodeJS.Timeout>();
103
+ const attemptedToLoadPhotosOnOpenRef = useRef(false);
346
104
 
347
- const getMorePhotos = async () => {
348
- if (hasNextPage && !loadingPhotos && currentIndex > -1 && selectedPicker === 'images') {
105
+ const getMorePhotos = useCallback(async () => {
106
+ if (
107
+ hasNextPageRef.current &&
108
+ !loadingPhotos &&
109
+ currentIndex > -1 &&
110
+ selectedPicker === 'images'
111
+ ) {
112
+ setPhotoError(false);
349
113
  setLoadingPhotos(true);
114
+ const endCursor = endCursorRef.current;
350
115
  try {
351
116
  const results = await getPhotos({
352
117
  after: endCursor,
353
118
  first: numberOfAttachmentImagesToLoadPerCall ?? 60,
354
119
  });
355
- if (endCursor) {
356
- setPhotos([...photos, ...results.assets]);
357
- } else {
358
- setPhotos(results.assets);
359
- }
360
- setEndCursor(results.endCursor);
361
- setHasNextPage(results.hasNextPage || false);
120
+ endCursorRef.current = results.endCursor;
121
+ setPhotos((prevPhotos) =>
122
+ endCursor ? [...prevPhotos, ...results.assets] : results.assets,
123
+ );
124
+ hasNextPageRef.current = !!results.hasNextPage;
362
125
  } catch (error) {
363
- console.log(error);
364
126
  setPhotoError(true);
365
127
  }
366
128
  setLoadingPhotos(false);
367
129
  }
368
- };
130
+ }, [currentIndex, selectedPicker, loadingPhotos]);
369
131
 
370
132
  useEffect(() => {
371
133
  const backAction = () => {
@@ -381,27 +143,11 @@ export const AttachmentPicker = React.forwardRef(
381
143
  const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
382
144
 
383
145
  return () => backHandler.remove();
384
- }, [selectedPicker]);
146
+ }, [selectedPicker, closePicker]);
385
147
 
386
148
  useEffect(() => {
387
- const hideAttachmentPicker = () => {
388
- if (bottomSheetCloseOnKeyboardShowTimeout.current) {
389
- clearTimeout(bottomSheetCloseOnKeyboardShowTimeout.current);
390
- }
391
- setSelectedPicker(undefined);
392
- // This short timeout is to prevent a race condition
393
- // where the close function is called during the point when a internal container layout happens within the bottomsheet due to keyboard affecting the layout
394
- // 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
395
- // this short delay ensures that close function is always called after a container layout due to keyboard change
396
- bottomSheetCloseOnKeyboardShowTimeout.current = setTimeout(
397
- () => (ref as React.MutableRefObject<BottomSheet | undefined>).current?.close(),
398
- 150,
399
- );
400
- };
401
- const keyboardSubscription =
402
- Platform.OS === 'ios'
403
- ? Keyboard.addListener('keyboardWillShow', hideAttachmentPicker)
404
- : Keyboard.addListener('keyboardDidShow', hideAttachmentPicker);
149
+ const keyboardShowEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
150
+ const keyboardSubscription = Keyboard.addListener(keyboardShowEvent, closePicker);
405
151
 
406
152
  return () => {
407
153
  if (keyboardSubscription?.remove) {
@@ -410,38 +156,35 @@ export const AttachmentPicker = React.forwardRef(
410
156
  }
411
157
 
412
158
  // To keep compatibility with older versions of React Native, where `remove()` is not available
413
- if (Platform.OS === 'ios') {
414
- Keyboard.removeListener('keyboardWillShow', hideAttachmentPicker);
415
- } else {
416
- Keyboard.removeListener('keyboardDidShow', hideAttachmentPicker);
417
- }
418
- if (bottomSheetCloseOnKeyboardShowTimeout.current) {
419
- clearTimeout(bottomSheetCloseOnKeyboardShowTimeout.current);
420
- }
159
+ Keyboard.removeListener(keyboardShowEvent, closePicker);
421
160
  };
422
- }, []);
161
+ }, [closePicker]);
423
162
 
424
163
  useEffect(() => {
425
164
  if (currentIndex < 0) {
426
165
  setSelectedPicker(undefined);
427
166
  if (!loadingPhotos) {
428
- setEndCursor(undefined);
429
- setHasNextPage(true);
167
+ endCursorRef.current = undefined;
168
+ hasNextPageRef.current = true;
169
+ attemptedToLoadPhotosOnOpenRef.current = false;
170
+ setPhotoError(false);
430
171
  }
431
172
  }
432
- }, [currentIndex]);
173
+ }, [currentIndex, loadingPhotos]);
433
174
 
434
175
  useEffect(() => {
435
176
  if (
177
+ !attemptedToLoadPhotosOnOpenRef.current &&
436
178
  selectedPicker === 'images' &&
437
- endCursor === undefined &&
179
+ endCursorRef.current === undefined &&
438
180
  currentIndex > -1 &&
439
181
  !loadingPhotos
440
182
  ) {
441
- setPhotoError(false);
442
183
  getMorePhotos();
184
+ // we do this only once on open for avoiding to request permissions in rationale dialog again and again on Android
185
+ attemptedToLoadPhotosOnOpenRef.current = true;
443
186
  }
444
- }, [currentIndex, selectedPicker]);
187
+ }, [currentIndex, selectedPicker, getMorePhotos, loadingPhotos]);
445
188
 
446
189
  const selectedPhotos = photos.map((asset) => ({
447
190
  asset,
@@ -464,29 +207,43 @@ export const AttachmentPicker = React.forwardRef(
464
207
  * dimensions for screen and window, it is incorrect and we need to account for
465
208
  * this. If you use a translucent header bar more adjustments are needed.
466
209
  */
467
- const statusBarHeight = StatusBar.currentHeight ?? 0;
468
- const bottomBarHeight = fullScreenHeight - screenHeight - statusBarHeight;
469
- const androidBottomBarHeightAdjustment =
470
- Platform.OS === 'android'
471
- ? bottomBarHeight === statusBarHeight
472
- ? translucentStatusBar
473
- ? 0
474
- : StatusBar.currentHeight ?? 0
475
- : translucentStatusBar
476
- ? bottomBarHeight > statusBarHeight
477
- ? -bottomBarHeight + statusBarHeight
478
- : bottomBarHeight > 0
479
- ? -statusBarHeight
480
- : 0
481
- : bottomBarHeight > 0
482
- ? 0
483
- : statusBarHeight
484
- : 0;
210
+ const getAndroidBottomBarHeightAdjustment = (): number => {
211
+ if (Platform.OS === 'android') {
212
+ const statusBarHeight = StatusBar.currentHeight ?? 0;
213
+ const bottomBarHeight = fullScreenHeight - screenHeight - statusBarHeight;
214
+ if (bottomBarHeight === statusBarHeight) {
215
+ return translucentStatusBar ? 0 : statusBarHeight;
216
+ } else {
217
+ if (translucentStatusBar) {
218
+ if (bottomBarHeight > statusBarHeight) {
219
+ return -bottomBarHeight + statusBarHeight;
220
+ } else {
221
+ return bottomBarHeight > 0 ? -statusBarHeight : 0;
222
+ }
223
+ } else {
224
+ return bottomBarHeight > 0 ? 0 : statusBarHeight;
225
+ }
226
+ }
227
+ }
228
+ return 0;
229
+ };
230
+
231
+ const getInitialSnapPoint = (): number => {
232
+ if (attachmentPickerBottomSheetHeight !== undefined) {
233
+ return attachmentPickerBottomSheetHeight;
234
+ }
235
+ if (Platform.OS === 'android') {
236
+ return (
237
+ 308 +
238
+ (fullScreenHeight - screenHeight + getAndroidBottomBarHeightAdjustment()) -
239
+ handleHeight
240
+ );
241
+ } else {
242
+ return 308 + (fullScreenHeight - screenHeight);
243
+ }
244
+ };
485
245
 
486
- const initialSnapPoint =
487
- attachmentPickerBottomSheetHeight ?? Platform.OS === 'android'
488
- ? 308 + (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment) - handleHeight
489
- : 308 + (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment);
246
+ const initialSnapPoint = getInitialSnapPoint();
490
247
 
491
248
  const finalSnapPoint =
492
249
  Platform.OS === 'android'
@@ -531,12 +288,12 @@ export const AttachmentPicker = React.forwardRef(
531
288
  keyExtractor={(item) => item.asset.uri}
532
289
  numColumns={numberOfAttachmentPickerImageColumns ?? 3}
533
290
  onEndReached={getMorePhotos}
534
- renderItem={renderItem}
291
+ renderItem={renderAttachmentPickerItem}
535
292
  />
536
293
  </BottomSheet>
537
294
  {selectedPicker === 'images' && photoError && (
538
295
  <AttachmentPickerError
539
- attachmentPickerBottomSheetHeight={attachmentPickerBottomSheetHeight}
296
+ attachmentPickerBottomSheetHeight={initialSnapPoint}
540
297
  attachmentPickerErrorButtonText={attachmentPickerErrorButtonText}
541
298
  AttachmentPickerErrorImage={AttachmentPickerErrorImage}
542
299
  attachmentPickerErrorText={attachmentPickerErrorText}