@messenger-box/platform-mobile 10.0.3-alpha.18 → 10.0.3-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/lib/screens/inbox/components/DialogsListItem.js +423 -50
- package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +375 -51
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +175 -0
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +1 -0
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +191 -0
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +1 -0
- package/lib/screens/inbox/containers/Dialogs.js +382 -181
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +536 -48
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +1 -25
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +1 -1
- package/package.json +2 -2
- package/src/screens/inbox/components/DialogsListItem.tsx +624 -107
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +506 -114
- package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +35 -17
- package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
- package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
- package/src/screens/inbox/containers/Dialogs.tsx +531 -281
- package/src/screens/inbox/containers/ThreadConversationView.tsx +812 -37
|
@@ -1,28 +1,45 @@
|
|
|
1
|
-
import React, { useCallback, useMemo,
|
|
2
|
-
import {
|
|
3
|
-
FlatList,
|
|
4
|
-
Box,
|
|
5
|
-
Heading,
|
|
6
|
-
Input,
|
|
7
|
-
InputField,
|
|
8
|
-
Text,
|
|
9
|
-
Icon,
|
|
10
|
-
Center,
|
|
11
|
-
Spinner,
|
|
12
|
-
} from '@admin-layout/gluestack-ui-mobile';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { FlatList, Box, Heading, Input, InputField, Text, Center, Spinner } from '@admin-layout/gluestack-ui-mobile';
|
|
13
3
|
import { Ionicons } from '@expo/vector-icons';
|
|
14
|
-
import { useSelector
|
|
15
|
-
import { useNavigation, useRoute,
|
|
16
|
-
import { orderBy
|
|
4
|
+
import { useSelector } from 'react-redux';
|
|
5
|
+
import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';
|
|
6
|
+
import { orderBy } from 'lodash-es';
|
|
17
7
|
import { DialogsListItem } from '../components/DialogsListItem';
|
|
18
8
|
import { ServiceDialogsListItem } from '../components/ServiceDialogsListItem';
|
|
19
|
-
import {
|
|
9
|
+
import { useGetChannelsByUserWithServiceChannelsQuery } from 'common/graphql';
|
|
20
10
|
import { RoomType } from 'common';
|
|
21
11
|
import { userSelector } from '@adminide-stack/user-auth0-client';
|
|
22
|
-
import { CHANGE_SETTINGS_ACTION } from '@admin-layout/client';
|
|
23
12
|
import { config } from '../config';
|
|
24
13
|
import colors from 'tailwindcss/colors';
|
|
25
|
-
import { dialogsXstate
|
|
14
|
+
import { dialogsXstate } from './workflow/dialogs-xstate';
|
|
15
|
+
|
|
16
|
+
// Define custom actions and states for our component
|
|
17
|
+
const Actions = {
|
|
18
|
+
INITIAL_CONTEXT: 'INITIAL_CONTEXT',
|
|
19
|
+
ERROR_HANDLED: 'ERROR_HANDLED',
|
|
20
|
+
FETCH_CHANNELS: 'FETCH_CHANNELS',
|
|
21
|
+
APPEND_CHANNELS: 'APPEND_CHANNELS',
|
|
22
|
+
REFRESH_CHANNELS: 'REFRESH_CHANNELS',
|
|
23
|
+
SELECT_CHANNEL: 'SELECT_CHANNEL',
|
|
24
|
+
START_LOADING: 'START_LOADING',
|
|
25
|
+
STOP_LOADING: 'STOP_LOADING',
|
|
26
|
+
LOAD_MORE_CHANNELS: 'LOAD_MORE_CHANNELS',
|
|
27
|
+
SET_SEARCH_QUERY: 'SET_SEARCH_QUERY',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const BaseState = {
|
|
31
|
+
Idle: 'idle',
|
|
32
|
+
Error: 'error',
|
|
33
|
+
Loading: 'loading',
|
|
34
|
+
Done: 'done',
|
|
35
|
+
FetchChannels: 'fetchChannels',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const MainState = {
|
|
39
|
+
RefreshChannels: 'refreshChannels',
|
|
40
|
+
SelectChannel: 'selectChannel',
|
|
41
|
+
LoadMoreChannels: 'loadMoreChannels',
|
|
42
|
+
};
|
|
26
43
|
|
|
27
44
|
export interface InboxProps {
|
|
28
45
|
channelFilters?: Record<string, unknown>;
|
|
@@ -30,28 +47,28 @@ export interface InboxProps {
|
|
|
30
47
|
supportServices: boolean;
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
// Define state type for the state machine
|
|
34
|
-
interface SafeStateType {
|
|
35
|
-
context: {
|
|
36
|
-
channels: any[];
|
|
37
|
-
refreshing: boolean;
|
|
38
|
-
loading: boolean;
|
|
39
|
-
error: string | null;
|
|
40
|
-
searchQuery: string;
|
|
41
|
-
selectedChannelId: string | null;
|
|
42
|
-
channelRole: string | null;
|
|
43
|
-
channelFilters: Record<string, any>;
|
|
44
|
-
supportServices: boolean;
|
|
45
|
-
};
|
|
46
|
-
value: string;
|
|
47
|
-
matches?: (stateValue: string) => boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Interface for the return type to ensure proper typing
|
|
51
|
-
type UseSafeMachineReturnType = [SafeStateType & { matches: (stateValue: string) => boolean }, (event: any) => void];
|
|
52
|
-
|
|
53
50
|
// Create a safer version of useMachine to handle potential errors
|
|
54
|
-
function useSafeMachine(machine)
|
|
51
|
+
function useSafeMachine(machine) {
|
|
52
|
+
// Define the state type
|
|
53
|
+
interface SafeStateType {
|
|
54
|
+
context: {
|
|
55
|
+
channels: any[];
|
|
56
|
+
refreshing: boolean;
|
|
57
|
+
loading: boolean;
|
|
58
|
+
error: string | null;
|
|
59
|
+
searchQuery: string;
|
|
60
|
+
selectedChannelId: string | null;
|
|
61
|
+
channelRole: string | null;
|
|
62
|
+
channelFilters: Record<string, any>;
|
|
63
|
+
supportServices: boolean;
|
|
64
|
+
page: number;
|
|
65
|
+
hasMoreChannels: boolean;
|
|
66
|
+
loadingMore: boolean;
|
|
67
|
+
};
|
|
68
|
+
value: string;
|
|
69
|
+
matches?: (stateValue: string) => boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
// Initialize with default state
|
|
56
73
|
const [state, setState] = useState<SafeStateType>({
|
|
57
74
|
context: {
|
|
@@ -64,6 +81,9 @@ function useSafeMachine(machine): UseSafeMachineReturnType {
|
|
|
64
81
|
channelRole: null,
|
|
65
82
|
channelFilters: {},
|
|
66
83
|
supportServices: false,
|
|
84
|
+
page: 1,
|
|
85
|
+
hasMoreChannels: true,
|
|
86
|
+
loadingMore: false,
|
|
67
87
|
},
|
|
68
88
|
value: 'idle',
|
|
69
89
|
});
|
|
@@ -72,10 +92,10 @@ function useSafeMachine(machine): UseSafeMachineReturnType {
|
|
|
72
92
|
const send = useCallback((event) => {
|
|
73
93
|
try {
|
|
74
94
|
// Log event for debugging
|
|
75
|
-
console.log('
|
|
95
|
+
console.log('Event received:', event.type);
|
|
76
96
|
|
|
77
97
|
// Handle specific events manually
|
|
78
|
-
if (event.type ===
|
|
98
|
+
if (event.type === Actions.INITIAL_CONTEXT) {
|
|
79
99
|
setState((prev) => ({
|
|
80
100
|
...prev,
|
|
81
101
|
context: {
|
|
@@ -84,36 +104,61 @@ function useSafeMachine(machine): UseSafeMachineReturnType {
|
|
|
84
104
|
channelFilters: event.data?.channelFilters || {},
|
|
85
105
|
supportServices: event.data?.supportServices || false,
|
|
86
106
|
selectedChannelId: event.data?.selectedChannelId || null,
|
|
107
|
+
loading: true,
|
|
108
|
+
page: 1,
|
|
109
|
+
hasMoreChannels: true,
|
|
87
110
|
},
|
|
88
111
|
value: BaseState.FetchChannels,
|
|
89
112
|
}));
|
|
90
|
-
} else if (event.type ===
|
|
113
|
+
} else if (event.type === Actions.FETCH_CHANNELS) {
|
|
114
|
+
console.log('Setting channels:', event.data?.channels?.length || 0);
|
|
91
115
|
setState((prev) => ({
|
|
92
116
|
...prev,
|
|
93
117
|
context: {
|
|
94
118
|
...prev.context,
|
|
95
|
-
|
|
119
|
+
channels: event.data?.channels || [],
|
|
120
|
+
hasMoreChannels: (event.data?.channels?.length || 0) > 0,
|
|
121
|
+
loading: event.data?.stopLoading ? false : prev.context.loading,
|
|
122
|
+
refreshing: event.data?.stopLoading ? false : prev.context.refreshing,
|
|
123
|
+
loadingMore: false,
|
|
124
|
+
},
|
|
125
|
+
value: BaseState.Idle,
|
|
126
|
+
}));
|
|
127
|
+
} else if (event.type === Actions.APPEND_CHANNELS) {
|
|
128
|
+
const newChannels = event.data?.channels || [];
|
|
129
|
+
console.log('Appending channels:', newChannels.length);
|
|
130
|
+
|
|
131
|
+
setState((prev) => ({
|
|
132
|
+
...prev,
|
|
133
|
+
context: {
|
|
134
|
+
...prev.context,
|
|
135
|
+
channels: [...prev.context.channels, ...newChannels],
|
|
136
|
+
hasMoreChannels: newChannels.length >= 10, // If we got fewer than 10 channels, assume no more are available
|
|
137
|
+
page: prev.context.page + 1,
|
|
138
|
+
loadingMore: false,
|
|
96
139
|
},
|
|
140
|
+
value: BaseState.Idle,
|
|
97
141
|
}));
|
|
98
|
-
} else if (event.type ===
|
|
142
|
+
} else if (event.type === Actions.REFRESH_CHANNELS) {
|
|
99
143
|
setState((prev) => ({
|
|
100
144
|
...prev,
|
|
101
145
|
context: {
|
|
102
146
|
...prev.context,
|
|
103
147
|
refreshing: true,
|
|
148
|
+
page: 1,
|
|
149
|
+
hasMoreChannels: true,
|
|
104
150
|
},
|
|
105
151
|
value: MainState.RefreshChannels,
|
|
106
152
|
}));
|
|
107
|
-
} else if (event.type ===
|
|
153
|
+
} else if (event.type === Actions.SELECT_CHANNEL) {
|
|
108
154
|
setState((prev) => ({
|
|
109
155
|
...prev,
|
|
110
156
|
context: {
|
|
111
157
|
...prev.context,
|
|
112
158
|
selectedChannelId: event.data?.channelId || null,
|
|
113
159
|
},
|
|
114
|
-
value: MainState.SelectChannel,
|
|
115
160
|
}));
|
|
116
|
-
} else if (event.type ===
|
|
161
|
+
} else if (event.type === Actions.START_LOADING) {
|
|
117
162
|
setState((prev) => ({
|
|
118
163
|
...prev,
|
|
119
164
|
context: {
|
|
@@ -121,62 +166,66 @@ function useSafeMachine(machine): UseSafeMachineReturnType {
|
|
|
121
166
|
loading: true,
|
|
122
167
|
},
|
|
123
168
|
}));
|
|
124
|
-
} else if (event.type ===
|
|
169
|
+
} else if (event.type === Actions.STOP_LOADING) {
|
|
170
|
+
console.log('Explicitly stopping loading state');
|
|
125
171
|
setState((prev) => ({
|
|
126
172
|
...prev,
|
|
127
173
|
context: {
|
|
128
174
|
...prev.context,
|
|
129
175
|
loading: false,
|
|
176
|
+
refreshing: false,
|
|
177
|
+
loadingMore: false,
|
|
130
178
|
},
|
|
179
|
+
value: prev.value === BaseState.FetchChannels ? BaseState.Idle : prev.value,
|
|
131
180
|
}));
|
|
132
|
-
} else if (event.type ===
|
|
181
|
+
} else if (event.type === Actions.LOAD_MORE_CHANNELS) {
|
|
133
182
|
setState((prev) => ({
|
|
134
183
|
...prev,
|
|
135
184
|
context: {
|
|
136
185
|
...prev.context,
|
|
137
|
-
|
|
138
|
-
loading: false,
|
|
139
|
-
refreshing: false,
|
|
186
|
+
loadingMore: true,
|
|
140
187
|
},
|
|
141
|
-
value:
|
|
188
|
+
value: MainState.LoadMoreChannels,
|
|
142
189
|
}));
|
|
143
|
-
} else if (event.type ===
|
|
190
|
+
} else if (event.type === Actions.SET_SEARCH_QUERY) {
|
|
144
191
|
setState((prev) => ({
|
|
145
192
|
...prev,
|
|
146
193
|
context: {
|
|
147
194
|
...prev.context,
|
|
148
|
-
|
|
149
|
-
refreshing: false,
|
|
195
|
+
searchQuery: event.data?.searchQuery || '',
|
|
150
196
|
},
|
|
151
|
-
value: BaseState.Idle,
|
|
152
197
|
}));
|
|
153
|
-
} else if (event.type ===
|
|
198
|
+
} else if (event.type === Actions.ERROR_HANDLED) {
|
|
199
|
+
console.log('Error handled:', event.data?.message);
|
|
154
200
|
setState((prev) => ({
|
|
155
201
|
...prev,
|
|
156
202
|
context: {
|
|
157
203
|
...prev.context,
|
|
158
|
-
error: event.data?.message ||
|
|
204
|
+
error: event.data?.message || null,
|
|
159
205
|
loading: false,
|
|
160
206
|
refreshing: false,
|
|
207
|
+
loadingMore: false,
|
|
161
208
|
},
|
|
162
|
-
value: BaseState.
|
|
209
|
+
value: BaseState.Idle,
|
|
163
210
|
}));
|
|
164
211
|
}
|
|
165
|
-
} catch (
|
|
166
|
-
console.error('Error
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error('Error handling event:', error);
|
|
167
214
|
}
|
|
168
215
|
}, []);
|
|
169
216
|
|
|
170
|
-
// Add a matches
|
|
217
|
+
// Add a custom matches function to the state
|
|
171
218
|
const stateWithMatches = useMemo(() => {
|
|
172
219
|
return {
|
|
173
220
|
...state,
|
|
174
|
-
matches: (
|
|
221
|
+
matches: (checkState) => {
|
|
222
|
+
return state.value === checkState;
|
|
223
|
+
},
|
|
175
224
|
};
|
|
176
225
|
}, [state]);
|
|
177
226
|
|
|
178
227
|
// Return as a tuple to match useMachine API
|
|
179
|
-
return [stateWithMatches, send];
|
|
228
|
+
return [stateWithMatches, send] as const;
|
|
180
229
|
}
|
|
181
230
|
|
|
182
231
|
const DialogsComponent = (props: InboxProps) => {
|
|
@@ -185,9 +234,9 @@ const DialogsComponent = (props: InboxProps) => {
|
|
|
185
234
|
channelFilters.type = channelFilters?.type ?? RoomType.Direct;
|
|
186
235
|
const { params } = useRoute<any>();
|
|
187
236
|
const auth = useSelector(userSelector);
|
|
188
|
-
const dispatch = useDispatch();
|
|
189
237
|
const navigation = useNavigation<any>();
|
|
190
|
-
|
|
238
|
+
|
|
239
|
+
// Create a ref to track if component is mounted
|
|
191
240
|
const isMountedRef = useRef(true);
|
|
192
241
|
|
|
193
242
|
// Use our safer custom implementation instead of the problematic useMachine
|
|
@@ -238,6 +287,16 @@ const DialogsComponent = (props: InboxProps) => {
|
|
|
238
287
|
[send],
|
|
239
288
|
);
|
|
240
289
|
|
|
290
|
+
// Destructure context properties with safe getters
|
|
291
|
+
const channels = safeContextProperty('channels', []);
|
|
292
|
+
const refreshing = safeContextProperty('refreshing', false);
|
|
293
|
+
const loading = safeContextProperty('loading', false);
|
|
294
|
+
const searchQuery = safeContextProperty('searchQuery', '');
|
|
295
|
+
const selectedChannelId = safeContextProperty('selectedChannelId', null);
|
|
296
|
+
const loadingMore = safeContextProperty('loadingMore', false);
|
|
297
|
+
const hasMoreChannels = safeContextProperty('hasMoreChannels', true);
|
|
298
|
+
const page = safeContextProperty('page', 1);
|
|
299
|
+
|
|
241
300
|
// Use a ref to track the current machine snapshot for safer access
|
|
242
301
|
const stateRef = useRef(state);
|
|
243
302
|
|
|
@@ -246,6 +305,28 @@ const DialogsComponent = (props: InboxProps) => {
|
|
|
246
305
|
stateRef.current = state;
|
|
247
306
|
}, [state]);
|
|
248
307
|
|
|
308
|
+
// Avoid referencing state.context directly in places that might cause undefined errors
|
|
309
|
+
const safeGetContext = useCallback(() => {
|
|
310
|
+
if (stateRef.current && stateRef.current.context) {
|
|
311
|
+
return stateRef.current.context;
|
|
312
|
+
}
|
|
313
|
+
// Return default values if context is undefined
|
|
314
|
+
return {
|
|
315
|
+
channels: [],
|
|
316
|
+
refreshing: false,
|
|
317
|
+
loading: false,
|
|
318
|
+
error: null,
|
|
319
|
+
searchQuery: '',
|
|
320
|
+
selectedChannelId: null,
|
|
321
|
+
channelRole: null,
|
|
322
|
+
channelFilters: {},
|
|
323
|
+
supportServices: false,
|
|
324
|
+
page: 1,
|
|
325
|
+
hasMoreChannels: true,
|
|
326
|
+
loadingMore: false,
|
|
327
|
+
};
|
|
328
|
+
}, []);
|
|
329
|
+
|
|
249
330
|
// Use cleanup function to prevent setting state after unmount
|
|
250
331
|
useEffect(() => {
|
|
251
332
|
return () => {
|
|
@@ -253,11 +334,8 @@ const DialogsComponent = (props: InboxProps) => {
|
|
|
253
334
|
};
|
|
254
335
|
}, []);
|
|
255
336
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
loading: userChannelsLoading,
|
|
259
|
-
refetch: getChannelsRefetch,
|
|
260
|
-
} = useGetChannelsByUserWithServiceChannelsQuery({
|
|
337
|
+
// Apollo query for fetching channels
|
|
338
|
+
const { refetch: getChannelsRefetch } = useGetChannelsByUserWithServiceChannelsQuery({
|
|
261
339
|
variables: {
|
|
262
340
|
role: channelRole,
|
|
263
341
|
criteria: channelFilters,
|
|
@@ -265,277 +343,449 @@ const DialogsComponent = (props: InboxProps) => {
|
|
|
265
343
|
supportServiceCriteria: {
|
|
266
344
|
type: RoomType.Service,
|
|
267
345
|
},
|
|
346
|
+
limit: 15,
|
|
347
|
+
skip: 0,
|
|
268
348
|
},
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
type: 'FETCH_CHANNELS_SUCCESS',
|
|
274
|
-
data: { channels: allChannels },
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
onError: (error) => {
|
|
279
|
-
if (isMountedRef.current) {
|
|
280
|
-
safeSend({
|
|
281
|
-
type: 'ERROR',
|
|
282
|
-
data: { message: error.message },
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
},
|
|
349
|
+
fetchPolicy: 'cache-and-network',
|
|
350
|
+
nextFetchPolicy: 'network-only',
|
|
351
|
+
notifyOnNetworkStatusChange: true,
|
|
352
|
+
skip: true, // Skip automatic fetching as we'll control it via the state machine
|
|
286
353
|
});
|
|
287
354
|
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
type: DialogsActions.INITIAL_CONTEXT,
|
|
295
|
-
data: {
|
|
296
|
-
channelRole,
|
|
297
|
-
channelFilters: channelFiltersRef.current,
|
|
298
|
-
supportServices,
|
|
299
|
-
selectedChannelId: params?.channelId,
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
303
|
-
}, []); // Empty dependency array ensures this runs only once on mount
|
|
304
|
-
|
|
305
|
-
// Ref to track if we've already refreshed to prevent infinite loop
|
|
306
|
-
const hasRefreshedRef = useRef(false);
|
|
307
|
-
// Ref to store the current channelFilters to detect real changes
|
|
308
|
-
const channelFiltersRef = useRef(channelFilters);
|
|
309
|
-
|
|
310
|
-
// Shared function to handle channel refreshing
|
|
311
|
-
const refreshChannels = useCallback(() => {
|
|
312
|
-
// Skip if already refreshing to prevent loops
|
|
313
|
-
if (safeContextProperty('refreshing', false)) {
|
|
314
|
-
console.log('Skipping refresh - already in progress');
|
|
315
|
-
return Promise.resolve();
|
|
316
|
-
}
|
|
355
|
+
// Fetch channels implementation
|
|
356
|
+
const fetchChannelsDirectly = useCallback(
|
|
357
|
+
async (pageNum = 1, append = false) => {
|
|
358
|
+
try {
|
|
359
|
+
const context = safeGetContext();
|
|
360
|
+
console.log(`💫 FETCHING channels (page: ${pageNum}, append: ${append})`);
|
|
317
361
|
|
|
318
|
-
|
|
362
|
+
// Calculate skip based on page number (pagination)
|
|
363
|
+
const skipCount = (pageNum - 1) * 15;
|
|
319
364
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
365
|
+
const { data } = await getChannelsRefetch({
|
|
366
|
+
role: channelRole,
|
|
367
|
+
criteria: channelFilters,
|
|
368
|
+
supportServices: supportServices ? true : false,
|
|
369
|
+
supportServiceCriteria: {
|
|
370
|
+
type: RoomType.Service,
|
|
371
|
+
},
|
|
372
|
+
limit: 15,
|
|
373
|
+
skip: skipCount,
|
|
374
|
+
});
|
|
324
375
|
|
|
325
|
-
|
|
326
|
-
|
|
376
|
+
const allChannels = [...(data?.supportServiceChannels ?? []), ...(data?.channelsByUser ?? [])];
|
|
377
|
+
const filteredChannels =
|
|
378
|
+
allChannels?.filter((c) =>
|
|
379
|
+
c.members.some(
|
|
380
|
+
(u) => u !== null && u?.user?.id != auth?.id && u.user.__typename == 'UserAccount',
|
|
381
|
+
),
|
|
382
|
+
) ?? [];
|
|
383
|
+
const sortedChannels = (filteredChannels && orderBy(filteredChannels, ['updatedAt'], ['desc'])) || [];
|
|
384
|
+
|
|
385
|
+
console.log(`📊 Processed channels: ${sortedChannels.length} (page: ${pageNum}, skip: ${skipCount})`);
|
|
327
386
|
|
|
328
|
-
// Fetch the channels
|
|
329
|
-
return getChannelsRefetch({
|
|
330
|
-
role: channelRole,
|
|
331
|
-
criteria: currentFilters,
|
|
332
|
-
supportServices: supportServices ? true : false,
|
|
333
|
-
supportServiceCriteria: {
|
|
334
|
-
type: RoomType.Service,
|
|
335
|
-
},
|
|
336
|
-
})
|
|
337
|
-
.then((data) => {
|
|
338
387
|
if (isMountedRef.current) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
388
|
+
if (append) {
|
|
389
|
+
safeSend({
|
|
390
|
+
type: Actions.APPEND_CHANNELS,
|
|
391
|
+
data: { channels: sortedChannels },
|
|
392
|
+
});
|
|
393
|
+
} else {
|
|
394
|
+
safeSend({
|
|
395
|
+
type: Actions.FETCH_CHANNELS,
|
|
396
|
+
data: { channels: sortedChannels },
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Immediately stop loading state instead of waiting
|
|
401
|
+
safeSend({ type: Actions.STOP_LOADING });
|
|
348
402
|
}
|
|
349
|
-
})
|
|
350
|
-
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error('Error fetching channels:', error);
|
|
351
405
|
if (isMountedRef.current) {
|
|
352
|
-
console.error('Channel refresh error:', error.message);
|
|
353
406
|
safeSend({
|
|
354
|
-
type:
|
|
355
|
-
data: { message:
|
|
407
|
+
type: Actions.ERROR_HANDLED,
|
|
408
|
+
data: { message: 'Failed to fetch channels' },
|
|
356
409
|
});
|
|
357
410
|
}
|
|
358
|
-
}
|
|
359
|
-
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
[getChannelsRefetch, channelRole, channelFilters, supportServices, auth?.id, safeSend],
|
|
414
|
+
);
|
|
360
415
|
|
|
361
|
-
//
|
|
416
|
+
// Add a safety timeout to ensure loading state is eventually cleared
|
|
417
|
+
// even if the fetchChannelsDirectly function fails to complete
|
|
362
418
|
useEffect(() => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
419
|
+
if (loading) {
|
|
420
|
+
const safetyTimeout = setTimeout(() => {
|
|
421
|
+
console.log('⚠️ Safety timeout triggered - forcing loading state to stop');
|
|
422
|
+
if (isMountedRef.current) {
|
|
423
|
+
safeSend({ type: Actions.STOP_LOADING });
|
|
424
|
+
}
|
|
425
|
+
}, 5000); // 5 seconds safety timeout
|
|
426
|
+
|
|
427
|
+
return () => clearTimeout(safetyTimeout);
|
|
373
428
|
}
|
|
374
|
-
}, [
|
|
429
|
+
}, [loading, safeSend]);
|
|
375
430
|
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
431
|
+
// Add a faster refresh function with smaller dataset and timeout
|
|
432
|
+
const fastRefresh = useCallback(async () => {
|
|
433
|
+
try {
|
|
434
|
+
console.log('🔄 Fast refreshing channels...');
|
|
380
435
|
|
|
381
|
-
|
|
436
|
+
// Set a timeout to ensure refreshing state is cleared if the fetch fails
|
|
437
|
+
const clearRefreshingTimeout = setTimeout(() => {
|
|
438
|
+
if (isMountedRef.current) {
|
|
439
|
+
console.log('⚠️ Fast refresh timeout - stopping refresh state');
|
|
440
|
+
safeSend({ type: Actions.STOP_LOADING });
|
|
441
|
+
}
|
|
442
|
+
}, 3000); // 3 second timeout for refresh
|
|
443
|
+
|
|
444
|
+
// Perform the fetch with a smaller limit for faster results
|
|
445
|
+
const { data } = await getChannelsRefetch({
|
|
446
|
+
role: channelRole,
|
|
447
|
+
criteria: channelFilters,
|
|
448
|
+
supportServices: supportServices ? true : false,
|
|
449
|
+
supportServiceCriteria: {
|
|
450
|
+
type: RoomType.Service,
|
|
451
|
+
},
|
|
452
|
+
limit: 10,
|
|
453
|
+
skip: 0,
|
|
454
|
+
});
|
|
382
455
|
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
console.log('Refreshing on focus');
|
|
386
|
-
hasRefreshedRef.current = true;
|
|
387
|
-
refreshChannels();
|
|
388
|
-
}
|
|
456
|
+
// Cancel the timeout since we got a response
|
|
457
|
+
clearTimeout(clearRefreshingTimeout);
|
|
389
458
|
|
|
390
|
-
|
|
391
|
-
// Reset the ref when the screen loses focus
|
|
392
|
-
console.log('Screen unfocused, resetting refresh state');
|
|
393
|
-
hasRefreshedRef.current = false;
|
|
394
|
-
};
|
|
395
|
-
}, [isFocused, refreshChannels]),
|
|
396
|
-
);
|
|
459
|
+
if (!isMountedRef.current) return;
|
|
397
460
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
461
|
+
const allChannels = [...(data?.supportServiceChannels ?? []), ...(data?.channelsByUser ?? [])];
|
|
462
|
+
const filteredChannels =
|
|
463
|
+
allChannels?.filter((c) =>
|
|
464
|
+
c.members.some((u) => u !== null && u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
|
|
465
|
+
) ?? [];
|
|
466
|
+
const sortedChannels = (filteredChannels && orderBy(filteredChannels, ['updatedAt'], ['desc'])) || [];
|
|
467
|
+
|
|
468
|
+
console.log(`📊 Fast refresh completed: ${sortedChannels.length} channels`);
|
|
469
|
+
|
|
470
|
+
// Update the state with the new channels, but only if still mounted
|
|
471
|
+
if (isMountedRef.current) {
|
|
472
|
+
// Use a single update to prevent UI jumping
|
|
473
|
+
safeSend({
|
|
474
|
+
type: Actions.FETCH_CHANNELS,
|
|
475
|
+
data: {
|
|
476
|
+
channels: sortedChannels,
|
|
477
|
+
stopLoading: true,
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.error('Error during fast refresh:', error);
|
|
483
|
+
if (isMountedRef.current) {
|
|
484
|
+
safeSend({ type: Actions.STOP_LOADING });
|
|
485
|
+
}
|
|
403
486
|
}
|
|
487
|
+
}, [getChannelsRefetch, channelRole, channelFilters, supportServices, auth?.id, safeSend]);
|
|
404
488
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
489
|
+
// Process state changes and execute side effects
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
// Only execute if not already refreshing or loading to prevent loops
|
|
492
|
+
const context = safeGetContext();
|
|
493
|
+
const isAlreadyFetching = context.refreshing || context.loading;
|
|
494
|
+
|
|
495
|
+
if (!isAlreadyFetching) {
|
|
496
|
+
if (safeMatches(BaseState.FetchChannels)) {
|
|
497
|
+
console.log('🔄 Fetching channels...');
|
|
498
|
+
fetchChannelsDirectly(1, false);
|
|
499
|
+
} else if (safeMatches(MainState.RefreshChannels)) {
|
|
500
|
+
console.log('🔄 Refreshing channels...');
|
|
501
|
+
fetchChannelsDirectly(1, false);
|
|
502
|
+
} else if (safeMatches(MainState.LoadMoreChannels)) {
|
|
503
|
+
console.log('🔄 Loading more channels...');
|
|
504
|
+
fetchChannelsDirectly(page, true);
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
// Log that we're skipping the fetch due to already being in progress
|
|
508
|
+
console.log('⏩ Skipping fetch because isAlreadyFetching:', isAlreadyFetching);
|
|
509
|
+
}
|
|
510
|
+
}, [fetchChannelsDirectly, safeMatches, safeGetContext, state.value, page]);
|
|
408
511
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
512
|
+
// Add a debug log to track state transitions
|
|
513
|
+
useEffect(() => {
|
|
514
|
+
console.log('State changed to:', state.value);
|
|
515
|
+
console.log(
|
|
516
|
+
'Context:',
|
|
517
|
+
JSON.stringify({
|
|
518
|
+
channelsCount: channels.length,
|
|
519
|
+
loading,
|
|
520
|
+
refreshing,
|
|
521
|
+
}),
|
|
522
|
+
);
|
|
523
|
+
}, [state.value, channels.length, loading, refreshing]);
|
|
524
|
+
|
|
525
|
+
// Initialize state machine with props on mount
|
|
526
|
+
useEffect(() => {
|
|
527
|
+
if (isMountedRef.current) {
|
|
528
|
+
console.log('🚀 Initializing state machine with props', {
|
|
529
|
+
channelRole,
|
|
530
|
+
channelFilters,
|
|
531
|
+
supportServices,
|
|
532
|
+
selectedChannelId: params?.channelId,
|
|
533
|
+
});
|
|
417
534
|
|
|
418
|
-
const handleSelectChannel = useCallback(
|
|
419
|
-
(id: any, title: any) => {
|
|
420
535
|
safeSend({
|
|
421
|
-
type:
|
|
422
|
-
data: {
|
|
536
|
+
type: Actions.INITIAL_CONTEXT,
|
|
537
|
+
data: {
|
|
538
|
+
channelRole,
|
|
539
|
+
channelFilters,
|
|
540
|
+
supportServices,
|
|
541
|
+
selectedChannelId: params?.channelId,
|
|
542
|
+
},
|
|
423
543
|
});
|
|
424
544
|
|
|
425
|
-
if
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
545
|
+
// Add a safety measure to ensure loading is stopped even if fetch fails
|
|
546
|
+
const initSafetyTimeout = setTimeout(() => {
|
|
547
|
+
if (isMountedRef.current && loading) {
|
|
548
|
+
console.log('⚠️ Init safety timeout triggered - forcing loading state to stop');
|
|
549
|
+
safeSend({ type: Actions.STOP_LOADING });
|
|
550
|
+
}
|
|
551
|
+
}, 8000); // 8 seconds safety timeout
|
|
552
|
+
|
|
553
|
+
return () => clearTimeout(initSafetyTimeout);
|
|
554
|
+
}
|
|
555
|
+
}, []);
|
|
556
|
+
|
|
557
|
+
// Handle refresh on focus (when navigating back to this screen)
|
|
558
|
+
const focusRefreshRef = useRef<number | null>(null);
|
|
559
|
+
|
|
560
|
+
useFocusEffect(
|
|
561
|
+
useCallback(() => {
|
|
562
|
+
// Use a flag to ensure we only trigger refresh once per focus
|
|
563
|
+
let hasTriggeredRefresh = false;
|
|
564
|
+
|
|
565
|
+
// Reset the focus refresh tracking when component gets focus
|
|
566
|
+
const now = Date.now();
|
|
567
|
+
const lastRefresh = focusRefreshRef.current;
|
|
568
|
+
|
|
569
|
+
// Only refresh if at least 2 seconds have passed since last refresh
|
|
570
|
+
const shouldRefresh = lastRefresh === null || now - lastRefresh > 2000;
|
|
571
|
+
|
|
572
|
+
// Only refresh if component is mounted and not in initial state,
|
|
573
|
+
// and not already refreshing/loading
|
|
574
|
+
const context = safeGetContext();
|
|
575
|
+
const isAlreadyFetching = context.refreshing || context.loading;
|
|
576
|
+
|
|
577
|
+
if (
|
|
578
|
+
isMountedRef.current &&
|
|
579
|
+
!hasTriggeredRefresh &&
|
|
580
|
+
!isAlreadyFetching &&
|
|
581
|
+
shouldRefresh &&
|
|
582
|
+
(channels.length > 0 || safeMatches(BaseState.Idle))
|
|
583
|
+
) {
|
|
584
|
+
hasTriggeredRefresh = true;
|
|
585
|
+
focusRefreshRef.current = now;
|
|
586
|
+
|
|
587
|
+
// Use fast refresh for better performance
|
|
588
|
+
console.log('🔄 Focus effect: triggering fast refresh');
|
|
589
|
+
safeSend({
|
|
590
|
+
type: Actions.START_LOADING,
|
|
591
|
+
data: { refreshing: true },
|
|
431
592
|
});
|
|
593
|
+
|
|
594
|
+
// Execute fast refresh with a short delay to prevent UI jank
|
|
595
|
+
setTimeout(() => {
|
|
596
|
+
if (isMountedRef.current) {
|
|
597
|
+
fastRefresh();
|
|
598
|
+
}
|
|
599
|
+
}, 100);
|
|
432
600
|
} else {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
601
|
+
console.log('⏩ Skipping focus refresh:', {
|
|
602
|
+
isAlreadyFetching,
|
|
603
|
+
hasTriggeredRefresh,
|
|
604
|
+
shouldRefresh,
|
|
605
|
+
timeGap: lastRefresh === null ? 'first refresh' : now - lastRefresh,
|
|
438
606
|
});
|
|
439
607
|
}
|
|
608
|
+
|
|
609
|
+
return () => {
|
|
610
|
+
// Reset flag when focus is lost
|
|
611
|
+
hasTriggeredRefresh = false;
|
|
612
|
+
};
|
|
613
|
+
}, [safeSend, channels.length, safeMatches, safeGetContext, fastRefresh]),
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
// Navigation handlers
|
|
617
|
+
const handleSelectChannel = useCallback(
|
|
618
|
+
(id, title) => {
|
|
619
|
+
// Always update the selected channel ID, even if it's the same channel
|
|
620
|
+
safeSend({ type: Actions.SELECT_CHANNEL, data: { channelId: id } });
|
|
621
|
+
|
|
622
|
+
// Force navigation to the channel screen, even if it's already selected
|
|
623
|
+
// This ensures we can reopen the same channel multiple times
|
|
624
|
+
navigation.navigate(config.INBOX_MESSEGE_PATH, {
|
|
625
|
+
channelId: id,
|
|
626
|
+
role: channelRole,
|
|
627
|
+
title: title,
|
|
628
|
+
hideTabBar: true,
|
|
629
|
+
timestamp: new Date().getTime(), // Add timestamp to force a refresh when navigating to the same screen
|
|
630
|
+
});
|
|
440
631
|
},
|
|
441
|
-
[
|
|
632
|
+
[navigation, channelRole, safeSend],
|
|
442
633
|
);
|
|
443
634
|
|
|
444
635
|
const handleSelectServiceChannel = useCallback(
|
|
445
|
-
(id
|
|
446
|
-
safeSend({
|
|
447
|
-
|
|
448
|
-
|
|
636
|
+
(id, title, postParentId) => {
|
|
637
|
+
safeSend({ type: Actions.SELECT_CHANNEL, data: { channelId: id } });
|
|
638
|
+
navigation.navigate(postParentId || postParentId === 0 ? config.THREAD_MESSEGE_PATH : config.THREADS_PATH, {
|
|
639
|
+
channelId: id,
|
|
640
|
+
role: channelRole,
|
|
641
|
+
title: title,
|
|
642
|
+
postParentId: postParentId,
|
|
643
|
+
hideTabBar: true,
|
|
449
644
|
});
|
|
645
|
+
},
|
|
646
|
+
[navigation, channelRole, safeSend],
|
|
647
|
+
);
|
|
450
648
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
649
|
+
// Modified pull-to-refresh handler to use fast refresh
|
|
650
|
+
const handlePullToRefresh = useCallback(() => {
|
|
651
|
+
if (refreshing) {
|
|
652
|
+
console.log('⏩ Skipping refresh because already refreshing');
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Update the last refresh timestamp to prevent simultaneous focus refresh
|
|
657
|
+
const now = Date.now();
|
|
658
|
+
focusRefreshRef.current = now;
|
|
659
|
+
|
|
660
|
+
console.log('🔄 Pull-to-refresh triggered');
|
|
661
|
+
safeSend({
|
|
662
|
+
type: Actions.START_LOADING,
|
|
663
|
+
data: { refreshing: true },
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// Use the fast refresh approach for pull-to-refresh
|
|
667
|
+
fastRefresh();
|
|
668
|
+
}, [safeSend, refreshing, fastRefresh]);
|
|
669
|
+
|
|
670
|
+
// Search handler
|
|
671
|
+
const handleSearchChange = useCallback(
|
|
672
|
+
(text: string) => {
|
|
673
|
+
safeSend({
|
|
674
|
+
type: Actions.SET_SEARCH_QUERY,
|
|
675
|
+
data: { searchQuery: text },
|
|
676
|
+
});
|
|
476
677
|
},
|
|
477
|
-
[
|
|
678
|
+
[safeSend],
|
|
478
679
|
);
|
|
479
680
|
|
|
480
|
-
|
|
681
|
+
// Add loadMore handler with debounce to prevent multiple calls
|
|
682
|
+
const handleLoadMore = useCallback(() => {
|
|
683
|
+
if (!loadingMore && hasMoreChannels) {
|
|
684
|
+
console.log('Loading more channels at page:', page + 1);
|
|
685
|
+
safeSend({ type: Actions.LOAD_MORE_CHANNELS });
|
|
686
|
+
} else {
|
|
687
|
+
console.log('Skip loading more: loadingMore=', loadingMore, 'hasMoreChannels=', hasMoreChannels);
|
|
688
|
+
}
|
|
689
|
+
}, [safeSend, loadingMore, hasMoreChannels, page]);
|
|
481
690
|
|
|
482
691
|
return (
|
|
483
692
|
<Box className="p-2">
|
|
484
693
|
<FlatList
|
|
485
|
-
data={channels
|
|
486
|
-
onRefresh={
|
|
487
|
-
refreshing={
|
|
694
|
+
data={channels}
|
|
695
|
+
onRefresh={handlePullToRefresh}
|
|
696
|
+
refreshing={refreshing}
|
|
488
697
|
contentContainerStyle={{ minHeight: '100%' }}
|
|
489
698
|
ItemSeparatorComponent={() => <Box className="h-0.5 bg-gray-200" />}
|
|
490
|
-
renderItem={({ item: channel }
|
|
491
|
-
channel?.type === RoomType.Service ? (
|
|
699
|
+
renderItem={({ item: channel }) => {
|
|
700
|
+
return channel?.type === RoomType.Service ? (
|
|
492
701
|
<ServiceDialogsListItem
|
|
702
|
+
key={`service-${channel.id}`}
|
|
493
703
|
onOpen={handleSelectServiceChannel}
|
|
494
704
|
currentUser={auth}
|
|
495
705
|
channel={channel}
|
|
496
|
-
refreshing={
|
|
497
|
-
selectedChannelId={
|
|
706
|
+
refreshing={refreshing}
|
|
707
|
+
selectedChannelId={selectedChannelId}
|
|
498
708
|
role={channelRole}
|
|
499
709
|
/>
|
|
500
710
|
) : (
|
|
501
711
|
<DialogsListItem
|
|
712
|
+
key={`direct-${channel.id}`}
|
|
502
713
|
onOpen={handleSelectChannel}
|
|
503
714
|
currentUser={auth}
|
|
504
715
|
channel={channel}
|
|
505
|
-
selectedChannelId={
|
|
716
|
+
selectedChannelId={selectedChannelId}
|
|
717
|
+
forceRefresh={true}
|
|
506
718
|
/>
|
|
507
|
-
)
|
|
719
|
+
);
|
|
720
|
+
}}
|
|
721
|
+
ListFooterComponent={() =>
|
|
722
|
+
loadingMore ? (
|
|
723
|
+
<Center className="py-4">
|
|
724
|
+
<Spinner color={colors.blue[500]} size="small" />
|
|
725
|
+
</Center>
|
|
726
|
+
) : null
|
|
508
727
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
728
|
+
onEndReached={handleLoadMore}
|
|
729
|
+
onEndReachedThreshold={0.3}
|
|
730
|
+
initialNumToRender={10}
|
|
731
|
+
maxToRenderPerBatch={10}
|
|
732
|
+
windowSize={10}
|
|
733
|
+
removeClippedSubviews={true}
|
|
734
|
+
updateCellsBatchingPeriod={50}
|
|
735
|
+
ListEmptyComponent={() => {
|
|
736
|
+
console.log('Rendering ListEmptyComponent', { loading, refreshing, stateValue: state.value });
|
|
737
|
+
|
|
738
|
+
// Only show spinner during initial loading
|
|
739
|
+
if (loading && channels.length === 0) {
|
|
740
|
+
return (
|
|
741
|
+
<Center className="flex-1 justify-center items-center" style={{ height: 300 }}>
|
|
742
|
+
<Spinner color={colors.blue[500]} size="large" />
|
|
743
|
+
<Text className="mt-4 text-gray-500">Loading conversations...</Text>
|
|
514
744
|
</Center>
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
type: DialogsActions.SET_SEARCH_QUERY,
|
|
525
|
-
data: { searchQuery: text },
|
|
526
|
-
})
|
|
527
|
-
}
|
|
528
|
-
/>
|
|
529
|
-
</Input>
|
|
530
|
-
<Center className="mt-6">
|
|
531
|
-
<Ionicons name="chatbubbles" size={50} />
|
|
532
|
-
<Text>You don't have any messages yet!</Text>
|
|
533
|
-
</Center>
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Show empty state when no channels and not loading
|
|
749
|
+
return (
|
|
750
|
+
<Box className="p-6">
|
|
751
|
+
<Box className="mb-6">
|
|
752
|
+
<Heading className="text-2xl font-bold">Direct Messages</Heading>
|
|
753
|
+
<Text className="text-gray-600 mt-1">Private conversations with other users</Text>
|
|
534
754
|
</Box>
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
755
|
+
|
|
756
|
+
<Input
|
|
757
|
+
className="mb-8 h-[50] rounded-md border-gray-300 border"
|
|
758
|
+
size="md"
|
|
759
|
+
style={{
|
|
760
|
+
paddingVertical: 8,
|
|
761
|
+
marginBottom: 10,
|
|
762
|
+
borderColor: '#d1d5db',
|
|
763
|
+
borderRadius: 10,
|
|
764
|
+
}}
|
|
765
|
+
>
|
|
766
|
+
<InputField
|
|
767
|
+
placeholder="Search messages..."
|
|
768
|
+
onChangeText={handleSearchChange}
|
|
769
|
+
value={searchQuery}
|
|
770
|
+
/>
|
|
771
|
+
</Input>
|
|
772
|
+
|
|
773
|
+
<Center className="items-center" style={{ paddingVertical: 5 }}>
|
|
774
|
+
<Box className="w-16 h-16 rounded-full bg-blue-500 flex items-center justify-center mb-5">
|
|
775
|
+
<Ionicons name="chatbubble-ellipses" size={30} color="white" />
|
|
776
|
+
</Box>
|
|
777
|
+
|
|
778
|
+
<Text className="text-2xl font-bold text-center mb-2">No messages yet</Text>
|
|
779
|
+
|
|
780
|
+
<Text className="text-gray-600 text-center mb-8">
|
|
781
|
+
When you start conversations with others,{'\n'}
|
|
782
|
+
they'll appear here.
|
|
783
|
+
</Text>
|
|
784
|
+
</Center>
|
|
785
|
+
</Box>
|
|
786
|
+
);
|
|
787
|
+
}}
|
|
788
|
+
keyExtractor={(item) => `channel-${item.id}`}
|
|
539
789
|
/>
|
|
540
790
|
</Box>
|
|
541
791
|
);
|