@messenger-box/platform-mobile 10.0.3-alpha.23 → 10.0.3-alpha.232

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.
Files changed (116) hide show
  1. package/lib/components/messages-container-ui/BuildModeView.js +428 -0
  2. package/lib/components/messages-container-ui/BuildModeView.js.map +1 -0
  3. package/lib/components/messages-container-ui/MessagesContainerUI.js +55 -0
  4. package/lib/components/messages-container-ui/MessagesContainerUI.js.map +1 -0
  5. package/lib/components/messages-container-ui/PlanModeView.js +336 -0
  6. package/lib/components/messages-container-ui/PlanModeView.js.map +1 -0
  7. package/lib/compute.js +2 -3
  8. package/lib/compute.js.map +1 -1
  9. package/lib/index.js +1 -1
  10. package/lib/index.js.map +1 -1
  11. package/lib/module.js.map +1 -1
  12. package/lib/queries/inboxQueries.js +62 -0
  13. package/lib/queries/inboxQueries.js.map +1 -0
  14. package/lib/routes.json +2 -3
  15. package/lib/screens/inbox/DialogMessages.js +8 -3
  16. package/lib/screens/inbox/DialogMessages.js.map +1 -1
  17. package/lib/screens/inbox/DialogThreadMessages.js +6 -11
  18. package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
  19. package/lib/screens/inbox/DialogThreads.js +9 -11
  20. package/lib/screens/inbox/DialogThreads.js.map +1 -1
  21. package/lib/screens/inbox/Inbox.js.map +1 -1
  22. package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
  23. package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
  24. package/lib/screens/inbox/components/CachedImage/index.js +125 -96
  25. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  26. package/lib/screens/inbox/components/DialogItem.js +160 -0
  27. package/lib/screens/inbox/components/DialogItem.js.map +1 -0
  28. package/lib/screens/inbox/components/GiftedChatInboxComponent.js +315 -0
  29. package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
  30. package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js +3 -1
  31. package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js.map +1 -1
  32. package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js +194 -0
  33. package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js.map +1 -0
  34. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +149 -36
  35. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
  36. package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js +4 -5
  37. package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js.map +1 -1
  38. package/lib/screens/inbox/components/SubscriptionHandler.js +22 -0
  39. package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
  40. package/lib/screens/inbox/components/ThreadsViewItem.js +2 -4
  41. package/lib/screens/inbox/components/ThreadsViewItem.js.map +1 -1
  42. package/lib/screens/inbox/config/config.js +4 -2
  43. package/lib/screens/inbox/config/config.js.map +1 -1
  44. package/lib/screens/inbox/containers/ConversationView.js +1093 -1090
  45. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  46. package/lib/screens/inbox/containers/Dialogs.js +130 -577
  47. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  48. package/lib/screens/inbox/containers/ThreadConversationView.js +864 -1408
  49. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  50. package/lib/screens/inbox/containers/ThreadsView.js +9 -15
  51. package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
  52. package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
  53. package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
  54. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js +1 -1
  55. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -1
  56. package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -1
  57. package/package.json +10 -8
  58. package/CHANGELOG.md +0 -172
  59. package/jest.config.js +0 -24
  60. package/lib/screens/inbox/components/DialogsListItem.js +0 -548
  61. package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
  62. package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -489
  63. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
  64. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +0 -175
  65. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +0 -1
  66. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +0 -191
  67. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +0 -1
  68. package/lib/screens/inbox/containers/workflow/conversation-xstate.js +0 -380
  69. package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +0 -1
  70. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +0 -211
  71. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +0 -1
  72. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +0 -438
  73. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +0 -1
  74. package/rollup.config.mjs +0 -45
  75. package/src/components/index.ts +0 -0
  76. package/src/compute.ts +0 -63
  77. package/src/index.ts +0 -7
  78. package/src/module.ts +0 -10
  79. package/src/navigation/InboxNavigation.tsx +0 -102
  80. package/src/navigation/index.ts +0 -1
  81. package/src/screens/inbox/DialogMessages.tsx +0 -21
  82. package/src/screens/inbox/DialogThreadMessages.tsx +0 -97
  83. package/src/screens/inbox/DialogThreads.tsx +0 -125
  84. package/src/screens/inbox/Inbox.tsx +0 -17
  85. package/src/screens/inbox/components/CachedImage/consts.ts +0 -6
  86. package/src/screens/inbox/components/CachedImage/index.tsx +0 -223
  87. package/src/screens/inbox/components/DialogsHeader.tsx +0 -30
  88. package/src/screens/inbox/components/DialogsListItem.tsx +0 -819
  89. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +0 -679
  90. package/src/screens/inbox/components/SlackMessageContainer/ImageViewerModal.tsx +0 -113
  91. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +0 -313
  92. package/src/screens/inbox/components/SlackMessageContainer/SlackMessage.tsx +0 -145
  93. package/src/screens/inbox/components/SlackMessageContainer/index.ts +0 -3
  94. package/src/screens/inbox/components/SmartLoader.tsx +0 -61
  95. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +0 -301
  96. package/src/screens/inbox/components/ThreadsViewItem.tsx +0 -233
  97. package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +0 -145
  98. package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +0 -159
  99. package/src/screens/inbox/config/config.ts +0 -15
  100. package/src/screens/inbox/config/index.ts +0 -1
  101. package/src/screens/inbox/containers/ConversationView.tsx +0 -1784
  102. package/src/screens/inbox/containers/Dialogs.tsx +0 -829
  103. package/src/screens/inbox/containers/SupportServiceDialogs.tsx +0 -119
  104. package/src/screens/inbox/containers/ThreadConversationView.tsx +0 -2295
  105. package/src/screens/inbox/containers/ThreadsView.tsx +0 -224
  106. package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +0 -20
  107. package/src/screens/inbox/containers/workflow/conversation-xstate.ts +0 -313
  108. package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +0 -196
  109. package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +0 -401
  110. package/src/screens/inbox/hooks/useSafeDialogThreadsMachine.ts +0 -136
  111. package/src/screens/inbox/index.ts +0 -37
  112. package/src/screens/inbox/machines/threadsMachine.ts +0 -147
  113. package/src/screens/inbox/workflow/dialog-threads-xstate.ts +0 -163
  114. package/src/screens/index.ts +0 -4
  115. package/tsconfig.json +0 -13
  116. package/webpack.config.js +0 -58
