1- import React , { createContext , useCallback , useState } from 'react' ;
1+ import React , { createContext , useCallback , useRef , useState } from 'react' ;
2+ import type { FlatList } from 'react-native' ;
23
34import { useChannelHandler } from '@sendbird/uikit-chat-hooks' ;
45import {
6+ ContextValue ,
57 Logger ,
68 NOOP ,
79 SendbirdFileMessage ,
810 SendbirdGroupChannel ,
11+ SendbirdMessage ,
912 SendbirdUser ,
1013 SendbirdUserMessage ,
1114 isDifferentChannel ,
15+ useFreshCallback ,
1216 useUniqHandlerId ,
1317} from '@sendbird/uikit-utils' ;
1418
1519import ProviderLayout from '../../../components/ProviderLayout' ;
20+ import { MESSAGE_FOCUS_ANIMATION_DELAY } from '../../../constants' ;
1621import { useLocalization , useSendbirdChat } from '../../../hooks/useContext' ;
1722import type { PubSub } from '../../../utils/pubsub' ;
18- import type {
19- GroupChannelContextsType ,
20- GroupChannelModule ,
21- GroupChannelPubSubContextPayload ,
22- GroupChannelScrollToMessageFunc ,
23- } from '../types' ;
23+ import type { GroupChannelContextsType , GroupChannelModule , GroupChannelPubSubContextPayload } from '../types' ;
24+ import { GroupChannelProps } from '../types' ;
2425
2526export const GroupChannelContexts : GroupChannelContextsType = {
2627 Fragment : createContext ( {
2728 headerTitle : '' ,
2829 channel : { } as SendbirdGroupChannel ,
2930 setMessageToEdit : NOOP ,
3031 setMessageToReply : NOOP ,
31- scrollToMessage : ( ( ) => false ) as GroupChannelScrollToMessageFunc ,
32- __internalSetScrollToMessageFunc : ( _func : ( ) => GroupChannelScrollToMessageFunc ) => {
33- // noop
34- } ,
3532 } ) ,
3633 TypingIndicator : createContext ( {
3734 typingUsers : [ ] as SendbirdUser [ ] ,
@@ -40,6 +37,16 @@ export const GroupChannelContexts: GroupChannelContextsType = {
4037 publish : NOOP ,
4138 subscribe : ( ) => NOOP ,
4239 } as PubSub < GroupChannelPubSubContextPayload > ) ,
40+ MessageList : createContext ( {
41+ flatListRef : { current : null } ,
42+ scrollToMessage : ( ) => false ,
43+ lazyScrollToBottom : ( ) => {
44+ // noop
45+ } ,
46+ lazyScrollToIndex : ( ) => {
47+ // noop
48+ } ,
49+ } as MessageListContextValue ) ,
4350} ;
4451
4552export const GroupChannelContextsProvider : GroupChannelModule [ 'Provider' ] = ( {
@@ -48,6 +55,8 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
4855 enableTypingIndicator,
4956 keyboardAvoidOffset = 0 ,
5057 groupChannelPubSub,
58+ messages,
59+ onUpdateSearchItem,
5160} ) => {
5261 if ( ! channel ) throw new Error ( 'GroupChannel is not provided to GroupChannelModule' ) ;
5362
@@ -58,11 +67,10 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
5867 const [ typingUsers , setTypingUsers ] = useState < SendbirdUser [ ] > ( [ ] ) ;
5968 const [ messageToEdit , setMessageToEdit ] = useState < SendbirdUserMessage | SendbirdFileMessage > ( ) ;
6069 const [ messageToReply , setMessageToReply ] = useState < SendbirdUserMessage | SendbirdFileMessage > ( ) ;
61- const [ scrollToMessage , __internalSetScrollToMessageFunc ] = useState < GroupChannelScrollToMessageFunc > ( ( ) => ( ) => {
62- Logger . error (
63- 'You should render `src/domain/groupChannel/component/GroupChannelMessageList.tsx` component first to use scrollToMessage.' ,
64- ) ;
65- return false ;
70+
71+ const { flatListRef, lazyScrollToIndex, lazyScrollToBottom, scrollToMessage } = useScrollActions ( {
72+ messages,
73+ onUpdateSearchItem,
6674 } ) ;
6775
6876 const updateInputMode = ( mode : 'send' | 'edit' | 'reply' , message ?: SendbirdUserMessage | SendbirdFileMessage ) => {
@@ -115,16 +123,99 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
115123 setMessageToEdit : useCallback ( ( message ) => updateInputMode ( 'edit' , message ) , [ ] ) ,
116124 messageToReply,
117125 setMessageToReply : useCallback ( ( message ) => updateInputMode ( 'reply' , message ) , [ ] ) ,
118- scrollToMessage,
119- __internalSetScrollToMessageFunc,
120126 } }
121127 >
122- < GroupChannelContexts . TypingIndicator . Provider value = { { typingUsers } } >
123- < GroupChannelContexts . PubSub . Provider value = { groupChannelPubSub } >
124- { children }
125- </ GroupChannelContexts . PubSub . Provider >
126- </ GroupChannelContexts . TypingIndicator . Provider >
128+ < GroupChannelContexts . PubSub . Provider value = { groupChannelPubSub } >
129+ < GroupChannelContexts . TypingIndicator . Provider value = { { typingUsers } } >
130+ < GroupChannelContexts . MessageList . Provider
131+ value = { {
132+ flatListRef,
133+ scrollToMessage,
134+ lazyScrollToIndex,
135+ lazyScrollToBottom,
136+ } }
137+ >
138+ { children }
139+ </ GroupChannelContexts . MessageList . Provider >
140+ </ GroupChannelContexts . TypingIndicator . Provider >
141+ </ GroupChannelContexts . PubSub . Provider >
127142 </ GroupChannelContexts . Fragment . Provider >
128143 </ ProviderLayout >
129144 ) ;
130145} ;
146+
147+ type MessageListContextValue = ContextValue < GroupChannelContextsType [ 'MessageList' ] > ;
148+ const useScrollActions = ( params : Pick < GroupChannelProps [ 'Provider' ] , 'messages' | 'onUpdateSearchItem' > ) => {
149+ const { messages, onUpdateSearchItem } = params ;
150+ const flatListRef = useRef < FlatList < SendbirdMessage > > ( null ) ;
151+
152+ // FIXME: Workaround, should run after data has been applied to UI.
153+ const lazyScrollToBottom = useFreshCallback < MessageListContextValue [ 'lazyScrollToIndex' ] > ( ( params ) => {
154+ if ( ! flatListRef . current ) {
155+ logFlatListRefWarning ( ) ;
156+ return ;
157+ }
158+
159+ setTimeout ( ( ) => {
160+ flatListRef . current ?. scrollToOffset ( { offset : 0 , animated : params ?. animated ?? false } ) ;
161+ } , params ?. timeout ?? 0 ) ;
162+ } ) ;
163+
164+ // FIXME: Workaround, should run after data has been applied to UI.
165+ const lazyScrollToIndex = useFreshCallback < MessageListContextValue [ 'lazyScrollToIndex' ] > ( ( params ) => {
166+ if ( ! flatListRef . current ) {
167+ logFlatListRefWarning ( ) ;
168+ return ;
169+ }
170+
171+ setTimeout ( ( ) => {
172+ flatListRef . current ?. scrollToIndex ( {
173+ index : params ?. index ?? 0 ,
174+ animated : params ?. animated ?? false ,
175+ viewPosition : params ?. viewPosition ?? 0.5 ,
176+ } ) ;
177+ } , params ?. timeout ?? 0 ) ;
178+ } ) ;
179+
180+ const scrollToMessage = useFreshCallback < MessageListContextValue [ 'scrollToMessage' ] > ( ( messageId , options ) => {
181+ if ( ! flatListRef . current ) {
182+ logFlatListRefWarning ( ) ;
183+ return false ;
184+ }
185+
186+ const foundMessageIndex = messages . findIndex ( ( it ) => it . messageId === messageId ) ;
187+ const isIncludedInList = foundMessageIndex > - 1 ;
188+
189+ if ( isIncludedInList ) {
190+ if ( options ?. focusAnimated ) {
191+ setTimeout (
192+ ( ) => onUpdateSearchItem ( { startingPoint : messages [ foundMessageIndex ] . createdAt } ) ,
193+ MESSAGE_FOCUS_ANIMATION_DELAY ,
194+ ) ;
195+ }
196+ lazyScrollToIndex ( {
197+ index : foundMessageIndex ,
198+ animated : true ,
199+ timeout : 0 ,
200+ viewPosition : options ?. viewPosition ,
201+ } ) ;
202+ return true ;
203+ } else {
204+ return false ;
205+ }
206+ } ) ;
207+
208+ return {
209+ flatListRef,
210+ lazyScrollToIndex,
211+ lazyScrollToBottom,
212+ scrollToMessage,
213+ } ;
214+ } ;
215+
216+ const logFlatListRefWarning = ( ) => {
217+ Logger . warn (
218+ 'Cannot find flatListRef.current, please render FlatList and pass the flatListRef' +
219+ 'or please try again after FlatList has been rendered.' ,
220+ ) ;
221+ } ;
0 commit comments