@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.
- package/CHANGELOG.md +8 -0
- package/lib/screens/inbox/components/CachedImage/index.js +0 -19
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/DialogsListItem.js +423 -50
- package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +375 -51
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +175 -0
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +1 -0
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +191 -0
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +1 -0
- package/lib/screens/inbox/containers/Dialogs.js +536 -66
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +95 -23
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +211 -0
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +1 -0
- package/package.json +2 -2
- package/src/screens/inbox/components/CachedImage/index.tsx +9 -9
- package/src/screens/inbox/components/DialogsListItem.tsx +624 -107
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +506 -114
- package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +35 -17
- package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
- package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
- package/src/screens/inbox/containers/Dialogs.tsx +711 -169
- 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
|
-
|
|
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: '
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
}, [
|
|
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={() =>
|
|
161
|
-
className="flex-1 border-gray-200 rounded-md
|
|
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
|
|
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);
|