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.
Files changed (84) hide show
  1. package/lib/commonjs/components/Channel/Channel.js +13 -3
  2. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  3. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js +3 -1
  4. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  5. package/lib/commonjs/components/Channel/hooks/useCreatePaginatedMessageListContext.js +5 -3
  6. package/lib/commonjs/components/Channel/hooks/useCreatePaginatedMessageListContext.js.map +1 -1
  7. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js +38 -21
  8. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
  9. package/lib/commonjs/components/Message/MessageSimple/MessageBubble.js +157 -0
  10. package/lib/commonjs/components/Message/MessageSimple/MessageBubble.js.map +1 -0
  11. package/lib/commonjs/components/Message/MessageSimple/MessageSimple.js +29 -124
  12. package/lib/commonjs/components/Message/MessageSimple/MessageSimple.js.map +1 -1
  13. package/lib/commonjs/components/MessageList/MessageFlashList.js +56 -65
  14. package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
  15. package/lib/commonjs/components/MessageList/MessageList.js +20 -8
  16. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  17. package/lib/commonjs/components/MessageList/hooks/useMessageList.js +5 -3
  18. package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
  19. package/lib/commonjs/contexts/channelContext/ChannelContext.js.map +1 -1
  20. package/lib/commonjs/contexts/paginatedMessageListContext/PaginatedMessageListContext.js.map +1 -1
  21. package/lib/commonjs/hooks/usePrunableMessageList.js +63 -0
  22. package/lib/commonjs/hooks/usePrunableMessageList.js.map +1 -0
  23. package/lib/commonjs/version.json +1 -1
  24. package/lib/module/components/Channel/Channel.js +13 -3
  25. package/lib/module/components/Channel/Channel.js.map +1 -1
  26. package/lib/module/components/Channel/hooks/useCreateChannelContext.js +3 -1
  27. package/lib/module/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  28. package/lib/module/components/Channel/hooks/useCreatePaginatedMessageListContext.js +5 -3
  29. package/lib/module/components/Channel/hooks/useCreatePaginatedMessageListContext.js.map +1 -1
  30. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js +38 -21
  31. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
  32. package/lib/module/components/Message/MessageSimple/MessageBubble.js +157 -0
  33. package/lib/module/components/Message/MessageSimple/MessageBubble.js.map +1 -0
  34. package/lib/module/components/Message/MessageSimple/MessageSimple.js +29 -124
  35. package/lib/module/components/Message/MessageSimple/MessageSimple.js.map +1 -1
  36. package/lib/module/components/MessageList/MessageFlashList.js +56 -65
  37. package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
  38. package/lib/module/components/MessageList/MessageList.js +20 -8
  39. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  40. package/lib/module/components/MessageList/hooks/useMessageList.js +5 -3
  41. package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
  42. package/lib/module/contexts/channelContext/ChannelContext.js.map +1 -1
  43. package/lib/module/contexts/paginatedMessageListContext/PaginatedMessageListContext.js.map +1 -1
  44. package/lib/module/hooks/usePrunableMessageList.js +63 -0
  45. package/lib/module/hooks/usePrunableMessageList.js.map +1 -0
  46. package/lib/module/version.json +1 -1
  47. package/lib/typescript/components/Channel/Channel.d.ts +1 -1
  48. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  49. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts +1 -1
  50. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts.map +1 -1
  51. package/lib/typescript/components/Channel/hooks/useCreatePaginatedMessageListContext.d.ts +1 -1
  52. package/lib/typescript/components/Channel/hooks/useCreatePaginatedMessageListContext.d.ts.map +1 -1
  53. package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewData.d.ts.map +1 -1
  54. package/lib/typescript/components/Message/MessageSimple/MessageBubble.d.ts +11 -0
  55. package/lib/typescript/components/Message/MessageSimple/MessageBubble.d.ts.map +1 -0
  56. package/lib/typescript/components/Message/MessageSimple/MessageSimple.d.ts.map +1 -1
  57. package/lib/typescript/components/MessageList/MessageFlashList.d.ts +10 -1
  58. package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
  59. package/lib/typescript/components/MessageList/MessageList.d.ts +1 -1
  60. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  61. package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +1 -0
  62. package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts.map +1 -1
  63. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts +6 -0
  64. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts.map +1 -1
  65. package/lib/typescript/contexts/paginatedMessageListContext/PaginatedMessageListContext.d.ts +5 -0
  66. package/lib/typescript/contexts/paginatedMessageListContext/PaginatedMessageListContext.d.ts.map +1 -1
  67. package/lib/typescript/hooks/usePrunableMessageList.d.ts +19 -0
  68. package/lib/typescript/hooks/usePrunableMessageList.d.ts.map +1 -0
  69. package/lib/typescript/hooks/useTranslatedMessage.d.ts +4 -0
  70. package/lib/typescript/hooks/useTranslatedMessage.d.ts.map +1 -1
  71. package/package.json +4 -4
  72. package/src/components/Channel/Channel.tsx +9 -1
  73. package/src/components/Channel/hooks/useCreateChannelContext.ts +3 -0
  74. package/src/components/Channel/hooks/useCreatePaginatedMessageListContext.ts +3 -1
  75. package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts +59 -29
  76. package/src/components/Message/MessageSimple/MessageBubble.tsx +235 -0
  77. package/src/components/Message/MessageSimple/MessageSimple.tsx +30 -193
  78. package/src/components/MessageList/MessageFlashList.tsx +69 -66
  79. package/src/components/MessageList/MessageList.tsx +37 -14
  80. package/src/components/MessageList/hooks/useMessageList.ts +3 -2
  81. package/src/contexts/channelContext/ChannelContext.tsx +6 -0
  82. package/src/contexts/paginatedMessageListContext/PaginatedMessageListContext.tsx +5 -0
  83. package/src/hooks/usePrunableMessageList.ts +79 -0
  84. 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 { dateSeparatorsRef, messageGroupStylesRef, processedMessageList, rawMessageList } =
341
- useMessageList({
342
- isFlashList: true,
343
- noGroupByUser,
344
- threadList,
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: autoScrollToRecent || threadList ? 10 : undefined,
399
+ autoscrollToBottomThreshold: autoscrollToRecent ? 1 : undefined,
373
400
  startRenderingFromBottom: true,
374
401
  };
375
- }, [autoScrollToRecent, threadList]);
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
- if (indexOfParentInMessageList !== -1) {
419
- flashListRef.current?.scrollToIndex({
420
- animated: true,
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
- setAutoScrollToRecent(false);
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
- () => ({ ...styles.listContainer, ...listContainer, ...additionalFlashListProps?.style }),
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={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 { dateSeparatorsRef, messageGroupStylesRef, processedMessageList, rawMessageList } =
326
- useMessageList({
327
- isLiveStreaming,
328
- noGroupByUser,
329
- threadList,
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 = useMemo(
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
- scrollToBottomIfNeeded();
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
- setAutoscrollToRecent(shouldForceScrollToRecent);
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
- }, [channel, threadList, processedMessageList, shouldScrollToRecentOnNewOwnMessageRef]);
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
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "8.6.1"
2
+ "version": "8.6.2-beta.1"
3
3
  }