Skip to content

Commit 4991aa3

Browse files
authored
Merge pull request #256 from sendbird/fix/search-item-scroll-issue
[CLNP-7468] fix: resolve search item scroll issue
2 parents b696e3b + 8ea23d0 commit 4991aa3

File tree

3 files changed

+75
-9
lines changed

3 files changed

+75
-9
lines changed

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
2626
const { sdk, sbOptions, groupChannelFragmentOptions } = useSendbirdChat();
2727
const { setMessageToEdit, setMessageToReply } = useContext(GroupChannelContexts.Fragment);
2828
const groupChannelPubSub = useContext(GroupChannelContexts.PubSub);
29-
const { flatListRef, lazyScrollToBottom, lazyScrollToIndex, onPressReplyMessageInThread } = useContext(
29+
const { flatListRef, lazyScrollToBottom, lazyScrollToMessageId, onPressReplyMessageInThread } = useContext(
3030
GroupChannelContexts.MessageList,
3131
);
3232

@@ -40,6 +40,7 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
4040
const viewableMessages = useRef<SendbirdMessage[]>();
4141
const hasUserMarkedAsUnreadRef = useRef(false);
4242
const [unreadFirstMessage, setUnreadFirstMessage] = useState<SendbirdMessage | undefined>(undefined);
43+
const pendingBottomReachedRef = useRef<{ timeout: number; timestamp: number } | null>(null);
4344

4445
const updateHasSeenNewLine = useCallback(
4546
(hasSeenNewLine: boolean) => {
@@ -63,14 +64,16 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
6364

6465
const scrollToMessageWithCreatedAt = useFreshCallback(
6566
(createdAt: number, focusAnimated: boolean, timeout: number): boolean => {
66-
const foundMessageIndex = props.messages.findIndex((it) => it.createdAt === createdAt);
67-
const isIncludedInList = foundMessageIndex > -1;
67+
const foundMessage = props.messages.find((it) => it.createdAt === createdAt);
68+
const isIncludedInList = !!foundMessage;
69+
pendingBottomReachedRef.current = null;
6870

6971
if (isIncludedInList) {
7072
if (focusAnimated) {
7173
setTimeout(() => props.onUpdateSearchItem({ startingPoint: createdAt }), MESSAGE_FOCUS_ANIMATION_DELAY);
7274
}
73-
lazyScrollToIndex({ index: foundMessageIndex, animated: true, timeout });
75+
pendingBottomReachedRef.current = { timeout, timestamp: Date.now() };
76+
lazyScrollToMessageId({ messageId: foundMessage.messageId, animated: true, timeout });
7477
} else {
7578
if (props.channel.messageOffsetTimestamp <= createdAt) {
7679
if (focusAnimated) {
@@ -346,6 +349,23 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
346349
},
347350
);
348351

352+
const onBottomReached = useFreshCallback(() => {
353+
if (props.hasNext()) {
354+
if (pendingBottomReachedRef.current) {
355+
const currentTime = Date.now();
356+
const elapsedTime = currentTime - pendingBottomReachedRef.current.timestamp;
357+
358+
const timeoutThreshold = 500;
359+
if (elapsedTime >= pendingBottomReachedRef.current.timeout + timeoutThreshold) {
360+
props.onBottomReached?.();
361+
pendingBottomReachedRef.current = null;
362+
}
363+
} else {
364+
props.onBottomReached?.();
365+
}
366+
}
367+
});
368+
349369
return (
350370
<ChannelMessageList
351371
{...props}
@@ -359,6 +379,7 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
359379
onPressNewMessagesButton={scrollToBottom}
360380
onPressScrollToBottomButton={scrollToBottom}
361381
onPressMarkAsUnreadMessage={onPressMarkAsUnreadMessage}
382+
onBottomReached={onBottomReached}
362383
unreadFirstMessage={unreadFirstMessage}
363384
unreadMessagesFloatingProps={unreadMessagesFloatingPropsRef.current}
364385
/>

packages/uikit-react-native/src/domain/groupChannel/module/moduleContext.tsx

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export const GroupChannelContexts: GroupChannelContextsType = {
4646
lazyScrollToIndex: () => {
4747
// noop
4848
},
49+
lazyScrollToMessageId: () => {
50+
// noop
51+
},
4952
} as MessageListContextValue),
5053
};
5154

@@ -68,10 +71,11 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
6871
const [messageToEdit, setMessageToEdit] = useState<SendbirdUserMessage | SendbirdFileMessage>();
6972
const [messageToReply, setMessageToReply] = useState<SendbirdUserMessage | SendbirdFileMessage>();
7073

71-
const { flatListRef, lazyScrollToIndex, lazyScrollToBottom, scrollToMessage } = useScrollActions({
72-
messages,
73-
onUpdateSearchItem,
74-
});
74+
const { flatListRef, lazyScrollToIndex, lazyScrollToBottom, scrollToMessage, lazyScrollToMessageId } =
75+
useScrollActions({
76+
messages,
77+
onUpdateSearchItem,
78+
});
7579

7680
const updateInputMode = (mode: 'send' | 'edit' | 'reply', message?: SendbirdUserMessage | SendbirdFileMessage) => {
7781
if (mode === 'send' || !message) {
@@ -143,6 +147,7 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
143147
scrollToMessage,
144148
lazyScrollToIndex,
145149
lazyScrollToBottom,
150+
lazyScrollToMessageId,
146151
onPressReplyMessageInThread,
147152
}}
148153
>
@@ -159,9 +164,11 @@ type MessageListContextValue = ContextValue<GroupChannelContextsType['MessageLis
159164
const useScrollActions = (params: Pick<GroupChannelProps['Provider'], 'messages' | 'onUpdateSearchItem'>) => {
160165
const { messages, onUpdateSearchItem } = params;
161166
const flatListRef = useRef<FlatList<SendbirdMessage>>(null);
167+
const messagesRef = useRef(messages);
168+
messagesRef.current = messages;
162169

163170
// FIXME: Workaround, should run after data has been applied to UI.
164-
const lazyScrollToBottom = useFreshCallback<MessageListContextValue['lazyScrollToIndex']>((params) => {
171+
const lazyScrollToBottom = useFreshCallback<MessageListContextValue['lazyScrollToBottom']>((params) => {
165172
if (!flatListRef.current) {
166173
logFlatListRefWarning();
167174
return;
@@ -188,6 +195,33 @@ const useScrollActions = (params: Pick<GroupChannelProps['Provider'], 'messages'
188195
}, params?.timeout ?? 0);
189196
});
190197

198+
// FIXME: Workaround, should run after data has been applied to UI.
199+
const lazyScrollToMessageId = useFreshCallback<MessageListContextValue['lazyScrollToMessageId']>((params) => {
200+
if (!flatListRef.current) {
201+
logFlatListRefWarning();
202+
return;
203+
}
204+
205+
setTimeout(() => {
206+
let messageIndex = 0;
207+
if (params?.messageId) {
208+
const foundMessageIndex = messagesRef.current.findIndex((it) => it.messageId === params.messageId);
209+
if (foundMessageIndex > -1) {
210+
messageIndex = foundMessageIndex;
211+
} else {
212+
Logger.warn('Message with messageId not found:', params.messageId);
213+
return;
214+
}
215+
}
216+
217+
flatListRef.current?.scrollToIndex({
218+
index: messageIndex,
219+
animated: params?.animated ?? false,
220+
viewPosition: params?.viewPosition ?? 0.5,
221+
});
222+
}, params?.timeout ?? 0);
223+
});
224+
191225
const scrollToMessage = useFreshCallback<MessageListContextValue['scrollToMessage']>((messageId, options) => {
192226
if (!flatListRef.current) {
193227
logFlatListRefWarning();
@@ -221,6 +255,7 @@ const useScrollActions = (params: Pick<GroupChannelProps['Provider'], 'messages'
221255
lazyScrollToIndex,
222256
lazyScrollToBottom,
223257
scrollToMessage,
258+
lazyScrollToMessageId,
224259
};
225260
};
226261

packages/uikit-react-native/src/domain/groupChannel/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ export interface GroupChannelContextsType {
186186
timeout?: number;
187187
viewPosition?: number;
188188
}) => void;
189+
/**
190+
* Call the FlatList function asynchronously to scroll to a message by messageId lazily.
191+
* to avoid scrolling before data rendering has been committed.
192+
* */
193+
lazyScrollToMessageId: (params?: {
194+
messageId?: number;
195+
animated?: boolean;
196+
timeout?: number;
197+
viewPosition?: number;
198+
}) => void;
189199

190200
onPressReplyMessageInThread?: (parentMessage: SendbirdSendableMessage, startingPoint?: number) => void;
191201
}>;

0 commit comments

Comments
 (0)