Skip to content

Commit 381af4f

Browse files
committed
chore: update @sendbird/chat to v4.19.2 and refactor mark as unread functionality
1 parent ff17348 commit 381af4f

File tree

16 files changed

+266
-186
lines changed

16 files changed

+266
-186
lines changed

docs-validation/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@react-native-firebase/messaging": "^14.7.0",
1616
"@react-navigation/native": "^6.1.17",
1717
"@react-navigation/native-stack": "^6.10.0",
18-
"@sendbird/chat": "^4.18.0",
18+
"@sendbird/chat": "^4.19.2",
1919
"date-fns": "^4.1.0",
2020
"react": "18.2.0",
2121
"react-native": "0.74.3",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
]
9999
},
100100
"resolutions": {
101-
"@sendbird/chat": "4.18.0",
101+
"@sendbird/chat": "4.19.2",
102102
"@types/react": "^18"
103103
}
104104
}

packages/uikit-chat-hooks/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"typescript": "5.2.2"
5656
},
5757
"peerDependencies": {
58-
"@sendbird/chat": "^4.18.0",
58+
"@sendbird/chat": "^4.19.2",
5959
"react": ">=16.13.1"
6060
},
6161
"react-native-builder-bob": {

packages/uikit-react-native/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@openspacelabs/react-native-zoomable-view": "^2.1.5",
6363
"@sendbird/uikit-chat-hooks": "3.9.5",
6464
"@sendbird/uikit-react-native-foundation": "3.9.5",
65-
"@sendbird/uikit-tools": "0.0.7",
65+
"@sendbird/uikit-tools": "0.0.10",
6666
"@sendbird/uikit-utils": "3.9.5"
6767
},
6868
"devDependencies": {
@@ -111,7 +111,8 @@
111111
"@react-native-community/netinfo": ">=9.3.0",
112112
"@react-native-documents/picker": ">=10.0.0",
113113
"@react-native-firebase/messaging": ">=14.4.0",
114-
"@sendbird/chat": "^4.18.0",
114+
"@sendbird/chat": "^4.19.2",
115+
"@sendbird/uikit-tools": ">=0.0.10",
115116
"@sendbird/react-native-scrollview-enhancer": "*",
116117
"date-fns": ">=2.28.0",
117118
"expo-av": ">=12.0.4",

packages/uikit-react-native/src/components/ChannelMessageList/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
396396
if (message.isUserMessage()) {
397397
sheetItems.push(menu.copy(message));
398398
if (!channel.isEphemeral) {
399-
if (channel.isGroupChannel() && sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread) {
399+
if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
400400
sheetItems.push(menu.markAsUnread(message));
401401
}
402402
if (isMyMessage(message, currentUserId) && message.sendingStatus === 'succeeded') {
@@ -427,7 +427,7 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
427427
} else if (sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
428428
sheetItems.push(menu.reply(message));
429429
}
430-
if (sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread) {
430+
if (sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
431431
sheetItems.push(menu.markAsUnread(message));
432432
}
433433
}

packages/uikit-react-native/src/components/GroupChannelMessageRenderer/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,8 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
313313
});
314314

315315
const shouldRenderNewLine = useMemo(() => {
316-
return sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread && isFirstUnreadMessage;
317-
}, [sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread, isFirstUnreadMessage]);
316+
return sbOptions.uikit.groupChannel.channel.enableMarkAsUnread && isFirstUnreadMessage;
317+
}, [sbOptions.uikit.groupChannel.channel.enableMarkAsUnread, isFirstUnreadMessage]);
318318

