@messenger-box/platform-mobile 10.0.3-alpha.20 → 10.0.3-alpha.201

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 (112) 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 +58 -20
  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 -115
  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 +67 -47
  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 +1099 -1094
  45. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  46. package/lib/screens/inbox/containers/Dialogs.js +132 -534
  47. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  48. package/lib/screens/inbox/containers/ThreadConversationView.js +876 -1357
  49. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  50. package/lib/screens/inbox/containers/ThreadsView.js +81 -54
  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 +108 -0
  55. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -0
  56. package/lib/screens/inbox/workflow/dialog-threads-xstate.js +151 -0
  57. package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -0
  58. package/package.json +9 -7
  59. package/CHANGELOG.md +0 -164
  60. package/jest.config.js +0 -24
  61. package/lib/screens/inbox/components/DialogsListItem.js +0 -548
  62. package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
  63. package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -489
  64. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
  65. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +0 -175
  66. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +0 -1
  67. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +0 -191
  68. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +0 -1
  69. package/lib/screens/inbox/containers/workflow/conversation-xstate.js +0 -380
  70. package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +0 -1
  71. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +0 -211
  72. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +0 -1
  73. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +0 -438
  74. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +0 -1
  75. package/rollup.config.mjs +0 -45
  76. package/src/components/index.ts +0 -0
  77. package/src/compute.ts +0 -63
  78. package/src/index.ts +0 -7
  79. package/src/module.ts +0 -10
  80. package/src/navigation/InboxNavigation.tsx +0 -102
  81. package/src/navigation/index.ts +0 -1
  82. package/src/screens/inbox/DialogMessages.tsx +0 -21
  83. package/src/screens/inbox/DialogThreadMessages.tsx +0 -97
  84. package/src/screens/inbox/DialogThreads.tsx +0 -129
  85. package/src/screens/inbox/Inbox.tsx +0 -17
  86. package/src/screens/inbox/components/CachedImage/consts.ts +0 -6
  87. package/src/screens/inbox/components/CachedImage/index.tsx +0 -223
  88. package/src/screens/inbox/components/DialogsHeader.tsx +0 -30
  89. package/src/screens/inbox/components/DialogsListItem.tsx +0 -819
  90. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +0 -679
  91. package/src/screens/inbox/components/SlackMessageContainer/ImageViewerModal.tsx +0 -113
  92. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +0 -313
  93. package/src/screens/inbox/components/SlackMessageContainer/SlackMessage.tsx +0 -145
  94. package/src/screens/inbox/components/SlackMessageContainer/index.ts +0 -3
  95. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +0 -301
  96. package/src/screens/inbox/components/ThreadsViewItem.tsx +0 -321
  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 -1782
  102. package/src/screens/inbox/containers/Dialogs.tsx +0 -794
  103. package/src/screens/inbox/containers/SupportServiceDialogs.tsx +0 -119
  104. package/src/screens/inbox/containers/ThreadConversationView.tsx +0 -2312
  105. package/src/screens/inbox/containers/ThreadsView.tsx +0 -305
  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/index.ts +0 -4
  111. package/tsconfig.json +0 -13
  112. package/webpack.config.js +0 -58
@@ -1,794 +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
- 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
- });
375
-
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})`);
386
-
387
- if (isMountedRef.current) {
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 });
402
- }
403
- } catch (error) {
404
- console.error('Error fetching channels:', error);
405
- if (isMountedRef.current) {
406
- safeSend({
407
- type: Actions.ERROR_HANDLED,
408
- data: { message: 'Failed to fetch channels' },
409
- });
410
- }
411
- }
412
- },
413
- [getChannelsRefetch, channelRole, channelFilters, supportServices, auth?.id, safeSend],
414
- );
415
-
416
- // Add a safety timeout to ensure loading state is eventually cleared
417
- // even if the fetchChannelsDirectly function fails to complete
418
- useEffect(() => {
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);
428
- }
429
- }, [loading, safeSend]);
430
-
431
- // Add a faster refresh function with smaller dataset and timeout
432
- const fastRefresh = useCallback(async () => {
433
- try {
434
- console.log('🔄 Fast refreshing channels...');
435
-
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
- });
455
-
456
- // Cancel the timeout since we got a response
457
- clearTimeout(clearRefreshingTimeout);
458
-
459
- if (!isMountedRef.current) return;
460
-
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
- }
486
- }
487
- }, [getChannelsRefetch, channelRole, channelFilters, supportServices, auth?.id, safeSend]);
488
-
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]);
511
-
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
- });
534
-
535
- safeSend({
536
- type: Actions.INITIAL_CONTEXT,
537
- data: {
538
- channelRole,
539
- channelFilters,
540
- supportServices,
541
- selectedChannelId: params?.channelId,
542
- },
543
- });
544
-
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 },
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);
600
- } else {
601
- console.log('⏩ Skipping focus refresh:', {
602
- isAlreadyFetching,
603
- hasTriggeredRefresh,
604
- shouldRefresh,
605
- timeGap: lastRefresh === null ? 'first refresh' : now - lastRefresh,
606
- });
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
- });
631
- },
632
- [navigation, channelRole, safeSend],
633
- );
634
-
635
- const handleSelectServiceChannel = useCallback(
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,
644
- });
645
- },
646
- [navigation, channelRole, safeSend],
647
- );
648
-
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
- });
677
- },
678
- [safeSend],
679
- );
680
-
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]);
690
-
691
- return (
692
- <Box className="p-2">
693
- <FlatList
694
- data={channels}
695
- onRefresh={handlePullToRefresh}
696
- refreshing={refreshing}
697
- contentContainerStyle={{ minHeight: '100%' }}
698
- ItemSeparatorComponent={() => <Box className="h-0.5 bg-gray-200" />}
699
- renderItem={({ item: channel }) => {
700
- return channel?.type === RoomType.Service ? (
701
- <ServiceDialogsListItem
702
- key={`service-${channel.id}`}
703
- onOpen={handleSelectServiceChannel}
704
- currentUser={auth}
705
- channel={channel}
706
- refreshing={refreshing}
707
- selectedChannelId={selectedChannelId}
708
- role={channelRole}
709
- />
710
- ) : (
711
- <DialogsListItem
712
- key={`direct-${channel.id}`}
713
- onOpen={handleSelectChannel}
714
- currentUser={auth}
715
- channel={channel}
716
- selectedChannelId={selectedChannelId}
717
- forceRefresh={true}
718
- />
719
- );
720
- }}
721
- ListFooterComponent={() =>
722
- loadingMore ? (
723
- <Center className="py-4">
724
- <Spinner color={colors.blue[500]} size="small" />
725
- </Center>
726
- ) : null
727
- }
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>
744
- </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>
754
- </Box>
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}`}
789
- />
790
- </Box>
791
- );
792
- };
793
-
794
- export const Dialogs = React.memo(DialogsComponent);