@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,819 +0,0 @@
1
- import React, { useMemo, useState, useCallback, useRef, useEffect } from 'react';
2
- import {
3
- Text,
4
- Image,
5
- Pressable,
6
- HStack,
7
- Box,
8
- AvatarGroup,
9
- Avatar,
10
- AvatarFallbackText,
11
- AvatarImage,
12
- AvatarBadge,
13
- View,
14
- } from '@admin-layout/gluestack-ui-mobile';
15
- import { format, isToday, isYesterday } from 'date-fns';
16
- import { useFocusEffect } from '@react-navigation/native';
17
- import { IChannel, SortEnum, IUserAccount } from 'common';
18
- import {
19
- useMessagesQuery,
20
- useOnChatMessageAddedSubscription,
21
- OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
22
- useUserAccountQuery,
23
- } from 'common/graphql';
24
- import { startCase } from 'lodash-es';
25
- import colors from 'tailwindcss/colors';
26
- import { dialogsListItemXstate, Actions, BaseState } from './workflow/dialogs-list-item-xstate';
27
-
28
- const createdAtText = (value: string) => {
29
- if (!value) return '';
30
- let date = new Date(value);
31
- if (isToday(date)) return 'Today';
32
- if (isYesterday(date)) return 'Yesterday';
33
- return format(new Date(value), 'MMM dd, yyyy');
34
- };
35
-
36
- export interface IDialogListChannel extends IChannel {
37
- users: IUserAccount[];
38
- }
39
-
40
- export interface IDialogListItemProps {
41
- currentUser?: any;
42
- users?: any;
43
- selectedChannelId?: any;
44
- channel?: any;
45
- onOpen: (id: any, title: any) => void;
46
- forceRefresh?: boolean;
47
- }
48
-
49
- // Create a safer version of useMachine to handle potential errors
50
- function useSafeMachine(machine) {
51
- // Define the state type
52
- interface SafeStateType {
53
- context: {
54
- channelId: string | null;
55
- currentUser: any;
56
- messages: any[];
57
- loading: boolean;
58
- error: string | null;
59
- title: string;
60
- channelMembers: any[];
61
- lastMessage: any;
62
- };
63
- value: string;
64
- matches?: (stateValue: string) => boolean;
65
- }
66
-
67
- // Initialize state with default values to prevent undefined errors
68
- const [state, setState] = useState<SafeStateType>({
69
- context: {
70
- channelId: null,
71
- currentUser: null,
72
- messages: [],
73
- loading: false,
74
- error: null,
75
- title: '',
76
- channelMembers: [],
77
- lastMessage: null,
78
- },
79
- value: BaseState.Idle,
80
- matches: (stateValue) => stateValue === BaseState.Idle,
81
- });
82
-
83
- // Implement a send function that updates state safely
84
- const send = useCallback((event) => {
85
- try {
86
- // Handle specific events manually
87
- if (event.type === Actions.INITIAL_CONTEXT) {
88
- setState((prev) => ({
89
- ...prev,
90
- context: {
91
- ...prev.context,
92
- channelId: event.data?.channelId || null,
93
- currentUser: event.data?.currentUser || null,
94
- loading: true,
95
- },
96
- value: BaseState.FetchingMessages,
97
- }));
98
- } else if (event.type === Actions.UPDATE_MESSAGES) {
99
- setState((prev) => {
100
- if (event.data?.messages) {
101
- // Handling bulk messages (initial load)
102
- const messages = event.data.messages;
103
-
104
- // If a direct last message was provided (from subscription), use it
105
- if (event.data?.directLastMessage) {
106
- console.log('Using direct last message from event:', {
107
- id: event.data.directLastMessage.id,
108
- message: event.data.directLastMessage.message?.substring(0, 20) + '...',
109
- });
110
-
111
- return {
112
- ...prev,
113
- context: {
114
- ...prev.context,
115
- messages: messages,
116
- lastMessage: event.data.directLastMessage,
117
- loading: false,
118
- },
119
- };
120
- }
121
-
122
- // Otherwise sort messages by date to find the most recent
123
- const sortedMessages = [...messages].sort(
124
- (a, b) =>
125
- new Date(b?.updatedAt || b?.createdAt).getTime() -
126
- new Date(a?.updatedAt || a?.createdAt).getTime(),
127
- );
128
-
129
- const newLastMessage = sortedMessages.length > 0 ? sortedMessages[0] : null;
130
-
131
- // Log the update for debugging
132
- if (newLastMessage) {
133
- console.log('Setting last message from bulk update:', {
134
- id: newLastMessage.id,
135
- message: newLastMessage.message?.substring(0, 20) + '...',
136
- date: newLastMessage.updatedAt || newLastMessage.createdAt,
137
- });
138
- }
139
-
140
- return {
141
- ...prev,
142
- context: {
143
- ...prev.context,
144
- messages: messages,
145
- lastMessage: newLastMessage,
146
- loading: false,
147
- },
148
- };
149
- } else if (event.data?.message) {
150
- // Handling single message (from subscription)
151
- const newMessage = event.data.message;
152
- const updatedMessages = [...prev.context.messages, newMessage];
153
-
154
- // Use the new message directly as the last message
155
- // This ensures immediate update on subscription
156
- console.log('Setting new message as lastMessage from subscription:', {
157
- id: newMessage.id,
158
- message: newMessage.message?.substring(0, 20) + '...',
159
- date: newMessage.createdAt || newMessage.updatedAt,
160
- });
161
-
162
- return {
163
- ...prev,
164
- context: {
165
- ...prev.context,
166
- messages: updatedMessages,
167
- lastMessage: newMessage, // Use the new message directly
168
- loading: false,
169
- },
170
- };
171
- }
172
- return prev;
173
- });
174
- } else if (event.type === Actions.SET_TITLE) {
175
- setState((prev) => ({
176
- ...prev,
177
- context: {
178
- ...prev.context,
179
- title: event.data?.title || '',
180
- },
181
- }));
182
- } else if (event.type === Actions.START_LOADING) {
183
- setState((prev) => ({
184
- ...prev,
185
- context: {
186
- ...prev.context,
187
- loading: true,
188
- },
189
- }));
190
- } else if (event.type === Actions.STOP_LOADING) {
191
- setState((prev) => ({
192
- ...prev,
193
- context: {
194
- ...prev.context,
195
- loading: false,
196
- },
197
- }));
198
- }
199
- } catch (error) {
200
- console.error('Error sending event to state machine:', error);
201
- }
202
- }, []);
203
-
204
- // Helper function to compute the last message from a list of messages
205
- const computeLastMessage = (messages) => {
206
- if (!messages || !messages.length) return null;
207
-
208
- // Always sort by dates in descending order (newest first)
209
- const sortedMessages = [...messages].sort((a, b) => {
210
- const dateA = new Date(a?.updatedAt || a?.createdAt).getTime();
211
- const dateB = new Date(b?.updatedAt || b?.createdAt).getTime();
212
- return dateB - dateA; // Newest first
213
- });
214
-
215
- // Return the most recent message
216
- const latestMsg = sortedMessages[0];
217
-
218
- // Debug log to see if we're selecting the right message
219
- if (latestMsg) {
220
- console.log('Selected most recent message:', {
221
- id: latestMsg.id,
222
- date: latestMsg.updatedAt || latestMsg.createdAt,
223
- text: latestMsg.message?.substring(0, 20) + '...',
224
- });
225
- }
226
-
227
- return latestMsg;
228
- };
229
-
230
- // Add a matches function that works with the current state value
231
- const stateWithMatches = useMemo(() => {
232
- return {
233
- ...state,
234
- matches: (stateValue) => {
235
- try {
236
- return state.value === stateValue;
237
- } catch (error) {
238
- console.error(`Error in matches function:`, error);
239
- return false;
240
- }
241
- },
242
- };
243
- }, [state]);
244
-
245
- // Return as a tuple to match useMachine API
246
- return [stateWithMatches, send] as const;
247
- }
248
-
249
- // LastMessage component definition
250
- const LastMessageComponent = ({ subscribeToNewMessages, title, lastMessage, channelId }) => {
251
- // Subscribe to new messages when component mounts
252
- React.useEffect(() => {
253
- // Subscribe and store the unsubscribe function
254
- const unsubscribe = subscribeToNewMessages();
255
- return () => {
256
- // Cleanup subscription on unmount
257
- if (unsubscribe && typeof unsubscribe === 'function') {
258
- unsubscribe();
259
- }
260
- };
261
- }, [channelId, subscribeToNewMessages]);
262
-
263
- // Debug output for component rendering
264
- React.useEffect(() => {
265
- console.log(`LastMessageComponent rendered for channel ${channelId}:`, {
266
- hasLastMessage: !!lastMessage,
267
- messageId: lastMessage?.id,
268
- messageText: lastMessage?.message?.substring(0, 20) + (lastMessage?.message?.length > 20 ? '...' : ''),
269
- date: lastMessage?.createdAt ? new Date(lastMessage.createdAt).toISOString() : 'none',
270
- hasFiles: lastMessage?.files?.data?.length > 0,
271
- });
272
- }, [lastMessage, channelId]);
273
-
274
- const count = 30;
275
- const channelTitle = title?.slice(0, count) + (title?.length > count ? '...' : '') || '';
276
-
277
- // Define message display text
278
- let displayMessage = 'No messages yet';
279
-
280
- if (lastMessage) {
281
- if (lastMessage.message && lastMessage.message.trim() !== '') {
282
- // Show text message
283
- displayMessage = lastMessage.message;
284
- } else if (lastMessage.files?.data?.length > 0) {
285
- // Show message with files
286
- displayMessage = '📎 File attachment';
287
- } else {
288
- // Default for empty message
289
- displayMessage = '(Empty message)';
290
- }
291
- }
292
-
293
- // Determine the date/time to display
294
- const displayDate = lastMessage?.createdAt
295
- ? createdAtText(lastMessage.createdAt)
296
- : lastMessage?.updatedAt
297
- ? createdAtText(lastMessage.updatedAt)
298
- : '';
299
-
300
- return (
301
- <HStack space={'sm'} className="flex-1 justify-between">
302
- <Box className="flex-[0.8]">
303
- <Text color={colors.gray[600]} className="text-base text-wrap flex-wrap font-semibold">
304
- {channelTitle}
305
- </Text>
306
- <Text color={colors.gray[600]} numberOfLines={1}>
307
- {displayMessage}
308
- </Text>
309
- </Box>
310
-
311
- <Box className="flex-[0.2]">
312
- <Text color={colors.gray[500]}>{displayDate}</Text>
313
- </Box>
314
- </HStack>
315
- );
316
- };
317
-
318
- /**
319
- * TODO:
320
- * - Get Reservation info: reservation date, status
321
- * - Add ability to get property information: name, logo
322
- */
323
- export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function DialogsListItem({
324
- currentUser,
325
- // users,
326
- selectedChannelId,
327
- channel,
328
- onOpen,
329
- forceRefresh,
330
- }) {
331
- // Create a ref to track if component is mounted
332
- const isMountedRef = useRef(true);
333
-
334
- // Define parentId early to avoid linter errors
335
- const parentId: any = null;
336
-
337
- // Use our safer custom implementation instead of the problematic useMachine
338
- const [state, send] = useSafeMachine(dialogsListItemXstate);
339
-
340
- // Define safe access functions
341
- const safeContext = useCallback(() => {
342
- try {
343
- return state?.context || {};
344
- } catch (error) {
345
- console.error('Error accessing state.context:', error);
346
- return {};
347
- }
348
- }, [state]);
349
-
350
- const safeContextProperty = useCallback(
351
- (property, defaultValue = null) => {
352
- try {
353
- return state?.context?.[property] ?? defaultValue;
354
- } catch (error) {
355
- console.error(`Error accessing state.context.${property}:`, error);
356
- return defaultValue;
357
- }
358
- },
359
- [state],
360
- );
361
-
362
- const safeSend = useCallback(
363
- (event) => {
364
- try {
365
- send(event);
366
- } catch (error) {
367
- console.error('Error sending event to state machine:', error, event);
368
- }
369
- },
370
- [send],
371
- );
372
-
373
- // Query hooks for fetching messages
374
- const {
375
- data: messagesQuery,
376
- loading: messageLoading,
377
- refetch: refetchMessages,
378
- subscribeToMore,
379
- } = useMessagesQuery({
380
- variables: {
381
- channelId: channel?.id?.toString(),
382
- parentId: parentId,
383
- limit: 10,
384
- },
385
- fetchPolicy: 'cache-and-network', // Use cache first, then network
386
- refetchWritePolicy: 'overwrite', // Ensure refetches overwrite existing data
387
- nextFetchPolicy: 'network-only', // Force subsequent fetches to use network
388
- onCompleted: (data) => {
389
- console.log(
390
- `Completed message query for channel ${channel?.id}:`,
391
- data?.messages?.data?.length ? 'Has messages' : 'No messages',
392
- );
393
- },
394
- onError: (error) => {
395
- console.error(`Error fetching messages for channel ${channel?.id}:`, error);
396
- },
397
- });
398
-
399
- const {
400
- data: newMessage,
401
- loading: newMsgLoading,
402
- error: newMsgError,
403
- }: any = useOnChatMessageAddedSubscription({
404
- variables: {
405
- channelId: channel?.id?.toString(),
406
- },
407
- });
408
-
409
- // Initialize the state machine with context data
410
- useEffect(() => {
411
- if (channel?.id) {
412
- safeSend({
413
- type: Actions.INITIAL_CONTEXT,
414
- data: { channelId: channel.id, currentUser },
415
- });
416
- }
417
-
418
- // Cleanup function
419
- return () => {
420
- isMountedRef.current = false;
421
- };
422
- }, [channel?.id, currentUser, safeSend]);
423
-
424
- // Function to force refresh the component state
425
- const refreshDialogState = useCallback(() => {
426
- if (channel?.id && isMountedRef.current) {
427
- console.log('Forcing dialog state refresh for channel:', channel?.id);
428
- // First ensure we're in a loading state to trigger UI updates
429
- safeSend({ type: Actions.START_LOADING });
430
-
431
- // Set up the options for the query to force network fetch
432
- const options = {
433
- channelId: channel?.id?.toString(),
434
- parentId: parentId,
435
- limit: 10,
436
- };
437
-
438
- // Refetch messages from server
439
- refetchMessages(options)
440
- .then((result) => {
441
- // Update the state with fresh data
442
- if (result.data?.messages?.data && isMountedRef.current) {
443
- console.log(
444
- `Refreshed ${result.data.messages.data.length} messages for channel ${channel?.id}`,
445
- );
446
-
447
- // Get the most recent message for debug comparison
448
- const sortedMessages = [...result.data.messages.data].sort(
449
- (a, b) =>
450
- new Date(b?.updatedAt || b?.createdAt).getTime() -
451
- new Date(a?.updatedAt || a?.createdAt).getTime(),
452
- );
453
-
454
- const latestMessage = sortedMessages.length > 0 ? sortedMessages[0] : null;
455
-
456
- if (latestMessage) {
457
- console.log('Latest message after refresh:', {
458
- id: latestMessage.id,
459
- message: latestMessage.message?.substring(0, 20) + '...',
460
- date: latestMessage.createdAt || latestMessage.updatedAt,
461
- });
462
- }
463
-
464
- safeSend({
465
- type: Actions.UPDATE_MESSAGES,
466
- data: { messages: result.data.messages.data },
467
- });
468
- }
469
- safeSend({ type: Actions.STOP_LOADING });
470
- })
471
- .catch((err) => {
472
- console.error('Error refreshing dialog state:', err);
473
- safeSend({ type: Actions.STOP_LOADING });
474
- });
475
- }
476
- }, [channel?.id, refetchMessages, safeSend, isMountedRef, parentId]);
477
-
478
- // Track if this is the first time the component renders
479
- const firstRenderRef = useRef(true);
480
-
481
- // Track the last time we refreshed to prevent too frequent refreshes
482
- const lastRefreshRef = useRef(0);
483
-
484
- // Fix messages not refreshing when coming back from detail screen
485
- useFocusEffect(
486
- React.useCallback(() => {
487
- if (!channel?.id) return;
488
-
489
- console.log('DialogsListItem focused for channel:', channel?.id);
490
-
491
- // Skip refresh on first render as it's handled by other effects
492
- if (firstRenderRef.current) {
493
- console.log('Skipping initial focus refresh for channel:', channel?.id);
494
- firstRenderRef.current = false;
495
- return;
496
- }
497
-
498
- // Always force a refetch when coming back to this screen
499
- console.log('FOCUS EFFECT: Force refetching messages on navigation back for channel:', channel?.id);
500
-
501
- // Show loading state
502
- safeSend({ type: Actions.START_LOADING });
503
-
504
- // Use a direct refetch with network-only policy to force fresh data
505
- const fetchFreshData = async () => {
506
- try {
507
- // Set up the options for the query to force network fetch
508
- const options = {
509
- channelId: channel?.id?.toString(),
510
- parentId: parentId,
511
- limit: 10,
512
- };
513
-
514
- // Force a network-only fetch by using refetch without extra options
515
- // Apollo will use the parent query's fetch policy which we've set to network-only
516
- const result = await refetchMessages(options);
517
-
518
- // Log the refreshed data
519
- console.log(
520
- `FOCUS EFFECT: Refetched ${result?.data?.messages?.data?.length || 0} messages for channel ${
521
- channel?.id
522
- }`,
523
- );
524
-
525
- if (result?.data?.messages?.data && isMountedRef.current) {
526
- // Compare with current state to check if we're getting fresh data
527
- const currentMessages = safeContextProperty('messages', []);
528
- const fetchedMessages = result.data.messages.data;
529
-
530
- // Log comparison to see if we got new data
531
- console.log('Data comparison:', {
532
- currentCount: currentMessages.length,
533
- fetchedCount: fetchedMessages.length,
534
- isDifferent: JSON.stringify(currentMessages) !== JSON.stringify(fetchedMessages),
535
- });
536
-
537
- // Update state with fresh data
538
- safeSend({
539
- type: Actions.UPDATE_MESSAGES,
540
- data: { messages: fetchedMessages },
541
- });
542
- }
543
- } catch (error) {
544
- console.error('Error refetching messages on focus:', error);
545
- } finally {
546
- if (isMountedRef.current) {
547
- safeSend({ type: Actions.STOP_LOADING });
548
- }
549
- }
550
- };
551
-
552
- // Execute fetch
553
- fetchFreshData();
554
-
555
- return () => {
556
- // Cleanup function when unfocused
557
- };
558
- }, [channel?.id, refetchMessages, safeSend, parentId, isMountedRef]),
559
- );
560
-
561
- // Update messages in state when query data is available
562
- React.useEffect(() => {
563
- if (messagesQuery?.messages?.data && isMountedRef.current) {
564
- const messages = messagesQuery.messages.data;
565
-
566
- // Count messages with files
567
- const messagesWithFiles = messages.filter((msg) => msg?.files?.data?.length > 0);
568
-
569
- console.log(
570
- `Updating messages for channel ${channel?.id}, count:`,
571
- messages.length,
572
- `(${messagesWithFiles.length} with files)`,
573
- );
574
-
575
- if (messages.length > 0) {
576
- // Log the first message for debugging
577
- console.log('First message sample:', {
578
- id: messages[0].id,
579
- message: messages[0].message?.substring(0, 20) + '...',
580
- createdAt: messages[0].createdAt,
581
- hasFiles: messages[0]?.files?.data?.length > 0,
582
- });
583
- }
584
-
585
- safeSend({
586
- type: Actions.UPDATE_MESSAGES,
587
- data: { messages },
588
- });
589
- }
590
- }, [messagesQuery, safeSend, channel?.id, isMountedRef]);
591
-
592
- // Force a refresh on initial mount to ensure we have fresh data
593
- useEffect(() => {
594
- if (channel?.id && isMountedRef.current) {
595
- const timer = setTimeout(() => {
596
- if (isMountedRef.current) {
597
- console.log('Initial data refresh for channel:', channel.id);
598
- refreshDialogState();
599
- }
600
- }, 100);
601
-
602
- return () => {
603
- clearTimeout(timer);
604
- };
605
- }
606
- }, [channel?.id, refreshDialogState, isMountedRef]);
607
-
608
- // Use forceRefresh prop to trigger immediate refresh
609
- useEffect(() => {
610
- if (forceRefresh && channel?.id && isMountedRef.current) {
611
- console.log(`Force refreshing messages for channel ${channel?.id} due to forceRefresh prop`);
612
-
613
- // Set a very slight delay to ensure component is fully mounted
614
- const timer = setTimeout(() => {
615
- if (isMountedRef.current && refetchMessages) {
616
- // Force a full network-only fetch
617
- refetchMessages({
618
- channelId: channel?.id?.toString(),
619
- parentId: parentId,
620
- limit: 10,
621
- })
622
- .then((result) => {
623
- if (result?.data?.messages?.data && isMountedRef.current) {
624
- console.log(
625
- `Force refresh completed for channel ${channel?.id} with ${result.data.messages.data.length} messages`,
626
- );
627
-
628
- // Update messages in state
629
- safeSend({
630
- type: Actions.UPDATE_MESSAGES,
631
- data: { messages: result.data.messages.data },
632
- });
633
- }
634
- })
635
- .catch((error) => {
636
- console.error(`Error force refreshing channel ${channel?.id}:`, error);
637
- });
638
- }
639
- }, 50);
640
-
641
- return () => clearTimeout(timer);
642
- }
643
- }, [channel?.id, forceRefresh, isMountedRef, refetchMessages, safeSend, parentId]);
644
-
645
- const channelMembers = useMemo(
646
- () =>
647
- channel?.members
648
- ?.filter((ch: any) => ch?.user?.id != currentUser?.id && ch?.user?.__typename == 'UserAccount')
649
- ?.map((m: any) => m?.user) ?? null,
650
- [currentUser, channel],
651
- );
652
-
653
- // Set title in state when channel members change
654
- useEffect(() => {
655
- if (channelMembers && isMountedRef.current) {
656
- const titleString =
657
- channelMembers
658
- ?.map((u: any) => u?.givenName + ' ' + (u?.familyName ?? ''))
659
- ?.filter((mu: any) => mu)
660
- ?.join(', ') ?? '';
661
-
662
- safeSend({
663
- type: Actions.SET_TITLE,
664
- data: { title: titleString },
665
- });
666
- }
667
- }, [channelMembers, safeSend, isMountedRef]);
668
-
669
- // Compute title with proper truncation
670
- const title = useMemo(() => {
671
- const titleString = safeContextProperty('title', '');
672
- const length = 30;
673
- return titleString.length > length ? titleString.substring(0, length - 3) + '...' : titleString;
674
- }, [safeContextProperty]);
675
-
676
- // Get the last message directly from state context instead of computing it
677
- const lastMessage = useMemo(() => {
678
- const message = safeContextProperty('lastMessage', null);
679
- // Add dependency on the messages array length to ensure re-render on new messages
680
- return message;
681
- }, [safeContextProperty, state.context?.messages?.length]);
682
-
683
- // Debug output for the component
684
- useEffect(() => {
685
- console.log(`DialogsListItem for channel ${channel?.id}: `, {
686
- hasLastMessage: !!lastMessage,
687
- message: lastMessage?.message?.substring(0, 20) + (lastMessage?.message?.length > 20 ? '...' : ''),
688
- messagesCount: safeContextProperty('messages', []).length,
689
- });
690
- }, [channel?.id, lastMessage, safeContextProperty]);
691
-
692
- return (
693
- <Pressable
694
- onPress={() => onOpen(channel?.id, title)}
695
- className="flex-1 border-gray-200 rounded-md dark:border-gray-600 dark:bg-gray-700"
696
- style={{ borderBottomWidth: 1, borderColor: '#e5e7eb', marginVertical: 0, paddingHorizontal: 10 }}
697
- >
698
- <HStack space={'md'} className="flex-1 w-[100%] py-3 items-center">
699
- <Box className="flex-[0.1] items-start pl-3">
700
- <AvatarGroup>
701
- {channelMembers &&
702
- channelMembers?.length > 0 &&
703
- channelMembers?.slice(0, 1)?.map((ch: any, i: Number) => (
704
- <Avatar
705
- key={'dialogs-list-' + i}
706
- size={'sm'}
707
- className={`bg-transparent top-[${i == 1 ? '4' : '0'}] right-[${
708
- i == 1 ? '-2' : '0'
709
- }] z-[${i == 1 ? 5 : 1}]`}
710
- >
711
- <AvatarFallbackText>{startCase(ch?.username?.charAt(0))}</AvatarFallbackText>
712
-
713
- {channelMembers?.length > 1 && (
714
- <AvatarBadge
715
- style={{
716
- width: '100%',
717
- height: '100%',
718
- backgroundColor: '#e5e7eb',
719
- borderRadius: 5,
720
- }}
721
- className="items-center justify-center bg-gray-200 rounded-md"
722
- >
723
- <Text style={{ fontSize: 12, fontWeight: 'bold', color: '#000' }}>
724
- {channelMembers?.length}
725
- </Text>
726
- </AvatarBadge>
727
- )}
728
- {channelMembers?.length == 1 && (
729
- <>
730
- <AvatarImage
731
- alt="user image"
732
- style={{ borderRadius: 6, borderWidth: 2, borderColor: '#fff' }}
733
- source={{
734
- uri: ch?.picture,
735
- }}
736
- />
737
- <AvatarBadge style={{ width: 10, height: 10 }} />
738
- </>
739
- )}
740
- </Avatar>
741
- ))}
742
- </AvatarGroup>
743
- </Box>
744
- <Box className="flex-1">
745
- <LastMessageComponent
746
- key={`last-msg-${lastMessage?.id || 'none'}-${safeContextProperty('messages', []).length}`}
747
- title={title}
748
- lastMessage={lastMessage}
749
- channelId={channel?.id}
750
- subscribeToNewMessages={() =>
751
- subscribeToMore({
752
- document: CHAT_MESSAGE_ADDED,
753
- variables: {
754
- channelId: channel.id?.toString(),
755
- },
756
- updateQuery: (prev, { subscriptionData }: any) => {
757
- if (!subscriptionData.data) return prev;
758
- const newMessage: any = subscriptionData?.data?.chatMessageAdded;
759
-
760
- // Add debug info for the new message
761
- console.log('New message added (subscription):', {
762
- channelId: channel.id,
763
- messageId: newMessage.id,
764
- message: newMessage.message?.substring(0, 20) + '...',
765
- hasFiles: newMessage?.files?.data?.length > 0,
766
- filesCount: newMessage?.files?.data?.length || 0,
767
- timestamp: newMessage.createdAt || newMessage.updatedAt,
768
- });
769
-
770
- // Force message update (double approach to ensure it updates)
771
- if (isMountedRef.current) {
772
- // Update through state machine directly
773
- console.log('Updating messages with new subscription message');
774
-
775
- // First, update the messages array with the new message
776
- const currentMessages = safeContextProperty('messages', []);
777
- const updatedMessages = [...currentMessages, newMessage];
778
-
779
- // Then directly set the new message as the last message
780
- // This ensures the UI updates immediately
781
- safeSend({
782
- type: Actions.UPDATE_MESSAGES,
783
- data: {
784
- messages: updatedMessages,
785
- // Directly set this message as the last message
786
- directLastMessage: newMessage,
787
- },
788
- });
789
-
790
- // No need for additional refresh - the direct update is enough
791
- // This prevents unnecessary network requests and UI flicker
792
- }
793
-
794
- // Update the Apollo cache
795
- const existingMessages: any = prev?.messages;
796
- const previousData = existingMessages?.data
797
- ? [...existingMessages.data, newMessage]
798
- : [];
799
- const totalMsgCount = existingMessages?.totalCount + 1;
800
- const merged = {
801
- ...prev,
802
- messages: {
803
- ...existingMessages,
804
- data: previousData,
805
- totalCount: totalMsgCount,
806
- },
807
- };
808
- return merged;
809
- },
810
- })
811
- }
812
- />
813
- </Box>
814
- </HStack>
815
- </Pressable>
816
- );
817
- };
818
-
819
- export const DialogsListItem = React.memo(DialogsListItemComponent);