stream-chat-react-native-core 5.12.0-beta.2 → 5.12.0-beta.4
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 +40 -24
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useTargetedMessage.js +3 -3
- package/lib/commonjs/components/Channel/hooks/useTargetedMessage.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelPreview.js +9 -2
- package/lib/commonjs/components/ChannelPreview/ChannelPreview.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +96 -80
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/utils/getReadStates.js +9 -4
- package/lib/commonjs/components/MessageList/utils/getReadStates.js.map +1 -1
- package/lib/commonjs/hooks/useAppStateListener.js +14 -10
- package/lib/commonjs/hooks/useAppStateListener.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Channel/Channel.js +40 -24
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/Channel/hooks/useTargetedMessage.js +3 -3
- package/lib/module/components/Channel/hooks/useTargetedMessage.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelPreview.js +9 -2
- package/lib/module/components/ChannelPreview/ChannelPreview.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +96 -80
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/utils/getReadStates.js +9 -4
- package/lib/module/components/MessageList/utils/getReadStates.js.map +1 -1
- package/lib/module/hooks/useAppStateListener.js +14 -10
- package/lib/module/hooks/useAppStateListener.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/package.json +1 -1
- package/src/components/Channel/Channel.tsx +35 -15
- package/src/components/Channel/hooks/useTargetedMessage.ts +3 -3
- package/src/components/ChannelPreview/ChannelPreview.tsx +7 -0
- package/src/components/MessageList/MessageList.tsx +75 -58
- package/src/components/MessageList/utils/getReadStates.ts +3 -4
- package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +2 -0
- package/src/hooks/useAppStateListener.ts +14 -11
- package/src/version.json +1 -1
|
@@ -833,22 +833,42 @@ const ChannelWithContext = <
|
|
|
833
833
|
*/
|
|
834
834
|
const loadChannelAtFirstUnreadMessage = () => {
|
|
835
835
|
if (!channel) return;
|
|
836
|
-
|
|
837
|
-
if (unreadCount <= scrollToFirstUnreadThreshold) return;
|
|
838
|
-
// temporarily clear existing messages so that messageList component gets a list change and does not scroll to any unread message first before loading completes
|
|
839
|
-
setMessages([]);
|
|
836
|
+
let unreadMessageIdToScrollTo: string | undefined;
|
|
840
837
|
// query for messages around the last read date
|
|
841
|
-
return channelQueryCallRef.current(
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
messages
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
838
|
+
return channelQueryCallRef.current(
|
|
839
|
+
async () => {
|
|
840
|
+
setLoading(true);
|
|
841
|
+
const lastReadDate = channel.lastRead();
|
|
842
|
+
// if last read date is present we can just fetch messages around that date
|
|
843
|
+
// last read date not being present is an edge case if somewhere the user of SDK deletes the read state (this will usually never happen)
|
|
844
|
+
if (lastReadDate) {
|
|
845
|
+
setHasNoMoreRecentMessagesToLoad(false); // we are jumping to a message, hence we do not know for sure anymore if there are no more recent messages
|
|
846
|
+
// get totally 30 messages... max 15 before last read date and max 15 after last read date
|
|
847
|
+
// ref: https://github.com/GetStream/chat/pull/2588
|
|
848
|
+
await channel.query(
|
|
849
|
+
{
|
|
850
|
+
messages: {
|
|
851
|
+
created_at_around: lastReadDate,
|
|
852
|
+
limit: 30,
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
'new',
|
|
856
|
+
);
|
|
857
|
+
unreadMessageIdToScrollTo = channel.state.messages.find(
|
|
858
|
+
(m) => lastReadDate < m.created_at,
|
|
859
|
+
)?.id;
|
|
860
|
+
} else {
|
|
861
|
+
// we just load the latest messages (25 is the default) and we cant scroll to first unread message
|
|
862
|
+
await channel.state.loadMessageIntoState('latest');
|
|
863
|
+
}
|
|
864
|
+
setLoading(false);
|
|
865
|
+
},
|
|
866
|
+
() => {
|
|
867
|
+
if (unreadMessageIdToScrollTo) {
|
|
868
|
+
setTargetedMessage(unreadMessageIdToScrollTo);
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
);
|
|
852
872
|
};
|
|
853
873
|
|
|
854
874
|
/**
|
|
@@ -14,7 +14,7 @@ export const useTargetedMessage = (messageId?: string) => {
|
|
|
14
14
|
};
|
|
15
15
|
}, []);
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const setTargetedMessageTimeoutRef = useRef((messageId: string) => {
|
|
18
18
|
clearTargetedMessageCall.current && clearTimeout(clearTargetedMessageCall.current);
|
|
19
19
|
|
|
20
20
|
clearTargetedMessageCall.current = setTimeout(() => {
|
|
@@ -22,10 +22,10 @@ export const useTargetedMessage = (messageId?: string) => {
|
|
|
22
22
|
}, 3000);
|
|
23
23
|
|
|
24
24
|
setTargetedMessage(messageId);
|
|
25
|
-
};
|
|
25
|
+
});
|
|
26
26
|
|
|
27
27
|
return {
|
|
28
|
-
setTargetedMessage:
|
|
28
|
+
setTargetedMessage: setTargetedMessageTimeoutRef.current,
|
|
29
29
|
targetedMessage,
|
|
30
30
|
};
|
|
31
31
|
};
|
|
@@ -52,6 +52,13 @@ const ChannelPreviewWithContext = <
|
|
|
52
52
|
const channelLastMessage = channel.lastMessage();
|
|
53
53
|
const channelLastMessageString = `${channelLastMessage?.id}${channelLastMessage?.updated_at}`;
|
|
54
54
|
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const { unsubscribe } = client.on('notification.mark_read', () => {
|
|
57
|
+
setUnread(channel.countUnread());
|
|
58
|
+
});
|
|
59
|
+
return unsubscribe;
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
55
62
|
useEffect(() => {
|
|
56
63
|
if (
|
|
57
64
|
channelLastMessage &&
|
|
@@ -272,7 +272,6 @@ const MessageListWithContext = <
|
|
|
272
272
|
overlay,
|
|
273
273
|
reloadChannel,
|
|
274
274
|
ScrollToBottomButton,
|
|
275
|
-
scrollToFirstUnreadThreshold,
|
|
276
275
|
selectedPicker,
|
|
277
276
|
setFlatListRef,
|
|
278
277
|
setMessages,
|
|
@@ -332,8 +331,7 @@ const MessageListWithContext = <
|
|
|
332
331
|
* If the prop `initialScrollToFirstUnreadMessage` was enabled, then we scroll to the unread msg and set it to true
|
|
333
332
|
* If not, the default offset of 0 for flatList means that it has been set already
|
|
334
333
|
*/
|
|
335
|
-
const
|
|
336
|
-
|
|
334
|
+
const [isInitialScrollDone, setInitialScrollDone] = useState(!initialScrollToFirstUnreadMessage);
|
|
337
335
|
const channelResyncScrollSet = useRef<boolean>(true);
|
|
338
336
|
|
|
339
337
|
/**
|
|
@@ -341,6 +339,11 @@ const MessageListWithContext = <
|
|
|
341
339
|
*/
|
|
342
340
|
const scrollToDebounceTimeoutRef = useRef<NodeJS.Timeout>();
|
|
343
341
|
|
|
342
|
+
/**
|
|
343
|
+
* The timeout id used to lazier load the initial scroll set flag
|
|
344
|
+
*/
|
|
345
|
+
const initialScrollSettingTimeoutRef = useRef<NodeJS.Timeout>();
|
|
346
|
+
|
|
344
347
|
/**
|
|
345
348
|
* If a messageId was requested to scroll to but was unloaded,
|
|
346
349
|
* this flag keeps track of it to scroll to it after loading the message
|
|
@@ -423,29 +426,39 @@ const MessageListWithContext = <
|
|
|
423
426
|
}, [disabled]);
|
|
424
427
|
|
|
425
428
|
useEffect(() => {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
429
|
+
const getShouldMarkReadAutomatically = (): boolean => {
|
|
430
|
+
if (loading || !channel) {
|
|
431
|
+
// nothing to do
|
|
432
|
+
return false;
|
|
433
|
+
} else if (channel.countUnread() > 0) {
|
|
434
|
+
if (!initialScrollToFirstUnreadMessage) {
|
|
435
|
+
/*
|
|
436
|
+
* In this case MessageList won't scroll to first unread message when opened, so we can mark
|
|
437
|
+
* the channel as read right after opening.
|
|
438
|
+
* */
|
|
439
|
+
return true;
|
|
440
|
+
} else {
|
|
441
|
+
/*
|
|
442
|
+
* In this case MessageList will be opened to first unread message.
|
|
443
|
+
* But if there are were not enough unread messages, so that scrollToBottom button was not shown
|
|
444
|
+
* then MessageList won't need to scroll up. So we can safely mark the channel as read right after opening.
|
|
445
|
+
*
|
|
446
|
+
* NOTE: we must ensure that initial scroll is done, otherwise we do not wait till the unread scroll is finished
|
|
447
|
+
* */
|
|
448
|
+
if (scrollToBottomButtonVisible) return false;
|
|
449
|
+
/* if scrollToBottom button was not visible, wait till
|
|
450
|
+
* - initial scroll is done (indicates that if scrolling to index was needed it was triggered)
|
|
451
|
+
* */
|
|
452
|
+
return isInitialScrollDone;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
if (getShouldMarkReadAutomatically()) {
|
|
446
459
|
markRead();
|
|
447
460
|
}
|
|
448
|
-
}, [loading]);
|
|
461
|
+
}, [loading, scrollToBottomButtonVisible, isInitialScrollDone]);
|
|
449
462
|
|
|
450
463
|
useEffect(() => {
|
|
451
464
|
const lastReceivedMessage = getLastReceivedMessage(messageList);
|
|
@@ -492,7 +505,7 @@ const MessageListWithContext = <
|
|
|
492
505
|
|
|
493
506
|
if (threadList || hasNoMoreRecentMessagesToLoad) {
|
|
494
507
|
scrollToBottomIfNeeded();
|
|
495
|
-
} else
|
|
508
|
+
} else {
|
|
496
509
|
setScrollToBottomButtonVisible(true);
|
|
497
510
|
}
|
|
498
511
|
|
|
@@ -529,23 +542,17 @@ const MessageListWithContext = <
|
|
|
529
542
|
if (!channel || (!channel.initialized && !channel.offlineMode)) return null;
|
|
530
543
|
|
|
531
544
|
const lastRead = channel.lastRead();
|
|
532
|
-
const countUnread = channel.countUnread();
|
|
533
545
|
|
|
534
546
|
function isMessageUnread(messageArrayIndex: number): boolean {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const isLatestMessageSetShown = !!channel.state.messageSets.find(
|
|
539
|
-
(set) => set.isCurrent && set.isLatest,
|
|
540
|
-
);
|
|
541
|
-
return isLatestMessageSetShown && messageArrayIndex <= countUnread - 1;
|
|
547
|
+
const msg = messageList?.[messageArrayIndex];
|
|
548
|
+
if (lastRead && msg?.created_at) {
|
|
549
|
+
return lastRead < msg.created_at;
|
|
542
550
|
}
|
|
551
|
+
return false;
|
|
543
552
|
}
|
|
544
553
|
const isCurrentMessageUnread = isMessageUnread(index);
|
|
545
|
-
const isLastMessageUnread = isMessageUnread(index + 1);
|
|
546
|
-
|
|
547
554
|
const showUnreadUnderlay = isCurrentMessageUnread && scrollToBottomButtonVisible;
|
|
548
|
-
const insertInlineUnreadIndicator = showUnreadUnderlay && !
|
|
555
|
+
const insertInlineUnreadIndicator = showUnreadUnderlay && !isMessageUnread(index + 1); // show only if previous message is read
|
|
549
556
|
|
|
550
557
|
if (message.type === 'system') {
|
|
551
558
|
return (
|
|
@@ -742,8 +749,8 @@ const MessageListWithContext = <
|
|
|
742
749
|
maybeCallOnEndReached();
|
|
743
750
|
}
|
|
744
751
|
|
|
745
|
-
// Show scrollToBottom button once scroll position goes beyond
|
|
746
|
-
const isScrollAtBottom = offset <=
|
|
752
|
+
// Show scrollToBottom button once scroll position goes beyond 150.
|
|
753
|
+
const isScrollAtBottom = offset <= 150;
|
|
747
754
|
const showScrollToBottomButton = !isScrollAtBottom || !hasNoMoreRecentMessagesToLoad;
|
|
748
755
|
|
|
749
756
|
const shouldMarkRead =
|
|
@@ -829,21 +836,16 @@ const MessageListWithContext = <
|
|
|
829
836
|
* Note: This effect fires on every list change with a small debounce so that scrolling isnt abrupted by an immediate rerender
|
|
830
837
|
*/
|
|
831
838
|
useEffect(() => {
|
|
832
|
-
if (scrollToDebounceTimeoutRef.current) clearTimeout(scrollToDebounceTimeoutRef.current);
|
|
833
839
|
scrollToDebounceTimeoutRef.current = setTimeout(() => {
|
|
840
|
+
if (initialScrollToFirstUnreadMessage) {
|
|
841
|
+
initialScrollSettingTimeoutRef.current = setTimeout(() => {
|
|
842
|
+
// small timeout to ensure that handleScroll is called after scrollToIndex to set this flag
|
|
843
|
+
setInitialScrollDone(true);
|
|
844
|
+
}, 500);
|
|
845
|
+
}
|
|
834
846
|
// goToMessage method might have requested to scroll to a message
|
|
835
847
|
let messageIdToScroll: string | undefined = messageIdToScrollToRef.current;
|
|
836
|
-
|
|
837
|
-
if (
|
|
838
|
-
!initialScrollSet.current &&
|
|
839
|
-
initialScrollToFirstUnreadMessage &&
|
|
840
|
-
countUnread > scrollToFirstUnreadThreshold
|
|
841
|
-
) {
|
|
842
|
-
// find the first unread message, if we have to initially scroll to an unread message
|
|
843
|
-
if (messageList.length >= countUnread) {
|
|
844
|
-
messageIdToScroll = messageList[countUnread - 1].id;
|
|
845
|
-
}
|
|
846
|
-
} else if (targetedMessage && messageIdLastScrolledToRef.current !== targetedMessage) {
|
|
848
|
+
if (targetedMessage && messageIdLastScrolledToRef.current !== targetedMessage) {
|
|
847
849
|
// if some messageId was targeted but not scrolledTo yet
|
|
848
850
|
// we have scroll to there after loading completes
|
|
849
851
|
messageIdToScroll = targetedMessage;
|
|
@@ -862,14 +864,13 @@ const MessageListWithContext = <
|
|
|
862
864
|
messageIdToScrollToRef.current = undefined;
|
|
863
865
|
// keep track of this messageId, so that we dont scroll to again for targeted message change
|
|
864
866
|
messageIdLastScrolledToRef.current = messageIdToScroll;
|
|
865
|
-
if (!initialScrollSet.current && initialScrollToFirstUnreadMessage) {
|
|
866
|
-
initialScrollSet.current = true;
|
|
867
|
-
} else {
|
|
868
|
-
setTargetedMessage(messageIdToScroll);
|
|
869
|
-
}
|
|
870
867
|
}
|
|
871
868
|
}, 150);
|
|
872
|
-
|
|
869
|
+
return () => {
|
|
870
|
+
clearTimeout(scrollToDebounceTimeoutRef.current);
|
|
871
|
+
clearTimeout(initialScrollSettingTimeoutRef.current);
|
|
872
|
+
};
|
|
873
|
+
}, [targetedMessage, initialScrollToFirstUnreadMessage, messageList]);
|
|
873
874
|
|
|
874
875
|
const messagesWithImages =
|
|
875
876
|
legacyImageViewerSwipeBehaviour &&
|
|
@@ -1012,6 +1013,17 @@ const MessageListWithContext = <
|
|
|
1012
1013
|
return null;
|
|
1013
1014
|
};
|
|
1014
1015
|
|
|
1016
|
+
// We need to omit the style related props from the additionalFlatListProps and add them directly instead of spreading
|
|
1017
|
+
let additionalFlatListPropsExcludingStyle:
|
|
1018
|
+
| Omit<NonNullable<typeof additionalFlatListProps>, 'style' | 'contentContainerStyle'>
|
|
1019
|
+
| undefined;
|
|
1020
|
+
|
|
1021
|
+
if (additionalFlatListProps) {
|
|
1022
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1023
|
+
const { contentContainerStyle, style, ...rest } = additionalFlatListProps;
|
|
1024
|
+
additionalFlatListPropsExcludingStyle = rest;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1015
1027
|
return (
|
|
1016
1028
|
<View
|
|
1017
1029
|
style={[styles.container, { backgroundColor: white_snow }, container]}
|
|
@@ -1021,7 +1033,11 @@ const MessageListWithContext = <
|
|
|
1021
1033
|
CellRendererComponent={
|
|
1022
1034
|
shouldApplyAndroidWorkaround ? InvertedCellRendererComponent : undefined
|
|
1023
1035
|
}
|
|
1024
|
-
contentContainerStyle={[
|
|
1036
|
+
contentContainerStyle={[
|
|
1037
|
+
styles.contentContainer,
|
|
1038
|
+
additionalFlatListProps?.contentContainerStyle,
|
|
1039
|
+
contentContainer,
|
|
1040
|
+
]}
|
|
1025
1041
|
data={messageList}
|
|
1026
1042
|
/** Disables the MessageList UI. Which means, message actions, reactions won't work. */
|
|
1027
1043
|
extraData={disabled || !hasNoMoreRecentMessagesToLoad}
|
|
@@ -1049,11 +1065,12 @@ const MessageListWithContext = <
|
|
|
1049
1065
|
style={[
|
|
1050
1066
|
styles.listContainer,
|
|
1051
1067
|
listContainer,
|
|
1068
|
+
additionalFlatListProps?.style,
|
|
1052
1069
|
shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined,
|
|
1053
1070
|
]}
|
|
1054
1071
|
testID='message-flat-list'
|
|
1055
1072
|
viewabilityConfig={flatListViewabilityConfig}
|
|
1056
|
-
{...
|
|
1073
|
+
{...additionalFlatListPropsExcludingStyle}
|
|
1057
1074
|
/>
|
|
1058
1075
|
{!loading && (
|
|
1059
1076
|
<>
|
|
@@ -25,10 +25,9 @@ export const getReadStates = <
|
|
|
25
25
|
/**
|
|
26
26
|
* Channel read state is stored by user and we only care about users who aren't the client
|
|
27
27
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const members = Object.values(read);
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
const { [clientUserId ?? '']: _ignore, ...filteredRead } = read;
|
|
30
|
+
const members = Object.values(filteredRead);
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Track number of members who have read previous messages
|
|
@@ -43,6 +43,7 @@ exports[`Thread should match thread snapshot 1`] = `
|
|
|
43
43
|
"flexGrow": 1,
|
|
44
44
|
"paddingBottom": 4,
|
|
45
45
|
},
|
|
46
|
+
undefined,
|
|
46
47
|
Object {},
|
|
47
48
|
]
|
|
48
49
|
}
|
|
@@ -180,6 +181,7 @@ exports[`Thread should match thread snapshot 1`] = `
|
|
|
180
181
|
},
|
|
181
182
|
Object {},
|
|
182
183
|
undefined,
|
|
184
|
+
undefined,
|
|
183
185
|
],
|
|
184
186
|
]
|
|
185
187
|
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { AppState, AppStateStatus } from 'react-native';
|
|
3
3
|
|
|
4
4
|
export const useAppStateListener = (onForeground?: () => void, onBackground?: () => void) => {
|
|
5
5
|
const appStateRef = useRef(AppState.currentState);
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const onForegroundRef = useRef(onForeground);
|
|
7
|
+
const onBackgroundRef = useRef(onBackground);
|
|
8
|
+
|
|
9
|
+
// setting refs to avoid passing the functions as dependencies to useEffect
|
|
10
|
+
onForegroundRef.current = onForeground;
|
|
11
|
+
onBackgroundRef.current = onBackground;
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
|
8
15
|
const prevAppState = appStateRef.current;
|
|
9
16
|
if (prevAppState.match(/inactive|background/) && nextAppState === 'active') {
|
|
10
|
-
|
|
17
|
+
onForegroundRef.current?.();
|
|
11
18
|
} else if (prevAppState === 'active' && nextAppState.match(/inactive|background/)) {
|
|
12
|
-
|
|
19
|
+
onBackgroundRef.current?.();
|
|
13
20
|
}
|
|
14
21
|
appStateRef.current = nextAppState;
|
|
15
|
-
}
|
|
16
|
-
[onBackground, onForeground],
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
22
|
+
};
|
|
20
23
|
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
|
21
24
|
|
|
22
25
|
return () => {
|
|
@@ -28,5 +31,5 @@ export const useAppStateListener = (onForeground?: () => void, onBackground?: ()
|
|
|
28
31
|
AppState.removeEventListener('change', handleAppStateChange);
|
|
29
32
|
}
|
|
30
33
|
};
|
|
31
|
-
}, [
|
|
34
|
+
}, []);
|
|
32
35
|
};
|
package/src/version.json
CHANGED