Skip to content

Commit df48e2c

Browse files
committed
feat: add scrollToMessage to group channel contexts
1 parent b43552d commit df48e2c

File tree

3 files changed

+75
-12
lines changed

3 files changed

+75
-12
lines changed

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

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import ChannelMessageList from '../../../components/ChannelMessageList';
1010
import { MESSAGE_FOCUS_ANIMATION_DELAY, MESSAGE_SEARCH_SAFE_SCROLL_DELAY } from '../../../constants';
1111
import { useLocalization, useSendbirdChat } from '../../../hooks/useContext';
1212
import { GroupChannelContexts } from '../module/moduleContext';
13-
import type { GroupChannelProps } from '../types';
13+
import type { GroupChannelProps, GroupChannelScrollToMessageFunc } from '../types';
1414

1515
const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
1616
const toast = useToast();
1717
const { STRINGS } = useLocalization();
1818
const { sdk } = useSendbirdChat();
19-
const { setMessageToEdit, setMessageToReply } = useContext(GroupChannelContexts.Fragment);
19+
const { setMessageToEdit, setMessageToReply, __internalSetScrollToMessageFunc } = useContext(
20+
GroupChannelContexts.Fragment,
21+
);
2022
const { subscribe } = useContext(GroupChannelContexts.PubSub);
2123

2224
const id = useUniqHandlerId('GroupChannelMessageList');
@@ -31,13 +33,31 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
3133
};
3234

3335
// FIXME: Workaround, should run after data has been applied to UI.
34-
const lazyScrollToIndex = (index = 0, animated = false, timeout = 0) => {
36+
const lazyScrollToIndex = (index = 0, animated = false, timeout = 0, viewPosition = 0.5) => {
3537
setTimeout(() => {
36-
ref.current?.scrollToIndex({ index, animated, viewPosition: 0.5 });
38+
ref.current?.scrollToIndex({ index, animated, viewPosition });
3739
}, timeout);
3840
};
3941

40-
const scrollToMessage = useFreshCallback((createdAt: number, focusAnimated = false): boolean => {
42+
const scrollToMessage = useFreshCallback<GroupChannelScrollToMessageFunc>((messageId, options) => {
43+
const foundMessageIndex = props.messages.findIndex((it) => it.messageId === messageId);
44+
const isIncludedInList = foundMessageIndex > -1;
45+
46+
if (isIncludedInList) {
47+
if (options?.focusAnimated) {
48+
setTimeout(
49+
() => props.onUpdateSearchItem({ startingPoint: props.messages[foundMessageIndex].createdAt }),
50+
MESSAGE_FOCUS_ANIMATION_DELAY,
51+
);
52+
}
53+
lazyScrollToIndex(foundMessageIndex, true, 0, options?.viewPosition);
54+
return true;
55+
} else {
56+
return false;
57+
}
58+
});
59+
60+
const scrollToMessageWithCreatedAt = useFreshCallback((createdAt: number, focusAnimated = false): boolean => {
4161
const foundMessageIndex = props.messages.findIndex((it) => it.createdAt === createdAt);
4262
const isIncludedInList = foundMessageIndex > -1;
4363

@@ -54,7 +74,6 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
5474
return false;
5575
}
5676
}
57-
5877
return true;
5978
});
6079

@@ -102,17 +121,21 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
102121
});
103122
}, [props.scrolledAwayFromBottom]);
104123

105-
// Only trigger once when message list mount with initial props.searchItem
106-
// - Search screen + searchItem > mount message list
107-
// - Reset message list + searchItem > re-mount message list
108124
useEffect(() => {
125+
// Only trigger once when message list mount with initial props.searchItem
126+
// - Search screen + searchItem > mount message list
127+
// - Reset message list + searchItem > re-mount message list
109128
if (isFirstMount && props.searchItem) {
110-
scrollToMessage(props.searchItem.startingPoint);
129+
scrollToMessageWithCreatedAt(props.searchItem.startingPoint);
111130
}
112131
}, [isFirstMount]);
113132

133+
useEffect(() => {
134+
__internalSetScrollToMessageFunc(() => scrollToMessage);
135+
}, []);
136+
114137
const onPressParentMessage = useFreshCallback((message: SendbirdMessage) => {
115-
const canScrollToParent = scrollToMessage(message.createdAt, true);
138+
const canScrollToParent = scrollToMessageWithCreatedAt(message.createdAt, true);
116139
if (!canScrollToParent) toast.show(STRINGS.TOAST.FIND_PARENT_MSG_ERROR, 'error');
117140
});
118141

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { createContext, useCallback, useState } from 'react';
22