319319
return (
320320
<Box paddingHorizontal={16} marginBottom={messageGap}>

packages/uikit-react-native/src/contexts/SendbirdChatCtx.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export type SendbirdChatContextType = {
6969
readonly enableReactions: boolean;
7070
readonly enableReactionsSupergroup: boolean;
7171
readonly enableOgtag: boolean;
72-
readonly enableMarkAsUnread: boolean;
7372
};
7473
setting: {
7574
readonly enableMessageSearch: boolean;
@@ -202,7 +201,6 @@ export const SendbirdChatProvider = ({
202201
...configsWithAppAttr(sdkInstance).groupChannel,
203202
channel: {
204203
...configsWithAppAttr(sdkInstance).groupChannel.channel,
205-
enableMarkAsUnread: true,
206204
},
207205
},
208206
},

packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelMessageList.tsx

Lines changed: 154 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ViewToken } from '@react-native/virtualized-lists';
2-
import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
2+
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
33

44
import { useToast } from '@sendbird/uikit-react-native-foundation';
55
import { useGroupChannelHandler } from '@sendbird/uikit-tools';
@@ -28,21 +28,31 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
2828
const { sdk, sbOptions, groupChannelFragmentOptions } = useSendbirdChat();
2929
const { setMessageToEdit, setMessageToReply } = useContext(GroupChannelContexts.Fragment);
3030
const groupChannelPubSub = useContext(GroupChannelContexts.PubSub);
31-
const {
32-
flatListRef,
33-
lazyScrollToBottom,
34-
lazyScrollToIndex,
35-
onPressReplyMessageInThread,
36-
isNewLineInViewport,
37-
hasSeenNewLine,
38-
hasUserMarkedAsUnread,
39-
updateIsNewLineInViewport,
40-
updateHasSeenNewLine,
41-
updateHasUserMarkedAsUnread,
42-
} = useContext(GroupChannelContexts.MessageList);
31+
const { flatListRef, lazyScrollToBottom, lazyScrollToIndex, onPressReplyMessageInThread } = useContext(
32+
GroupChannelContexts.MessageList,
33+
);
4334

4435
const isFirstMount = useIsFirstMount();
4536

37+
const { hasSeenNewLineRef, isNewLineInViewportRef, updateHasSeenNewLine, updateIsNewLineInViewport } =
38+
useNewLineTracker({
39+
onNewLineSeenChange: props.onNewLineSeenChange,
40+
});
41+
42+
const viewableMessages = useRef<SendbirdMessage[]>();
43+
const hasUserMarkedAsUnreadRef = useRef(false);
44+
const [unreadFirstMessage, setUnreadFirstMessage] = useState<SendbirdMessage | undefined>(undefined);
45+
46+
const updateHasUserMarkedAsUnread = useCallback(
47+
(hasUserMarkedAsUnread: boolean) => {
48+
if (hasUserMarkedAsUnreadRef.current !== hasUserMarkedAsUnread) {
49+
hasUserMarkedAsUnreadRef.current = hasUserMarkedAsUnread;
50+
props.onUserMarkedAsUnreadChange?.(hasUserMarkedAsUnread);
51+
}
52+
},
53+
[props.onUserMarkedAsUnreadChange],
54+
);
55+
4656
const scrollToMessageWithCreatedAt = useFreshCallback(
4757
(createdAt: number, focusAnimated: boolean, timeout: number): boolean => {
4858
const foundMessageIndex = props.messages.findIndex((it) => it.createdAt === createdAt);
@@ -80,6 +90,105 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
8090
}
8191
});
8292

