11import 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
44import { useToast } from '@sendbird/uikit-react-native-foundation' ;
55import { 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+ } ;
273330export default React . memo ( GroupChannelMessageList ) ;
0 commit comments