Skip to content

Commit 7a9acb5

Browse files
committed
fix: message list improvements and channel first message mark unread improvements
1 parent 7f54037 commit 7a9acb5

File tree

7 files changed

+60
-61
lines changed

7 files changed

+60
-61
lines changed

package/src/components/Channel/Channel.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ import { ScrollToBottomButton as ScrollToBottomButtonDefault } from '../MessageL
175175
import { StickyHeader as StickyHeaderDefault } from '../MessageList/StickyHeader';
176176
import { TypingIndicator as TypingIndicatorDefault } from '../MessageList/TypingIndicator';
177177
import { TypingIndicatorContainer as TypingIndicatorContainerDefault } from '../MessageList/TypingIndicatorContainer';
178+
import { UnreadMessagesNotification as UnreadMessagesNotificationDefault } from '../MessageList/UnreadMessagesNotification';
178179
import { MessageActionList as MessageActionListDefault } from '../MessageMenu/MessageActionList';
179180
import { MessageActionListItem as MessageActionListItemDefault } from '../MessageMenu/MessageActionListItem';
180181
import { MessageMenu as MessageMenuDefault } from '../MessageMenu/MessageMenu';
@@ -183,7 +184,6 @@ import { MessageUserReactions as MessageUserReactionsDefault } from '../MessageM
183184
import { MessageUserReactionsAvatar as MessageUserReactionsAvatarDefault } from '../MessageMenu/MessageUserReactionsAvatar';
184185
import { MessageUserReactionsItem as MessageUserReactionsItemDefault } from '../MessageMenu/MessageUserReactionsItem';
185186
import { Reply as ReplyDefault } from '../Reply/Reply';
186-
import { UnreadMessagesNotification as UnreadMessagesNotificationDefault } from '../MessageList/UnreadMessagesNotification';
187187