93+
const onPressUnreadMessagesFloatingCloseButton = useCallback(() => {
94+
updateHasSeenNewLine(true);
95+
updateHasUserMarkedAsUnread(false);
96+
props.resetNewMessages?.();
97+
confirmAndMarkAsRead([props.channel]);
98+
}, [updateHasSeenNewLine, updateHasUserMarkedAsUnread, props.channel.url, props.resetNewMessages]);
99+
100+
const findUnreadFirstMessage = useFreshCallback((isNewLineExistInChannel: boolean) => {
101+
if (!sbOptions.uikit.groupChannel.channel.enableMarkAsUnread || !isNewLineExistInChannel) {
102+
return;
103+
}
104+
105+
const foundUnreadFirstMessage = props.messages.find((msg, index) => {
106+
const isMarkedAsUnreadMessage = props.channel.myLastRead === msg.createdAt - 1;
107+
if (isMarkedAsUnreadMessage) {
108+
return true;
109+
}
110+
111+
let isFirstUnreadAfterReadMessages = false;
112+
if (index < props.messages.length - 1) {
113+
const prevMessage = props.messages[index + 1];
114+
const hasNoPreviousAndNoPrevMessage = !props.hasPrevious?.() && prevMessage == null;
115+
const prevMessageIsRead = prevMessage != null && prevMessage.createdAt <= props.channel.myLastRead;
116+
const isMessageUnread = props.channel.myLastRead < msg.createdAt;
117+
isFirstUnreadAfterReadMessages = (hasNoPreviousAndNoPrevMessage || prevMessageIsRead) && isMessageUnread;
118+
}
119+
120+
return isFirstUnreadAfterReadMessages;
121+
});
122+
123+
return foundUnreadFirstMessage;
124+
});
125+
126+
useEffect(() => {
127+
if (!unreadFirstMessage) {
128+
const foundUnreadFirstMessage = findUnreadFirstMessage(props.isNewLineExistInChannel ?? false);
129+
if (foundUnreadFirstMessage) {
130+
processNewLineVisibility(foundUnreadFirstMessage);
131+
setUnreadFirstMessage(foundUnreadFirstMessage);
132+
}
133+
}
134+
}, [props.messages, props.channel.myLastRead, sbOptions.uikit.groupChannel.channel.enableMarkAsUnread]);
135+
136+
const processNewLineVisibility = useFreshCallback((unreadFirstMsg: SendbirdMessage | undefined) => {
137+
const isNewLineInViewport = !!viewableMessages.current?.some(
138+
(message) => message.messageId === unreadFirstMsg?.messageId,
139+
);
140+
141+
if (isNewLineInViewportRef.current !== isNewLineInViewport) {
142+
updateIsNewLineInViewport(isNewLineInViewport);
143+
if (!isNewLineInViewport || hasSeenNewLineRef.current) return;
144+
145+
updateHasSeenNewLine(true);
146+
if (hasUserMarkedAsUnreadRef.current) return;
147+
148+
if (0 < props.newMessages.length) {
149+
props.channel.markAsUnread(props.newMessages[0]);
150+
} else {
151+
props.channel.markAsRead();
152+
}
153+
}
154+
});
155+
156+
const onViewableItemsChanged = useFreshCallback(
157+
async (info: { viewableItems: Array<ViewToken<SendbirdMessage>>; changed: Array<ViewToken<SendbirdMessage>> }) => {
158+
if (!sbOptions.uikit.groupChannel.channel.enableMarkAsUnread) {
159+
return;
160+
}
161+
162+
viewableMessages.current = info.viewableItems.filter((token) => token.item).map((token) => token.item);
163+
processNewLineVisibility(unreadFirstMessage);
164+
},
165+
);
166+
167+
const onPressMarkAsUnreadMessage = useCallback(
168+
async (message: SendbirdUserMessage | SendbirdFileMessage) => {
169+
if (sbOptions.uikit.groupChannel.channel.enableMarkAsUnread && message) {
170+
await props.channel.markAsUnread(message);
171+
updateHasUserMarkedAsUnread(true);
172+
}
173+
},
174+
[sbOptions.uikit.groupChannel.channel.enableMarkAsUnread, updateHasUserMarkedAsUnread],
175+
);
176+
177+
const unreadMessagesFloatingProps: UnreadMessagesFloatingProps = useMemo(() => {
178+
return {
179+
visible:
180+
sbOptions.uikit.groupChannel.channel.enableMarkAsUnread &&
181+
0 < props.channel.unreadMessageCount &&
182+
!isNewLineInViewportRef.current,
183+
onPressClose: onPressUnreadMessagesFloatingCloseButton,
184+
unreadMessageCount: props.channel.unreadMessageCount,
185+
};
186+
}, [
187+
isNewLineInViewportRef.current,
188+
props.channel.unreadMessageCount,
189+
sbOptions.uikit.groupChannel.channel.enableMarkAsUnread,
190+
]);
191+
83192
useGroupChannelHandler(sdk, {
84193
onReactionUpdated(channel, event) {
85194
if (isDifferentChannel(channel, props.channel)) return;
@@ -126,6 +235,12 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
126235
scrollToBottom(false);
127236
break;
128237
}
238+
case 'ON_MARKED_AS_UNREAD_BY_CURRENT_USER': {
239+
const foundUnreadFirstMessage = findUnreadFirstMessage(true);
240+
processNewLineVisibility(foundUnreadFirstMessage);
241+
setUnreadFirstMessage(foundUnreadFirstMessage);
242+
break;
243+
}
129244
}
130245
});
131246
}, [props.scrolledAwayFromBottom]);
@@ -169,89 +284,6 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
169284
},
170285
);
171286