@@ -1,829 +0,0 @@
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';
3
- import { Ionicons } from '@expo/vector-icons';
4
- import { useSelector } from 'react-redux';
5
- import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';
6
- import { orderBy } from 'lodash-es';
7
- import { DialogsListItem } from '../components/DialogsListItem';
8
- import { ServiceDialogsListItem } from '../components/ServiceDialogsListItem';
9
- import { useGetChannelsByUserWithServiceChannelsQuery } from 'common/graphql';
10
- import { RoomType } from 'common';
11
- import { userSelector } from '@adminide-stack/user-auth0-client';
12
- import { config } from '../config';
13
- import colors from 'tailwindcss/colors';
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
- };
43
-
44
- export interface InboxProps {
45
- channelFilters?: Record<string, unknown>;
46
- channelRole?: string;
47
- supportServices: boolean;
48
- }
49
-
50
- // Create a safer version of useMachine to handle potential errors
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
-
72
- // Initialize with default state
73
- const [state, setState] = useState<SafeStateType>({
74
- context: {
75
- channels: [],
76
- refreshing: false,
77
- loading: false,
78
- error: null,
79
- searchQuery: '',
80
- selectedChannelId: null,
81
- channelRole: null,
82
- channelFilters: {},
83
- supportServices: false,
84
- page: 1,
85
- hasMoreChannels: true,
86
- loadingMore: false,
87
- },
88
- value: 'idle',
89
- });
90
-
91
- // Create a safe send function
92
- const send = useCallback((event) => {
93
- try {
94
- // Log event for debugging
95
- console.log('Event received:', event.type);
96
-
97
- // Handle specific events manually
98
- if (event.type === Actions.INITIAL_CONTEXT) {
99
- setState((prev) => ({
100
- ...prev,
101
- context: {
102
- ...prev.context,
103
- channelRole: event.data?.channelRole || null,
104
- channelFilters: event.data?.channelFilters || {},
105
- supportServices: event.data?.supportServices || false,
106
- selectedChannelId: event.data?.selectedChannelId || null,
107
- loading: true,
108
- page: 1,
109
- hasMoreChannels: true,
110
- },
111
- value: BaseState.FetchChannels,
112
- }));
113
- } else if (event.type === Actions.FETCH_CHANNELS) {
114
- console.log('Setting channels:', event.data?.channels?.length || 0);
115
- setState((prev) => ({
116
- ...prev,
117
- context: {
118
- ...prev.context,
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,
139
- },
140
- value: BaseState.Idle,
141
- }));
142
- } else if (event.type === Actions.REFRESH_CHANNELS) {
143
- setState((prev) => ({
144
- ...prev,
145
- context: {
146
- ...prev.context,
147
- refreshing: true,
148
- page: 1,
149
- hasMoreChannels: true,
150
- },
151
- value: MainState.RefreshChannels,
152
- }));
153
- } else if (event.type === Actions.SELECT_CHANNEL) {
154
- setState((prev) => ({
155
- ...prev,
156
- context: {
157
- ...prev.context,
158
- selectedChannelId: event.data?.channelId || null,
159
- },
160
- }));
161
- } else if (event.type === Actions.START_LOADING) {
162
- setState((prev) => ({
163
- ...prev,
164
- context: {
165
- ...prev.context,
166
- loading: true,
167
- },
168
- }));
169
- } else if (event.type === Actions.STOP_LOADING) {
170
- console.log('Explicitly stopping loading state');
171
- setState((prev) => ({
172
- ...prev,
173
- context: {
174
- ...prev.context,
175
- loading: false,
176
- refreshing: false,
177
- loadingMore: false,
178
- },
179
- value: prev.value === BaseState.FetchChannels ? BaseState.Idle : prev.value,
180
- }));
181
- } else if (event.type === Actions.LOAD_MORE_CHANNELS) {
182
- setState((prev) => ({
183
- ...prev,
184
- context: {
185
- ...prev.context,
186
- loadingMore: true,
187
- },
188
- value: MainState.LoadMoreChannels,
189
- }));
190
- } else if (event.type === Actions.SET_SEARCH_QUERY) {
191
- setState((prev) => ({
192
- ...prev,
193
- context: {
194
- ...prev.context,
195
- searchQuery: event.data?.searchQuery || '',
196
- },
197
- }));
198
- } else if (event.type === Actions.ERROR_HANDLED) {
199
- console.log('Error handled:', event.data?.message);
200
- setState((prev) => ({
201
- ...prev,
202
- context: {
203
- ...prev.context,
204
- error: event.data?.message || null,
205
- loading: false,
206
- refreshing: false,
207
- loadingMore: false,
208
- },
209
- value: BaseState.Idle,
210
- }));
211
- }
212
- } catch (error) {
213
- console.error('Error handling event:', error);
214
- }
215
- }, []);
216
-
217
- // Add a custom matches function to the state
218
- const stateWithMatches = useMemo(() => {
219
- return {
220
- ...state,
221
- matches: (checkState) => {
222
- return state.value === checkState;
223
- },
224
- };
225
- }, [state]);
226
-
227
- // Return as a tuple to match useMachine API
228
- return [stateWithMatches, send] as const;
229
- }
230
-
231
- const DialogsComponent = (props: InboxProps) => {
232
- const { channelFilters: channelFilterProp, channelRole, supportServices } = props;
233
- const channelFilters = { ...channelFilterProp };
234
- channelFilters.type = channelFilters?.type ?? RoomType.Direct;
235
- const { params } = useRoute<any>();
236
- const auth = useSelector(userSelector);
237
- const navigation = useNavigation<any>();
238
-
239
- // Create a ref to track if component is mounted
240
- const isMountedRef = useRef(true);
241
-
242
- // Use our safer custom implementation instead of the problematic useMachine
243
- const [state, send] = useSafeMachine(dialogsXstate);
244
-
245
- // Define safe functions first to avoid "used before declaration" errors
246
- const safeContext = useCallback(() => {
247
- try {
248
- return state?.context || {};
249
- } catch (error) {
250
- console.error('Error accessing state.context:', error);
251
- return {};
252
- }
253
- }, [state]);
254
-
255
- const safeContextProperty = useCallback(
256
- (property, defaultValue = null) => {
257
- try {
258
- return state?.context?.[property] ?? defaultValue;
259
- } catch (error) {
260
- console.error(`Error accessing state.context.${property}:`, error);
261
- return defaultValue;
262
- }
263
- },
264
- [state],
265
- );
266
-
267
- const safeMatches = useCallback(
268
- (stateValue) => {
269
- try {
270
- return state?.matches?.(stateValue) || false;
271
- } catch (error) {
272
- console.error(`Error calling state.matches with ${stateValue}:`, error);
273
- return false;
274
- }
275
- },
276
- [state],
277
- );
278
-
279
- const safeSend = useCallback(
280
- (event) => {
281
- try {
282
- send(event);
283
- } catch (error) {
284
- console.error('Error sending event to state machine:', error, event);
285
- }
286
- },
287
- [send],
288
- );
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
-
300
- // Use a ref to track the current machine snapshot for safer access
301
- const stateRef = useRef(state);
302
-
303
- // Keep the ref updated with the latest snapshot
304
- useEffect(() => {
305
- stateRef.current = state;
306
- }, [state]);
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
-
330
- // Use cleanup function to prevent setting state after unmount
331
- useEffect(() => {
332
- return () => {
333
- isMountedRef.current = false;
334
- };
335
- }, []);
336
-
337
- // Apollo query for fetching channels
338
- const { refetch: getChannelsRefetch } = useGetChannelsByUserWithServiceChannelsQuery({
339
- variables: {
340
- role: channelRole,
341
- criteria: channelFilters,
342
- supportServices: supportServices ? true : false,
343
- supportServiceCriteria: {
344
- type: RoomType.Service,
345
- },
346
- limit: 15,
347
- skip: 0,
348
- },
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
353
- });
354
-
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})`);
361
-
362
- // Calculate skip based on page number (pagination)
363
- const skipCount = (pageNum - 1) * 15;
364
-
365
- // Add timeout to prevent hanging requests
366
- const fetchPromise = getChannelsRefetch({
367
- role: channelRole,
368
- criteria: channelFilters,
369
- supportServices: supportServices ? true : false,
370
- supportServiceCriteria: {
371
- type: RoomType.Service,
372
- },
373
- limit: 15,
374
- skip: skipCount,
375
- });
376
-
377
- // Set a timeout to abort long-running requests
378
- const timeoutPromise = new Promise((_, reject) =>
379
- setTimeout(() => reject(new Error('Request timeout')), 8000),
380
- );
381
-
382
- // Race the fetch against the timeout
383
- const result = (await Promise.race([fetchPromise, timeoutPromise])) as any;
384
- const data = result?.data || {};
385
-
386
- const allChannels = [...(data?.supportServiceChannels ?? []), ...(data?.channelsByUser ?? [])];
387
-
388
- // Optimize filtering by using more efficient approach
389
- const filteredChannels =
390
- allChannels?.filter((c) => {
391
- if (!c || !c.members) return false;
392
-
393
- // Early return pattern for better performance
394
- for (const member of c.members) {
395
- if (
396
- member &&
397
- member.user &&
398
- member.user.id !== auth?.id &&
399
- member.user.__typename === 'UserAccount'
400
- ) {
401
- return true;
402
- }
403
- }
404
- return false;
405
- }) ?? [];
406
-
407
- // Use more efficient sorting
408
- const sortedChannels =
409
- filteredChannels.sort((a, b) => {
410
- const dateA = new Date(a.updatedAt);
411
- const dateB = new Date(b.updatedAt);
412
- return dateB.getTime() - dateA.getTime();
413
- }) || [];
414
-
415
- console.log(`📊 Processed channels: ${sortedChannels.length} (page: ${pageNum}, skip: ${skipCount})`);
416
-
417
- if (isMountedRef.current) {
418
- if (append) {
419
- safeSend({
420
- type: Actions.APPEND_CHANNELS,
421
- data: { channels: sortedChannels },
422
- });
423
- } else {
424
- safeSend({
425
- type: Actions.FETCH_CHANNELS,
426
- data: { channels: sortedChannels, stopLoading: true },
427
- });
428
- }
429
-
430
- // No need for another stop loading call as we included stopLoading: true above
431
- }
432
- } catch (error) {
433
- console.error('Error fetching channels:', error);
434
- if (isMountedRef.current) {
435
- safeSend({
436
- type: Actions.ERROR_HANDLED,
437
- data: { message: 'Failed to fetch channels' },
438
- });
439
- }
440
- }
441
- },
442
- [getChannelsRefetch, channelRole, channelFilters, supportServices, auth?.id, safeSend],
443
- );
444
-
445
- // Optimize safety timeout to use a shorter duration
446
- useEffect(() => {
447
- if (loading) {
448
- const safetyTimeout = setTimeout(() => {
449
- console.log('⚠️ Safety timeout triggered - forcing loading state to stop');
450
- if (isMountedRef.current) {
451
- safeSend({ type: Actions.STOP_LOADING });
452
- }
453
- }, 3000); // Reduced from 5000 to 3000 ms
454
-
455
- return () => clearTimeout(safetyTimeout);
456
- }
457
- }, [loading, safeSend]);
458
-
459
- // Add a faster refresh function with smaller dataset and timeout
460
- const fastRefresh = useCallback(async () => {
461
- try {
462
- console.log('🔄 Fast refreshing channels...');
463
-
464
- // Set a timeout to ensure refreshing state is cleared if the fetch fails
465
- const clearRefreshingTimeout = setTimeout(() => {
466
- if (isMountedRef.current) {
467
- console.log('⚠️ Fast refresh timeout - stopping refresh state');
468
- safeSend({ type: Actions.STOP_LOADING });
469
- }
470
- }, 3000); // 3 second timeout for refresh
471
-
472
- // Perform the fetch with a smaller limit for faster results
473
- const { data } = await getChannelsRefetch({
474
- role: channelRole,
475
- criteria: channelFilters,
476
- supportServices: supportServices ? true : false,
477
- supportServiceCriteria: {
478
- type: RoomType.Service,
479
- },
480
- limit: 10,
481
- skip: 0,
482
- });
483
-
484
- // Cancel the timeout since we got a response
485
- clearTimeout(clearRefreshingTimeout);
486
-
487
- if (!isMountedRef.current) return;
488
-
489
- const allChannels = [...(data?.supportServiceChannels ?? []), ...(data?.channelsByUser ?? [])];
490
- const filteredChannels =
491
- allChannels?.filter((c) =>
492
- c.members.some((u) => u !== null && u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
493
- ) ?? [];
494
- const sortedChannels = (filteredChannels && orderBy(filteredChannels, ['updatedAt'], ['desc'])) || [];
495
-
496
- console.log(`📊 Fast refresh completed: ${sortedChannels.length} channels`);
497
-
498
- // Update the state with the new channels, but only if still mounted
499
- if (isMountedRef.current) {
500
- // Use a single update to prevent UI jumping
501
- safeSend({
502
- type: Actions.FETCH_CHANNELS,
503
- data: {
504
- channels: sortedChannels,
505
- stopLoading: true,
506
- },
507
- });
508
- }
509
- } catch (error) {
510
- console.error('Error during fast refresh:', error);
511
- if (isMountedRef.current) {
512
- safeSend({ type: Actions.STOP_LOADING });
513
- }
514
- }
515
- }, [getChannelsRefetch, channelRole, channelFilters, supportServices, auth?.id, safeSend]);
516
-
517
- // Process state changes and execute side effects
518
- useEffect(() => {
519
- // Only execute if not already refreshing or loading to prevent loops
520
- const context = safeGetContext();
521
- const isAlreadyFetching = context.refreshing || context.loading;
522
-
523
- if (!isAlreadyFetching) {
524
- if (safeMatches(BaseState.FetchChannels)) {
525
- console.log('🔄 Fetching channels...');
526
- fetchChannelsDirectly(1, false);
527
- } else if (safeMatches(MainState.RefreshChannels)) {
528
- console.log('🔄 Refreshing channels...');
529
- fetchChannelsDirectly(1, false);
530
- } else if (safeMatches(MainState.LoadMoreChannels)) {
531
- console.log('🔄 Loading more channels...');
532
- fetchChannelsDirectly(page, true);
533
- }
534
- } else {
535
- // Log that we're skipping the fetch due to already being in progress
536
- console.log('⏩ Skipping fetch because isAlreadyFetching:', isAlreadyFetching);
537
- }
538
- }, [fetchChannelsDirectly, safeMatches, safeGetContext, state.value, page]);
539
-
540
- // Add a debug log to track state transitions
541
- useEffect(() => {
542
- console.log('State changed to:', state.value);
543
- console.log(
544
- 'Context:',
545
- JSON.stringify({
546
- channelsCount: channels.length,
547
- loading,
548
- refreshing,
549
- }),
550
- );
551
- }, [state.value, channels.length, loading, refreshing]);
552
-
553
- // Initialize state machine with props on mount
554
- useEffect(() => {
555
- if (isMountedRef.current) {
556
- console.log('🚀 Initializing state machine with props', {
557
- channelRole,
558
- channelFilters,
559
- supportServices,
560
- selectedChannelId: params?.channelId,
561
- });
562
-
563
- safeSend({
564
- type: Actions.INITIAL_CONTEXT,
565
- data: {
566
- channelRole,
567
- channelFilters,
568
- supportServices,
569
- selectedChannelId: params?.channelId,
570
- },
571
- });
572
-
573
- // Add a safety measure to ensure loading is stopped even if fetch fails
574
- const initSafetyTimeout = setTimeout(() => {
575
- if (isMountedRef.current && loading) {
576
- console.log('⚠️ Init safety timeout triggered - forcing loading state to stop');
577
- safeSend({ type: Actions.STOP_LOADING });
578
- }
579
- }, 8000); // 8 seconds safety timeout
580
-
581
- return () => clearTimeout(initSafetyTimeout);
582
- }
583
- }, []);
584
-
585
- // Handle refresh on focus (when navigating back to this screen)
586
- const focusRefreshRef = useRef<number | null>(null);
587
-
588
- useFocusEffect(
589
- useCallback(() => {
590
- // Use a flag to ensure we only trigger refresh once per focus
591
- let hasTriggeredRefresh = false;
592
-
593
- // Reset the focus refresh tracking when component gets focus
594
- const now = Date.now();
595
- const lastRefresh = focusRefreshRef.current;
596
-
597
- // Only refresh if at least 2 seconds have passed since last refresh
598
- const shouldRefresh = lastRefresh === null || now - lastRefresh > 2000;
599
-
600
- // Only refresh if component is mounted and not in initial state,
601
- // and not already refreshing/loading
602
- const context = safeGetContext();
603
- const isAlreadyFetching = context.refreshing || context.loading;
604
-
605
- if (
606
- isMountedRef.current &&
607
- !hasTriggeredRefresh &&
608
- !isAlreadyFetching &&
609
- shouldRefresh &&
610
- (channels.length > 0 || safeMatches(BaseState.Idle))
611
- ) {
612
- hasTriggeredRefresh = true;
613
- focusRefreshRef.current = now;
614
-
615
- // Use fast refresh for better performance
616
- console.log('🔄 Focus effect: triggering fast refresh');
617
- safeSend({
618
- type: Actions.START_LOADING,
619
- data: { refreshing: true },
620
- });
621
-
622
- // Execute fast refresh with a short delay to prevent UI jank
623
- setTimeout(() => {
624
- if (isMountedRef.current) {
625
- fastRefresh();
626
- }
627
- }, 100);
628
- } else {
629
- console.log('⏩ Skipping focus refresh:', {
630
- isAlreadyFetching,
631
- hasTriggeredRefresh,
632
- shouldRefresh,
633
- timeGap: lastRefresh === null ? 'first refresh' : now - lastRefresh,
634
- });
635
- }
636
-
637
- return () => {
638
- // Reset flag when focus is lost
639
- hasTriggeredRefresh = false;
640
- };
641
- }, [safeSend, channels.length, safeMatches, safeGetContext, fastRefresh]),
642
- );
643
-
644
- // Navigation handlers
645
- const handleSelectChannel = useCallback(
646
- (id, title) => {
647
- // Always update the selected channel ID, even if it's the same channel
648
- safeSend({ type: Actions.SELECT_CHANNEL, data: { channelId: id } });
649
-
650
- // Force navigation to the channel screen, even if it's already selected
651
- // This ensures we can reopen the same channel multiple times
652
- navigation.navigate(config.INBOX_MESSEGE_PATH, {
653
- channelId: id,
654
- role: channelRole,
655
- title: title,
656
- hideTabBar: true,
657
- timestamp: new Date().getTime(), // Add timestamp to force a refresh when navigating to the same screen
658
- });
659
- },
660
- [navigation, channelRole, safeSend],
661
- );
662
-
663
- const handleSelectServiceChannel = useCallback(
664
- (id, title, postParentId) => {
665
- safeSend({ type: Actions.SELECT_CHANNEL, data: { channelId: id } });
666
- navigation.navigate(postParentId || postParentId === 0 ? config.THREAD_MESSEGE_PATH : config.THREADS_PATH, {
667
- channelId: id,
668
- role: channelRole,
669
- title: title,
670
- postParentId: postParentId,
671
- hideTabBar: true,
672
- });
673
- },
674
- [navigation, channelRole, safeSend],
675
- );
676
-
677
- // Modified pull-to-refresh handler to use fast refresh
678
- const handlePullToRefresh = useCallback(() => {
679
- if (refreshing) {
680
- console.log('⏩ Skipping refresh because already refreshing');
681
- return;
682
- }
683
-
684
- // Update the last refresh timestamp to prevent simultaneous focus refresh
685
- const now = Date.now();
686
- focusRefreshRef.current = now;
687
-
688
- console.log('🔄 Pull-to-refresh triggered');
689
- safeSend({
690
- type: Actions.START_LOADING,
691
- data: { refreshing: true },
692
- });
693
-
694
- // Use the fast refresh approach for pull-to-refresh
695
- fastRefresh();
696
- }, [safeSend, refreshing, fastRefresh]);
697
-
698
- // Search handler
699
- const handleSearchChange = useCallback(
700
- (text: string) => {
701
- safeSend({
702
- type: Actions.SET_SEARCH_QUERY,
703
- data: { searchQuery: text },
704
- });
705
- },
706
- [safeSend],
707
- );
708
-
709
- // Add loadMore handler with debounce to prevent multiple calls
710
- const handleLoadMore = useCallback(() => {
711
- if (!loadingMore && hasMoreChannels) {
712
- console.log('Loading more channels at page:', page + 1);
713
- safeSend({ type: Actions.LOAD_MORE_CHANNELS });
714
- } else {
715
- console.log('Skip loading more: loadingMore=', loadingMore, 'hasMoreChannels=', hasMoreChannels);
716
- }
717
- }, [safeSend, loadingMore, hasMoreChannels, page]);
718
-
719
- return (
720
- <Box className="p-2">
721
- <FlatList
722
- data={channels}
723
- onRefresh={handlePullToRefresh}
724
- refreshing={refreshing}
725
- contentContainerStyle={{ minHeight: '100%' }}
726
- ItemSeparatorComponent={() => <Box className="h-0.5 bg-gray-200" />}
727
- renderItem={({ item: channel }) => {
728
- // Use memoized key for better list performance
729
- const key = `${channel.type === RoomType.Service ? 'service' : 'direct'}-${channel.id}`;
730
-
731
- return channel?.type === RoomType.Service ? (
732
- <ServiceDialogsListItem
733
- key={key}
734
- onOpen={handleSelectServiceChannel}
735
- currentUser={auth}
736
- channel={channel}
737
- refreshing={refreshing}
738
- selectedChannelId={selectedChannelId}
739
- role={channelRole}
740
- />
741
- ) : (
742
- <DialogsListItem
743
- key={key}
744
- onOpen={handleSelectChannel}
745
- currentUser={auth}
746
- channel={channel}
747
- selectedChannelId={selectedChannelId}
748
- forceRefresh={false} // Change to false to avoid unnecessary refreshes
749
- />
750
- );
751
- }}
752
- ListFooterComponent={() =>
753
- loadingMore ? (
754
- <Center className="py-4">
755
- <Spinner color={colors.blue[500]} size="small" />
756
- </Center>
757
- ) : null
758
- }
759
- onEndReached={handleLoadMore}
760
- onEndReachedThreshold={0.5} // Increased from 0.3 for earlier loading
761
- initialNumToRender={5} // Reduced from 10 for faster initial render
762
- maxToRenderPerBatch={5} // Reduced from 10 for smoother rendering
763
- windowSize={5} // Reduced from 10 to maintain fewer items in memory
764
- removeClippedSubviews={true}
765
- updateCellsBatchingPeriod={100} // Increased from 50 to batch updates better
766
- getItemLayout={(data, index) =>
767
- // Pre-calculate item dimensions for more efficient rendering
768
- ({ length: 80, offset: 80 * index, index })
769
- }
770
- keyExtractor={(item) => `channel-${item.id}`}
771
- ListEmptyComponent={() => {
772
- console.log('Rendering ListEmptyComponent', { loading, refreshing, stateValue: state.value });
773
-
774
- // Only show spinner during initial loading
775
- if (loading && channels.length === 0) {
776
- return (
777
- <Center className="flex-1 justify-center items-center" style={{ height: 300 }}>
778
- <Spinner color={colors.blue[500]} size="large" />
779
- <Text className="mt-4 text-gray-500">Loading conversations...</Text>
780
- </Center>
781
- );
782
- }
783
-
784
- // Show empty state when no channels and not loading
785
- return (
786
- <Box className="p-6">
787
- <Box className="mb-6">
788
- <Heading className="text-2xl font-bold">Direct Messages</Heading>
789
- <Text className="text-gray-600 mt-1">Private conversations with other users</Text>
790
- </Box>
791
-
792
- <Input
793
- className="mb-8 h-[50] rounded-md border-gray-300 border"
794
- size="md"
795
- style={{
796
- paddingVertical: 8,
797
- marginBottom: 10,
798
- borderColor: '#d1d5db',
799
- borderRadius: 10,
800
- }}
801
- >
802
- <InputField
803
- placeholder="Search messages..."
804
- onChangeText={handleSearchChange}
805
- value={searchQuery}
806
- />
807
- </Input>
808
-
809
- <Center className="items-center" style={{ paddingVertical: 5 }}>
810
- <Box className="w-16 h-16 rounded-full bg-blue-500 flex items-center justify-center mb-5">
811
- <Ionicons name="chatbubble-ellipses" size={30} color="white" />
812
- </Box>
813
-
814
- <Text className="text-2xl font-bold text-center mb-2">No messages yet</Text>
815
-
816
- <Text className="text-gray-600 text-center mb-8">
817
- When you start conversations with others,{'\n'}
818
- they'll appear here.
819
- </Text>
820
- </Center>
821
- </Box>
822
- );
823
- }}
824
- />
825
- </Box>
826
- );
827
- };
828
-
829
- export const Dialogs = React.memo(DialogsComponent);