@messenger-box/platform-mobile 10.0.3-alpha.19 → 10.0.3-alpha.22

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 (26) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/screens/inbox/components/CachedImage/index.js +0 -19
  3. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  4. package/lib/screens/inbox/components/DialogsListItem.js +423 -50
  5. package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
  6. package/lib/screens/inbox/components/ServiceDialogsListItem.js +375 -51
  7. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
  8. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +175 -0
  9. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +1 -0
  10. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +191 -0
  11. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +1 -0
  12. package/lib/screens/inbox/containers/Dialogs.js +536 -66
  13. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  14. package/lib/screens/inbox/containers/ThreadConversationView.js +95 -23
  15. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  16. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +211 -0
  17. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +1 -0
  18. package/package.json +2 -2
  19. package/src/screens/inbox/components/CachedImage/index.tsx +9 -9
  20. package/src/screens/inbox/components/DialogsListItem.tsx +624 -107
  21. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +506 -114
  22. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +35 -17
  23. package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
  24. package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
  25. package/src/screens/inbox/containers/Dialogs.tsx +711 -169
  26. package/src/screens/inbox/containers/ThreadConversationView.tsx +151 -35
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useMemo, useState, useCallback, useRef, useEffect } from 'react';
2
2
  import {
3
3
  Text,
4
4
  Image,
@@ -23,6 +23,7 @@ import {
23
23
  } from 'common/graphql';
24
24
  import { startCase } from 'lodash-es';
25
25
  import colors from 'tailwindcss/colors';
26
+ import { dialogsListItemXstate, Actions, BaseState } from './workflow/dialogs-list-item-xstate';
26
27
 
27
28
  const createdAtText = (value: string) => {
28
29
  if (!value) return '';
@@ -42,8 +43,278 @@ export interface IDialogListItemProps {
42
43
  selectedChannelId?: any;
43
44
  channel?: any;
44
45
  onOpen: (id: any, title: any) => void;
46
+ forceRefresh?: boolean;
45
47
  }
46
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
+
47
318
  /**
48
319
  * TODO:
49
320
  * - Get Reservation info: reservation date, status
@@ -55,9 +326,51 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
55
326
  selectedChannelId,
56
327
  channel,
57
328
  onOpen,
329
+ forceRefresh,
58
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
59
335
  const parentId: any = null;
60
- const [messages, setMessages] = useState<any>([]);
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
61
374
  const {
62
375
  data: messagesQuery,
63
376
  loading: messageLoading,
@@ -68,14 +381,19 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
68
381
  channelId: channel?.id?.toString(),
69
382
  parentId: parentId,
70
383
  limit: 10,
71
- // sort: {
72
- // key: 'updatedAt',
73
- // value: SortEnum.Desc,
74
- // },
75
- //limit: 25,
76
384
  },
77
- fetchPolicy: 'cache-and-network',
78
- refetchWritePolicy: 'merge',
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
+ },
79
397
  });
80
398
 
81
399
  const {
@@ -88,53 +406,241 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
88
406
  },
89
407
  });
90
408
 
91
- useFocusEffect(
92
- React.useCallback(() => {
93
- // Do something when the screen is focused
94
- refetchMessages({
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 = {
95
433
  channelId: channel?.id?.toString(),
96
434
  parentId: parentId,
97
435
  limit: 10,
98
- // sort: {
99
- // key: 'updatedAt',
100
- // value: SortEnum.Desc,
101
- // },
102
- //limit: 25
103
- });
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();
104
554
 
105
555
  return () => {
106
- // Do something when the screen is unfocused
107
- // Useful for cleanup functions
556
+ // Cleanup function when unfocused
108
557
  };
109
- }, []),
558
+ }, [channel?.id, refetchMessages, safeSend, parentId, isMountedRef]),
110
559
  );
111
560
 
561
+ // Update messages in state when query data is available
112
562
  React.useEffect(() => {
113
- if (messagesQuery) {
114
- if (messagesQuery?.messages?.data?.length) {
115
- const msg = messagesQuery?.messages?.data;
116
- setMessages((pre: any[]) => [...pre, ...msg]);
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
+ });
117
583
  }
584
+
585
+ safeSend({
586
+ type: Actions.UPDATE_MESSAGES,
587
+ data: { messages },
588
+ });
118
589
  }
119
- }, [messagesQuery]);
590
+ }, [messagesQuery, safeSend, channel?.id, isMountedRef]);
120
591
 
121
- const lastMessage = useMemo(() => {
122
- if (!messages?.length) {
123
- return null;
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
+ };
124
605
  }
125
- // const { data } = messagesQuery.messages;
126
- const data = messages;
127
- const filteredData: any = data?.filter((p: any) => p?.message !== '');
128
- // return filteredData[0];
129
- let filteredLastMessage =
130
- filteredData && filteredData?.length
131
- ? filteredData?.reduce((a, b) => {
132
- return new Date(a?.updatedAt) > new Date(b?.updatedAt) ? a : b;
133
- }, []) ?? null
134
- : null;
135
- return filteredLastMessage;
136
- //return data[data.length - 1];
137
- }, [messages]);
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]);
138
644
 
139
645
  const channelMembers = useMemo(
140
646
  () =>
@@ -144,21 +650,49 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
144
650
  [currentUser, channel],
145
651
  );
146
652
 
147
- const title = useMemo(() => {
148
- const titleString =
149
- channelMembers
150
- ?.map((u: any) => u?.givenName + ' ' + (u?.familyName ?? ''))
151
- ?.filter((mu: any) => mu)
152
- ?.join(', ') ?? '';
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(', ') ?? '';
153
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', '');
154
672
  const length = 30;
155
673
  return titleString.length > length ? titleString.substring(0, length - 3) + '...' : titleString;
156
- }, [channelMembers]);
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]);
157
691
 
158
692
  return (
159
693
  <Pressable
160
- onPress={() => channel?.id !== selectedChannelId && onOpen(channel?.id, title)}
161
- className="flex-1 border-gray-200 rounded-md dark:border-gray-600 dark:bg-gray-700"
694
+ onPress={() => onOpen(channel?.id, title)}
695
+ className="flex-1 border-gray-200 rounded-md dark:border-gray-600 dark:bg-gray-700"
162
696
  style={{ borderBottomWidth: 1, borderColor: '#e5e7eb', marginVertical: 0, paddingHorizontal: 10 }}
163
697
  >
164
698
  <HStack space={'md'} className="flex-1 w-[100%] py-3 items-center">
@@ -207,22 +741,9 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
207
741
  ))}
208
742
  </AvatarGroup>
209
743
  </Box>
210
- <Box className="flex-1 bg-gray-400">
211
- {/* <HStack space={1} flex={1} direction={'row'} justifyContent={'center'} alignItems={'center'}>
212
- <Box flex={0.8}>
213
- <Text color="gray.600" fontSize="lg" flexWrap={'wrap'} fontWeight="semibold">
214
- {title}
215
- </Text>
216
- <Text color="gray.600" noOfLines={1}>
217
- {lastMessage?.message ?? ''}
218
- </Text>
219
- </Box>
220
-
221
- <Box flex={0.2}>
222
- <Text color="gray.500">{lastMessage ? createdAtText(lastMessage?.createdAt) : ''}</Text>
223
- </Box>
224
- </HStack> */}
744
+ <Box className="flex-1">
225
745
  <LastMessageComponent
746
+ key={`last-msg-${lastMessage?.id || 'none'}-${safeContextProperty('messages', []).length}`}
226
747
  title={title}
227
748
  lastMessage={lastMessage}
228
749
  channelId={channel?.id}
@@ -235,6 +756,42 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
235
756
  updateQuery: (prev, { subscriptionData }: any) => {
236
757
  if (!subscriptionData.data) return prev;
237
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
238
795
  const existingMessages: any = prev?.messages;
239
796
  const previousData = existingMessages?.data
240
797
  ? [...existingMessages.data, newMessage]
@@ -253,50 +810,10 @@ export const DialogsListItemComponent: React.FC<IDialogListItemProps> = function
253
810
  })