172-
const onPressUnreadMessagesFloatingCloseButton = useCallback(() => {
173-
updateHasSeenNewLine?.(true);
174-
updateHasUserMarkedAsUnread?.(false);
175-
confirmAndMarkAsRead([props.channel]);
176-
}, [updateHasSeenNewLine, updateHasUserMarkedAsUnread, props.channel.url]);
177-
178-
const unreadMessagesFloatingProps: UnreadMessagesFloatingProps = useMemo(() => {
179-
return {
180-
visible:
181-
sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread &&
182-
0 < props.channel.unreadMessageCount &&
183-
!!isNewLineInViewport,
184-
onPressClose: onPressUnreadMessagesFloatingCloseButton,
185-
unreadMessageCount: props.channel.unreadMessageCount,
186-
};
187-
}, [
188-
isNewLineInViewport,
189-
props.channel.unreadMessageCount,
190-
sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread,
191-
]);
192-
193-
const unreadFirstMessageRef = useRef<SendbirdMessage | undefined>(undefined);
194-
useEffect(() => {
195-
if (!sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread) {
196-
return;
197-
}
198-
199-
if (!unreadFirstMessageRef.current) {
200-
unreadFirstMessageRef.current = props.messages.find((msg, index) => {
201-
const isMarkedAsUnreadMessage = props.channel.myLastRead === msg.createdAt - 1;
202-
let isFirstUnreadAfterReadMessages = false;
203-
if (index > 0) {
204-
const prevMessage = props.messages[index - 1];
205-
isFirstUnreadAfterReadMessages =
206-
prevMessage.createdAt <= props.channel.myLastRead && props.channel.myLastRead < msg.createdAt;
207-
}
208-
209-
return isMarkedAsUnreadMessage || isFirstUnreadAfterReadMessages;
210-
});
211-
}
212-
}, [props.messages, props.channel.myLastRead, sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread]);
213-
214-
const onViewableItemsChanged = useFreshCallback(
215-
async (info: { viewableItems: Array<ViewToken<SendbirdMessage>>; changed: Array<ViewToken<SendbirdMessage>> }) => {
216-
if (!sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread) {
217-
return;
218-
}
219-
220-
const foundViewableUnreadFirstMessage = info.viewableItems.find(
221-
(token) => token.item.createdAt === unreadFirstMessageRef.current?.createdAt,
222-
);
223-
224-
const isUnreadFirstMessageInView = foundViewableUnreadFirstMessage !== undefined;
225-
if (isNewLineInViewport !== isUnreadFirstMessageInView) {
226-
updateIsNewLineInViewport?.(isUnreadFirstMessageInView);
227-
if (!isUnreadFirstMessageInView || hasSeenNewLine) {
228-
return;
229-
}
230-
231-
updateHasSeenNewLine?.(true);
232-
if (hasUserMarkedAsUnread) {
233-
return;
234-
}
235-
236-
if (0 < props.newMessages.length) {
237-
await props.channel.markAsUnread(props.newMessages[0]);
238-
} else {
239-
await props.channel.markAsRead();
240-
}
241-
}
242-
},
243-
);
244-
245-
const onPressMarkAsUnreadMessage = useCallback(
246-
async (message: SendbirdUserMessage | SendbirdFileMessage) => {
247-
if (sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread && message) {
248-
await props.channel.markAsUnread(message);
249-
updateHasUserMarkedAsUnread?.(true);
250-
}
251-
},
252-
[sbOptions.uikitWithAppInfo.groupChannel.channel.enableMarkAsUnread, updateHasUserMarkedAsUnread],
253-
);
254-
255287
return (
256288
<ChannelMessageList
257289
{...props}
@@ -264,10 +296,35 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
264296
onPressNewMessagesButton={scrollToBottom}
265297
onPressScrollToBottomButton={scrollToBottom}
266298
onPressMarkAsUnreadMessage={onPressMarkAsUnreadMessage}
267-
unreadFirstMessage={unreadFirstMessageRef.current}
299+
unreadFirstMessage={unreadFirstMessage}
268300
unreadMessagesFloatingProps={unreadMessagesFloatingProps}
269301
/>
270302
);
271303
};
272304

305+
const useNewLineTracker = (params: Pick<GroupChannelProps['MessageList'], 'onNewLineSeenChange'>) => {
306+
const hasSeenNewLineRef = useRef(false);
307+
const isNewLineInViewportRef = useRef(false);
308+
309+
const updateHasSeenNewLine = useCallback(
310+
(hasSeenNewLine: boolean) => {
311+
if (hasSeenNewLineRef.current !== hasSeenNewLine) {
312+
hasSeenNewLineRef.current = hasSeenNewLine;
313+
params.onNewLineSeenChange?.(hasSeenNewLine);
314+
}
315+
},
316+
[params.onNewLineSeenChange],
317+
);
318+
319+
const updateIsNewLineInViewport = useCallback((isNewLineInViewport: boolean) => {
320+
isNewLineInViewportRef.current = isNewLineInViewport;
321+
}, []);
322+
323+
return {
324+
hasSeenNewLineRef,
325+
isNewLineInViewportRef,
326+
updateHasSeenNewLine,
327+
updateIsNewLineInViewport,
328+
};
329+
};
273330
export default React.memo(GroupChannelMessageList);

0 commit comments

Comments
 (0)