stream-chat-react-native-core 5.22.2-beta.8 → 5.23.0-beta.2
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 +587 -384
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +170 -179
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js +6 -1
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.js +36 -0
- package/lib/commonjs/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.js.map +1 -0
- package/lib/commonjs/contexts/channelsStateContext/ChannelsStateContext.js +1 -1
- package/lib/commonjs/contexts/channelsStateContext/ChannelsStateContext.js.map +1 -1
- package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +31 -15
- package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
- package/lib/commonjs/i18n/fr.json +15 -15
- package/lib/commonjs/i18n/hi.json +15 -15
- package/lib/commonjs/i18n/it.json +15 -15
- package/lib/commonjs/i18n/nl.json +15 -15
- package/lib/commonjs/i18n/ru.json +15 -15
- package/lib/commonjs/i18n/tr.json +15 -15
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Channel/Channel.js +587 -384
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +170 -179
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/hooks/useMessageList.js +6 -1
- package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/module/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.js +36 -0
- package/lib/module/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.js.map +1 -0
- package/lib/module/contexts/channelsStateContext/ChannelsStateContext.js +1 -1
- package/lib/module/contexts/channelsStateContext/ChannelsStateContext.js.map +1 -1
- package/lib/module/contexts/messageInputContext/MessageInputContext.js +31 -15
- package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
- package/lib/module/i18n/fr.json +15 -15
- package/lib/module/i18n/hi.json +15 -15
- package/lib/module/i18n/it.json +15 -15
- package/lib/module/i18n/nl.json +15 -15
- package/lib/module/i18n/ru.json +15 -15
- package/lib/module/i18n/tr.json +15 -15
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +6 -1
- package/lib/typescript/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.d.ts +4 -0
- package/lib/typescript/i18n/fr.json +15 -15
- package/lib/typescript/i18n/hi.json +15 -15
- package/lib/typescript/i18n/it.json +15 -15
- package/lib/typescript/i18n/nl.json +15 -15
- package/lib/typescript/i18n/ru.json +15 -15
- package/lib/typescript/i18n/tr.json +15 -15
- package/package.json +1 -1
- package/src/components/Channel/Channel.tsx +237 -61
- package/src/components/MessageList/MessageList.tsx +190 -180
- package/src/components/MessageList/__tests__/useMessageList.test.tsx +5 -2
- package/src/components/MessageList/hooks/useMessageList.ts +8 -1
- package/src/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.ts +44 -0
- package/src/contexts/__tests__/index.test.tsx +1 -1
- package/src/contexts/channelsStateContext/ChannelsStateContext.tsx +1 -1
- package/src/contexts/messageInputContext/MessageInputContext.tsx +7 -1
- package/src/i18n/fr.json +15 -15
- package/src/i18n/hi.json +15 -15
- package/src/i18n/it.json +15 -15
- package/src/i18n/nl.json +15 -15
- package/src/i18n/ru.json +15 -15
- package/src/i18n/tr.json +15 -15
- package/src/version.json +1 -1
|
@@ -9,11 +9,15 @@ import {
|
|
|
9
9
|
ViewToken,
|
|
10
10
|
} from 'react-native';
|
|
11
11
|
|
|
12
|
+
import type { FormatMessageResponse } from 'stream-chat';
|
|
13
|
+
|
|
12
14
|
import {
|
|
13
15
|
isMessageWithStylesReadByAndDateSeparator,
|
|
14
16
|
MessageType,
|
|
15
17
|
useMessageList,
|
|
16
18
|
} from './hooks/useMessageList';
|
|
19
|
+
import { useShouldScrollToRecentOnNewOwnMessage } from './hooks/useShouldScrollToRecentOnNewOwnMessage';
|
|
20
|
+
|
|
17
21
|
import { InlineLoadingMoreIndicator } from './InlineLoadingMoreIndicator';
|
|
18
22
|
import { InlineLoadingMoreRecentIndicator } from './InlineLoadingMoreRecentIndicator';
|
|
19
23
|
import { InlineLoadingMoreThreadIndicator } from './InlineLoadingMoreThreadIndicator';
|
|
@@ -298,21 +302,33 @@ const MessageListWithContext = <
|
|
|
298
302
|
[myMessageTheme, theme],
|
|
299
303
|
);
|
|
300
304
|
|
|
301
|
-
|
|
305
|
+
/**
|
|
306
|
+
* NOTE: rawMessageList changes only when messages array state changes
|
|
307
|
+
* processedMessageList changes on any state change
|
|
308
|
+
*/
|
|
309
|
+
const { processedMessageList, rawMessageList } = useMessageList<StreamChatGenerics>({
|
|
302
310
|
noGroupByUser,
|
|
303
311
|
threadList,
|
|
304
312
|
});
|
|
305
313
|
const messageListLengthBeforeUpdate = useRef(0);
|
|
306
|
-
const messageListLengthAfterUpdate =
|
|
314
|
+
const messageListLengthAfterUpdate = processedMessageList.length;
|
|
307
315
|
|
|
308
316
|
/**
|
|
309
317
|
* We need topMessage and channelLastRead values to set the initial scroll position.
|
|
310
318
|
* So these values only get used if `initialScrollToFirstUnreadMessage` prop is true.
|
|
311
319
|
*/
|
|
312
|
-
const topMessageBeforeUpdate = useRef<
|
|
313
|
-
const
|
|
320
|
+
const topMessageBeforeUpdate = useRef<FormatMessageResponse<StreamChatGenerics>>();
|
|
321
|
+
const latestNonCurrentMessageBeforeUpdateRef =
|
|
322
|
+
useRef<FormatMessageResponse<StreamChatGenerics>>();
|
|
323
|
+
const topMessageAfterUpdate: FormatMessageResponse<StreamChatGenerics> | undefined =
|
|
324
|
+
rawMessageList[0];
|
|
325
|
+
|
|
326
|
+
const shouldScrollToRecentOnNewOwnMessageRef = useShouldScrollToRecentOnNewOwnMessage(
|
|
327
|
+
rawMessageList,
|
|
328
|
+
client.userID,
|
|
329
|
+
);
|
|
314
330
|
|
|
315
|
-
const [
|
|
331
|
+
const [autoscrollToRecent, setAutoscrollToRecent] = useState(false);
|
|
316
332
|
|
|
317
333
|
/**
|
|
318
334
|
* We want to call onEndReached and onStartReached only once, per content length.
|
|
@@ -347,20 +363,17 @@ const MessageListWithContext = <
|
|
|
347
363
|
/**
|
|
348
364
|
* The timeout id used to temporarily load the initial scroll set flag
|
|
349
365
|
*/
|
|
350
|
-
const
|
|
366
|
+
const onScrollEventTimeoutRef = useRef<NodeJS.Timeout>();
|
|
351
367
|
|
|
352
|
-
/**
|
|
353
|
-
* If a messageId was requested to scroll to but was unloaded,
|
|
354
|
-
* this flag keeps track of it to scroll to it after loading the message
|
|
355
|
-
*/
|
|
356
|
-
const messageIdToScrollToRef = useRef<string>();
|
|
357
368
|
/**
|
|
358
369
|
* Last messageID that was scrolled to after loading a new message list,
|
|
359
370
|
* this flag keeps track of it so that we dont scroll to it again on target message set
|
|
360
371
|
*/
|
|
361
372
|
const messageIdLastScrolledToRef = useRef<string>();
|
|
362
373
|
const [hasMoved, setHasMoved] = useState(false);
|
|
363
|
-
const [lastReceivedId, setLastReceivedId] = useState(
|
|
374
|
+
const [lastReceivedId, setLastReceivedId] = useState(
|
|
375
|
+
getLastReceivedMessage(processedMessageList)?.id,
|
|
376
|
+
);
|
|
364
377
|
const [scrollToBottomButtonVisible, setScrollToBottomButtonVisible] = useState(false);
|
|
365
378
|
|
|
366
379
|
const [stickyHeaderDate, setStickyHeaderDate] = useState<Date | undefined>();
|
|
@@ -426,22 +439,6 @@ const MessageListWithContext = <
|
|
|
426
439
|
onEndReachedTracker.current = {};
|
|
427
440
|
});
|
|
428
441
|
|
|
429
|
-
/**
|
|
430
|
-
* Disables the pagination trackers for a second
|
|
431
|
-
* This is used to prevent the onEndReached and onStartReached from firing
|
|
432
|
-
* when we scroll to the bottom or top of the list automatically without user interaction
|
|
433
|
-
* Ex: for targeted message scroll
|
|
434
|
-
*/
|
|
435
|
-
const tempDisablePaginationTrackersRef = useRef((messageListLength: number) => {
|
|
436
|
-
clearTimeout(tempDisablePaginationTrackersTimeoutRef.current);
|
|
437
|
-
onStartReachedTracker.current[messageListLength] = true;
|
|
438
|
-
onEndReachedTracker.current[messageListLength] = true;
|
|
439
|
-
tempDisablePaginationTrackersTimeoutRef.current = setTimeout(() => {
|
|
440
|
-
onStartReachedTracker.current[messageListLength] = false;
|
|
441
|
-
onEndReachedTracker.current[messageListLength] = false;
|
|
442
|
-
}, 1000);
|
|
443
|
-
});
|
|
444
|
-
|
|
445
442
|
useEffect(() => {
|
|
446
443
|
setScrollToBottomButtonVisible(false);
|
|
447
444
|
}, [disabled]);
|
|
@@ -482,29 +479,21 @@ const MessageListWithContext = <
|
|
|
482
479
|
}, [loading, scrollToBottomButtonVisible, isInitialScrollDone]);
|
|
483
480
|
|
|
484
481
|
useEffect(() => {
|
|
485
|
-
const lastReceivedMessage = getLastReceivedMessage(
|
|
486
|
-
|
|
487
|
-
const hasNewMessage = lastReceivedId !== lastReceivedMessage?.id;
|
|
488
|
-
const isMyMessage = lastReceivedMessage?.user?.id === client.userID;
|
|
489
|
-
|
|
482
|
+
const lastReceivedMessage = getLastReceivedMessage(processedMessageList);
|
|
490
483
|
setLastReceivedId(lastReceivedMessage?.id);
|
|
491
484
|
|
|
492
485
|
/**
|
|
493
486
|
* Scroll down when
|
|
494
|
-
*
|
|
495
|
-
* 2. new message list is small than the one before update - channel has resynced
|
|
496
|
-
* 3. created_at timestamp of top message before update is lesser than created_at timestamp of top message after update - channel has resynced
|
|
487
|
+
* created_at timestamp of top message before update is lesser than created_at timestamp of top message after update - channel has resynced
|
|
497
488
|
*/
|
|
498
489
|
const scrollToBottomIfNeeded = () => {
|
|
499
|
-
if (!client || !channel ||
|
|
490
|
+
if (!client || !channel || rawMessageList.length === 0) {
|
|
500
491
|
return;
|
|
501
492
|
}
|
|
502
493
|
if (
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
topMessageAfterUpdate?.created_at &&
|
|
507
|
-
topMessageBeforeUpdate.current.created_at < topMessageAfterUpdate.created_at)
|
|
494
|
+
topMessageBeforeUpdate.current?.created_at &&
|
|
495
|
+
topMessageAfterUpdate?.created_at &&
|
|
496
|
+
topMessageBeforeUpdate.current.created_at < topMessageAfterUpdate.created_at
|
|
508
497
|
) {
|
|
509
498
|
channelResyncScrollSet.current = false;
|
|
510
499
|
setScrollToBottomButtonVisible(false);
|
|
@@ -545,12 +534,52 @@ const MessageListWithContext = <
|
|
|
545
534
|
|
|
546
535
|
messageListLengthBeforeUpdate.current = messageListLengthAfterUpdate;
|
|
547
536
|
topMessageBeforeUpdate.current = topMessageAfterUpdate;
|
|
548
|
-
}, [
|
|
537
|
+
}, [
|
|
538
|
+
threadList,
|
|
539
|
+
hasNoMoreRecentMessagesToLoad,
|
|
540
|
+
messageListLengthAfterUpdate,
|
|
541
|
+
topMessageAfterUpdate?.id,
|
|
542
|
+
]);
|
|
549
543
|
|
|
550
544
|
useEffect(() => {
|
|
551
|
-
|
|
552
|
-
|
|
545
|
+
if (!rawMessageList.length) return;
|
|
546
|
+
const notLatestSet = !threadList && channel.state.messages !== channel.state.latestMessages;
|
|
547
|
+
if (notLatestSet) {
|
|
548
|
+
latestNonCurrentMessageBeforeUpdateRef.current =
|
|
549
|
+
channel.state.latestMessages[channel.state.latestMessages.length - 1];
|
|
550
|
+
setAutoscrollToRecent(false);
|
|
551
|
+
setScrollToBottomButtonVisible(true);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const latestNonCurrentMessageBeforeUpdate = latestNonCurrentMessageBeforeUpdateRef.current;
|
|
555
|
+
latestNonCurrentMessageBeforeUpdateRef.current = undefined;
|
|
556
|
+
const latestCurrentMessageAfterUpdate = rawMessageList[rawMessageList.length - 1];
|
|
557
|
+
if (!latestCurrentMessageAfterUpdate) {
|
|
558
|
+
setAutoscrollToRecent(true);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const didMergeMessageSetsWithNoUpdates =
|
|
562
|
+
latestNonCurrentMessageBeforeUpdate?.id === latestCurrentMessageAfterUpdate.id;
|
|
563
|
+
// if didMergeMessageSetsWithNoUpdates=false, we got new messages
|
|
564
|
+
// so we should scroll to bottom if we are near the bottom already
|
|
565
|
+
setAutoscrollToRecent(!didMergeMessageSetsWithNoUpdates);
|
|
566
|
+
|
|
567
|
+
if (!didMergeMessageSetsWithNoUpdates) {
|
|
568
|
+
const shouldScrollToRecentOnNewOwnMessage = shouldScrollToRecentOnNewOwnMessageRef.current();
|
|
569
|
+
// we should scroll to bottom where ever we are now
|
|
570
|
+
// as we have sent a new own message
|
|
571
|
+
if (shouldScrollToRecentOnNewOwnMessage) {
|
|
572
|
+
setTimeout(() => {
|
|
573
|
+
flatListRef.current?.scrollToOffset({
|
|
574
|
+
animated: true,
|
|
575
|
+
offset: 0,
|
|
576
|
+
});
|
|
577
|
+
}, 150); // flatlist might take a bit to update, so a small delay is needed
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}, [rawMessageList, threadList]);
|
|
553
581
|
|
|
582
|
+
// TODO: do not apply on RN 0.73 and above
|
|
554
583
|
const shouldApplyAndroidWorkaround = inverted && Platform.OS === 'android';
|
|
555
584
|
|
|
556
585
|
const renderItem = ({
|
|
@@ -563,14 +592,27 @@ const MessageListWithContext = <
|
|
|
563
592
|
if (!channel || channel.disconnected || (!channel.initialized && !channel.offlineMode))
|
|
564
593
|
return null;
|
|
565
594
|
|
|
595
|
+
const unreadCount = channel.countUnread();
|
|
566
596
|
const lastRead = channel.lastRead();
|
|
567
597
|
|
|
568
598
|
function isMessageUnread(messageArrayIndex: number): boolean {
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
599
|
+
const isLatestMessageSetShown = !!channel.state.messageSets.find(
|
|
600
|
+
(set) => set.isCurrent && set.isLatest,
|
|
601
|
+
);
|
|
602
|
+
const msg = processedMessageList?.[messageArrayIndex];
|
|
603
|
+
if (!isLatestMessageSetShown) {
|
|
604
|
+
if (
|
|
605
|
+
channel.state.latestMessages.length !== 0 &&
|
|
606
|
+
unreadCount > channel.state.latestMessages.length
|
|
607
|
+
) {
|
|
608
|
+
return messageArrayIndex <= unreadCount - channel.state.latestMessages.length - 1;
|
|
609
|
+
} else if (lastRead && msg.created_at) {
|
|
610
|
+
return lastRead < msg.created_at;
|
|
611
|
+
}
|
|
612
|
+
return false;
|
|
613
|
+
} else {
|
|
614
|
+
return messageArrayIndex <= unreadCount - 1;
|
|
572
615
|
}
|
|
573
|
-
return false;
|
|
574
616
|
}
|
|
575
617
|
|
|
576
618
|
const isCurrentMessageUnread = isMessageUnread(index);
|
|
@@ -593,61 +635,40 @@ const MessageListWithContext = <
|
|
|
593
635
|
}
|
|
594
636
|
|
|
595
637
|
const wrapMessageInTheme = client.userID === message.user?.id && !!myMessageTheme;
|
|
638
|
+
const renderDateSeperator = isMessageWithStylesReadByAndDateSeparator(message) &&
|
|
639
|
+
message.dateSeparator && <InlineDateSeparator date={message.dateSeparator} />;
|
|
640
|
+
const renderMessage = (
|
|
641
|
+
<Message
|
|
642
|
+
goToMessage={goToMessage}
|
|
643
|
+
groupStyles={isMessageWithStylesReadByAndDateSeparator(message) ? message.groupStyles : []}
|
|
644
|
+
isTargetedMessage={targetedMessage === message.id}
|
|
645
|
+
lastReceivedId={
|
|
646
|
+
lastReceivedId === message.id || message.quoted_message_id ? lastReceivedId : undefined
|
|
647
|
+
}
|
|
648
|
+
message={message}
|
|
649
|
+
onThreadSelect={onThreadSelect}
|
|
650
|
+
showUnreadUnderlay={showUnreadUnderlay}
|
|
651
|
+
style={[{ paddingHorizontal: screenPadding }, messageContainer]}
|
|
652
|
+
threadList={threadList}
|
|
653
|
+
/>
|
|
654
|
+
);
|
|
596
655
|
return wrapMessageInTheme ? (
|
|
597
656
|
<>
|
|
598
|
-
{shouldApplyAndroidWorkaround &&
|
|
599
|
-
isMessageWithStylesReadByAndDateSeparator(message) &&
|
|
600
|
-
message.dateSeparator && <InlineDateSeparator date={message.dateSeparator} />}
|
|
657
|
+
{shouldApplyAndroidWorkaround && renderDateSeperator}
|
|
601
658
|
<ThemeProvider mergedStyle={modifiedTheme}>
|
|
602
|
-
<View testID={`message-list-item-${index}`}>
|
|
603
|
-
<Message
|
|
604
|
-
goToMessage={goToMessage}
|
|
605
|
-
groupStyles={
|
|
606
|
-
isMessageWithStylesReadByAndDateSeparator(message) ? message.groupStyles : []
|
|
607
|
-
}
|
|
608
|
-
isTargetedMessage={targetedMessage === message.id}
|
|
609
|
-
lastReceivedId={lastReceivedId === message.id ? lastReceivedId : undefined}
|
|
610
|
-
message={message}
|
|
611
|
-
onThreadSelect={onThreadSelect}
|
|
612
|
-
showUnreadUnderlay={showUnreadUnderlay}
|
|
613
|
-
style={[{ paddingHorizontal: screenPadding }, messageContainer]}
|
|
614
|
-
threadList={threadList}
|
|
615
|
-
/>
|
|
616
|
-
</View>
|
|
659
|
+
<View testID={`message-list-item-${index}`}>{renderMessage}</View>
|
|
617
660
|
</ThemeProvider>
|
|
618
|
-
{!shouldApplyAndroidWorkaround &&
|
|
619
|
-
isMessageWithStylesReadByAndDateSeparator(message) &&
|
|
620
|
-
message.dateSeparator && <InlineDateSeparator date={message.dateSeparator} />}
|
|
661
|
+
{!shouldApplyAndroidWorkaround && renderDateSeperator}
|
|
621
662
|
{/* Adding indicator below the messages, since the list is inverted */}
|
|
622
663
|
{insertInlineUnreadIndicator && <InlineUnreadIndicator />}
|
|
623
664
|
</>
|
|
624
665
|
) : (
|
|
625
666
|
<>
|
|
626
667
|
<View testID={`message-list-item-${index}`}>
|
|
627
|
-
{shouldApplyAndroidWorkaround &&
|
|
628
|
-
|
|
629
|
-
message.dateSeparator && <InlineDateSeparator date={message.dateSeparator} />}
|
|
630
|
-
<Message
|
|
631
|
-
goToMessage={goToMessage}
|
|
632
|
-
groupStyles={
|
|
633
|
-
isMessageWithStylesReadByAndDateSeparator(message) ? message.groupStyles : []
|
|
634
|
-
}
|
|
635
|
-
isTargetedMessage={targetedMessage === message.id}
|
|
636
|
-
lastReceivedId={
|
|
637
|
-
lastReceivedId === message.id || message.quoted_message_id
|
|
638
|
-
? lastReceivedId
|
|
639
|
-
: undefined
|
|
640
|
-
}
|
|
641
|
-
message={message}
|
|
642
|
-
onThreadSelect={onThreadSelect}
|
|
643
|
-
showUnreadUnderlay={showUnreadUnderlay}
|
|
644
|
-
style={[{ paddingHorizontal: screenPadding }, messageContainer]}
|
|
645
|
-
threadList={threadList}
|
|
646
|
-
/>
|
|
668
|
+
{shouldApplyAndroidWorkaround && renderDateSeperator}
|
|
669
|
+
{renderMessage}
|
|
647
670
|
</View>
|
|
648
|
-
{!shouldApplyAndroidWorkaround &&
|
|
649
|
-
isMessageWithStylesReadByAndDateSeparator(message) &&
|
|
650
|
-
message.dateSeparator && <InlineDateSeparator date={message.dateSeparator} />}
|
|
671
|
+
{!shouldApplyAndroidWorkaround && renderDateSeperator}
|
|
651
672
|
{/* Adding indicator below the messages, since the list is inverted */}
|
|
652
673
|
{insertInlineUnreadIndicator && <InlineUnreadIndicator />}
|
|
653
674
|
</>
|
|
@@ -679,12 +700,15 @@ const MessageListWithContext = <
|
|
|
679
700
|
*/
|
|
680
701
|
const maybeCallOnStartReached = async (limit?: number) => {
|
|
681
702
|
// If onStartReached has already been called for given data length, then ignore.
|
|
682
|
-
if (
|
|
703
|
+
if (
|
|
704
|
+
processedMessageList?.length &&
|
|
705
|
+
onStartReachedTracker.current[processedMessageList.length]
|
|
706
|
+
) {
|
|
683
707
|
return;
|
|
684
708
|
}
|
|
685
709
|
|
|
686
|
-
if (
|
|
687
|
-
onStartReachedTracker.current[
|
|
710
|
+
if (processedMessageList?.length) {
|
|
711
|
+
onStartReachedTracker.current[processedMessageList.length] = true;
|
|
688
712
|
}
|
|
689
713
|
|
|
690
714
|
const callback = () => {
|
|
@@ -716,12 +740,12 @@ const MessageListWithContext = <
|
|
|
716
740
|
*/
|
|
717
741
|
const maybeCallOnEndReached = async () => {
|
|
718
742
|
// If onEndReached has already been called for given messageList length, then ignore.
|
|
719
|
-
if (
|
|
743
|
+
if (processedMessageList?.length && onEndReachedTracker.current[processedMessageList.length]) {
|
|
720
744
|
return;
|
|
721
745
|
}
|
|
722
746
|
|
|
723
|
-
if (
|
|
724
|
-
onEndReachedTracker.current[
|
|
747
|
+
if (processedMessageList?.length) {
|
|
748
|
+
onEndReachedTracker.current[processedMessageList.length] = true;
|
|
725
749
|
}
|
|
726
750
|
|
|
727
751
|
const callback = () => {
|
|
@@ -749,21 +773,16 @@ const MessageListWithContext = <
|
|
|
749
773
|
}
|
|
750
774
|
};
|
|
751
775
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
*/
|
|
759
|
-
const onScrollEvent: ScrollViewProps['onScroll'] = (event) => {
|
|
776
|
+
const onUserScrollEvent: NonNullable<ScrollViewProps['onScroll']> = (event) => {
|
|
777
|
+
const nativeEvent = event.nativeEvent;
|
|
778
|
+
clearTimeout(onScrollEventTimeoutRef.current);
|
|
779
|
+
const offset = nativeEvent.contentOffset.y;
|
|
780
|
+
const visibleLength = nativeEvent.layoutMeasurement.height;
|
|
781
|
+
const contentLength = nativeEvent.contentSize.height;
|
|
760
782
|
if (!channel || !channelResyncScrollSet.current) {
|
|
761
783
|
return;
|
|
762
784
|
}
|
|
763
785
|
|
|
764
|
-
const offset = event.nativeEvent.contentOffset.y;
|
|
765
|
-
const visibleLength = event.nativeEvent.layoutMeasurement.height;
|
|
766
|
-
const contentLength = event.nativeEvent.contentSize.height;
|
|
767
786
|
// Check if scroll has reached either start of end of list.
|
|
768
787
|
const isScrollAtStart = offset < 100;
|
|
769
788
|
const isScrollAtEnd = contentLength - visibleLength - offset < 100;
|
|
@@ -775,32 +794,38 @@ const MessageListWithContext = <
|
|
|
775
794
|
if (isScrollAtEnd) {
|
|
776
795
|
maybeCallOnEndReached();
|
|
777
796
|
}
|
|
797
|
+
};
|
|
778
798
|
|
|
799
|
+
const handleScroll: ScrollViewProps['onScroll'] = (event) => {
|
|
800
|
+
const offset = event.nativeEvent.contentOffset.y;
|
|
779
801
|
// Show scrollToBottom button once scroll position goes beyond 150.
|
|
780
802
|
const isScrollAtBottom = offset <= 150;
|
|
781
803
|
|
|
804
|
+
const notLatestSet = channel.state.messages !== channel.state.latestMessages;
|
|
805
|
+
|
|
806
|
+
const showScrollToBottomButton =
|
|
807
|
+
(!threadList && notLatestSet) || !isScrollAtBottom || !hasNoMoreRecentMessagesToLoad;
|
|
808
|
+
|
|
782
809
|
/**
|
|
783
|
-
* Following if condition covers following cases:
|
|
784
810
|
* 1. If I scroll up -> show scrollToBottom button.
|
|
785
811
|
* 2. If I scroll to bottom of screen
|
|
786
812
|
* |-> hide scrollToBottom button.
|
|
787
813
|
* |-> if channel is unread, call markRead().
|
|
788
814
|
*/
|
|
789
|
-
|
|
790
|
-
const showScrollToBottomButton = !isScrollAtBottom || !hasNoMoreRecentMessagesToLoad;
|
|
815
|
+
setScrollToBottomButtonVisible(showScrollToBottomButton);
|
|
791
816
|
|
|
792
817
|
const shouldMarkRead =
|
|
793
|
-
!threadList &&
|
|
818
|
+
!threadList &&
|
|
819
|
+
!notLatestSet &&
|
|
820
|
+
offset <= 0 &&
|
|
821
|
+
hasNoMoreRecentMessagesToLoad &&
|
|
822
|
+
channel.countUnread() > 0;
|
|
794
823
|
|
|
795
824
|
if (shouldMarkRead) {
|
|
796
825
|
markRead();
|
|
797
826
|
}
|
|
798
827
|
|
|
799
|
-
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
const handleScroll: ScrollViewProps['onScroll'] = (event) => {
|
|
803
|
-
onScrollEvent(event);
|
|
828
|
+
setInitialScrollDone(false);
|
|
804
829
|
|
|
805
830
|
if (onListScroll) {
|
|
806
831
|
onListScroll(event);
|
|
@@ -808,7 +833,10 @@ const MessageListWithContext = <
|
|
|
808
833
|
};
|
|
809
834
|
|
|
810
835
|
const goToNewMessages = async () => {
|
|
811
|
-
|
|
836
|
+
const isNotLatestSet = channel.state.messages !== channel.state.latestMessages;
|
|
837
|
+
if (isNotLatestSet && hasNoMoreRecentMessagesToLoad) {
|
|
838
|
+
loadChannelAroundMessage({});
|
|
839
|
+
} else if (!hasNoMoreRecentMessagesToLoad) {
|
|
812
840
|
resetPaginationTrackersRef.current();
|
|
813
841
|
await reloadChannel();
|
|
814
842
|
} else if (flatListRef.current) {
|
|
@@ -830,10 +858,6 @@ const MessageListWithContext = <
|
|
|
830
858
|
>((info) => {
|
|
831
859
|
// We got a failure as we tried to scroll to an item that was outside the render length
|
|
832
860
|
if (!flatListRef.current) return;
|
|
833
|
-
const dataLength = flatListRef.current.props?.data?.length;
|
|
834
|
-
if (dataLength) {
|
|
835
|
-
tempDisablePaginationTrackersRef.current(dataLength);
|
|
836
|
-
}
|
|
837
861
|
// we don't know the actual size of all items but we can see the average, so scroll to the closest offset
|
|
838
862
|
flatListRef.current.scrollToOffset({
|
|
839
863
|
animated: false,
|
|
@@ -843,17 +867,14 @@ const MessageListWithContext = <
|
|
|
843
867
|
// with a little delay to wait for scroll to offset to complete, we can then scroll to the index
|
|
844
868
|
failScrollTimeoutId.current = setTimeout(() => {
|
|
845
869
|
try {
|
|
846
|
-
if (dataLength) {
|
|
847
|
-
tempDisablePaginationTrackersRef.current(dataLength);
|
|
848
|
-
}
|
|
849
870
|
flatListRef.current?.scrollToIndex({
|
|
850
871
|
animated: false,
|
|
851
872
|
index: info.index,
|
|
852
873
|
viewPosition: 0.5, // try to place message in the center of the screen
|
|
853
874
|
});
|
|
854
|
-
// in case the target message was cleared out
|
|
855
|
-
// the state being set again will trigger the highlight again
|
|
856
875
|
if (messageIdLastScrolledToRef.current) {
|
|
876
|
+
// in case the target message was cleared out
|
|
877
|
+
// the state being set again will trigger the highlight again
|
|
857
878
|
setTargetedMessage(messageIdLastScrolledToRef.current);
|
|
858
879
|
}
|
|
859
880
|
scrollToIndexFailedRetryCountRef.current = 0;
|
|
@@ -862,10 +883,6 @@ const MessageListWithContext = <
|
|
|
862
883
|
!onScrollToIndexFailedRef.current ||
|
|
863
884
|
scrollToIndexFailedRetryCountRef.current > MAX_RETRIES_AFTER_SCROLL_FAILURE
|
|
864
885
|
) {
|
|
865
|
-
console.log(
|
|
866
|
-
`Scrolling to index failed after ${MAX_RETRIES_AFTER_SCROLL_FAILURE} retries`,
|
|
867
|
-
e,
|
|
868
|
-
);
|
|
869
886
|
scrollToIndexFailedRetryCountRef.current = 0;
|
|
870
887
|
return;
|
|
871
888
|
}
|
|
@@ -882,34 +899,27 @@ const MessageListWithContext = <
|
|
|
882
899
|
// this onScrollToIndexFailed will be called again
|
|
883
900
|
});
|
|
884
901
|
|
|
885
|
-
const goToMessage =
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
messageIdToScrollToRef.current = messageId; // keep track of the id to scroll afterwards
|
|
908
|
-
loadChannelAroundMessage({ messageId }); // now try to load the message and whats around it
|
|
909
|
-
resetPaginationTrackersRef.current();
|
|
910
|
-
},
|
|
911
|
-
[messageList],
|
|
912
|
-
);
|
|
902
|
+
const goToMessage = (messageId: string) => {
|
|
903
|
+
const indexOfParentInMessageList = processedMessageList.findIndex(
|
|
904
|
+
(message) => message?.id === messageId,
|
|
905
|
+
);
|
|
906
|
+
if (indexOfParentInMessageList !== -1 && flatListRef.current) {
|
|
907
|
+
clearTimeout(failScrollTimeoutId.current);
|
|
908
|
+
scrollToIndexFailedRetryCountRef.current = 0;
|
|
909
|
+
// keep track of this messageId, so that we dont scroll to again in useEffect for targeted message change
|
|
910
|
+
messageIdLastScrolledToRef.current = messageId;
|
|
911
|
+
setTargetedMessage(messageId);
|
|
912
|
+
// now scroll to it with animated=true (in useEffect animated=false is used)
|
|
913
|
+
flatListRef.current.scrollToIndex({
|
|
914
|
+
animated: true,
|
|
915
|
+
index: indexOfParentInMessageList,
|
|
916
|
+
viewPosition: 0.5, // try to place message in the center of the screen
|
|
917
|
+
});
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
// the message we want was not loaded yet, so lets load it
|
|
921
|
+
loadChannelAroundMessage({ messageId });
|
|
922
|
+
};
|
|
913
923
|
|
|
914
924
|
/**
|
|
915
925
|
* Check if a messageId needs to be scrolled to after list loads, and scroll to it
|
|
@@ -922,26 +932,24 @@ const MessageListWithContext = <
|
|
|
922
932
|
initialScrollSettingTimeoutRef.current = setTimeout(() => {
|
|
923
933
|
// small timeout to ensure that handleScroll is called after scrollToIndex to set this flag
|
|
924
934
|
setInitialScrollDone(true);
|
|
925
|
-
},
|
|
935
|
+
}, 2000);
|
|
926
936
|
}
|
|
927
|
-
|
|
928
|
-
let messageIdToScroll: string | undefined = messageIdToScrollToRef.current;
|
|
937
|
+
let messageIdToScroll: string | undefined;
|
|
929
938
|
if (targetedMessage && messageIdLastScrolledToRef.current !== targetedMessage) {
|
|
930
939
|
// if some messageId was targeted but not scrolledTo yet
|
|
931
940
|
// we have scroll to there after loading completes
|
|
932
941
|
messageIdToScroll = targetedMessage;
|
|
933
942
|
}
|
|
934
943
|
if (!messageIdToScroll) return;
|
|
935
|
-
const indexOfParentInMessageList =
|
|
944
|
+
const indexOfParentInMessageList = processedMessageList.findIndex(
|
|
936
945
|
(message) => message?.id === messageIdToScroll,
|
|
937
946
|
);
|
|
938
947
|
if (indexOfParentInMessageList !== -1 && flatListRef.current) {
|
|
939
948
|
// By a fresh scroll we should clear the retries for the previous failed scroll
|
|
940
949
|
clearTimeout(scrollToDebounceTimeoutRef.current);
|
|
941
950
|
clearTimeout(failScrollTimeoutId.current);
|
|
942
|
-
//
|
|
943
|
-
|
|
944
|
-
tempDisablePaginationTrackersRef.current(messageList.length);
|
|
951
|
+
// keep track of this messageId, so that we dont scroll to again for targeted message change
|
|
952
|
+
messageIdLastScrolledToRef.current = messageIdToScroll;
|
|
945
953
|
// reset the retry count
|
|
946
954
|
scrollToIndexFailedRetryCountRef.current = 0;
|
|
947
955
|
// now scroll to it
|
|
@@ -950,17 +958,13 @@ const MessageListWithContext = <
|
|
|
950
958
|
index: indexOfParentInMessageList,
|
|
951
959
|
viewPosition: 0.5, // try to place message in the center of the screen
|
|
952
960
|
});
|
|
953
|
-
// reset the messageId tracker to not scroll to that again
|
|
954
|
-
messageIdToScrollToRef.current = undefined;
|
|
955
|
-
// keep track of this messageId, so that we dont scroll to again for targeted message change
|
|
956
|
-
messageIdLastScrolledToRef.current = messageIdToScroll;
|
|
957
961
|
}
|
|
958
|
-
},
|
|
959
|
-
}, [targetedMessage, initialScrollToFirstUnreadMessage
|
|
962
|
+
}, 50);
|
|
963
|
+
}, [targetedMessage, initialScrollToFirstUnreadMessage]);
|
|
960
964
|
|
|
961
965
|
const messagesWithImages =
|
|
962
966
|
legacyImageViewerSwipeBehaviour &&
|
|
963
|
-
|
|
967
|
+
processedMessageList.filter((message) => {
|
|
964
968
|
const isMessageTypeDeleted = message.type === 'deleted';
|
|
965
969
|
if (!isMessageTypeDeleted && message.attachments) {
|
|
966
970
|
return message.attachments.some(
|
|
@@ -1030,11 +1034,11 @@ const MessageListWithContext = <
|
|
|
1030
1034
|
};
|
|
1031
1035
|
const onScrollBeginDrag: ScrollViewProps['onScrollBeginDrag'] = (event) => {
|
|
1032
1036
|
!hasMoved && selectedPicker && setHasMoved(true);
|
|
1033
|
-
|
|
1037
|
+
onUserScrollEvent(event);
|
|
1034
1038
|
};
|
|
1035
1039
|
const onScrollEndDrag: ScrollViewProps['onScrollEndDrag'] = (event) => {
|
|
1036
1040
|
hasMoved && selectedPicker && setHasMoved(false);
|
|
1037
|
-
|
|
1041
|
+
onUserScrollEvent(event);
|
|
1038
1042
|
};
|
|
1039
1043
|
|
|
1040
1044
|
const refCallback = (ref: FlatListType<MessageType<StreamChatGenerics>>) => {
|
|
@@ -1054,7 +1058,7 @@ const MessageListWithContext = <
|
|
|
1054
1058
|
if (debugRef.current.setSendEventParams)
|
|
1055
1059
|
debugRef.current.setSendEventParams({
|
|
1056
1060
|
action: thread ? 'ThreadList' : 'Messages',
|
|
1057
|
-
data:
|
|
1061
|
+
data: processedMessageList,
|
|
1058
1062
|
});
|
|
1059
1063
|
}
|
|
1060
1064
|
|
|
@@ -1130,20 +1134,26 @@ const MessageListWithContext = <
|
|
|
1130
1134
|
additionalFlatListProps?.contentContainerStyle,
|
|
1131
1135
|
contentContainer,
|
|
1132
1136
|
]}
|
|
1133
|
-
data={messageList}
|
|
1134
1137
|
/** Disables the MessageList UI. Which means, message actions, reactions won't work. */
|
|
1138
|
+
data={processedMessageList}
|
|
1135
1139
|
extraData={disabled || !hasNoMoreRecentMessagesToLoad}
|
|
1136
1140
|
inverted={shouldApplyAndroidWorkaround ? false : inverted}
|
|
1137
1141
|
keyboardShouldPersistTaps='handled'
|
|
1138
1142
|
keyExtractor={keyExtractor}
|
|
1139
1143
|
ListEmptyComponent={renderListEmptyComponent}
|
|
1140
1144
|
ListFooterComponent={ListFooterComponent}
|
|
1145
|
+
/**
|
|
1146
|
+
if autoscrollToTopThreshold is 10, we scroll to recent if before new list update it was already at the bottom (10 offset or below)
|
|
1147
|
+
minIndexForVisible = 1 means that beyond item at index 1 will not change position on list updates
|
|
1148
|
+
minIndexForVisible is not used when autoscrollToTopThreshold = 10
|
|
1149
|
+
*/
|
|
1141
1150
|
ListHeaderComponent={ListHeaderComponent}
|
|
1142
1151
|
maintainVisibleContentPosition={{
|
|
1143
|
-
autoscrollToTopThreshold:
|
|
1152
|
+
autoscrollToTopThreshold: autoscrollToRecent ? 10 : undefined,
|
|
1144
1153
|
minIndexForVisible: 1,
|
|
1145
1154
|
}}
|
|
1146
1155
|
maxToRenderPerBatch={30}
|
|
1156
|
+
onMomentumScrollEnd={onUserScrollEvent}
|
|
1147
1157
|
onScroll={handleScroll}
|
|
1148
1158
|
onScrollBeginDrag={onScrollBeginDrag}
|
|
1149
1159
|
onScrollEndDrag={onScrollEndDrag}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { FC } from 'react';
|
|
2
2
|
|
|
3
3
|
import { renderHook } from '@testing-library/react-hooks';
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import type { DefaultGenerics, StreamChat } from 'stream-chat';
|
|
6
6
|
|
|
7
7
|
import { useCreatePaginatedMessageListContext } from '../../../components/Channel/hooks/useCreatePaginatedMessageListContext';
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { generateMessage } from '../../../mock-builders/generator/message';
|
|
16
16
|
import { generateUser } from '../../../mock-builders/generator/user';
|
|
17
17
|
import { getTestClientWithUser } from '../../../mock-builders/mock';
|
|
18
|
+
import type { DefaultStreamChatGenerics } from '../../../types/types';
|
|
18
19
|
import { useMessageList } from '../hooks/useMessageList';
|
|
19
20
|
|
|
20
21
|
const clientUser = generateUser();
|
|
@@ -60,6 +61,8 @@ describe('useMessageList', () => {
|
|
|
60
61
|
{ wrapper: Providers },
|
|
61
62
|
);
|
|
62
63
|
const reversedMessages = messages.reverse();
|
|
63
|
-
expect(result.current.map(({ id }) => id)).toEqual(
|
|
64
|
+
expect(result.current.processedMessageList.map(({ id }) => id)).toEqual(
|
|
65
|
+
reversedMessages.map(({ id }) => id),
|
|
66
|
+
);
|
|
64
67
|
});
|
|
65
68
|
});
|
|
@@ -103,7 +103,14 @@ export const useMessageList = <
|
|
|
103
103
|
readBy: msg.id ? readData[msg.id] || false : false,
|
|
104
104
|
}));
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
const processedMessageList = [
|
|
107
107
|
...messagesWithStylesReadByAndDateSeparator,
|
|
108
108
|
].reverse() as MessageType<StreamChatGenerics>[];
|
|
109
|
+
const rawMessageList = messageList;
|
|
110
|
+
return {
|
|
111
|
+
/** Messages enriched with dates/readby/groups and also reversed in order */
|
|
112
|
+
processedMessageList,
|
|
113
|
+
/** Raw messages from the channel state */
|
|
114
|
+
rawMessageList,
|
|
115
|
+
};
|
|
109
116
|
};
|