33
import { useChannelHandler } from '@sendbird/uikit-chat-hooks';
44
import {
5+
Logger,
56
NOOP,
67
SendbirdFileMessage,
78
SendbirdGroupChannel,
@@ -14,14 +15,25 @@ import {
1415
import ProviderLayout from '../../../components/ProviderLayout';
1516
import { useLocalization, useSendbirdChat } from '../../../hooks/useContext';
1617
import type { PubSub } from '../../../utils/pubsub';
17-
import type { GroupChannelContextsType, GroupChannelModule, GroupChannelPubSubContextPayload } from '../types';
18+
import type {
19+
GroupChannelContextsType,
20+
GroupChannelModule,
21+
GroupChannelPubSubContextPayload,
22+
GroupChannelScrollToMessageFunc,
23+
} from '../types';
1824

1925
export const GroupChannelContexts: GroupChannelContextsType = {
2026
Fragment: createContext({
2127
headerTitle: '',
2228
channel: {} as SendbirdGroupChannel,
2329
setMessageToEdit: NOOP,
2430
setMessageToReply: NOOP,
31+
scrollToMessage: (_: number) => {
32+
// noop
33+
},
34+
__internalSetScrollToMessageFunc: (_: () => GroupChannelScrollToMessageFunc) => {
35+
// noop
36+
},
2537
}),
2638
TypingIndicator: createContext({
2739
typingUsers: [] as SendbirdUser[],
@@ -48,6 +60,11 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
4860
const [typingUsers, setTypingUsers] = useState<SendbirdUser[]>([]);
4961
const [messageToEdit, setMessageToEdit] = useState<SendbirdUserMessage | SendbirdFileMessage>();
5062
const [messageToReply, setMessageToReply] = useState<SendbirdUserMessage | SendbirdFileMessage>();
63+
const [scrollToMessage, __internalSetScrollToMessageFunc] = useState<GroupChannelScrollToMessageFunc>(() => () => {
64+
Logger.error(
65+
'You should render `src/domain/groupChannel/component/GroupChannelMessageList.tsx` component first to use scrollToMessage.',
66+
);
67+
});
5168

5269
const updateInputMode = (mode: 'send' | 'edit' | 'reply', message?: SendbirdUserMessage | SendbirdFileMessage) => {
5370
if (mode === 'send' || !message) {
@@ -99,6 +116,8 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
99116
setMessageToEdit: useCallback((message) => updateInputMode('edit', message), []),
100117
messageToReply,
101118
setMessageToReply: useCallback((message) => updateInputMode('reply', message), []),
119+
scrollToMessage,
120+
__internalSetScrollToMessageFunc,
102121
}}
103122
>
104123
<GroupChannelContexts.TypingIndicator.Provider value={{ typingUsers }}>

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@ import type { ChannelMessageListProps } from '../../components/ChannelMessageLis
1919
import type { CommonComponent } from '../../types';
2020
import type { PubSub } from '../../utils/pubsub';
2121

22+
/**
23+
* Function that scrolls to a message within a group channel.
24+
* @param messageId {number} - The id of the message to scroll.
25+
* @param options {object} - Scroll options (optional).
26+
* @param options.focusAnimated {boolean} - Enable a shake animation on the message component upon completion of scrolling.
27+
* @param options.viewPosition {number} - Position information to adjust the visible area during scrolling. bottom(0) ~ top(1.0)
28+
*
29+
* @example
30+
* ```
31+
* const { scrollToMessage } = useContext(GroupChannelContexts.Fragment);
32+
* const messageIncludedInMessageList = scrollToMessage(lastMessage.messageId, { focusAnimated: true, viewPosition: 1 });
33+
* if (!messageIncludedInMessageList) console.warn('Message not found in the message list.');
34+
* ```
35+
* */
36+
export type GroupChannelScrollToMessageFunc = (
37+
messageId: number,
38+
options?: { focusAnimated?: boolean; viewPosition?: number },
39+
) => void;
40+
2241
export interface GroupChannelProps {
2342
Fragment: {
2443
channel: SendbirdGroupChannel;
@@ -112,6 +131,8 @@ export interface GroupChannelContextsType {
112131
setMessageToEdit: (msg?: SendbirdUserMessage | SendbirdFileMessage) => void;
113132
messageToReply?: SendbirdUserMessage | SendbirdFileMessage;
114133
setMessageToReply: (msg?: SendbirdUserMessage | SendbirdFileMessage) => void;
134+
scrollToMessage: GroupChannelScrollToMessageFunc;
135+
__internalSetScrollToMessageFunc: (func: () => GroupChannelScrollToMessageFunc) => void;
115136
}>;
116137
TypingIndicator: React.Context<{
117138
typingUsers: SendbirdUser[];

0 commit comments

Comments
 (0)