11import { type NavigationProp , type RouteProp , useNavigation , useRoute } from '@react-navigation/native' ;
2- import React , { useEffect , useReducer } from 'react' ;
2+ import React , { useCallback , useEffect , useReducer , useRef } from 'react' ;
33import { FlatList , Text , View } from 'react-native' ;
44import { shallowEqual } from 'react-redux' ;
55
@@ -17,7 +17,7 @@ import { type IGetRoomRoles, type TSubscriptionModel, type TUserModel } from '..
1717import I18n from '../../i18n' ;
1818import { useAppSelector } from '../../lib/hooks/useAppSelector' ;
1919import { usePermissions } from '../../lib/hooks/usePermissions' ;
20- import { compareServerVersion , getRoomTitle , isGroupChat } from '../../lib/methods/helpers' ;
20+ import { compareServerVersion , getRoomTitle , isGroupChat , useDebounce } from '../../lib/methods/helpers' ;
2121import { handleIgnore } from '../../lib/methods/helpers/handleIgnore' ;
2222import { showConfirmationAlert } from '../../lib/methods/helpers/info' ;
2323import log from '../../lib/methods/helpers/log' ;
@@ -41,7 +41,6 @@ import {
4141 type TRoomType
4242} from './helpers' ;
4343import styles from './styles' ;
44- import { sanitizeLikeString } from '../../lib/database/utils' ;
4544
4645const PAGE_SIZE = 25 ;
4746
@@ -76,6 +75,8 @@ const RoomMembersView = (): React.ReactElement => {
7675 const { params } = useRoute < RouteProp < ModalStackParamList , 'RoomMembersView' > > ( ) ;
7776 const navigation = useNavigation < NavigationProp < ModalStackParamList , 'RoomMembersView' > > ( ) ;
7877
78+ const latestSearchRequest = useRef ( 0 ) ;
79+
7980 const { isMasterDetail, serverVersion, useRealName, user, loading } = useAppSelector (
8081 state => ( {
8182 isMasterDetail : state . app . isMasterDetail ,
@@ -95,7 +96,7 @@ const RoomMembersView = (): React.ReactElement => {
9596 ( state : IRoomMembersViewState , newState : Partial < IRoomMembersViewState > ) => ( { ...state , ...newState } ) ,
9697 {
9798 isLoading : false ,
98- allUsers : false ,
99+ allUsers : true ,
99100 filtering : '' ,
100101 members : [ ] ,
101102 room : params . room || ( { } as TSubscriptionModel ) ,
@@ -123,38 +124,84 @@ const RoomMembersView = (): React.ReactElement => {
123124
124125 useEffect ( ( ) => {
125126 const subscription = params ?. room ?. observe && params . room . observe ( ) . subscribe ( changes => updateState ( { room : changes } ) ) ;
126- setHeader ( false ) ;
127- fetchMembers ( false ) ;
127+ setHeader ( true ) ;
128128 return ( ) => subscription ?. unsubscribe ( ) ;
129129 } , [ ] ) ;
130130
131+ const fetchRoles = ( ) => {
132+ if ( isGroupChat ( state . room ) ) {
133+ return ;
134+ }
135+ if (
136+ muteUserPermission ||
137+ setLeaderPermission ||
138+ setOwnerPermission ||
139+ setModeratorPermission ||
140+ removeUserPermission ||
141+ editTeamMemberPermission ||
142+ viewAllTeamChannelsPermission ||
143+ viewAllTeamsPermission
144+ ) {
145+ fetchRoomMembersRoles ( state . room . t as any , state . room . rid , updateState ) ;
146+ }
147+ } ;
148+
149+ const fetchMembers = useCallback ( async ( ) => {
150+ const { members, isLoading, end, room, filter, page, allUsers } = state ;
151+ const { t } = room ;
152+
153+ if ( isLoading || end ) {
154+ return ;
155+ }
156+
157+ const requestId = ++ latestSearchRequest . current ;
158+ updateState ( { isLoading : true } ) ;
159+
160+ try {
161+ const membersResult = await getRoomMembers ( {
162+ rid : room . rid ,
163+ roomType : t ,
164+ type : allUsers ? 'all' : 'online' ,
165+ filter,
166+ skip : PAGE_SIZE * page ,
167+ limit : PAGE_SIZE ,
168+ allUsers
169+ } ) ;
170+
171+ if ( requestId !== latestSearchRequest . current ) {
172+ return ;
173+ }
174+
175+ const existingIds = new Set ( members . map ( m => m . _id ) ) ;
176+ const membersResultFiltered = membersResult ?. filter ( ( member : TUserModel ) => ! existingIds . has ( member . _id ) ) ;
177+
178+ // Safety check: if page is 0, we replace the list entirely
179+ const newMembers = page === 0 ? membersResultFiltered : [ ...members , ...( membersResultFiltered || [ ] ) ] ;
180+ const isEnd = membersResult ?. length < PAGE_SIZE ;
181+
182+ updateState ( {
183+ members : newMembers ,
184+ isLoading : false ,
185+ end : isEnd ,
186+ page : page + 1
187+ } ) ;
188+ } catch ( e ) {
189+ log ( e ) ;
190+ if ( requestId === latestSearchRequest . current ) {
191+ updateState ( { isLoading : false } ) ;
192+ }
193+ }
194+ } , [ state . isLoading , state . end , state . room . t , state . filter , state . page , state . allUsers ] ) ;
195+
131196 useEffect ( ( ) => {
132197 const unsubscribe = navigation . addListener ( 'focus' , ( ) => {
133- const { allUsers } = state ;
134- fetchMembers ( allUsers ) ;
198+ fetchMembers ( ) ;
135199 } ) ;
136200
137201 return unsubscribe ;
138202 } , [ navigation ] ) ;
139203
140204 useEffect ( ( ) => {
141- const fetchRoles = ( ) => {
142- if ( isGroupChat ( state . room ) ) {
143- return ;
144- }
145- if (
146- muteUserPermission ||
147- setLeaderPermission ||
148- setOwnerPermission ||
149- setModeratorPermission ||
150- removeUserPermission ||
151- editTeamMemberPermission ||
152- viewAllTeamChannelsPermission ||
153- viewAllTeamsPermission
154- ) {
155- fetchRoomMembersRoles ( state . room . t as any , state . room . rid , updateState ) ;
156- }
157- } ;
158205 fetchRoles ( ) ;
159206 } , [
160207 muteUserPermission ,
@@ -164,13 +211,35 @@ const RoomMembersView = (): React.ReactElement => {
164211 removeUserPermission ,
165212 editTeamMemberPermission ,
166213 viewAllTeamChannelsPermission ,
167- viewAllTeamsPermission
214+ viewAllTeamsPermission ,
215+ state . room ?. rid ,
216+ state . room ?. t
168217 ] ) ;
169218
219+ useEffect ( ( ) => {
220+ fetchMembers ( ) ;
221+ } , [ state . filter , state . allUsers ] ) ;
222+
223+ const debounceFilterChange = useDebounce ( ( text : string ) => {
224+ const trimmedFilter = text . trim ( ) ;
225+
226+ if ( ! trimmedFilter ) {
227+ latestSearchRequest . current += 1 ;
228+ }
229+
230+ updateState ( {
231+ filter : trimmedFilter ,
232+ page : 0 ,
233+ members : [ ] ,
234+ end : false ,
235+ isLoading : false
236+ } ) ;
237+ } , 500 ) ;
238+
170239 const toggleStatus = ( status : boolean ) => {
171240 try {
172- updateState ( { members : [ ] , allUsers : status , end : false } ) ;
173- fetchMembers ( status ) ;
241+ // We only update ' allUsers'. 'filter' remains in state, so the next fetch uses both.
242+ updateState ( { members : [ ] , allUsers : status , end : false , page : 0 } ) ;
174243 setHeader ( status ) ;
175244 } catch ( e ) {
176245 log ( e ) ;
@@ -189,14 +258,14 @@ const RoomMembersView = (): React.ReactElement => {
189258 options : [
190259 {
191260 title : I18n . t ( 'Online' ) ,
192- onPress : ( ) => toggleStatus ( true ) ,
193- right : ( ) => < Radio check = { allUsers } /> ,
261+ onPress : ( ) => toggleStatus ( false ) ,
262+ right : ( ) => < Radio check = { ! allUsers } /> ,
194263 testID : 'room-members-view-toggle-status-online'
195264 } ,
196265 {
197266 title : I18n . t ( 'All' ) ,
198- onPress : ( ) => toggleStatus ( false ) ,
199- right : ( ) => < Radio check = { ! allUsers } /> ,
267+ onPress : ( ) => toggleStatus ( true ) ,
268+ right : ( ) => < Radio check = { allUsers } /> ,
200269 testID : 'room-members-view-toggle-status-all'
201270 }
202271 ]
@@ -348,49 +417,10 @@ const RoomMembersView = (): React.ReactElement => {
348417 } ) ;
349418 } ;
350419
351- const fetchMembers = async ( status : boolean ) => {
352- const { members, isLoading, end, room, filter, page } = state ;
353- const { t } = room ;
354-
355- if ( isLoading || end ) {
356- return ;
357- }
358-
359- updateState ( { isLoading : true } ) ;
360- try {
361- const membersResult = await getRoomMembers ( {
362- rid : room . rid ,
363- roomType : t ,
364- type : ! status ? 'all' : 'online' ,
365- filter,
366- skip : PAGE_SIZE * page ,
367- limit : PAGE_SIZE ,
368- allUsers : ! status
369- } ) ;
370- const end = membersResult ?. length < PAGE_SIZE ;
371- const membersResultFiltered = membersResult ?. filter ( ( member : TUserModel ) => ! members . some ( m => m . _id === member . _id ) ) ;
372- updateState ( {
373- members : [ ...members , ...membersResultFiltered ] ,
374- isLoading : false ,
375- end,
376- page : page + 1
377- } ) ;
378- } catch ( e ) {
379- log ( e ) ;
380- updateState ( { isLoading : false } ) ;
381- }
382- } ;
383-
384- const filter = sanitizeLikeString ( state . filter . toLowerCase ( ) ) || '' ;
385- const filteredMembers =
386- state . members && state . members . length > 0 && state . filter
387- ? state . members . filter ( m => m . username . toLowerCase ( ) . match ( filter ) || m . name ?. toLowerCase ( ) . match ( filter ) )
388- : null ;
389-
390420 return (
391421 < SafeAreaView testID = 'room-members-view' >
392422 < FlatList
393- data = { filteredMembers || state . members }
423+ data = { state . members }
394424 renderItem = { ( { item } ) => (
395425 < View style = { { backgroundColor : colors . surfaceRoom } } >
396426 < UserItem
@@ -412,12 +442,12 @@ const RoomMembersView = (): React.ReactElement => {
412442 t = { state . room . t }
413443 abacAttributes = { state . room . abacAttributes }
414444 />
415- < SearchBox onChangeText = { text => updateState ( { filter : text . trim ( ) } ) } testID = 'room-members-view-search' />
445+ < SearchBox onChangeText = { text => debounceFilterChange ( text ) } testID = 'room-members-view-search' />
416446 </ >
417447 }
418448 ListFooterComponent = { ( ) => ( state . isLoading ? < ActivityIndicator /> : null ) }
419449 onEndReachedThreshold = { 0.1 }
420- onEndReached = { ( ) => fetchMembers ( state . allUsers ) }
450+ onEndReached = { ( ) => fetchMembers ( ) }
421451 ListEmptyComponent = { ( ) =>
422452 state . end ? (
423453 < Text style = { [ styles . noResult , { color : colors . fontTitlesLabels } ] } > { I18n . t ( 'No_members_found' ) } </ Text >
0 commit comments