stream-chat-react-native-core 9.1.0 → 9.1.1-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.
- package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +318 -160
- package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +318 -160
- package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +46 -1
- package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx +396 -172
- package/src/version.json +1 -1
package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
FlatList,
|
|
4
3
|
I18nManager,
|
|
5
4
|
LayoutChangeEvent,
|
|
6
5
|
NativeScrollEvent,
|
|
7
6
|
NativeSyntheticEvent,
|
|
7
|
+
ScrollView,
|
|
8
|
+
StyleProp,
|
|
8
9
|
StyleSheet,
|
|
9
10
|
View,
|
|
11
|
+
ViewStyle,
|
|
10
12
|
} from 'react-native';
|
|
11
13
|
|
|
12
|
-
import Animated, {
|
|
13
|
-
cancelAnimation,
|
|
14
|
-
ZoomIn,
|
|
15
|
-
ZoomOut,
|
|
16
|
-
LinearTransition,
|
|
17
|
-
useAnimatedStyle,
|
|
18
|
-
useSharedValue,
|
|
19
|
-
withSpring,
|
|
20
|
-
} from 'react-native-reanimated';
|
|
14
|
+
import Animated, { LinearTransition, ZoomIn, ZoomOut } from 'react-native-reanimated';
|
|
21
15
|
|
|
22
16
|
import {
|
|
23
17
|
isLocalAudioAttachment,
|
|
@@ -39,16 +33,30 @@ import { isSoundPackageAvailable } from '../../../../native';
|
|
|
39
33
|
import { primitives } from '../../../../theme';
|
|
40
34
|
|
|
41
35
|
const END_ANCHOR_THRESHOLD = 16;
|
|
42
|
-
const
|
|
36
|
+
const ATTACHMENT_PREVIEW_ANIMATION_DURATION = 200;
|
|
37
|
+
const TRAILING_SPACER_RELEASE_DELAY = ATTACHMENT_PREVIEW_ANIMATION_DURATION + 80;
|
|
43
38
|
const MAX_AUDIO_ATTACHMENTS_CONTAINER_WIDTH = 560;
|
|
39
|
+
const attachmentPreviewEntering = ZoomIn.duration(ATTACHMENT_PREVIEW_ANIMATION_DURATION);
|
|
40
|
+
const attachmentPreviewExiting = ZoomOut.duration(ATTACHMENT_PREVIEW_ANIMATION_DURATION);
|
|
41
|
+
const attachmentPreviewLayout = LinearTransition.duration(ATTACHMENT_PREVIEW_ANIMATION_DURATION);
|
|
44
42
|
|
|
45
43
|
export type AttachmentUploadListPreviewPropsWithContext = Record<string, never>;
|
|
46
44
|
|
|
47
|
-
const AttachmentPreviewCell = ({
|
|
45
|
+
const AttachmentPreviewCell = ({
|
|
46
|
+
children,
|
|
47
|
+
onLayout,
|
|
48
|
+
style,
|
|
49
|
+
}: {
|
|
50
|
+
children: React.ReactNode;
|
|
51
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
52
|
+
style?: StyleProp<ViewStyle>;
|
|
53
|
+
}) => (
|
|
48
54
|
<Animated.View
|
|
49
|
-
entering={
|
|
50
|
-
exiting={
|
|
51
|
-
layout={
|
|
55
|
+
entering={attachmentPreviewEntering}
|
|
56
|
+
exiting={attachmentPreviewExiting}
|
|
57
|
+
layout={attachmentPreviewLayout}
|
|
58
|
+
onLayout={onLayout}
|
|
59
|
+
style={style}
|
|
52
60
|
>
|
|
53
61
|
{children}
|
|
54
62
|
</Animated.View>
|
|
@@ -65,6 +73,16 @@ const ItemSeparatorComponent = () => {
|
|
|
65
73
|
return <View style={[styles.itemSeparator, itemSeparator]} />;
|
|
66
74
|
};
|
|
67
75
|
|
|
76
|
+
const useLazyRef = <T,>(getInitialValue: () => T) => {
|
|
77
|
+
const ref = useRef<T | null>(null);
|
|
78
|
+
|
|
79
|
+
if (ref.current === null) {
|
|
80
|
+
ref.current = getInitialValue();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return ref as React.RefObject<T>;
|
|
84
|
+
};
|
|
85
|
+
|
|
68
86
|
const getIsAudioAttachmentPreview =
|
|
69
87
|
(soundPackageAvailable: boolean) =>
|
|
70
88
|
(
|
|
@@ -88,25 +106,46 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
88
106
|
const { attachmentManager } = useMessageComposer();
|
|
89
107
|
const { attachments } = useAttachmentManagerState();
|
|
90
108
|
const isRTL = I18nManager.isRTL;
|
|
91
|
-
const attachmentListRef = useRef<
|
|
92
|
-
const soundPackageAvailable = isSoundPackageAvailable();
|
|
93
|
-
const isAudioAttachmentPreview =
|
|
109
|
+
const attachmentListRef = useRef<ScrollView>(null);
|
|
110
|
+
const soundPackageAvailable = useMemo(() => isSoundPackageAvailable(), []);
|
|
111
|
+
const isAudioAttachmentPreview = useMemo(
|
|
112
|
+
() => getIsAudioAttachmentPreview(soundPackageAvailable),
|
|
113
|
+
[soundPackageAvailable],
|
|
114
|
+
);
|
|
115
|
+
const dataRef = useLazyRef<LocalAttachment[]>(() => []);
|
|
116
|
+
const previousDataRef = useLazyRef<LocalAttachment[]>(() => []);
|
|
94
117
|
const previousNonAudioAttachmentsLengthRef = useRef(0);
|
|
95
118
|
const contentWidthRef = useRef(0);
|
|
96
119
|
const itemsContentWidthRef = useRef(0);
|
|
97
120
|
const viewportWidthRef = useRef(0);
|
|
98
121
|
const scrollOffsetXRef = useRef(0);
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
122
|
+
const attachmentCellWidthsRef = useLazyRef<Record<string, number>>(() => ({}));
|
|
123
|
+
const preparedRemovalIdsRef = useLazyRef<Set<string>>(() => new Set());
|
|
124
|
+
const spacerReleaseFramesRef = useLazyRef<Set<number>>(() => new Set());
|
|
125
|
+
const spacerReleaseTimeoutsRef = useLazyRef<Set<ReturnType<typeof setTimeout>>>(() => new Set());
|
|
126
|
+
const shouldScrollToEndOnContentSizeChangeRef = useRef(false);
|
|
127
|
+
const trailingSpacerWidthRef = useRef(0);
|
|
128
|
+
const [trailingSpacerWidth, setTrailingSpacerWidth] = useState(0);
|
|
129
|
+
const previewAttachments = useMemo(
|
|
130
|
+
() =>
|
|
131
|
+
attachments.filter(
|
|
132
|
+
(attachment) =>
|
|
133
|
+
!(audioRecordingSendOnComplete && isLocalVoiceRecordingAttachment(attachment)),
|
|
134
|
+
),
|
|
135
|
+
[attachments, audioRecordingSendOnComplete],
|
|
136
|
+
);
|
|
137
|
+
const audioAttachments = useMemo(
|
|
138
|
+
() => previewAttachments.filter(isAudioAttachmentPreview),
|
|
139
|
+
[isAudioAttachmentPreview, previewAttachments],
|
|
140
|
+
);
|
|
141
|
+
const nonAudioAttachments = useMemo(
|
|
142
|
+
() => previewAttachments.filter((attachment) => !isAudioAttachmentPreview(attachment)),
|
|
143
|
+
[isAudioAttachmentPreview, previewAttachments],
|
|
104
144
|
);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
const data = useMemo(
|
|
146
|
+
() => (isRTL ? nonAudioAttachments.toReversed() : nonAudioAttachments),
|
|
147
|
+
[isRTL, nonAudioAttachments],
|
|
108
148
|
);
|
|
109
|
-
const data = isRTL ? nonAudioAttachments.toReversed() : nonAudioAttachments;
|
|
110
149
|
|
|
111
150
|
const {
|
|
112
151
|
theme: {
|
|
@@ -116,90 +155,254 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
116
155
|
},
|
|
117
156
|
} = useTheme();
|
|
118
157
|
|
|
119
|
-
const
|
|
120
|
-
(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
const scrollToOffset = useCallback((offset: number, animated = false) => {
|
|
159
|
+
const nextOffset = Math.max(0, offset);
|
|
160
|
+
|
|
161
|
+
attachmentListRef.current?.scrollTo({
|
|
162
|
+
animated,
|
|
163
|
+
x: nextOffset,
|
|
164
|
+
});
|
|
165
|
+
scrollOffsetXRef.current = nextOffset;
|
|
166
|
+
}, []);
|
|
167
|
+
|
|
168
|
+
const setTrailingSpacerLayoutWidth = useCallback((width: number) => {
|
|
169
|
+
const nextWidth = Math.max(0, width);
|
|
170
|
+
trailingSpacerWidthRef.current = nextWidth;
|
|
171
|
+
setTrailingSpacerWidth(nextWidth);
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
const prepareTrailingSpacer = useCallback(
|
|
175
|
+
(width: number) => {
|
|
176
|
+
if (width <= 0) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const nextWidth = trailingSpacerWidthRef.current + width;
|
|
181
|
+
setTrailingSpacerLayoutWidth(nextWidth);
|
|
182
|
+
},
|
|
183
|
+
[setTrailingSpacerLayoutWidth],
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const scheduleTrailingSpacerRelease = useCallback(
|
|
187
|
+
(width: number) => {
|
|
188
|
+
if (width <= 0) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const timeout = setTimeout(() => {
|
|
193
|
+
spacerReleaseTimeoutsRef.current.delete(timeout);
|
|
194
|
+
|
|
195
|
+
const firstFrame = requestAnimationFrame(() => {
|
|
196
|
+
spacerReleaseFramesRef.current.delete(firstFrame);
|
|
197
|
+
|
|
198
|
+
const secondFrame = requestAnimationFrame(() => {
|
|
199
|
+
spacerReleaseFramesRef.current.delete(secondFrame);
|
|
200
|
+
setTrailingSpacerLayoutWidth(trailingSpacerWidthRef.current - width);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
spacerReleaseFramesRef.current.add(secondFrame);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
spacerReleaseFramesRef.current.add(firstFrame);
|
|
207
|
+
}, TRAILING_SPACER_RELEASE_DELAY);
|
|
208
|
+
|
|
209
|
+
spacerReleaseTimeoutsRef.current.add(timeout);
|
|
210
|
+
},
|
|
211
|
+
[setTrailingSpacerLayoutWidth, spacerReleaseFramesRef, spacerReleaseTimeoutsRef],
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const getRemovalMetrics = useCallback(
|
|
215
|
+
(ids: string[], baseData: LocalAttachment[]) => {
|
|
216
|
+
const removedIds = new Set(ids);
|
|
217
|
+
const fallbackCellWidth = baseData.length
|
|
218
|
+
? itemsContentWidthRef.current / baseData.length
|
|
219
|
+
: 0;
|
|
220
|
+
const offsetBefore = scrollOffsetXRef.current;
|
|
221
|
+
const oldMaxOffset = Math.max(0, itemsContentWidthRef.current - viewportWidthRef.current);
|
|
222
|
+
const wasNearEnd = oldMaxOffset - offsetBefore <= END_ANCHOR_THRESHOLD;
|
|
223
|
+
let contentOffset = 0;
|
|
224
|
+
let removedContentWidth = 0;
|
|
225
|
+
let anchorCorrectionWidth = 0;
|
|
226
|
+
|
|
227
|
+
baseData.forEach((attachment) => {
|
|
228
|
+
const attachmentId = attachment.localMetadata.id;
|
|
229
|
+
const cellWidth = attachmentCellWidthsRef.current[attachmentId] ?? fallbackCellWidth;
|
|
230
|
+
|
|
231
|
+
if (removedIds.has(attachmentId)) {
|
|
232
|
+
removedContentWidth += cellWidth;
|
|
233
|
+
if (contentOffset <= offsetBefore) {
|
|
234
|
+
anchorCorrectionWidth += cellWidth;
|
|
235
|
+
}
|
|
125
236
|
}
|
|
237
|
+
|
|
238
|
+
contentOffset += cellWidth;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!removedContentWidth) {
|
|
242
|
+
return {
|
|
243
|
+
removedContentWidth: 0,
|
|
244
|
+
scrollCorrectionWidth: 0,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
removedContentWidth,
|
|
250
|
+
scrollCorrectionWidth: wasNearEnd
|
|
251
|
+
? removedContentWidth
|
|
252
|
+
: Math.min(anchorCorrectionWidth, removedContentWidth),
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
[attachmentCellWidthsRef],
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const applyRemovalScrollCorrection = useCallback(
|
|
259
|
+
(removedContentWidth: number, scrollCorrectionWidth: number) => {
|
|
260
|
+
if (removedContentWidth <= 0 || isRTL) {
|
|
126
261
|
return;
|
|
127
262
|
}
|
|
128
263
|
|
|
129
|
-
const
|
|
130
|
-
|
|
264
|
+
const offsetBefore = scrollOffsetXRef.current;
|
|
265
|
+
const nextContentWidth = Math.max(0, itemsContentWidthRef.current - removedContentWidth);
|
|
266
|
+
const nextMaxOffset = Math.max(0, nextContentWidth - viewportWidthRef.current);
|
|
267
|
+
const nextOffset = Math.min(nextMaxOffset, Math.max(0, offsetBefore - scrollCorrectionWidth));
|
|
268
|
+
|
|
269
|
+
if (nextOffset !== offsetBefore) {
|
|
270
|
+
scrollToOffset(nextOffset, true);
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
[isRTL, scrollToOffset],
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const prepareForRemoval = useCallback(
|
|
277
|
+
(ids: string[], baseData: LocalAttachment[]) => {
|
|
278
|
+
const { removedContentWidth, scrollCorrectionWidth } = getRemovalMetrics(ids, baseData);
|
|
279
|
+
|
|
280
|
+
if (!removedContentWidth) {
|
|
131
281
|
return;
|
|
132
282
|
}
|
|
133
283
|
|
|
134
|
-
|
|
135
|
-
|
|
284
|
+
if (!isRTL) {
|
|
285
|
+
prepareTrailingSpacer(removedContentWidth);
|
|
286
|
+
}
|
|
287
|
+
applyRemovalScrollCorrection(removedContentWidth, scrollCorrectionWidth);
|
|
288
|
+
ids.forEach((id) => preparedRemovalIdsRef.current.add(id));
|
|
289
|
+
},
|
|
290
|
+
[
|
|
291
|
+
applyRemovalScrollCorrection,
|
|
292
|
+
getRemovalMetrics,
|
|
293
|
+
isRTL,
|
|
294
|
+
preparedRemovalIdsRef,
|
|
295
|
+
prepareTrailingSpacer,
|
|
296
|
+
],
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const removeAttachments = useCallback(
|
|
300
|
+
(ids: string[]) => {
|
|
301
|
+
prepareForRemoval(ids, dataRef.current);
|
|
302
|
+
attachmentManager.removeAttachments(ids);
|
|
136
303
|
},
|
|
137
|
-
[
|
|
304
|
+
[attachmentManager, dataRef, prepareForRemoval],
|
|
138
305
|
);
|
|
139
306
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
307
|
+
useLayoutEffect(() => {
|
|
308
|
+
const previousData = previousDataRef.current;
|
|
309
|
+
const nextIds = new Set(data.map((attachment) => attachment.localMetadata.id));
|
|
310
|
+
const removedIds = previousData
|
|
311
|
+
.map((attachment) => attachment.localMetadata.id)
|
|
312
|
+
.filter((id) => !nextIds.has(id));
|
|
313
|
+
|
|
314
|
+
if (removedIds.length) {
|
|
315
|
+
const { removedContentWidth } = getRemovalMetrics(removedIds, previousData);
|
|
316
|
+
const unpreparedRemovedIds = removedIds.filter(
|
|
317
|
+
(id) => !preparedRemovalIdsRef.current.has(id),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const didPrepareAfterRemovalCommit = unpreparedRemovedIds.length > 0;
|
|
321
|
+
|
|
322
|
+
if (didPrepareAfterRemovalCommit) {
|
|
323
|
+
prepareForRemoval(unpreparedRemovedIds, previousData);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
removedIds.forEach((id) => preparedRemovalIdsRef.current.delete(id));
|
|
327
|
+
if (!isRTL) {
|
|
328
|
+
scheduleTrailingSpacerRelease(removedContentWidth);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
previousDataRef.current = data;
|
|
333
|
+
dataRef.current = data;
|
|
334
|
+
}, [
|
|
335
|
+
data,
|
|
336
|
+
dataRef,
|
|
337
|
+
getRemovalMetrics,
|
|
338
|
+
isRTL,
|
|
339
|
+
preparedRemovalIdsRef,
|
|
340
|
+
prepareForRemoval,
|
|
341
|
+
previousDataRef,
|
|
342
|
+
scheduleTrailingSpacerRelease,
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
useEffect(
|
|
346
|
+
() => () => {
|
|
347
|
+
spacerReleaseFramesRef.current.forEach(cancelAnimationFrame);
|
|
348
|
+
spacerReleaseFramesRef.current.clear();
|
|
349
|
+
spacerReleaseTimeoutsRef.current.forEach(clearTimeout);
|
|
350
|
+
spacerReleaseTimeoutsRef.current.clear();
|
|
351
|
+
},
|
|
352
|
+
[spacerReleaseFramesRef, spacerReleaseTimeoutsRef],
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const renderAttachmentPreview = useCallback(
|
|
356
|
+
(attachment: LocalAttachment) => {
|
|
357
|
+
if (isLocalImageAttachment(attachment)) {
|
|
143
358
|
return (
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/>
|
|
150
|
-
</AttachmentPreviewCell>
|
|
359
|
+
<ImageAttachmentUploadPreview
|
|
360
|
+
attachment={attachment}
|
|
361
|
+
handleRetry={attachmentManager.uploadAttachment}
|
|
362
|
+
removeAttachments={removeAttachments}
|
|
363
|
+
/>
|
|
151
364
|
);
|
|
152
|
-
} else if (isLocalVoiceRecordingAttachment(
|
|
365
|
+
} else if (isLocalVoiceRecordingAttachment(attachment)) {
|
|
153
366
|
return (
|
|
154
|
-
<
|
|
367
|
+
<AudioAttachmentUploadPreview
|
|
368
|
+
attachment={attachment}
|
|
369
|
+
handleRetry={attachmentManager.uploadAttachment}
|
|
370
|
+
removeAttachments={removeAttachments}
|
|
371
|
+
/>
|
|
372
|
+
);
|
|
373
|
+
} else if (isLocalAudioAttachment(attachment)) {
|
|
374
|
+
if (soundPackageAvailable) {
|
|
375
|
+
return (
|
|
155
376
|
<AudioAttachmentUploadPreview
|
|
156
|
-
attachment={
|
|
377
|
+
attachment={attachment}
|
|
157
378
|
handleRetry={attachmentManager.uploadAttachment}
|
|
158
|
-
removeAttachments={
|
|
379
|
+
removeAttachments={removeAttachments}
|
|
159
380
|
/>
|
|
160
|
-
</AttachmentPreviewCell>
|
|
161
|
-
);
|
|
162
|
-
} else if (isLocalAudioAttachment(item)) {
|
|
163
|
-
if (isSoundPackageAvailable()) {
|
|
164
|
-
return (
|
|
165
|
-
<AttachmentPreviewCell>
|
|
166
|
-
<AudioAttachmentUploadPreview
|
|
167
|
-
attachment={item}
|
|
168
|
-
handleRetry={attachmentManager.uploadAttachment}
|
|
169
|
-
removeAttachments={attachmentManager.removeAttachments}
|
|
170
|
-
/>
|
|
171
|
-
</AttachmentPreviewCell>
|
|
172
381
|
);
|
|
173
382
|
} else {
|
|
174
383
|
return (
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
/>
|
|
181
|
-
</AttachmentPreviewCell>
|
|
384
|
+
<FileAttachmentUploadPreview
|
|
385
|
+
attachment={attachment}
|
|
386
|
+
handleRetry={attachmentManager.uploadAttachment}
|
|
387
|
+
removeAttachments={removeAttachments}
|
|
388
|
+
/>
|
|
182
389
|
);
|
|
183
390
|
}
|
|
184
|
-
} else if (isVideoAttachment(
|
|
391
|
+
} else if (isVideoAttachment(attachment)) {
|
|
185
392
|
return (
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
/>
|
|
192
|
-
</AttachmentPreviewCell>
|
|
393
|
+
<VideoAttachmentUploadPreview
|
|
394
|
+
attachment={attachment}
|
|
395
|
+
handleRetry={attachmentManager.uploadAttachment}
|
|
396
|
+
removeAttachments={removeAttachments}
|
|
397
|
+
/>
|
|
193
398
|
);
|
|
194
|
-
} else if (isLocalFileAttachment(
|
|
399
|
+
} else if (isLocalFileAttachment(attachment)) {
|
|
195
400
|
return (
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
/>
|
|
202
|
-
</AttachmentPreviewCell>
|
|
401
|
+
<FileAttachmentUploadPreview
|
|
402
|
+
attachment={attachment}
|
|
403
|
+
handleRetry={attachmentManager.uploadAttachment}
|
|
404
|
+
removeAttachments={removeAttachments}
|
|
405
|
+
/>
|
|
203
406
|
);
|
|
204
407
|
} else return null;
|
|
205
408
|
},
|
|
@@ -208,8 +411,9 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
208
411
|
FileAttachmentUploadPreview,
|
|
209
412
|
ImageAttachmentUploadPreview,
|
|
210
413
|
VideoAttachmentUploadPreview,
|
|
211
|
-
attachmentManager.removeAttachments,
|
|
212
414
|
attachmentManager.uploadAttachment,
|
|
415
|
+
removeAttachments,
|
|
416
|
+
soundPackageAvailable,
|
|
213
417
|
],
|
|
214
418
|
);
|
|
215
419
|
|
|
@@ -217,62 +421,61 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
217
421
|
scrollOffsetXRef.current = event.nativeEvent.contentOffset.x;
|
|
218
422
|
}, []);
|
|
219
423
|
|
|
220
|
-
const
|
|
221
|
-
(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
424
|
+
const scrollToEndOffset = useCallback(
|
|
425
|
+
(contentWidth: number, animated = true) => {
|
|
426
|
+
if (isRTL) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
scrollToOffset(Math.max(0, contentWidth - viewportWidthRef.current), animated);
|
|
431
|
+
},
|
|
432
|
+
[isRTL, scrollToOffset],
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const onLayoutHandler = useCallback((event: LayoutChangeEvent) => {
|
|
436
|
+
viewportWidthRef.current = event.nativeEvent.layout.width;
|
|
437
|
+
}, []);
|
|
438
|
+
|
|
439
|
+
const onAttachmentCellLayout = useCallback(
|
|
440
|
+
(id: string, event: LayoutChangeEvent) => {
|
|
441
|
+
attachmentCellWidthsRef.current[id] = event.nativeEvent.layout.width;
|
|
225
442
|
},
|
|
226
|
-
[
|
|
443
|
+
[attachmentCellWidthsRef],
|
|
227
444
|
);
|
|
228
445
|
|
|
229
446
|
const onContentSizeChangeHandler = useCallback(
|
|
230
447
|
(width: number) => {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
448
|
+
const scrollableContentWidth = width;
|
|
449
|
+
const itemsContentWidth = Math.max(
|
|
450
|
+
0,
|
|
451
|
+
scrollableContentWidth - trailingSpacerWidthRef.current,
|
|
452
|
+
);
|
|
234
453
|
const previousContentWidth = contentWidthRef.current;
|
|
235
454
|
contentWidthRef.current = itemsContentWidth;
|
|
236
455
|
itemsContentWidthRef.current = itemsContentWidth;
|
|
237
|
-
updateRtlLeadingSpacerWidth(itemsContentWidth, viewportWidthRef.current);
|
|
238
456
|
|
|
239
|
-
if (
|
|
457
|
+
if (
|
|
458
|
+
shouldScrollToEndOnContentSizeChangeRef.current &&
|
|
459
|
+
itemsContentWidth > previousContentWidth
|
|
460
|
+
) {
|
|
461
|
+
shouldScrollToEndOnContentSizeChangeRef.current = false;
|
|
462
|
+
scrollToEndOffset(scrollableContentWidth);
|
|
240
463
|
return;
|
|
241
464
|
}
|
|
242
465
|
|
|
243
|
-
|
|
244
|
-
const newMaxOffset = Math.max(0, itemsContentWidth - viewportWidthRef.current);
|
|
245
|
-
const offsetBefore = scrollOffsetXRef.current;
|
|
246
|
-
const wasNearEnd = oldMaxOffset - offsetBefore <= END_ANCHOR_THRESHOLD;
|
|
247
|
-
const overshoot = Math.max(0, offsetBefore - newMaxOffset);
|
|
248
|
-
const shouldAnchorEnd = wasNearEnd || overshoot > 0;
|
|
249
|
-
|
|
250
|
-
if (!shouldAnchorEnd) {
|
|
466
|
+
if (!previousContentWidth || itemsContentWidth >= previousContentWidth) {
|
|
251
467
|
return;
|
|
252
468
|
}
|
|
253
469
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
offset: newMaxOffset,
|
|
258
|
-
});
|
|
259
|
-
scrollOffsetXRef.current = newMaxOffset;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (isRTL) {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
470
|
+
const actualMaxOffset = Math.max(0, scrollableContentWidth - viewportWidthRef.current);
|
|
471
|
+
const offsetBefore = scrollOffsetXRef.current;
|
|
472
|
+
const overshoot = Math.max(0, offsetBefore - actualMaxOffset);
|
|
265
473
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
cancelAnimation(endShrinkCompensationX);
|
|
269
|
-
endShrinkCompensationX.value = compensation;
|
|
270
|
-
endShrinkCompensationX.value = withSpring(0, {
|
|
271
|
-
duration: END_SHRINK_COMPENSATION_DURATION,
|
|
272
|
-
});
|
|
474
|
+
if (overshoot > END_ANCHOR_THRESHOLD) {
|
|
475
|
+
scrollToOffset(actualMaxOffset);
|
|
273
476
|
}
|
|
274
477
|
},
|
|
275
|
-
[
|
|
478
|
+
[scrollToEndOffset, scrollToOffset],
|
|
276
479
|
);
|
|
277
480
|
|
|
278
481
|
useEffect(() => {
|
|
@@ -285,20 +488,8 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
285
488
|
return;
|
|
286
489
|
}
|
|
287
490
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
requestAnimationFrame(() => {
|
|
291
|
-
if (isRTL) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
attachmentListRef.current?.scrollToEnd({ animated: true });
|
|
296
|
-
});
|
|
297
|
-
}, [endShrinkCompensationX, isRTL, nonAudioAttachments.length]);
|
|
298
|
-
|
|
299
|
-
const animatedListWrapperStyle = useAnimatedStyle(() => ({
|
|
300
|
-
transform: [{ translateX: endShrinkCompensationX.value }],
|
|
301
|
-
}));
|
|
491
|
+
shouldScrollToEndOnContentSizeChangeRef.current = true;
|
|
492
|
+
}, [nonAudioAttachments.length]);
|
|
302
493
|
|
|
303
494
|
if (!previewAttachments.length) {
|
|
304
495
|
return null;
|
|
@@ -308,9 +499,9 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
308
499
|
<>
|
|
309
500
|
{audioAttachments.length ? (
|
|
310
501
|
<Animated.View
|
|
311
|
-
entering={
|
|
312
|
-
exiting={
|
|
313
|
-
layout={
|
|
502
|
+
entering={attachmentPreviewEntering}
|
|
503
|
+
exiting={attachmentPreviewExiting}
|
|
504
|
+
layout={attachmentPreviewLayout}
|
|
314
505
|
style={[styles.audioAttachmentsContainer, audioAttachmentsContainer]}
|
|
315
506
|
>
|
|
316
507
|
{audioAttachments.map((attachment) => (
|
|
@@ -318,7 +509,7 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
318
509
|
<AudioAttachmentUploadPreview
|
|
319
510
|
attachment={attachment}
|
|
320
511
|
handleRetry={attachmentManager.uploadAttachment}
|
|
321
|
-
removeAttachments={
|
|
512
|
+
removeAttachments={removeAttachments}
|
|
322
513
|
/>
|
|
323
514
|
</AttachmentPreviewCell>
|
|
324
515
|
))}
|
|
@@ -327,33 +518,46 @@ const UnMemoizedAttachmentUploadPreviewList = () => {
|
|
|
327
518
|
|
|
328
519
|
{data.length ? (
|
|
329
520
|
<Animated.View
|
|
330
|
-
entering={
|
|
331
|
-
exiting={
|
|
332
|
-
layout={
|
|
521
|
+
entering={attachmentPreviewEntering}
|
|
522
|
+
exiting={attachmentPreviewExiting}
|
|
523
|
+
layout={attachmentPreviewLayout}
|
|
524
|
+
style={styles.flatListContainer}
|
|
333
525
|
>
|
|
334
|
-
<
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
526
|
+
<ScrollView
|
|
527
|
+
contentContainerStyle={styles.flatListContentContainer}
|
|
528
|
+
horizontal
|
|
529
|
+
onContentSizeChange={onContentSizeChangeHandler}
|
|
530
|
+
onLayout={onLayoutHandler}
|
|
531
|
+
onScroll={onScrollHandler}
|
|
532
|
+
ref={attachmentListRef}
|
|
533
|
+
scrollEventThrottle={16}
|
|
534
|
+
showsHorizontalScrollIndicator={false}
|
|
535
|
+
style={[styles.flatList, flatList]}
|
|
536
|
+
testID={'attachment-upload-preview-list'}
|
|
537
|
+
>
|
|
538
|
+
{data.map((attachment, index) => {
|
|
539
|
+
const attachmentId = attachment.localMetadata.id;
|
|
540
|
+
|
|
541
|
+
return (
|
|
542
|
+
<AttachmentPreviewCell
|
|
543
|
+
key={attachmentId}
|
|
544
|
+
onLayout={(event) => onAttachmentCellLayout(attachmentId, event)}
|
|
545
|
+
style={styles.attachmentPreviewCell}
|
|
546
|
+
>
|
|
547
|
+
{index > 0 ? <ItemSeparatorComponent /> : null}
|
|
548
|
+
<View collapsable={false} style={styles.attachmentPreviewContent}>
|
|
549
|
+
{renderAttachmentPreview(attachment)}
|
|
550
|
+
</View>
|
|
551
|
+
</AttachmentPreviewCell>
|
|
552
|
+
);
|
|
553
|
+
})}
|
|
554
|
+
{!isRTL ? (
|
|
555
|
+
<View
|
|
556
|
+
pointerEvents={'none'}
|
|
557
|
+
style={[styles.trailingSpacer, { width: trailingSpacerWidth }]}
|
|
558
|
+
/>
|
|
559
|
+
) : null}
|
|
560
|
+
</ScrollView>
|
|
357
561
|
</Animated.View>
|
|
358
562
|
) : null}
|
|
359
563
|
</>
|
|
@@ -373,17 +577,37 @@ const MemoizedAttachmentUploadPreviewListWithContext = React.memo(
|
|
|
373
577
|
export const AttachmentUploadPreviewList = () => <MemoizedAttachmentUploadPreviewListWithContext />;
|
|
374
578
|
|
|
375
579
|
const styles = StyleSheet.create({
|
|
580
|
+
attachmentPreviewCell: {
|
|
581
|
+
alignItems: 'flex-start',
|
|
582
|
+
flexDirection: 'row',
|
|
583
|
+
flexShrink: 0,
|
|
584
|
+
},
|
|
585
|
+
attachmentPreviewContent: {
|
|
586
|
+
flexShrink: 0,
|
|
587
|
+
},
|
|
376
588
|
audioAttachmentsContainer: {
|
|
377
589
|
maxWidth: MAX_AUDIO_ATTACHMENTS_CONTAINER_WIDTH,
|
|
378
590
|
width: '100%',
|
|
379
591
|
},
|
|
380
592
|
flatList: {
|
|
381
|
-
overflow: 'visible',
|
|
382
593
|
direction: 'ltr',
|
|
594
|
+
flexGrow: 0,
|
|
595
|
+
overflow: 'visible',
|
|
596
|
+
},
|
|
597
|
+
flatListContentContainer: {
|
|
598
|
+
alignItems: 'flex-start',
|
|
599
|
+
},
|
|
600
|
+
flatListContainer: {
|
|
601
|
+
alignSelf: 'flex-start',
|
|
602
|
+
flexShrink: 1,
|
|
603
|
+
maxWidth: '100%',
|
|
383
604
|
},
|
|
384
605
|
itemSeparator: {
|
|
385
606
|
width: primitives.spacingXs,
|
|
386
607
|
},
|
|
608
|
+
trailingSpacer: {
|
|
609
|
+
flexShrink: 0,
|
|
610
|
+
},
|
|
387
611
|
});
|
|
388
612
|
|
|
389
613
|
AttachmentUploadPreviewList.displayName =
|