stream-chat-react-native-core 8.6.1 → 8.6.2-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/Channel/Channel.js +13 -3
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js +3 -1
- package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useCreatePaginatedMessageListContext.js +5 -3
- package/lib/commonjs/components/Channel/hooks/useCreatePaginatedMessageListContext.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js +38 -21
- package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
- package/lib/commonjs/components/Message/MessageSimple/MessageBubble.js +157 -0
- package/lib/commonjs/components/Message/MessageSimple/MessageBubble.js.map +1 -0
- package/lib/commonjs/components/Message/MessageSimple/MessageSimple.js +29 -124
- package/lib/commonjs/components/Message/MessageSimple/MessageSimple.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageFlashList.js +56 -65
- package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +20 -8
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js +5 -3
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/commonjs/contexts/channelContext/ChannelContext.js.map +1 -1
- package/lib/commonjs/contexts/paginatedMessageListContext/PaginatedMessageListContext.js.map +1 -1
- package/lib/commonjs/hooks/usePrunableMessageList.js +63 -0
- package/lib/commonjs/hooks/usePrunableMessageList.js.map +1 -0
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Channel/Channel.js +13 -3
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/Channel/hooks/useCreateChannelContext.js +3 -1
- package/lib/module/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
- package/lib/module/components/Channel/hooks/useCreatePaginatedMessageListContext.js +5 -3
- package/lib/module/components/Channel/hooks/useCreatePaginatedMessageListContext.js.map +1 -1
- package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js +38 -21
- package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
- package/lib/module/components/Message/MessageSimple/MessageBubble.js +157 -0
- package/lib/module/components/Message/MessageSimple/MessageBubble.js.map +1 -0
- package/lib/module/components/Message/MessageSimple/MessageSimple.js +29 -124
- package/lib/module/components/Message/MessageSimple/MessageSimple.js.map +1 -1
- package/lib/module/components/MessageList/MessageFlashList.js +56 -65
- package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +20 -8
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/hooks/useMessageList.js +5 -3
- package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/module/contexts/channelContext/ChannelContext.js.map +1 -1
- package/lib/module/contexts/paginatedMessageListContext/PaginatedMessageListContext.js.map +1 -1
- package/lib/module/hooks/usePrunableMessageList.js +63 -0
- package/lib/module/hooks/usePrunableMessageList.js.map +1 -0
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts +1 -1
- package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useCreatePaginatedMessageListContext.d.ts +1 -1
- package/lib/typescript/components/Channel/hooks/useCreatePaginatedMessageListContext.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewData.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageSimple/MessageBubble.d.ts +11 -0
- package/lib/typescript/components/Message/MessageSimple/MessageBubble.d.ts.map +1 -0
- package/lib/typescript/components/Message/MessageSimple/MessageSimple.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageFlashList.d.ts +10 -1
- package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +1 -0
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts.map +1 -1
- package/lib/typescript/contexts/channelContext/ChannelContext.d.ts +6 -0
- package/lib/typescript/contexts/channelContext/ChannelContext.d.ts.map +1 -1
- package/lib/typescript/contexts/paginatedMessageListContext/PaginatedMessageListContext.d.ts +5 -0
- package/lib/typescript/contexts/paginatedMessageListContext/PaginatedMessageListContext.d.ts.map +1 -1
- package/lib/typescript/hooks/usePrunableMessageList.d.ts +19 -0
- package/lib/typescript/hooks/usePrunableMessageList.d.ts.map +1 -0
- package/lib/typescript/hooks/useTranslatedMessage.d.ts +4 -0
- package/lib/typescript/hooks/useTranslatedMessage.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components/Channel/Channel.tsx +9 -1
- package/src/components/Channel/hooks/useCreateChannelContext.ts +3 -0
- package/src/components/Channel/hooks/useCreatePaginatedMessageListContext.ts +3 -1
- package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts +59 -29
- package/src/components/Message/MessageSimple/MessageBubble.tsx +235 -0
- package/src/components/Message/MessageSimple/MessageSimple.tsx +30 -193
- package/src/components/MessageList/MessageFlashList.tsx +69 -66
- package/src/components/MessageList/MessageList.tsx +37 -14
- package/src/components/MessageList/hooks/useMessageList.ts +3 -2
- package/src/contexts/channelContext/ChannelContext.tsx +6 -0
- package/src/contexts/paginatedMessageListContext/PaginatedMessageListContext.tsx +5 -0
- package/src/hooks/usePrunableMessageList.ts +79 -0
- package/src/version.json +1 -1
|
@@ -118,6 +118,7 @@ type MessageFlashListPropsWithContext = Pick<
|
|
|
118
118
|
| 'StickyHeader'
|
|
119
119
|
| 'targetedMessage'
|
|
120
120
|
| 'threadList'
|
|
121
|
+
| 'maximumMessageLimit'
|
|
121
122
|
> &
|
|
122
123
|
Pick<ChatContextValue, 'client'> &
|
|
123
124
|
Pick<ImageGalleryContextValue, 'setMessages'> &
|
|
@@ -200,6 +201,15 @@ type MessageFlashListPropsWithContext = Pick<
|
|
|
200
201
|
* ```
|
|
201
202
|
*/
|
|
202
203
|
setFlatListRef?: (ref: FlashListRef<LocalMessage> | null) => void;
|
|
204
|
+
/**
|
|
205
|
+
* If true, the message list will be used in a live-streaming scenario.
|
|
206
|
+
* This flag is used to make sure that the auto scroll behaves well, if multiple messages are received.
|
|
207
|
+
*
|
|
208
|
+
* This flag is experimental and is subject to change. Please test thoroughly before using it.
|
|
209
|
+
*
|
|
210
|
+
* @experimental
|
|
211
|
+
*/
|
|
212
|
+
isLiveStreaming?: boolean;
|
|
203
213
|
};
|
|
204
214
|
|
|
205
215
|
const WAIT_FOR_SCROLL_TIMEOUT = 0;
|
|
@@ -262,6 +272,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
262
272
|
InlineDateSeparator,
|
|
263
273
|
InlineUnreadIndicator,
|
|
264
274
|
isListActive = false,
|
|
275
|
+
isLiveStreaming = false,
|
|
265
276
|
legacyImageViewerSwipeBehaviour,
|
|
266
277
|
loadChannelAroundMessage,
|
|
267
278
|
loading,
|
|
@@ -271,6 +282,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
271
282
|
loadMoreRecentThread,
|
|
272
283
|
loadMoreThread,
|
|
273
284
|
markRead,
|
|
285
|
+
maximumMessageLimit,
|
|
274
286
|
Message,
|
|
275
287
|
MessageSystem,
|
|
276
288
|
myMessageTheme,
|
|
@@ -303,7 +315,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
303
315
|
const [scrollToBottomButtonVisible, setScrollToBottomButtonVisible] = useState(false);
|
|
304
316
|
const [isUnreadNotificationOpen, setIsUnreadNotificationOpen] = useState<boolean>(false);
|
|
305
317
|
const [stickyHeaderDate, setStickyHeaderDate] = useState<Date | undefined>();
|
|
306
|
-
const [autoScrollToRecent, setAutoScrollToRecent] = useState(false);
|
|
307
318
|
|
|
308
319
|
const stickyHeaderDateRef = useRef<Date | undefined>(undefined);
|
|
309
320
|
/**
|
|
@@ -337,12 +348,18 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
337
348
|
[myMessageThemeString, theme],
|
|
338
349
|
);
|
|
339
350
|
|
|
340
|
-
const {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
351
|
+
const {
|
|
352
|
+
dateSeparatorsRef,
|
|
353
|
+
messageGroupStylesRef,
|
|
354
|
+
processedMessageList,
|
|
355
|
+
rawMessageList,
|
|
356
|
+
viewabilityChangedCallback,
|
|
357
|
+
} = useMessageList({
|
|
358
|
+
isFlashList: true,
|
|
359
|
+
isLiveStreaming,
|
|
360
|
+
noGroupByUser,
|
|
361
|
+
threadList,
|
|
362
|
+
});
|
|
346
363
|
|
|
347
364
|
/**
|
|
348
365
|
* We need topMessage and channelLastRead values to set the initial scroll position.
|
|
@@ -366,13 +383,23 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
366
383
|
[processedMessageList],
|
|
367
384
|
);
|
|
368
385
|
|
|
386
|
+
const [autoscrollToRecent, setAutoscrollToRecent] = useState(true);
|
|
387
|
+
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
if (autoscrollToRecent && flashListRef.current) {
|
|
390
|
+
flashListRef.current.scrollToEnd({
|
|
391
|
+
animated: true,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}, [autoscrollToRecent]);
|
|
395
|
+
|
|
369
396
|
const maintainVisibleContentPosition = useMemo(() => {
|
|
370
397
|
return {
|
|
371
398
|
animateAutoscrollToBottom: true,
|
|
372
|
-
autoscrollToBottomThreshold:
|
|
399
|
+
autoscrollToBottomThreshold: autoscrollToRecent ? 1 : undefined,
|
|
373
400
|
startRenderingFromBottom: true,
|
|
374
401
|
};
|
|
375
|
-
}, [
|
|
402
|
+
}, [autoscrollToRecent]);
|
|
376
403
|
|
|
377
404
|
useEffect(() => {
|
|
378
405
|
if (disabled) {
|
|
@@ -380,6 +407,18 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
380
407
|
}
|
|
381
408
|
}, [disabled]);
|
|
382
409
|
|
|
410
|
+
const indexToScrollToRef = useRef<number | undefined>(undefined);
|
|
411
|
+
|
|
412
|
+
const initialIndexToScrollTo = useMemo(() => {
|
|
413
|
+
return targetedMessage
|
|
414
|
+
? processedMessageList.findIndex((message) => message?.id === targetedMessage)
|
|
415
|
+
: -1;
|
|
416
|
+
}, [processedMessageList, targetedMessage]);
|
|
417
|
+
|
|
418
|
+
useEffect(() => {
|
|
419
|
+
indexToScrollToRef.current = initialIndexToScrollTo;
|
|
420
|
+
}, [initialIndexToScrollTo]);
|
|
421
|
+
|
|
383
422
|
/**
|
|
384
423
|
* Check if a messageId needs to be scrolled to after list loads, and scroll to it
|
|
385
424
|
* Note: This effect fires on every list change with a small debounce so that scrolling isnt abrupted by an immediate rerender
|
|
@@ -415,28 +454,15 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
415
454
|
const indexOfParentInMessageList = processedMessageList.findIndex(
|
|
416
455
|
(message) => message?.id === messageId,
|
|
417
456
|
);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
index: indexOfParentInMessageList,
|
|
422
|
-
viewPosition: 0.5,
|
|
423
|
-
});
|
|
424
|
-
setTargetedMessage(messageId);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
457
|
+
|
|
458
|
+
indexToScrollToRef.current = indexOfParentInMessageList;
|
|
459
|
+
|
|
427
460
|
try {
|
|
428
461
|
if (indexOfParentInMessageList === -1) {
|
|
429
462
|
clearTimeout(scrollToDebounceTimeoutRef.current);
|
|
430
|
-
await loadChannelAroundMessage({ messageId });
|
|
463
|
+
await loadChannelAroundMessage({ messageId, setTargetedMessage });
|
|
464
|
+
} else {
|
|
431
465
|
setTargetedMessage(messageId);
|
|
432
|
-
|
|
433
|
-
// now scroll to it with animated=true
|
|
434
|
-
flashListRef.current?.scrollToIndex({
|
|
435
|
-
animated: true,
|
|
436
|
-
index: indexOfParentInMessageList,
|
|
437
|
-
viewPosition: 0.5, // try to place message in the center of the screen
|
|
438
|
-
});
|
|
439
|
-
return;
|
|
440
466
|
}
|
|
441
467
|
} catch (e) {
|
|
442
468
|
console.warn('Error while scrolling to message', e);
|
|
@@ -471,25 +497,23 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
471
497
|
setScrollToBottomButtonVisible(false);
|
|
472
498
|
resetPaginationTrackersRef.current();
|
|
473
499
|
|
|
474
|
-
setAutoScrollToRecent(true);
|
|
475
500
|
setTimeout(() => {
|
|
476
501
|
channelResyncScrollSet.current = true;
|
|
477
502
|
if (channel.countUnread() > 0) {
|
|
478
503
|
markRead();
|
|
479
504
|
}
|
|
480
|
-
setAutoScrollToRecent(false);
|
|
481
505
|
}, WAIT_FOR_SCROLL_TIMEOUT);
|
|
482
506
|
}
|
|
483
507
|
};
|
|
484
508
|
|
|
485
|
-
if (isMessageRemovedFromMessageList) {
|
|
509
|
+
if (isMessageRemovedFromMessageList && !maximumMessageLimit) {
|
|
486
510
|
scrollToBottomIfNeeded();
|
|
487
511
|
}
|
|
488
512
|
|
|
489
513
|
messageListLengthBeforeUpdate.current = messageListLengthAfterUpdate;
|
|
490
514
|
topMessageBeforeUpdate.current = topMessageAfterUpdate;
|
|
491
515
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
492
|
-
}, [messageListLengthAfterUpdate, topMessageAfterUpdate?.id]);
|
|
516
|
+
}, [messageListLengthAfterUpdate, topMessageAfterUpdate?.id, maximumMessageLimit]);
|
|
493
517
|
|
|
494
518
|
useEffect(() => {
|
|
495
519
|
if (!processedMessageList.length) {
|
|
@@ -500,16 +524,18 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
500
524
|
if (notLatestSet) {
|
|
501
525
|
latestNonCurrentMessageBeforeUpdateRef.current =
|
|
502
526
|
channel.state.latestMessages[channel.state.latestMessages.length - 1];
|
|
503
|
-
|
|
527
|
+
setAutoscrollToRecent(false);
|
|
504
528
|
setScrollToBottomButtonVisible(true);
|
|
505
529
|
return;
|
|
530
|
+
} else {
|
|
531
|
+
indexToScrollToRef.current = undefined;
|
|
532
|
+
setAutoscrollToRecent(true);
|
|
506
533
|
}
|
|
507
534
|
const latestNonCurrentMessageBeforeUpdate = latestNonCurrentMessageBeforeUpdateRef.current;
|
|
508
535
|
latestNonCurrentMessageBeforeUpdateRef.current = undefined;
|
|
509
536
|
|
|
510
537
|
const latestCurrentMessageAfterUpdate = processedMessageList[processedMessageList.length - 1];
|
|
511
538
|
if (!latestCurrentMessageAfterUpdate) {
|
|
512
|
-
setAutoScrollToRecent(true);
|
|
513
539
|
return;
|
|
514
540
|
}
|
|
515
541
|
const didMergeMessageSetsWithNoUpdates =
|
|
@@ -527,28 +553,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
527
553
|
}
|
|
528
554
|
}, [channel, processedMessageList, shouldScrollToRecentOnNewOwnMessageRef, threadList]);
|
|
529
555
|
|
|
530
|
-
/**
|
|
531
|
-
* Effect to scroll to the bottom of the message list when a new message is received if the scroll to bottom button is not visible.
|
|
532
|
-
*/
|
|
533
|
-
useEffect(() => {
|
|
534
|
-
const handleEvent = (event: Event) => {
|
|
535
|
-
if (event.message?.user?.id !== client.userID) {
|
|
536
|
-
if (!scrollToBottomButtonVisible) {
|
|
537
|
-
flashListRef.current?.scrollToEnd({
|
|
538
|
-
animated: true,
|
|
539
|
-
});
|
|
540
|
-
} else {
|
|
541
|
-
setAutoScrollToRecent(false);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
const listener: ReturnType<typeof channel.on> = channel.on('message.new', handleEvent);
|
|
546
|
-
|
|
547
|
-
return () => {
|
|
548
|
-
listener?.unsubscribe();
|
|
549
|
-
};
|
|
550
|
-
}, [channel, client.userID, scrollToBottomButtonVisible]);
|
|
551
|
-
|
|
552
556
|
/**
|
|
553
557
|
* Effect to mark the channel as read when the user scrolls to the bottom of the message list.
|
|
554
558
|
*/
|
|
@@ -695,6 +699,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
695
699
|
if (!viewableItems) {
|
|
696
700
|
return;
|
|
697
701
|
}
|
|
702
|
+
viewabilityChangedCallback({ inverted: false, viewableItems });
|
|
698
703
|
if (!hideStickyDateHeader) {
|
|
699
704
|
updateStickyHeaderDateIfNeeded(viewableItems);
|
|
700
705
|
}
|
|
@@ -1074,23 +1079,15 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
1074
1079
|
}
|
|
1075
1080
|
|
|
1076
1081
|
const flatListStyle = useMemo(
|
|
1077
|
-
() =>
|
|
1082
|
+
() => [styles.listContainer, listContainer, additionalFlashListProps?.style],
|
|
1078
1083
|
[additionalFlashListProps?.style, listContainer],
|
|
1079
1084
|
);
|
|
1080
1085
|
|
|
1081
1086
|
const flatListContentContainerStyle = useMemo(
|
|
1082
|
-
() =>
|
|
1083
|
-
...styles.contentContainer,
|
|
1084
|
-
...contentContainer,
|
|
1085
|
-
}),
|
|
1087
|
+
() => [styles.contentContainer, contentContainer],
|
|
1086
1088
|
[contentContainer],
|
|
1087
1089
|
);
|
|
1088
1090
|
|
|
1089
|
-
const getItemType = useStableCallback((item: LocalMessage) => {
|
|
1090
|
-
const type = getItemTypeInternal(item);
|
|
1091
|
-
return client.userID === item.user?.id ? `own-${type}` : type;
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
1091
|
const currentListHeightRef = useRef<number | undefined>(undefined);
|
|
1095
1092
|
|
|
1096
1093
|
const onLayout = useStableCallback((e: LayoutChangeEvent) => {
|
|
@@ -1136,7 +1133,10 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
1136
1133
|
contentContainerStyle={flatListContentContainerStyle}
|
|
1137
1134
|
data={processedMessageList}
|
|
1138
1135
|
drawDistance={800}
|
|
1139
|
-
getItemType={
|
|
1136
|
+
getItemType={getItemTypeInternal}
|
|
1137
|
+
initialScrollIndex={
|
|
1138
|
+
indexToScrollToRef.current === -1 ? undefined : indexToScrollToRef.current
|
|
1139
|
+
}
|
|
1140
1140
|
keyboardShouldPersistTaps='handled'
|
|
1141
1141
|
keyExtractor={keyExtractor}
|
|
1142
1142
|
ListFooterComponent={FooterComponent}
|
|
@@ -1150,6 +1150,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
1150
1150
|
onViewableItemsChanged={stableOnViewableItemsChanged}
|
|
1151
1151
|
ref={refCallback}
|
|
1152
1152
|
renderItem={renderItem}
|
|
1153
|
+
scrollEventThrottle={isLiveStreaming ? 16 : undefined}
|
|
1153
1154
|
showsVerticalScrollIndicator={false}
|
|
1154
1155
|
style={flatListStyle}
|
|
1155
1156
|
testID='message-flash-list'
|
|
@@ -1204,6 +1205,7 @@ export const MessageFlashList = (props: MessageFlashListProps) => {
|
|
|
1204
1205
|
loading,
|
|
1205
1206
|
LoadingIndicator,
|
|
1206
1207
|
markRead,
|
|
1208
|
+
maximumMessageLimit,
|
|
1207
1209
|
NetworkDownIndicator,
|
|
1208
1210
|
reloadChannel,
|
|
1209
1211
|
scrollToFirstUnreadThreshold,
|
|
@@ -1263,6 +1265,7 @@ export const MessageFlashList = (props: MessageFlashListProps) => {
|
|
|
1263
1265
|
loadMoreRecentThread,
|
|
1264
1266
|
loadMoreThread,
|
|
1265
1267
|
markRead,
|
|
1268
|
+
maximumMessageLimit,
|
|
1266
1269
|
Message,
|
|
1267
1270
|
MessageSystem,
|
|
1268
1271
|
myMessageTheme,
|
|
@@ -140,6 +140,7 @@ type MessageListPropsWithContext = Pick<
|
|
|
140
140
|
| 'StickyHeader'
|
|
141
141
|
| 'targetedMessage'
|
|
142
142
|
| 'threadList'
|
|
143
|
+
| 'maximumMessageLimit'
|
|
143
144
|
> &
|
|
144
145
|
Pick<ChatContextValue, 'client'> &
|
|
145
146
|
Pick<ImageGalleryContextValue, 'setMessages'> &
|
|
@@ -275,6 +276,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
275
276
|
loadMoreRecentThread,
|
|
276
277
|
loadMoreThread,
|
|
277
278
|
markRead,
|
|
279
|
+
maximumMessageLimit,
|
|
278
280
|
Message,
|
|
279
281
|
MessageSystem,
|
|
280
282
|
myMessageTheme,
|
|
@@ -322,12 +324,17 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
322
324
|
* NOTE: rawMessageList changes only when messages array state changes
|
|
323
325
|
* processedMessageList changes on any state change
|
|
324
326
|
*/
|
|
325
|
-
const {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
const {
|
|
328
|
+
dateSeparatorsRef,
|
|
329
|
+
messageGroupStylesRef,
|
|
330
|
+
processedMessageList,
|
|
331
|
+
rawMessageList,
|
|
332
|
+
viewabilityChangedCallback,
|
|
333
|
+
} = useMessageList({
|
|
334
|
+
isLiveStreaming,
|
|
335
|
+
noGroupByUser,
|
|
336
|
+
threadList,
|
|
337
|
+
});
|
|
331
338
|
const messageListLengthBeforeUpdate = useRef(0);
|
|
332
339
|
const messageListLengthAfterUpdate = processedMessageList.length;
|
|
333
340
|
|
|
@@ -348,10 +355,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
348
355
|
|
|
349
356
|
const minIndexForVisible = Math.min(1, processedMessageList.length);
|
|
350
357
|
|
|
351
|
-
const autoscrollToTopThreshold =
|
|
352
|
-
() => (isLiveStreaming ? 64 : autoscrollToRecent ? 10 : undefined),
|
|
353
|
-
[autoscrollToRecent, isLiveStreaming],
|
|
354
|
-
);
|
|
358
|
+
const autoscrollToTopThreshold = autoscrollToRecent ? (isLiveStreaming ? 300 : 10) : undefined;
|
|
355
359
|
|
|
356
360
|
const maintainVisibleContentPosition = useMemo(
|
|
357
361
|
() => ({
|
|
@@ -503,6 +507,8 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
503
507
|
}: {
|
|
504
508
|
viewableItems: ViewToken[] | undefined;
|
|
505
509
|
}) => {
|
|
510
|
+
viewabilityChangedCallback({ inverted, viewableItems });
|
|
511
|
+
|
|
506
512
|
if (!viewableItems) {
|
|
507
513
|
return;
|
|
508
514
|
}
|
|
@@ -632,13 +638,18 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
632
638
|
};
|
|
633
639
|
|
|
634
640
|
if (threadList || isMessageRemovedFromMessageList) {
|
|
635
|
-
|
|
641
|
+
if (maximumMessageLimit) {
|
|
642
|
+
// pruning has happened, reset the trackers
|
|
643
|
+
resetPaginationTrackersRef.current();
|
|
644
|
+
} else {
|
|
645
|
+
scrollToBottomIfNeeded();
|
|
646
|
+
}
|
|
636
647
|
}
|
|
637
648
|
|
|
638
649
|
messageListLengthBeforeUpdate.current = messageListLengthAfterUpdate;
|
|
639
650
|
topMessageBeforeUpdate.current = topMessageAfterUpdate;
|
|
640
651
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
641
|
-
}, [threadList, messageListLengthAfterUpdate, topMessageAfterUpdate?.id]);
|
|
652
|
+
}, [threadList, messageListLengthAfterUpdate, topMessageAfterUpdate?.id, maximumMessageLimit]);
|
|
642
653
|
|
|
643
654
|
useEffect(() => {
|
|
644
655
|
if (threadList) {
|
|
@@ -673,7 +684,11 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
673
684
|
!didMergeMessageSetsWithNoUpdates ||
|
|
674
685
|
processedMessageList.length - messageListLengthBeforeUpdate.current > 0;
|
|
675
686
|
|
|
676
|
-
|
|
687
|
+
// we don't want this behaviour while pruning, as it may scroll unnecessarily in
|
|
688
|
+
// certain scenarios
|
|
689
|
+
if ((maximumMessageLimit && shouldForceScrollToRecent) || !maximumMessageLimit) {
|
|
690
|
+
setAutoscrollToRecent(shouldForceScrollToRecent);
|
|
691
|
+
}
|
|
677
692
|
|
|
678
693
|
if (!didMergeMessageSetsWithNoUpdates) {
|
|
679
694
|
const shouldScrollToRecentOnNewOwnMessage = shouldScrollToRecentOnNewOwnMessageRef.current();
|
|
@@ -688,7 +703,13 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
688
703
|
}, WAIT_FOR_SCROLL_TIMEOUT); // flatlist might take a bit to update, so a small delay is needed
|
|
689
704
|
}
|
|
690
705
|
}
|
|
691
|
-
}, [
|
|
706
|
+
}, [
|
|
707
|
+
channel,
|
|
708
|
+
threadList,
|
|
709
|
+
processedMessageList,
|
|
710
|
+
shouldScrollToRecentOnNewOwnMessageRef,
|
|
711
|
+
maximumMessageLimit,
|
|
712
|
+
]);
|
|
692
713
|
|
|
693
714
|
const goToMessage = useStableCallback(async (messageId: string) => {
|
|
694
715
|
const indexOfParentInMessageList = processedMessageList.findIndex(
|
|
@@ -1288,6 +1309,7 @@ export const MessageList = (props: MessageListProps) => {
|
|
|
1288
1309
|
loadChannelAroundMessage,
|
|
1289
1310
|
loading,
|
|
1290
1311
|
LoadingIndicator,
|
|
1312
|
+
maximumMessageLimit,
|
|
1291
1313
|
markRead,
|
|
1292
1314
|
NetworkDownIndicator,
|
|
1293
1315
|
reloadChannel,
|
|
@@ -1348,6 +1370,7 @@ export const MessageList = (props: MessageListProps) => {
|
|
|
1348
1370
|
loadMoreRecentThread,
|
|
1349
1371
|
loadMoreThread,
|
|
1350
1372
|
markRead,
|
|
1373
|
+
maximumMessageLimit,
|
|
1351
1374
|
Message,
|
|
1352
1375
|
MessageSystem,
|
|
1353
1376
|
myMessageTheme,
|
|
@@ -56,7 +56,7 @@ export const useMessageList = (params: UseMessageListParams) => {
|
|
|
56
56
|
const { hideDateSeparators, maxTimeBetweenGroupedMessages } = useChannelContext();
|
|
57
57
|
const { deletedMessagesVisibilityType, getMessagesGroupStyles = getGroupStyles } =
|
|
58
58
|
useMessagesContext();
|
|
59
|
-
const { messages } = usePaginatedMessageListContext();
|
|
59
|
+
const { messages, viewabilityChangedCallback } = usePaginatedMessageListContext();
|
|
60
60
|
const { threadMessages } = useThreadContext();
|
|
61
61
|
const messageList = threadList ? threadMessages : messages;
|
|
62
62
|
|
|
@@ -129,7 +129,8 @@ export const useMessageList = (params: UseMessageListParams) => {
|
|
|
129
129
|
processedMessageList: data,
|
|
130
130
|
/** Raw messages from the channel state */
|
|
131
131
|
rawMessageList: messageList,
|
|
132
|
+
viewabilityChangedCallback,
|
|
132
133
|
}),
|
|
133
|
-
[data, messageList],
|
|
134
|
+
[data, messageList, viewabilityChangedCallback],
|
|
134
135
|
);
|
|
135
136
|
};
|
|
@@ -147,6 +147,12 @@ export type ChannelContextValue = {
|
|
|
147
147
|
* to still consider them grouped together
|
|
148
148
|
*/
|
|
149
149
|
maxTimeBetweenGroupedMessages?: number;
|
|
150
|
+
/**
|
|
151
|
+
* The maximum number of messages that can be loaded into the state when new messages arrive.
|
|
152
|
+
* Any excess messages will be pruned from the back of the list (oldest first), unless we are
|
|
153
|
+
* currently near them within the viewport.
|
|
154
|
+
*/
|
|
155
|
+
maximumMessageLimit?: number;
|
|
150
156
|
/**
|
|
151
157
|
* Custom UI component for sticky header of channel.
|
|
152
158
|
*
|
|
@@ -2,6 +2,7 @@ import React, { PropsWithChildren, useContext } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import type { ChannelState } from 'stream-chat';
|
|
4
4
|
|
|
5
|
+
import { ViewabilityChangedCallbackInput } from '../../hooks/usePrunableMessageList';
|
|
5
6
|
import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
|
|
6
7
|
|
|
7
8
|
import { isTestEnvironment } from '../utils/isTestEnvironment';
|
|
@@ -24,6 +25,10 @@ export type PaginatedMessageListContextValue = {
|
|
|
24
25
|
* Messages from client state
|
|
25
26
|
*/
|
|
26
27
|
messages: ChannelState['messages'];
|
|
28
|
+
/**
|
|
29
|
+
* A callback that is to be passed to onViewableItemsChanged in the underlying `MessageList`
|
|
30
|
+
*/
|
|
31
|
+
viewabilityChangedCallback: (config: ViewabilityChangedCallbackInput) => void;
|
|
27
32
|
/**
|
|
28
33
|
* Has more messages to load
|
|
29
34
|
*/
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ViewToken } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Channel } from 'stream-chat';
|
|
6
|
+
|
|
7
|
+
import { useStableCallback } from './useStableCallback';
|
|
8
|
+
|
|
9
|
+
import { ChannelPropsWithContext } from '../components';
|
|
10
|
+
|
|
11
|
+
export type VisibleRangeConfig = { first: number; last: number; inverted: boolean };
|
|
12
|
+
export type ViewabilityChangedCallbackInput = {
|
|
13
|
+
viewableItems: ViewToken[] | undefined;
|
|
14
|
+
inverted: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// The number of messages from an edge we want to be viewing before we stop pruning
|
|
18
|
+
const calculateSafeGap = (maximumMessages: number) => 0.2 * maximumMessages;
|
|
19
|
+
|
|
20
|
+
const isNearEnd = ({
|
|
21
|
+
rangeConfig,
|
|
22
|
+
maximumMessageLimit,
|
|
23
|
+
}: {
|
|
24
|
+
rangeConfig: VisibleRangeConfig;
|
|
25
|
+
maximumMessageLimit: number;
|
|
26
|
+
}) => {
|
|
27
|
+
const { first, last, inverted } = rangeConfig;
|
|
28
|
+
|
|
29
|
+
const safeGap = calculateSafeGap(maximumMessageLimit);
|
|
30
|
+
|
|
31
|
+
if (!inverted) return first <= safeGap;
|
|
32
|
+
|
|
33
|
+
return last >= maximumMessageLimit - 1 - safeGap;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function usePrunableMessageList({
|
|
37
|
+
// setter to update the array used by the List
|
|
38
|
+
setMessages: rawSetMessages,
|
|
39
|
+
maximumMessageLimit,
|
|
40
|
+
}: {
|
|
41
|
+
setMessages: (channel: Channel) => void;
|
|
42
|
+
} & Pick<ChannelPropsWithContext, 'maximumMessageLimit'>) {
|
|
43
|
+
// Track visible index range (index in `channel.messages`)
|
|
44
|
+
const visibleRangeConfigRef = useRef<VisibleRangeConfig>({ first: 0, inverted: true, last: -1 });
|
|
45
|
+
|
|
46
|
+
const viewabilityChangedCallback = useStableCallback(
|
|
47
|
+
({ viewableItems, inverted = true }: ViewabilityChangedCallbackInput) => {
|
|
48
|
+
if (!viewableItems?.length || maximumMessageLimit == null) return;
|
|
49
|
+
let first = Infinity;
|
|
50
|
+
let last = -1;
|
|
51
|
+
for (const v of viewableItems) {
|
|
52
|
+
if (v.index == null) continue;
|
|
53
|
+
if (v.index < first) first = v.index;
|
|
54
|
+
if (v.index > last) last = v.index;
|
|
55
|
+
}
|
|
56
|
+
if (first !== Infinity) visibleRangeConfigRef.current = { first, inverted, last };
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Prune when length exceeds MAX, but only if the viewport is far from the back edge
|
|
61
|
+
const setMessages = useStableCallback((channel: Channel) => {
|
|
62
|
+
const rangeConfig = visibleRangeConfigRef.current;
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
maximumMessageLimit == null ||
|
|
66
|
+
channel.state.messages.length <= maximumMessageLimit ||
|
|
67
|
+
isNearEnd({ maximumMessageLimit, rangeConfig })
|
|
68
|
+
) {
|
|
69
|
+
rawSetMessages(channel);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
channel.state.pruneOldest(maximumMessageLimit);
|
|
74
|
+
|
|
75
|
+
rawSetMessages(channel);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { setMessages, viewabilityChangedCallback };
|
|
79
|
+
}
|
package/src/version.json
CHANGED