188188
export type MarkReadFunctionOptions = {
189189
/**
@@ -646,8 +646,8 @@ const ChannelWithContext = <
646646
threadMessages,
647647
TypingIndicator = TypingIndicatorDefault,
648648
TypingIndicatorContainer = TypingIndicatorContainerDefault,
649-
UploadProgressIndicator = UploadProgressIndicatorDefault,
650649
UnreadMessagesNotification = UnreadMessagesNotificationDefault,
650+
UploadProgressIndicator = UploadProgressIndicatorDefault,
651651
UrlPreview = CardDefault,
652652
VideoThumbnail = VideoThumbnailDefault,
653653
} = props;
@@ -662,6 +662,7 @@ const ChannelWithContext = <
662662
} = useTheme();
663663
const [editing, setEditing] = useState<MessageType<StreamChatGenerics> | undefined>(undefined);
664664
const [error, setError] = useState<Error | boolean>(false);
665+
const [lastRead, setLastRead] = useState<ChannelContextValue<StreamChatGenerics>['lastRead']>();
665666
const [quotedMessage, setQuotedMessage] = useState<MessageType<StreamChatGenerics> | undefined>(
666667
undefined,
667668
);
@@ -777,6 +778,7 @@ const ChannelWithContext = <
777778
useEffect(() => {
778779
let listener: ReturnType<typeof channel.on>;
779780
const initChannel = async () => {
781+
setLastRead(new Date());
780782
if (!channel || !shouldSyncChannel || channel.offlineMode) return;
781783
let errored = false;
782784

@@ -796,6 +798,7 @@ const ChannelWithContext = <
796798
}
797799

798800
if (client.user?.id && channel.state.read[client.user.id]) {
801+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
799802
const { user, ...ownReadState } = channel.state.read[client.user.id];
800803
setChannelUnreadState(ownReadState);
801804
}
@@ -807,6 +810,7 @@ const ChannelWithContext = <
807810
client.user &&
808811
channel.countUnread() > scrollToFirstUnreadThreshold
809812
) {
813+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
810814
const { user, ...ownReadState } = channel.state.read[client.user.id];
811815
await loadChannelAtFirstUnreadMessage({
812816
channelUnreadState: ownReadState,
@@ -890,15 +894,15 @@ const ChannelWithContext = <
890894
} else {
891895
try {
892896
const response = await channel.markRead();
893-
if (updateChannelUnreadState && response) {
897+
if (updateChannelUnreadState && response && lastRead) {
894898
setChannelUnreadState({
895-
last_read: new Date(),
899+
last_read: lastRead,
896900
last_read_message_id: response?.event.last_read_message_id,
897901
unread_messages: 0,
898902
});
899903
}
900-
} catch (error) {
901-
console.log('Error marking channel as read:', error);
904+
} catch (err) {
905+
console.log('Error marking channel as read:', err);
902906
}
903907
}
904908
},
@@ -1645,6 +1649,7 @@ const ChannelWithContext = <
16451649
hideDateSeparators,
16461650
hideStickyDateHeader,
16471651
isChannelActive: shouldSyncChannel,
1652+
lastRead,
16481653
loadChannelAroundMessage,
16491654
loadChannelAtFirstUnreadMessage,
16501655
loading: channelMessagesState.loading,
@@ -1655,8 +1660,9 @@ const ChannelWithContext = <
16551660
NetworkDownIndicator,
16561661
read: channelState.read ?? {},
16571662
reloadChannel,
1658-
setChannelUnreadState,
16591663
scrollToFirstUnreadThreshold,
1664+
setChannelUnreadState,
1665+
setLastRead,
16601666
setTargetedMessage,
16611667
StickyHeader,
16621668
targetedMessage,
@@ -1849,8 +1855,8 @@ const ChannelWithContext = <
18491855
targetedMessage,
18501856
TypingIndicator,
18511857
TypingIndicatorContainer,
1852-
updateMessage,
18531858
UnreadMessagesNotification,
1859+
updateMessage,
18541860
UrlPreview,
18551861
VideoThumbnail,
18561862
});

package/src/components/Channel/hooks/useCreateChannelContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const useCreateChannelContext = <
1717
hideDateSeparators,
1818
hideStickyDateHeader,
1919
isChannelActive,
20+
lastRead,
2021
loadChannelAroundMessage,
2122
loadChannelAtFirstUnreadMessage,
2223
loading,
@@ -29,6 +30,7 @@ export const useCreateChannelContext = <
2930
reloadChannel,
3031
scrollToFirstUnreadThreshold,
3132
setChannelUnreadState,
33+
setLastRead,
3234
setTargetedMessage,
3335
StickyHeader,
3436
targetedMessage,
@@ -38,6 +40,7 @@ export const useCreateChannelContext = <
3840
watchers,
3941
}: ChannelContextValue<StreamChatGenerics>) => {
4042
const channelId = channel?.id;
43+
const lastReadTime = lastRead?.getTime();
4144
const membersLength = Object.keys(members).length;
4245

4346
const readUsers = Object.values(read);
@@ -58,6 +61,7 @@ export const useCreateChannelContext = <
5861
hideDateSeparators,
5962
hideStickyDateHeader,
6063
isChannelActive,
64+
lastRead,
6165
loadChannelAroundMessage,
6266
loadChannelAtFirstUnreadMessage,
6367
loading,
@@ -70,6 +74,7 @@ export const useCreateChannelContext = <
7074
reloadChannel,
7175
scrollToFirstUnreadThreshold,
7276
setChannelUnreadState,
77+
setLastRead,
7378
setTargetedMessage,
7479
StickyHeader,
7580
targetedMessage,
@@ -84,6 +89,7 @@ export const useCreateChannelContext = <
8489
disabled,
8590
error,
8691
isChannelActive,
92+
lastReadTime,
8793
loading,
8894
membersLength,
8995
readUsersLength,

package/src/components/Channel/hooks/useMessageListPagination.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,9 @@ export const useMessageListPagination = <
206206
// If the last read message is already in the current message set, we don't need to load more messages, and we set the first unread message id as that is what we want to operate on.
207207
else if (lastReadMessageId) {
208208
const messageIdx = findInMessagesById(channel.state.messages, lastReadMessageId);
209-
const messagesLength = channel.state.messages.length;
210209
isInCurrentMessageSet = messageIdx !== -1;
211210
firstUnreadMessageId =
212-
messageIdx !== -1
213-
? messageIdx < messagesLength - 1
214-
? channel.state.messages[messageIdx + 1].id
215-
: channel.state.messages[messageIdx].id
216-
: undefined;
211+
messageIdx > -1 ? channel.state.messages[messageIdx + 1]?.id : undefined;
217212
} else {
218213
// TODO
219214
const lastReadTimestamp = last_read.getTime();

package/src/components/Chat/hooks/handleEventToSyncDB.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ export const handleEventToSyncDB = async <
107107
flush: flushOverride,
108108
reads: [
109109
{
110-
last_read_message_id: event.last_read_message_id,
111110
last_read: event.received_at as string,
111+
last_read_message_id: event.last_read_message_id,
112112
unread_messages: 0,
113113
user,
114114
},
@@ -128,8 +128,8 @@ export const handleEventToSyncDB = async <
128128
flush: flushOverride,
129129
reads: [
130130
{
131-
last_read_message_id: event.last_read_message_id,
132131
last_read: event.received_at as string,
132+
last_read_message_id: event.last_read_message_id,
133133
unread_messages: event.unread_messages,
134134
user,
135135
},

package/src/components/MessageList/MessageList.tsx

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ const MessageListWithContext = <
269269
reloadChannel,
270270
ScrollToBottomButton,
271271
selectedPicker,
272-
setFlatListRef,
273272
setChannelUnreadState,
273+
setFlatListRef,
274274
setMessages,
275275
setSelectedPicker,
276276
setTargetedMessage,
@@ -453,6 +453,22 @@ const MessageListWithContext = <
453453
}
454454
}, [disabled]);
455455

456+
useEffect(() => {
457+
const listener: ReturnType<typeof channel.on> = channel.on('message.new', (event) => {
458+
const newMessageToCurrentChannel = event.cid === channel.cid;
459+
const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel;
460+
461+
if (newMessageToCurrentChannel && mainChannelUpdated && !scrollToBottomButtonVisible) {
462+
markRead();
463+
}
464+
});
465+
466+
return () => {
467+
listener?.unsubscribe();
468+
};
469+
// eslint-disable-next-line react-hooks/exhaustive-deps
470+
}, []);
471+
456472
// TODO: Think if the useEffect is really needed?
457473
useEffect(() => {
458474
const lastReceivedMessage = getLastReceivedMessage(processedMessageList);
@@ -565,46 +581,18 @@ const MessageListWithContext = <
565581

566582
const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
567583
const lastReadTimestamp = channelUnreadState?.last_read.getTime();
568-
const isFirstMessage = index === processedMessageList.length - 1;
569584
const isNewestMessage = index === 0;
570585
const isLastReadMessage =
571586
channelUnreadState?.last_read_message_id === message.id ||
572587
(!channelUnreadState?.unread_messages && createdAtTimestamp === lastReadTimestamp);
573588

574-
const isFirstUnreadMessage =
575-
channelUnreadState?.first_unread_message_id === message.id ||
576-
(!!channelUnreadState?.unread_messages &&
577-
!!createdAtTimestamp &&
578-
!!lastReadTimestamp &&
579-
createdAtTimestamp > lastReadTimestamp &&
580-
isFirstMessage);
581-
582-
const showUnreadSeparatorAbove =
583-
!channelUnreadState?.last_read_message_id && isFirstUnreadMessage;
584-
585-
const showUnreadSeparatorBelow =
589+
const showUnreadSeparator =
586590
isLastReadMessage &&
587591
!isNewestMessage &&
592+
// The `channelUnreadState?.first_unread_message_id` is here for sent messages unread label
588593
(!!channelUnreadState?.first_unread_message_id || !!channelUnreadState?.unread_messages);
589594

590-
const showUnreadUnderlay =
591-
!!shouldShowUnreadUnderlay && (showUnreadSeparatorAbove || showUnreadSeparatorBelow);
592-
593-
if (message.type === 'system') {
594-
return (
595-
<View
596-
style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}
597-
testID={`message-list-item-${index}`}
598-
>
599-
{showUnreadSeparatorAbove && <InlineUnreadIndicator />}
600-
<MessageSystem
601-
message={message}
602-
style={[{ paddingHorizontal: screenPadding }, messageContainer]}
603-
/>
604-
{showUnreadSeparatorBelow && <InlineUnreadIndicator />}
605-
</View>
606-
);
607-
}
595+
const showUnreadUnderlay = !!shouldShowUnreadUnderlay && showUnreadSeparator;
608596

609597
const wrapMessageInTheme = client.userID === message.user?.id && !!myMessageTheme;
610598
const renderDateSeperator = isMessageWithStylesReadByAndDateSeparator(message) &&
@@ -626,10 +614,16 @@ const MessageListWithContext = <
626614
);
627615

628616
return (
629-
<View style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}>
630-
{/* Adding indicator below the messages, since the list is inverted */}
631-
{showUnreadSeparatorAbove && <InlineUnreadIndicator />}
632-
{wrapMessageInTheme ? (
617+
<View
618+
style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}
619+
testID={`message-list-item-${index}`}
620+
>
621+
{message.type === 'system' ? (
622+
<MessageSystem
623+
message={message}
624+
style={[{ paddingHorizontal: screenPadding }, messageContainer]}
625+
/>
626+
) : wrapMessageInTheme ? (
633627
<ThemeProvider mergedStyle={modifiedTheme}>
634628
<View testID={`message-list-item-${index}`}>
635629
{renderDateSeperator}
@@ -642,7 +636,7 @@ const MessageListWithContext = <
642636
{renderMessage}
643637
</View>
644638
)}
645-
{showUnreadSeparatorBelow && <InlineUnreadIndicator />}
639+
{showUnreadSeparator && <InlineUnreadIndicator />}
646640
</View>
647641
);
648642
};
@@ -768,7 +762,7 @@ const MessageListWithContext = <
768762
}
769763
};
770764