254
811
  }
255
812
  />
256
- {/* <Text
257
- flex={1}
258
- color="gray.600"
259
- p={0}
260
- m={0}
261
- w={'100%'}
262
- justifyContent={''}
263
- fontSize="lg"
264
- fontWeight="semibold"
265
- >
266
- {title}
267
- </Text> */}
268
- {/* <Text flex={0.1} color="gray.600">
269
- {lastMessage?.message ?? ''}
270
- </Text> */}
271
813
  </Box>
272
- {/* <Text flex={0.2} color="gray.500">
273
- {lastMessage ? createdAtText(lastMessage?.createdAt) : ''}
274
- </Text> */}
275
814
  </HStack>
276
815
  </Pressable>
277
816
  );
278
817
  };
279
818
 
280
- const LastMessageComponent = ({ subscribeToNewMessages, title, lastMessage, channelId }) => {
281
- React.useEffect(() => subscribeToNewMessages(), [channelId]);
282
- const count = 30;
283
- const channelTitle = title.slice(0, count) + (title.length > count ? '...' : '');
284
- return (
285
- <HStack space={'sm'} className="flex-1 justify-between">
286
- <Box className="flex-[0.8]">
287
- <Text color={colors.gray[600]} className="text-base text-wrap flex-wrap font-semibold">
288
- {channelTitle}
289
- </Text>
290
- <Text color={colors.gray[600]} numberOfLines={1}>
291
- {lastMessage?.message ?? ''}
292
- </Text>
293
- </Box>
294
-
295
- <Box className="flex-[0.2]">
296
- <Text color={colors.gray[500]}>{lastMessage ? createdAtText(lastMessage?.createdAt) : ''}</Text>
297
- </Box>
298
- </HStack>
299
- );
300
- };
301
-
302
819
  export const DialogsListItem = React.memo(DialogsListItemComponent);