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