771-
const handleScroll: ScrollViewProps['onScroll'] = async (event) => {
765+
const handleScroll: ScrollViewProps['onScroll'] = (event) => {
772766
const messageListHasMessages = processedMessageList.length > 0;
773767
const offset = event.nativeEvent.contentOffset.y;
774768
// Show scrollToBottom button once scroll position goes beyond 150.
@@ -886,7 +880,7 @@ const MessageListWithContext = <
886880
if (initialScrollToFirstUnreadMessage) {
887881
clearTimeout(initialScrollSettingTimeoutRef.current);
888882
}
889-
let messageIdToScroll: string | undefined = targetedMessage;
883+
const messageIdToScroll: string | undefined = targetedMessage;
890884
if (!messageIdToScroll) return;
891885
const indexOfParentInMessageList = processedMessageList.findIndex(
892886
(message) => message?.id === messageIdToScroll,
@@ -1147,9 +1141,8 @@ const MessageListWithContext = <
11471141
<NetworkDownIndicator />
11481142
{isUnreadNotificationOpen && !threadList ? (
11491143
<UnreadMessagesNotification
1150-
unread={channel.countUnread()}
1151-
onPressHandler={onUnreadNotificationPress}
11521144
onCloseHandler={onUnreadNotificationClose}
1145+
onPressHandler={onUnreadNotificationPress}
11531146
/>
11541147
) : null}
11551148
</View>

package/src/components/MessageList/UnreadMessagesNotification.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ export type UnreadMessagesNotificationProps = {
1212
* Callback to handle the close event
1313
*/
1414
onCloseHandler?: () => void;
15-
/**
16-
* Number of unread messages
17-
*/
18-
unread?: number;
15+
1916
/**
2017
* If the notification is visible
2118
*/

package/src/contexts/channelContext/ChannelContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export type ChannelContextValue<
125125
read: ChannelState<StreamChatGenerics>['read'];
126126
reloadChannel: () => Promise<void>;
127127
scrollToFirstUnreadThreshold: number;
128+
setLastRead: React.Dispatch<React.SetStateAction<Date | undefined>>;
128129
setChannelUnreadState: React.Dispatch<
129130
React.SetStateAction<ChannelUnreadState<StreamChatGenerics> | undefined>
130131
>;
@@ -139,6 +140,7 @@ export type ChannelContextValue<
139140
enableMessageGroupingByUser?: boolean;
140141
isChannelActive?: boolean;
141142

143+
lastRead?: Date;
142144
loading?: boolean;
143145
/**
144146
* Maximum time in milliseconds that should occur between messages

0 commit comments

Comments
 (0)