@messenger-box/platform-mobile 10.0.3-alpha.36 → 10.0.3-alpha.38
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 +125 -93
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/DialogsListItem.js +74 -288
- package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +184 -415
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +0 -2
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/containers/ConversationView.js +621 -951
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +212 -628
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +409 -1364
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/package.json +3 -3
- package/src/screens/inbox/components/CachedImage/index.tsx +191 -140
- package/src/screens/inbox/components/DialogsListItem.tsx +122 -386
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +69 -377
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +2 -4
- package/src/screens/inbox/containers/ConversationView.tsx +793 -1064
- package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
- package/src/screens/inbox/containers/Dialogs.tsx +301 -763
- package/src/screens/inbox/containers/ThreadConversationView.tsx +661 -1887
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +0 -175
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +0 -1
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +0 -191
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +0 -1
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js +0 -380
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +0 -1
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +0 -211
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +0 -1
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +0 -438
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +0 -1
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Image,
|
|
12
12
|
Spinner,
|
|
13
13
|
Text,
|
|
14
|
+
Skeleton,
|
|
14
15
|
} from '@admin-layout/gluestack-ui-mobile';
|
|
15
16
|
import { Platform, TouchableHighlight, SafeAreaView, View } from 'react-native';
|
|
16
17
|
import { useFocusEffect, useIsFocused, useNavigation } from '@react-navigation/native';
|
|
@@ -21,7 +22,7 @@ import * as ImagePicker from 'expo-image-picker';
|
|
|
21
22
|
import { encode as atob } from 'base-64';
|
|
22
23
|
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
|
|
23
24
|
import { Actions, GiftedChat, IMessage, MessageText, Send, Composer, InputToolbar } from 'react-native-gifted-chat';
|
|
24
|
-
import { PreDefinedRole, RoomType, IExpoNotificationData, IFileInfo } from 'common';
|
|
25
|
+
import { PreDefinedRole, RoomType, IExpoNotificationData, IFileInfo, FileRefType, PostTypeEnum } from 'common';
|
|
25
26
|
import {
|
|
26
27
|
OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
|
|
27
28
|
useMessagesQuery,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
useSendMessagesMutation,
|
|
30
31
|
useViewChannelDetailQuery,
|
|
31
32
|
useAddDirectChannelMutation,
|
|
33
|
+
MessagesDocument,
|
|
32
34
|
} from 'common/graphql';
|
|
33
35
|
import { useUploadFilesNative } from '@messenger-box/platform-client';
|
|
34
36
|
import { objectId } from '@messenger-box/core';
|
|
@@ -37,13 +39,8 @@ import { format, isToday, isYesterday } from 'date-fns';
|
|
|
37
39
|
import { ImageViewerModal, SlackMessage } from '../components/SlackMessageContainer';
|
|
38
40
|
import CachedImage from '../components/CachedImage';
|
|
39
41
|
import { config } from '../config';
|
|
40
|
-
import {
|
|
41
|
-
conversationXstate,
|
|
42
|
-
Actions as ConversationActions,
|
|
43
|
-
BaseState,
|
|
44
|
-
MainState,
|
|
45
|
-
} from './workflow/conversation-xstate';
|
|
46
42
|
import colors from 'tailwindcss/colors';
|
|
43
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
47
44
|
|
|
48
45
|
// Define an extended interface for ImagePickerAsset with url property
|
|
49
46
|
interface ExtendedImagePickerAsset extends ImagePicker.ImagePickerAsset {
|
|
@@ -90,516 +87,268 @@ export interface AlertMessageAttachmentsInterface {
|
|
|
90
87
|
};
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
channelId: any;
|
|
99
|
-
channelMessages: any[];
|
|
100
|
-
totalCount: number;
|
|
101
|
-
skip: number;
|
|
102
|
-
loading: boolean;
|
|
103
|
-
loadingOldMessages: boolean;
|
|
104
|
-
error: any;
|
|
105
|
-
selectedImage: string;
|
|
106
|
-
files: any[];
|
|
107
|
-
images: any[];
|
|
108
|
-
messageText: string;
|
|
109
|
-
imageLoading: boolean;
|
|
110
|
-
};
|
|
111
|
-
value: string;
|
|
112
|
-
matches?: (stateValue: string) => boolean;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Initialize with default state
|
|
116
|
-
const [state, setState] = useState<SafeStateType>({
|
|
117
|
-
context: {
|
|
118
|
-
channelId: null,
|
|
119
|
-
channelMessages: [],
|
|
120
|
-
totalCount: 0,
|
|
121
|
-
skip: 0,
|
|
122
|
-
loading: false,
|
|
123
|
-
loadingOldMessages: false,
|
|
124
|
-
error: null,
|
|
125
|
-
selectedImage: '',
|
|
126
|
-
files: [],
|
|
127
|
-
images: [],
|
|
128
|
-
messageText: '',
|
|
129
|
-
imageLoading: false,
|
|
130
|
-
},
|
|
131
|
-
value: 'idle',
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Create a safe send function
|
|
135
|
-
const send = useCallback((event) => {
|
|
136
|
-
try {
|
|
137
|
-
// Log event for debugging
|
|
138
|
-
console.log('Event received:', event.type);
|
|
139
|
-
|
|
140
|
-
// Handle specific events manually
|
|
141
|
-
if (event.type === ConversationActions.INITIAL_CONTEXT) {
|
|
142
|
-
setState((prev) => ({
|
|
143
|
-
...prev,
|
|
144
|
-
context: {
|
|
145
|
-
...prev.context,
|
|
146
|
-
channelId: event.data?.channelId || null,
|
|
147
|
-
},
|
|
148
|
-
value: BaseState.FetchMessages,
|
|
149
|
-
}));
|
|
150
|
-
} else if (event.type === ConversationActions.SET_CHANNEL_MESSAGES) {
|
|
151
|
-
setState((prev) => ({
|
|
152
|
-
...prev,
|
|
153
|
-
context: {
|
|
154
|
-
...prev.context,
|
|
155
|
-
channelMessages: event.data?.messages || [],
|
|
156
|
-
totalCount: event.data?.totalCount || 0,
|
|
157
|
-
loading: false,
|
|
158
|
-
loadingOldMessages: false,
|
|
159
|
-
},
|
|
160
|
-
value: 'active',
|
|
161
|
-
}));
|
|
162
|
-
} else if (event.type === ConversationActions.CLEAR_MESSAGES) {
|
|
163
|
-
setState((prev) => ({
|
|
164
|
-
...prev,
|
|
165
|
-
context: {
|
|
166
|
-
...prev.context,
|
|
167
|
-
channelMessages: [],
|
|
168
|
-
totalCount: 0,
|
|
169
|
-
},
|
|
170
|
-
}));
|
|
171
|
-
} else if (event.type === ConversationActions.SET_MESSAGE_TEXT) {
|
|
172
|
-
setState((prev) => ({
|
|
173
|
-
...prev,
|
|
174
|
-
context: {
|
|
175
|
-
...prev.context,
|
|
176
|
-
messageText: event.data?.messageText || '',
|
|
177
|
-
},
|
|
178
|
-
}));
|
|
179
|
-
} else if (event.type === ConversationActions.FETCH_MORE_MESSAGES) {
|
|
180
|
-
setState((prev) => ({
|
|
181
|
-
...prev,
|
|
182
|
-
context: {
|
|
183
|
-
...prev.context,
|
|
184
|
-
loadingOldMessages: true,
|
|
185
|
-
},
|
|
186
|
-
value: MainState.FetchMoreMessages,
|
|
187
|
-
}));
|
|
188
|
-
} else if (event.type === ConversationActions.SET_IMAGE) {
|
|
189
|
-
setState((prev) => ({
|
|
190
|
-
...prev,
|
|
191
|
-
context: {
|
|
192
|
-
...prev.context,
|
|
193
|
-
selectedImage: event.data?.image || '',
|
|
194
|
-
images: event.data?.images || [],
|
|
195
|
-
imageLoading: false,
|
|
196
|
-
},
|
|
197
|
-
}));
|
|
198
|
-
} else if (event.type === ConversationActions.CLEAR_IMAGE) {
|
|
199
|
-
setState((prev) => ({
|
|
200
|
-
...prev,
|
|
201
|
-
context: {
|
|
202
|
-
...prev.context,
|
|
203
|
-
selectedImage: '',
|
|
204
|
-
images: [],
|
|
205
|
-
},
|
|
206
|
-
}));
|
|
207
|
-
} else if (event.type === ConversationActions.START_LOADING) {
|
|
208
|
-
setState((prev) => ({
|
|
209
|
-
...prev,
|
|
210
|
-
context: {
|
|
211
|
-
...prev.context,
|
|
212
|
-
loading: true,
|
|
213
|
-
},
|
|
214
|
-
}));
|
|
215
|
-
} else if (event.type === ConversationActions.STOP_LOADING) {
|
|
216
|
-
setState((prev) => ({
|
|
217
|
-
...prev,
|
|
218
|
-
context: {
|
|
219
|
-
...prev.context,
|
|
220
|
-
loading: false,
|
|
221
|
-
},
|
|
222
|
-
}));
|
|
223
|
-
} else if (event.type === ConversationActions.SEND_MESSAGE) {
|
|
224
|
-
setState((prev) => ({
|
|
225
|
-
...prev,
|
|
226
|
-
context: {
|
|
227
|
-
...prev.context,
|
|
228
|
-
loading: true,
|
|
229
|
-
},
|
|
230
|
-
value: MainState.SendMessage,
|
|
231
|
-
}));
|
|
232
|
-
} else if (event.type === ConversationActions.SEND_MESSAGE_WITH_FILE) {
|
|
233
|
-
setState((prev) => ({
|
|
234
|
-
...prev,
|
|
235
|
-
context: {
|
|
236
|
-
...prev.context,
|
|
237
|
-
loading: true,
|
|
238
|
-
},
|
|
239
|
-
value: MainState.SendMessageWithFile,
|
|
240
|
-
}));
|
|
241
|
-
} else if (event.type === ConversationActions.CREATE_DIRECT_CHANNEL) {
|
|
242
|
-
setState((prev) => ({
|
|
243
|
-
...prev,
|
|
244
|
-
context: {
|
|
245
|
-
...prev.context,
|
|
246
|
-
loading: true,
|
|
247
|
-
},
|
|
248
|
-
value: MainState.CreateDirectChannel,
|
|
249
|
-
}));
|
|
250
|
-
} else if (event.type === 'SEND_MESSAGE_SUCCESS' || event.type === 'SEND_MESSAGE_WITH_FILE_SUCCESS') {
|
|
251
|
-
setState((prev) => ({
|
|
252
|
-
...prev,
|
|
253
|
-
context: {
|
|
254
|
-
...prev.context,
|
|
255
|
-
loading: false,
|
|
256
|
-
messageText: '',
|
|
257
|
-
images: [],
|
|
258
|
-
selectedImage: '',
|
|
259
|
-
},
|
|
260
|
-
value: 'active',
|
|
261
|
-
}));
|
|
262
|
-
} else if (event.type === 'CREATE_DIRECT_CHANNEL_SUCCESS') {
|
|
263
|
-
setState((prev) => ({
|
|
264
|
-
...prev,
|
|
265
|
-
context: {
|
|
266
|
-
...prev.context,
|
|
267
|
-
loading: false,
|
|
268
|
-
channelId: event.data?.channelId || prev.context.channelId,
|
|
269
|
-
messageText: '',
|
|
270
|
-
},
|
|
271
|
-
value: BaseState.FetchMessages,
|
|
272
|
-
}));
|
|
273
|
-
} else if (event.type === 'FETCH_MORE_MESSAGES_SUCCESS') {
|
|
274
|
-
setState((prev) => {
|
|
275
|
-
const newMessages = event.data?.messages || [];
|
|
276
|
-
return {
|
|
277
|
-
...prev,
|
|
278
|
-
context: {
|
|
279
|
-
...prev.context,
|
|
280
|
-
loadingOldMessages: false,
|
|
281
|
-
channelMessages: uniqBy([...prev.context.channelMessages, ...newMessages], ({ id }) => id),
|
|
282
|
-
},
|
|
283
|
-
value: 'active',
|
|
284
|
-
};
|
|
285
|
-
});
|
|
286
|
-
} else if (event.type === 'ERROR') {
|
|
287
|
-
setState((prev) => ({
|
|
288
|
-
...prev,
|
|
289
|
-
context: {
|
|
290
|
-
...prev.context,
|
|
291
|
-
loading: false,
|
|
292
|
-
loadingOldMessages: false,
|
|
293
|
-
error: event.data?.message || 'Unknown error',
|
|
294
|
-
},
|
|
295
|
-
value: 'error',
|
|
296
|
-
}));
|
|
297
|
-
}
|
|
298
|
-
} catch (error) {
|
|
299
|
-
console.error('Error in send function:', error);
|
|
300
|
-
}
|
|
301
|
-
}, []);
|
|
302
|
-
|
|
303
|
-
// Add a custom matches function to the state
|
|
304
|
-
const stateWithMatches = useMemo(() => {
|
|
305
|
-
return {
|
|
306
|
-
...state,
|
|
307
|
-
matches: (checkState) => {
|
|
308
|
-
return state.value === checkState;
|
|
309
|
-
},
|
|
310
|
-
};
|
|
311
|
-
}, [state]);
|
|
312
|
-
|
|
313
|
-
// Return as a tuple to match useMachine API
|
|
314
|
-
return [stateWithMatches, send] as const;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMessage, ...rest }: any) => {
|
|
318
|
-
const [channelToTop, setChannelToTop] = useState(0);
|
|
319
|
-
|
|
320
|
-
// Create a ref to track if component is mounted
|
|
321
|
-
const isMountedRef = useRef(true);
|
|
322
|
-
|
|
323
|
-
// Use our safer custom implementation instead of the problematic useMachine
|
|
324
|
-
const [state, send] = useSafeMachine(conversationXstate);
|
|
325
|
-
|
|
326
|
-
// Define safe functions first to avoid "used before declaration" errors
|
|
327
|
-
const safeContext = useCallback(() => {
|
|
328
|
-
try {
|
|
329
|
-
return state?.context || {};
|
|
330
|
-
} catch (error) {
|
|
331
|
-
console.error('Error accessing state.context:', error);
|
|
332
|
-
return {};
|
|
333
|
-
}
|
|
334
|
-
}, [state]);
|
|
335
|
-
|
|
336
|
-
const safeContextProperty = useCallback(
|
|
337
|
-
(property, defaultValue = null) => {
|
|
338
|
-
try {
|
|
339
|
-
return state?.context?.[property] ?? defaultValue;
|
|
340
|
-
} catch (error) {
|
|
341
|
-
console.error(`Error accessing state.context.${property}:`, error);
|
|
342
|
-
return defaultValue;
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
[state],
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
const safeMatches = useCallback(
|
|
349
|
-
(stateValue) => {
|
|
350
|
-
try {
|
|
351
|
-
return state?.matches?.(stateValue) || false;
|
|
352
|
-
} catch (error) {
|
|
353
|
-
console.error(`Error calling state.matches with ${stateValue}:`, error);
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
[state],
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
const safeSend = useCallback(
|
|
361
|
-
(event) => {
|
|
362
|
-
try {
|
|
363
|
-
console.log('🛠️ Safe send event:', event.type);
|
|
364
|
-
send(event);
|
|
365
|
-
|
|
366
|
-
// For important events like SET_CHANNEL_MESSAGES, verify they worked
|
|
367
|
-
if (event.type === ConversationActions.SET_CHANNEL_MESSAGES) {
|
|
368
|
-
// Add a verification step to ensure state was updated
|
|
369
|
-
setTimeout(() => {
|
|
370
|
-
// Don't log for every check to avoid console spam
|
|
371
|
-
if (state.context.channelMessages.length === 0 && event.data?.messages?.length > 0) {
|
|
372
|
-
console.warn('🛠️ SET_CHANNEL_MESSAGES did not update state - messages still empty');
|
|
373
|
-
}
|
|
374
|
-
}, 100);
|
|
375
|
-
}
|
|
376
|
-
} catch (error) {
|
|
377
|
-
console.error('🛠️ Error in safeSend:', error, 'Event type:', event.type);
|
|
378
|
-
// Try direct state update as fallback for critical events
|
|
379
|
-
if (event.type === ConversationActions.SET_CHANNEL_MESSAGES && event.data?.messages) {
|
|
380
|
-
console.log('🛠️ Attempting fallback for SET_CHANNEL_MESSAGES');
|
|
381
|
-
try {
|
|
382
|
-
// Direct update as fallback
|
|
383
|
-
send({
|
|
384
|
-
type: 'DIRECT_STATE_UPDATE',
|
|
385
|
-
data: event.data,
|
|
386
|
-
});
|
|
387
|
-
} catch (fallbackError) {
|
|
388
|
-
console.error('🛠️ Even fallback failed:', fallbackError);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
},
|
|
393
|
-
[send, state.context.channelMessages.length],
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
// Immediately set initial context if needed
|
|
397
|
-
useEffect(() => {
|
|
398
|
-
if (ChannelId) {
|
|
399
|
-
console.log('Setting initial channel ID on mount:', ChannelId);
|
|
400
|
-
try {
|
|
401
|
-
send({
|
|
402
|
-
type: ConversationActions.INITIAL_CONTEXT,
|
|
403
|
-
data: { channelId: ChannelId },
|
|
404
|
-
});
|
|
405
|
-
} catch (error) {
|
|
406
|
-
console.error('Error sending initial context:', error);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}, []);
|
|
410
|
-
|
|
411
|
-
// Use a ref to track the current machine snapshot for safer access
|
|
412
|
-
const stateRef = useRef(state);
|
|
413
|
-
|
|
414
|
-
// Keep the ref updated with the latest snapshot
|
|
415
|
-
useEffect(() => {
|
|
416
|
-
stateRef.current = state;
|
|
417
|
-
}, [state]);
|
|
90
|
+
// Fix for the optimistic response types
|
|
91
|
+
type OptimisticPropsConfig = {
|
|
92
|
+
__typename: 'MachineConfiguration';
|
|
93
|
+
resource: string;
|
|
94
|
+
};
|
|
418
95
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
loadingOldMessages: false,
|
|
432
|
-
error: null,
|
|
433
|
-
selectedImage: '',
|
|
434
|
-
files: [],
|
|
435
|
-
images: [],
|
|
436
|
-
messageText: '',
|
|
437
|
-
imageLoading: false,
|
|
438
|
-
};
|
|
439
|
-
}, []);
|
|
96
|
+
const ConversationViewComponent = ({ channelId: initialChannelId, role, isShowThreadMessage, ...rest }: any) => {
|
|
97
|
+
// Core state management using React hooks instead of XState
|
|
98
|
+
const [channelId, setChannelId] = useState<string | null>(initialChannelId || null);
|
|
99
|
+
const [messageText, setMessageText] = useState('');
|
|
100
|
+
const [skip, setSkip] = useState(0);
|
|
101
|
+
const [loading, setLoading] = useState(false);
|
|
102
|
+
const [loadingOldMessages, setLoadingOldMessages] = useState(false);
|
|
103
|
+
const [error, setError] = useState<string | null>(null);
|
|
104
|
+
const [selectedImage, setSelectedImage] = useState<string>('');
|
|
105
|
+
const [images, setImages] = useState<any[]>([]);
|
|
106
|
+
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
107
|
+
const [imageObject, setImageObject] = useState<any>({});
|
|
440
108
|
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
};
|
|
446
|
-
}, []);
|
|
109
|
+
// Create refs for various operations
|
|
110
|
+
const messageRootListRef = useRef<any>(null);
|
|
111
|
+
const isMounted = useRef(true);
|
|
112
|
+
const fetchOldDebounceRef = useRef(false);
|
|
447
113
|
|
|
114
|
+
// Navigation and auth
|
|
448
115
|
const auth: any = useSelector(userSelector);
|
|
449
116
|
const currentRoute = navigationRef.isReady() ? navigationRef?.getCurrentRoute() : null;
|
|
450
117
|
const navigation = useNavigation<any>();
|
|
451
|
-
const [selectedImage, setImage] = useState<string>('');
|
|
452
|
-
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
453
|
-
const [imageObject, setImageObject] = useState<any>({});
|
|
454
|
-
const messageRootListRef = useRef<any>(null);
|
|
455
118
|
const isFocused = useIsFocused();
|
|
456
119
|
|
|
120
|
+
// Apollo mutations
|
|
457
121
|
const [addDirectChannel] = useAddDirectChannelMutation();
|
|
458
122
|
const { startUpload } = useUploadFilesNative();
|
|
459
123
|
const [sendMsg] = useSendMessagesMutation();
|
|
460
124
|
const [sendExpoNotificationOnPostMutation] = useSendExpoNotificationOnPostMutation();
|
|
461
125
|
|
|
126
|
+
// Apollo query for messages
|
|
462
127
|
const {
|
|
463
128
|
data,
|
|
464
129
|
loading: messageLoading,
|
|
465
130
|
refetch,
|
|
466
131
|
fetchMore: fetchMoreMessages,
|
|
467
132
|
subscribeToMore,
|
|
468
|
-
}
|
|
133
|
+
} = useMessagesQuery({
|
|
469
134
|
variables: {
|
|
470
|
-
channelId:
|
|
135
|
+
channelId: channelId?.toString(),
|
|
471
136
|
parentId: null,
|
|
472
137
|
limit: MESSAGES_PER_PAGE,
|
|
473
|
-
skip:
|
|
138
|
+
skip: skip,
|
|
474
139
|
},
|
|
475
|
-
skip: !
|
|
140
|
+
skip: !channelId,
|
|
476
141
|
fetchPolicy: 'cache-and-network',
|
|
477
142
|
nextFetchPolicy: 'cache-first',
|
|
478
143
|
refetchWritePolicy: 'merge',
|
|
479
|
-
|
|
480
|
-
console.log('MESSAGE QUERY COMPLETED:', queryData);
|
|
481
|
-
if (queryData?.messages?.data) {
|
|
482
|
-
console.log(
|
|
483
|
-
'Raw message data from query:',
|
|
484
|
-
JSON.stringify(queryData.messages.data).substring(0, 100) + '...',
|
|
485
|
-
);
|
|
486
|
-
console.log('Message count from query:', queryData.messages.data.length);
|
|
487
|
-
console.log('Total count from query:', queryData.messages.totalCount);
|
|
488
|
-
|
|
489
|
-
// DIRECT STATE UPDATE: Immediately update state to ensure messages are displayed
|
|
490
|
-
if (queryData.messages.data.length > 0) {
|
|
491
|
-
console.log('🔄 IMMEDIATE STATE UPDATE with messages:', queryData.messages.data.length);
|
|
492
|
-
safeSend({
|
|
493
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
494
|
-
data: {
|
|
495
|
-
messages: queryData.messages.data,
|
|
496
|
-
totalCount: queryData.messages.totalCount,
|
|
497
|
-
},
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
},
|
|
144
|
+
notifyOnNetworkStatusChange: true,
|
|
502
145
|
onError: (error) => {
|
|
503
|
-
|
|
504
|
-
// Report error to state machine
|
|
505
|
-
safeSend({ type: 'ERROR', data: { message: String(error) } });
|
|
146
|
+
setError(String(error));
|
|
506
147
|
},
|
|
507
148
|
});
|
|
508
|
-
// console.log('🔄 MESSAGE QUERY COMPLETED:', JSON.stringify(data));
|
|
509
|
-
// Modify the fetchMessagesDirectly function to use safe access
|
|
510
|
-
const fetchMessagesDirectly = useCallback(async () => {
|
|
511
|
-
const channelId = safeGetContext().channelId;
|
|
512
|
-
if (!channelId) {
|
|
513
|
-
console.warn('Cannot fetch messages: No channel ID');
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
149
|
|
|
517
|
-
|
|
518
|
-
|
|
150
|
+
// Extract messages from the query data
|
|
151
|
+
const channelMessages = useMemo(() => {
|
|
152
|
+
return data?.messages?.data || [];
|
|
153
|
+
}, [data?.messages?.data]);
|
|
519
154
|
|
|
520
|
-
|
|
521
|
-
|
|
155
|
+
// Get total message count
|
|
156
|
+
const totalCount = useMemo(() => {
|
|
157
|
+
return data?.messages?.totalCount || 0;
|
|
158
|
+
}, [data?.messages?.totalCount]);
|
|
522
159
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
160
|
+
// Clear messages when component unmounts
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
return () => {
|
|
163
|
+
isMounted.current = false;
|
|
164
|
+
};
|
|
165
|
+
}, []);
|
|
529
166
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
} else {
|
|
545
|
-
console.warn('Query returned no messages data');
|
|
546
|
-
send({ type: ConversationActions.STOP_LOADING });
|
|
167
|
+
// Update channelId from props or navigation params
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const currentChannelId = initialChannelId || currentRoute?.params?.channelId;
|
|
170
|
+
if (currentChannelId) {
|
|
171
|
+
setChannelId(currentChannelId);
|
|
172
|
+
}
|
|
173
|
+
}, [initialChannelId, currentRoute]);
|
|
174
|
+
|
|
175
|
+
// Focus/unfocus behavior
|
|
176
|
+
useFocusEffect(
|
|
177
|
+
React.useCallback(() => {
|
|
178
|
+
if (channelId) {
|
|
179
|
+
// Refresh messages when screen comes into focus
|
|
180
|
+
refetch();
|
|
547
181
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
182
|
+
return () => {
|
|
183
|
+
// Nothing needed on unfocus
|
|
184
|
+
};
|
|
185
|
+
}, [channelId, isFocused, refetch]),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Loading state for image selection
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (selectedImage) {
|
|
191
|
+
setLoading(false);
|
|
551
192
|
}
|
|
552
|
-
}, [
|
|
193
|
+
}, [selectedImage]);
|
|
553
194
|
|
|
195
|
+
// Fetch more messages function
|
|
554
196
|
const fetchMoreMessagesImpl = useCallback(async () => {
|
|
555
197
|
try {
|
|
198
|
+
setLoadingOldMessages(true);
|
|
556
199
|
const response = await fetchMoreMessages({
|
|
557
200
|
variables: {
|
|
558
|
-
channelId:
|
|
201
|
+
channelId: channelId?.toString(),
|
|
559
202
|
parentId: null,
|
|
560
|
-
skip:
|
|
203
|
+
skip: channelMessages.length,
|
|
561
204
|
},
|
|
205
|
+
// Let type policy handle the merge
|
|
562
206
|
});
|
|
563
207
|
|
|
208
|
+
setLoadingOldMessages(false);
|
|
564
209
|
if (!response?.data?.messages?.data) {
|
|
565
210
|
return { error: 'No messages returned' };
|
|
566
211
|
}
|
|
567
212
|
|
|
568
213
|
return { messages: response.data.messages.data };
|
|
569
214
|
} catch (error) {
|
|
215
|
+
setLoadingOldMessages(false);
|
|
216
|
+
setError(String(error));
|
|
570
217
|
return { error: String(error) };
|
|
571
218
|
}
|
|
572
|
-
}, [
|
|
219
|
+
}, [channelId, channelMessages.length, fetchMoreMessages]);
|
|
573
220
|
|
|
221
|
+
// Send message function
|
|
574
222
|
const sendMessageImpl = useCallback(async () => {
|
|
575
223
|
try {
|
|
224
|
+
// Store the current message text and clear input immediately for better UX
|
|
225
|
+
const currentMessageText = messageText;
|
|
226
|
+
setMessageText('');
|
|
227
|
+
|
|
576
228
|
const notificationData: IExpoNotificationData = {
|
|
577
229
|
url: config.INBOX_MESSEGE_PATH,
|
|
578
|
-
params: { channelId
|
|
230
|
+
params: { channelId, hideTabBar: true },
|
|
579
231
|
screen: 'DialogMessages',
|
|
580
232
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
581
233
|
};
|
|
582
234
|
|
|
235
|
+
// Create optimistic message with minimal structure required for UI rendering
|
|
236
|
+
const messageId = objectId();
|
|
237
|
+
const optimisticMessage = {
|
|
238
|
+
__typename: 'Post' as const,
|
|
239
|
+
id: messageId,
|
|
240
|
+
message: currentMessageText,
|
|
241
|
+
createdAt: new Date().toISOString(),
|
|
242
|
+
updatedAt: new Date().toISOString(),
|
|
243
|
+
author: {
|
|
244
|
+
__typename: 'UserAccount' as const,
|
|
245
|
+
id: auth?.id,
|
|
246
|
+
picture: auth?.picture || '',
|
|
247
|
+
givenName: auth?.givenName || '',
|
|
248
|
+
familyName: auth?.familyName || '',
|
|
249
|
+
email: auth?.email || '',
|
|
250
|
+
username: auth?.username || '',
|
|
251
|
+
alias: [] as string[],
|
|
252
|
+
tokens: [],
|
|
253
|
+
},
|
|
254
|
+
isDelivered: true,
|
|
255
|
+
isRead: false,
|
|
256
|
+
type: 'TEXT' as PostTypeEnum,
|
|
257
|
+
parentId: null,
|
|
258
|
+
fromServer: false,
|
|
259
|
+
channel: {
|
|
260
|
+
__typename: 'Channel' as const,
|
|
261
|
+
id: channelId,
|
|
262
|
+
},
|
|
263
|
+
// Required fields that Apollo expects in the cache
|
|
264
|
+
propsConfiguration: {
|
|
265
|
+
__typename: 'MachineConfiguration' as const,
|
|
266
|
+
resource: '' as any,
|
|
267
|
+
},
|
|
268
|
+
props: {},
|
|
269
|
+
files: {
|
|
270
|
+
__typename: 'FilesInfo' as const,
|
|
271
|
+
data: [],
|
|
272
|
+
totalCount: 0,
|
|
273
|
+
},
|
|
274
|
+
replies: {
|
|
275
|
+
__typename: 'Messages' as const,
|
|
276
|
+
data: [],
|
|
277
|
+
totalCount: 0,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
583
281
|
const response = await sendMsg({
|
|
584
282
|
variables: {
|
|
585
|
-
channelId
|
|
586
|
-
content:
|
|
283
|
+
channelId,
|
|
284
|
+
content: currentMessageText,
|
|
587
285
|
notificationParams: notificationData,
|
|
588
286
|
},
|
|
287
|
+
optimisticResponse: {
|
|
288
|
+
__typename: 'Mutation',
|
|
289
|
+
sendMessage: optimisticMessage,
|
|
290
|
+
},
|
|
291
|
+
update: (cache, { data }) => {
|
|
292
|
+
if (data?.sendMessage) {
|
|
293
|
+
try {
|
|
294
|
+
// Read the existing messages from the cache
|
|
295
|
+
const existingData = cache.readQuery<{
|
|
296
|
+
messages: {
|
|
297
|
+
__typename: string;
|
|
298
|
+
messagesRefId?: string;
|
|
299
|
+
data: any[];
|
|
300
|
+
totalCount: number;
|
|
301
|
+
};
|
|
302
|
+
}>({
|
|
303
|
+
query: MessagesDocument,
|
|
304
|
+
variables: {
|
|
305
|
+
channelId: channelId?.toString(),
|
|
306
|
+
parentId: null,
|
|
307
|
+
limit: MESSAGES_PER_PAGE,
|
|
308
|
+
skip: 0,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// If we don't have data yet in the cache, don't try to update
|
|
313
|
+
if (!existingData) return;
|
|
314
|
+
|
|
315
|
+
// Let the type policy handle the merging
|
|
316
|
+
cache.writeQuery({
|
|
317
|
+
query: MessagesDocument,
|
|
318
|
+
variables: {
|
|
319
|
+
channelId: channelId?.toString(),
|
|
320
|
+
parentId: null,
|
|
321
|
+
limit: MESSAGES_PER_PAGE,
|
|
322
|
+
skip: 0,
|
|
323
|
+
},
|
|
324
|
+
data: {
|
|
325
|
+
messages: {
|
|
326
|
+
...existingData.messages,
|
|
327
|
+
data: [data.sendMessage, ...existingData.messages.data],
|
|
328
|
+
totalCount: (existingData.messages.totalCount || 0) + 1,
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error('Error updating cache:', error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
589
337
|
});
|
|
590
338
|
|
|
591
339
|
return { message: response.data?.sendMessage };
|
|
592
340
|
} catch (error) {
|
|
341
|
+
setLoading(false);
|
|
342
|
+
setError(String(error));
|
|
593
343
|
return { error: String(error) };
|
|
594
344
|
}
|
|
595
|
-
}, [
|
|
345
|
+
}, [channelId, messageText, sendMsg, auth]);
|
|
596
346
|
|
|
597
|
-
//
|
|
347
|
+
// Image selection handler
|
|
598
348
|
const onSelectImages = async () => {
|
|
599
|
-
|
|
349
|
+
setLoading(true);
|
|
600
350
|
|
|
601
351
|
try {
|
|
602
|
-
console.log('Starting image picker...');
|
|
603
352
|
let imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
604
353
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
605
354
|
allowsEditing: true,
|
|
@@ -610,22 +359,10 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
610
359
|
});
|
|
611
360
|
|
|
612
361
|
if (!imageSource?.canceled) {
|
|
613
|
-
console.log(
|
|
614
|
-
'Image selected. Asset details:',
|
|
615
|
-
JSON.stringify({
|
|
616
|
-
uri: imageSource?.assets?.[0]?.uri?.substring(0, 30) + '...',
|
|
617
|
-
width: imageSource?.assets?.[0]?.width,
|
|
618
|
-
height: imageSource?.assets?.[0]?.height,
|
|
619
|
-
hasBase64: !!imageSource?.assets?.[0]?.base64,
|
|
620
|
-
hasUri: !!imageSource?.assets?.[0]?.uri,
|
|
621
|
-
}),
|
|
622
|
-
);
|
|
623
|
-
|
|
624
362
|
// Get the asset
|
|
625
363
|
const selectedAsset = imageSource?.assets?.[0];
|
|
626
364
|
if (!selectedAsset) {
|
|
627
|
-
|
|
628
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
365
|
+
setLoading(false);
|
|
629
366
|
return;
|
|
630
367
|
}
|
|
631
368
|
|
|
@@ -641,58 +378,49 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
641
378
|
mimeType: 'image/jpeg',
|
|
642
379
|
};
|
|
643
380
|
|
|
644
|
-
console.log('Prepared image asset for upload:', {
|
|
645
|
-
hasUrl: !!asset.url,
|
|
646
|
-
hasFileName: !!asset.fileName,
|
|
647
|
-
hasMimeType: !!asset.mimeType,
|
|
648
|
-
previewAvailable: !!previewImage,
|
|
649
|
-
});
|
|
650
|
-
|
|
651
381
|
// Update state with the new image
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
data: {
|
|
655
|
-
image: previewImage,
|
|
656
|
-
images: [asset], // Replace existing images with the new one
|
|
657
|
-
},
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
console.log('Image state updated successfully');
|
|
382
|
+
setSelectedImage(previewImage);
|
|
383
|
+
setImages([asset]);
|
|
661
384
|
} else {
|
|
662
|
-
|
|
663
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
385
|
+
setLoading(false);
|
|
664
386
|
}
|
|
665
387
|
} catch (error) {
|
|
666
|
-
|
|
667
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
388
|
+
setLoading(false);
|
|
668
389
|
}
|
|
669
390
|
};
|
|
670
391
|
|
|
671
|
-
//
|
|
392
|
+
// Add a state variable to track which message should show the skeleton
|
|
393
|
+
const [uploadingMessageId, setUploadingMessageId] = useState<string | null>(null);
|
|
394
|
+
|
|
395
|
+
// Send message with file function - update to set and clear uploadingMessageId
|
|
672
396
|
const sendMessageWithFileImpl = useCallback(async () => {
|
|
673
397
|
try {
|
|
674
|
-
|
|
398
|
+
// For file uploads, we still need loading state since we need to wait for the file upload
|
|
399
|
+
setLoading(true);
|
|
675
400
|
|
|
676
401
|
// Generate a unique post ID for the message
|
|
677
402
|
const postId = objectId();
|
|
678
|
-
|
|
403
|
+
|
|
404
|
+
// Set the message ID that should show the skeleton
|
|
405
|
+
setUploadingMessageId(postId);
|
|
679
406
|
|
|
680
407
|
// Prepare notification data
|
|
681
408
|
const notificationData: IExpoNotificationData = {
|
|
682
409
|
url: config.INBOX_MESSEGE_PATH,
|
|
683
|
-
params: { channelId
|
|
410
|
+
params: { channelId, hideTabBar: true },
|
|
684
411
|
screen: 'DialogMessages',
|
|
685
412
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
686
413
|
};
|
|
687
414
|
|
|
688
415
|
// Safety check for images
|
|
689
|
-
if (!
|
|
690
|
-
|
|
416
|
+
if (!images || images.length === 0) {
|
|
417
|
+
setLoading(false);
|
|
418
|
+
setUploadingMessageId(null);
|
|
691
419
|
return { error: 'No images available to upload' };
|
|
692
420
|
}
|
|
693
421
|
|
|
694
422
|
// Format the images for upload if needed
|
|
695
|
-
const imagesToUpload =
|
|
423
|
+
const imagesToUpload = images.map((img) => {
|
|
696
424
|
// Ensure the image has all required properties
|
|
697
425
|
return {
|
|
698
426
|
...img,
|
|
@@ -702,20 +430,73 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
702
430
|
};
|
|
703
431
|
});
|
|
704
432
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
433
|
+
// Store current message text and clear inputs immediately for better UX
|
|
434
|
+
const currentMessageText = messageText;
|
|
435
|
+
setMessageText('');
|
|
436
|
+
|
|
437
|
+
// Create a unique file ID for the optimistic response
|
|
438
|
+
const fileId = objectId();
|
|
439
|
+
|
|
440
|
+
// Create minimal optimistic message with file
|
|
441
|
+
const optimisticMessage = {
|
|
442
|
+
__typename: 'Post' as const,
|
|
443
|
+
id: postId,
|
|
444
|
+
message: currentMessageText || ' ',
|
|
445
|
+
createdAt: new Date().toISOString(),
|
|
446
|
+
updatedAt: new Date().toISOString(),
|
|
447
|
+
author: {
|
|
448
|
+
__typename: 'UserAccount' as const,
|
|
449
|
+
id: auth?.id,
|
|
450
|
+
picture: auth?.picture || '',
|
|
451
|
+
givenName: auth?.givenName || '',
|
|
452
|
+
familyName: auth?.familyName || '',
|
|
453
|
+
email: auth?.email || '',
|
|
454
|
+
username: auth?.username || '',
|
|
455
|
+
alias: [] as string[],
|
|
456
|
+
tokens: [],
|
|
457
|
+
},
|
|
458
|
+
isDelivered: true,
|
|
459
|
+
isRead: false,
|
|
460
|
+
type: 'TEXT' as PostTypeEnum,
|
|
461
|
+
parentId: null,
|
|
462
|
+
fromServer: false,
|
|
463
|
+
channel: {
|
|
464
|
+
__typename: 'Channel' as const,
|
|
465
|
+
id: channelId,
|
|
466
|
+
},
|
|
467
|
+
// Required fields that Apollo expects in the cache
|
|
468
|
+
propsConfiguration: {
|
|
469
|
+
__typename: 'MachineConfiguration' as const,
|
|
470
|
+
resource: '' as any,
|
|
471
|
+
},
|
|
472
|
+
props: {},
|
|
473
|
+
files: {
|
|
474
|
+
__typename: 'FilesInfo' as const,
|
|
475
|
+
data: [
|
|
476
|
+
{
|
|
477
|
+
__typename: 'FileInfo' as const,
|
|
478
|
+
id: fileId,
|
|
479
|
+
url: selectedImage,
|
|
480
|
+
name: imagesToUpload[0]?.name || 'image.jpg',
|
|
481
|
+
extension: 'jpg',
|
|
482
|
+
mimeType: 'image/jpeg',
|
|
483
|
+
height: imagesToUpload[0]?.height || 0,
|
|
484
|
+
width: imagesToUpload[0]?.width || 0,
|
|
485
|
+
size: imagesToUpload[0]?.fileSize || 0,
|
|
486
|
+
refType: 'Post' as FileRefType,
|
|
487
|
+
ref: postId,
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
totalCount: 1,
|
|
491
|
+
},
|
|
492
|
+
replies: {
|
|
493
|
+
__typename: 'Messages' as const,
|
|
494
|
+
data: [],
|
|
495
|
+
totalCount: 0,
|
|
496
|
+
},
|
|
497
|
+
};
|
|
716
498
|
|
|
717
499
|
// Upload the files
|
|
718
|
-
console.log('Starting file upload...');
|
|
719
500
|
const uploadResponse = await startUpload({
|
|
720
501
|
file: imagesToUpload,
|
|
721
502
|
saveUploadedFile: {
|
|
@@ -726,89 +507,154 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
726
507
|
},
|
|
727
508
|
});
|
|
728
509
|
|
|
729
|
-
console.log(
|
|
730
|
-
'Upload response received:',
|
|
731
|
-
uploadResponse?.data ? 'Has data' : 'No data',
|
|
732
|
-
'Error:',
|
|
733
|
-
uploadResponse?.error ? uploadResponse.error : 'None',
|
|
734
|
-
);
|
|
735
|
-
|
|
736
510
|
if (uploadResponse?.error) {
|
|
737
|
-
|
|
511
|
+
setLoading(false);
|
|
512
|
+
setUploadingMessageId(null);
|
|
738
513
|
return { error: String(uploadResponse.error) };
|
|
739
514
|
}
|
|
740
515
|
|
|
741
516
|
// Get uploaded file IDs
|
|
742
517
|
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
743
|
-
console.log(
|
|
744
|
-
'Uploaded files:',
|
|
745
|
-
uploadedFiles
|
|
746
|
-
? JSON.stringify(uploadedFiles.map((f) => ({ id: f.id, url: f.url?.substring(0, 30) + '...' })))
|
|
747
|
-
: 'null',
|
|
748
|
-
);
|
|
749
|
-
|
|
750
518
|
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
751
519
|
|
|
752
|
-
|
|
520
|
+
// Create a real version of the message with the actual file data
|
|
521
|
+
const realMessage = {
|
|
522
|
+
...optimisticMessage,
|
|
523
|
+
files: {
|
|
524
|
+
__typename: 'FilesInfo' as const,
|
|
525
|
+
data: uploadedFiles.map((file) => ({
|
|
526
|
+
__typename: 'FileInfo' as const,
|
|
527
|
+
id: file.id,
|
|
528
|
+
url: file.url,
|
|
529
|
+
name: file.name,
|
|
530
|
+
extension: file.extension,
|
|
531
|
+
mimeType: file.mimeType,
|
|
532
|
+
height: file.height,
|
|
533
|
+
width: file.width,
|
|
534
|
+
size: file.size,
|
|
535
|
+
refType: file.refType,
|
|
536
|
+
ref: postId,
|
|
537
|
+
})),
|
|
538
|
+
totalCount: uploadedFiles.length,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
753
541
|
|
|
754
542
|
// Send the message with the uploaded files
|
|
755
|
-
console.log('Sending message with files:', {
|
|
756
|
-
postId,
|
|
757
|
-
channelId: state.context.channelId,
|
|
758
|
-
content: state.context.messageText || ' ',
|
|
759
|
-
hasFiles: !!files,
|
|
760
|
-
fileCount: files?.length || 0,
|
|
761
|
-
});
|
|
762
|
-
|
|
763
543
|
const response = await sendMsg({
|
|
764
544
|
variables: {
|
|
765
545
|
postId,
|
|
766
|
-
channelId
|
|
767
|
-
content:
|
|
546
|
+
channelId,
|
|
547
|
+
content: currentMessageText || ' ', // Use a space if no text
|
|
768
548
|
files,
|
|
769
549
|
notificationParams: notificationData,
|
|
770
550
|
},
|
|
771
|
-
|
|
551
|
+
optimisticResponse: {
|
|
552
|
+
__typename: 'Mutation',
|
|
553
|
+
sendMessage: realMessage, // Use the message with real file data
|
|
554
|
+
},
|
|
555
|
+
update: (cache, { data }) => {
|
|
556
|
+
if (data?.sendMessage) {
|
|
557
|
+
try {
|
|
558
|
+
// Read the existing messages from the cache
|
|
559
|
+
const existingData = cache.readQuery<{
|
|
560
|
+
messages: {
|
|
561
|
+
__typename: string;
|
|
562
|
+
messagesRefId?: string;
|
|
563
|
+
data: any[];
|
|
564
|
+
totalCount: number;
|
|
565
|
+
};
|
|
566
|
+
}>({
|
|
567
|
+
query: MessagesDocument,
|
|
568
|
+
variables: {
|
|
569
|
+
channelId: channelId?.toString(),
|
|
570
|
+
parentId: null,
|
|
571
|
+
limit: MESSAGES_PER_PAGE,
|
|
572
|
+
skip: 0,
|
|
573
|
+
},
|
|
574
|
+
});
|
|
772
575
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
576
|
+
// If we don't have data yet in the cache, don't try to update
|
|
577
|
+
if (!existingData) return;
|
|
578
|
+
|
|
579
|
+
// Ensure the message has files data
|
|
580
|
+
const messageWithFiles = {
|
|
581
|
+
...data.sendMessage,
|
|
582
|
+
files: data.sendMessage.files || {
|
|
583
|
+
__typename: 'FilesInfo',
|
|
584
|
+
data: uploadedFiles.map((file) => ({
|
|
585
|
+
__typename: 'FileInfo',
|
|
586
|
+
id: file.id,
|
|
587
|
+
url: file.url,
|
|
588
|
+
name: file.name,
|
|
589
|
+
extension: file.extension,
|
|
590
|
+
mimeType: file.mimeType,
|
|
591
|
+
height: file.height,
|
|
592
|
+
width: file.width,
|
|
593
|
+
size: file.size,
|
|
594
|
+
refType: file.refType,
|
|
595
|
+
ref: postId,
|
|
596
|
+
})),
|
|
597
|
+
totalCount: uploadedFiles.length,
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// Let the type policy handle the merging
|
|
602
|
+
cache.writeQuery({
|
|
603
|
+
query: MessagesDocument,
|
|
604
|
+
variables: {
|
|
605
|
+
channelId: channelId?.toString(),
|
|
606
|
+
parentId: null,
|
|
607
|
+
limit: MESSAGES_PER_PAGE,
|
|
608
|
+
skip: 0,
|
|
609
|
+
},
|
|
610
|
+
data: {
|
|
611
|
+
messages: {
|
|
612
|
+
...existingData.messages,
|
|
613
|
+
data: [messageWithFiles, ...existingData.messages.data],
|
|
614
|
+
totalCount: (existingData.messages.totalCount || 0) + 1,
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
});
|
|
786
618
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
619
|
+
// Clear the images after successful send
|
|
620
|
+
setSelectedImage('');
|
|
621
|
+
setImages([]);
|
|
622
|
+
} catch (error) {
|
|
623
|
+
console.error('Error updating cache:', error);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
});
|
|
794
628
|
|
|
629
|
+
setLoading(false);
|
|
630
|
+
setUploadingMessageId(null);
|
|
795
631
|
return { message: response.data?.sendMessage };
|
|
796
632
|
} catch (error) {
|
|
797
|
-
|
|
633
|
+
setLoading(false);
|
|
634
|
+
setUploadingMessageId(null);
|
|
635
|
+
setError(String(error));
|
|
798
636
|
return { error: String(error) };
|
|
799
637
|
}
|
|
800
|
-
}, [
|
|
638
|
+
}, [channelId, messageText, images, selectedImage, startUpload, sendMsg, auth]);
|
|
801
639
|
|
|
640
|
+
// Create direct channel implementation
|
|
802
641
|
const createDirectChannelImpl = useCallback(async () => {
|
|
803
642
|
try {
|
|
643
|
+
setLoading(true);
|
|
804
644
|
if (
|
|
805
645
|
!rest?.isCreateNewChannel ||
|
|
806
646
|
rest?.newChannelData?.type !== RoomType?.Direct ||
|
|
807
647
|
!rest?.newChannelData?.userIds?.length
|
|
808
648
|
) {
|
|
649
|
+
setLoading(false);
|
|
809
650
|
return { error: 'Invalid channel data' };
|
|
810
651
|
}
|
|
811
652
|
|
|
653
|
+
// Store current message text
|
|
654
|
+
const currentMessageText = messageText;
|
|
655
|
+
// Clear message text immediately for better UX
|
|
656
|
+
setMessageText('');
|
|
657
|
+
|
|
812
658
|
const response = await addDirectChannel({
|
|
813
659
|
variables: {
|
|
814
660
|
receiver: [...(rest?.newChannelData?.userIds ?? [])],
|
|
@@ -817,10 +663,12 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
817
663
|
});
|
|
818
664
|
|
|
819
665
|
if (!response?.data?.createDirectChannel?.id) {
|
|
666
|
+
setLoading(false);
|
|
820
667
|
return { error: 'Failed to create channel' };
|
|
821
668
|
}
|
|
822
669
|
|
|
823
670
|
const newChannelId = response.data.createDirectChannel.id;
|
|
671
|
+
setChannelId(newChannelId);
|
|
824
672
|
|
|
825
673
|
const notificationData: IExpoNotificationData = {
|
|
826
674
|
url: config.INBOX_MESSEGE_PATH,
|
|
@@ -829,113 +677,102 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
829
677
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
830
678
|
};
|
|
831
679
|
|
|
680
|
+
// Create unique message ID for optimistic response
|
|
681
|
+
const messageId = objectId();
|
|
682
|
+
|
|
683
|
+
// Create minimal optimistic message
|
|
684
|
+
const optimisticMessage = {
|
|
685
|
+
__typename: 'Post' as const,
|
|
686
|
+
id: messageId,
|
|
687
|
+
message: currentMessageText,
|
|
688
|
+
createdAt: new Date().toISOString(),
|
|
689
|
+
updatedAt: new Date().toISOString(),
|
|
690
|
+
author: {
|
|
691
|
+
__typename: 'UserAccount' as const,
|
|
692
|
+
id: auth?.id,
|
|
693
|
+
picture: auth?.picture || '',
|
|
694
|
+
givenName: auth?.givenName || '',
|
|
695
|
+
familyName: auth?.familyName || '',
|
|
696
|
+
email: auth?.email || '',
|
|
697
|
+
username: auth?.username || '',
|
|
698
|
+
alias: [] as string[],
|
|
699
|
+
tokens: [],
|
|
700
|
+
},
|
|
701
|
+
isDelivered: true,
|
|
702
|
+
isRead: false,
|
|
703
|
+
type: 'TEXT' as PostTypeEnum,
|
|
704
|
+
parentId: null,
|
|
705
|
+
fromServer: false,
|
|
706
|
+
channel: {
|
|
707
|
+
__typename: 'Channel' as const,
|
|
708
|
+
id: newChannelId,
|
|
709
|
+
},
|
|
710
|
+
// Required fields that Apollo expects in the cache
|
|
711
|
+
propsConfiguration: {
|
|
712
|
+
__typename: 'MachineConfiguration' as const,
|
|
713
|
+
resource: '' as any,
|
|
714
|
+
},
|
|
715
|
+
props: {},
|
|
716
|
+
files: {
|
|
717
|
+
__typename: 'FilesInfo' as const,
|
|
718
|
+
data: [],
|
|
719
|
+
totalCount: 0,
|
|
720
|
+
},
|
|
721
|
+
replies: {
|
|
722
|
+
__typename: 'Messages' as const,
|
|
723
|
+
data: [],
|
|
724
|
+
totalCount: 0,
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// Send message in the new channel
|
|
832
729
|
await sendMsg({
|
|
833
730
|
variables: {
|
|
834
731
|
channelId: newChannelId,
|
|
835
|
-
content:
|
|
732
|
+
content: currentMessageText,
|
|
836
733
|
notificationParams: notificationData,
|
|
837
734
|
},
|
|
735
|
+
optimisticResponse: {
|
|
736
|
+
__typename: 'Mutation',
|
|
737
|
+
sendMessage: optimisticMessage,
|
|
738
|
+
},
|
|
739
|
+
update: (cache, { data }) => {
|
|
740
|
+
if (data?.sendMessage) {
|
|
741
|
+
try {
|
|
742
|
+
// For a new channel, we don't need to read existing data
|
|
743
|
+
// Just write the new message to the cache
|
|
744
|
+
cache.writeQuery({
|
|
745
|
+
query: MessagesDocument,
|
|
746
|
+
variables: {
|
|
747
|
+
channelId: newChannelId,
|
|
748
|
+
parentId: null,
|
|
749
|
+
limit: MESSAGES_PER_PAGE,
|
|
750
|
+
skip: 0,
|
|
751
|
+
},
|
|
752
|
+
data: {
|
|
753
|
+
messages: {
|
|
754
|
+
__typename: 'Messages',
|
|
755
|
+
messagesRefId: newChannelId,
|
|
756
|
+
data: [data.sendMessage],
|
|
757
|
+
totalCount: 1,
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
} catch (error) {
|
|
762
|
+
console.error('Error updating cache:', error);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
},
|
|
838
766
|
});
|
|
839
767
|
|
|
768
|
+
setLoading(false);
|
|
840
769
|
return { channelId: newChannelId };
|
|
841
770
|
} catch (error) {
|
|
771
|
+
setLoading(false);
|
|
772
|
+
setError(String(error));
|
|
842
773
|
return { error: String(error) };
|
|
843
774
|
}
|
|
844
|
-
}, [rest,
|
|
845
|
-
|
|
846
|
-
// Remove the implementation inside this effect
|
|
847
|
-
useEffect(() => {
|
|
848
|
-
// We've moved these implementations to useCallback hooks above
|
|
849
|
-
}, [state.value, sendMsg, refetch, fetchMoreMessages, addDirectChannel, startUpload, rest, state.context]);
|
|
850
|
-
|
|
851
|
-
React.useEffect(() => {
|
|
852
|
-
return () => {
|
|
853
|
-
send({ type: ConversationActions.CLEAR_MESSAGES });
|
|
854
|
-
};
|
|
855
|
-
}, []);
|
|
856
|
-
|
|
857
|
-
useFocusEffect(
|
|
858
|
-
React.useCallback(() => {
|
|
859
|
-
if (state.context.channelId) {
|
|
860
|
-
send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: state.context.channelId } });
|
|
861
|
-
}
|
|
862
|
-
return () => {
|
|
863
|
-
send({ type: ConversationActions.CLEAR_MESSAGES });
|
|
864
|
-
};
|
|
865
|
-
}, [state.context.channelId, isFocused]),
|
|
866
|
-
);
|
|
867
|
-
|
|
868
|
-
React.useEffect(() => {
|
|
869
|
-
const currentChannelId = ChannelId || currentRoute?.params?.channelId;
|
|
870
|
-
if (currentChannelId) {
|
|
871
|
-
console.log('Setting initial channel ID:', currentChannelId);
|
|
872
|
-
send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: currentChannelId } });
|
|
873
|
-
}
|
|
874
|
-
}, [ChannelId, currentRoute]);
|
|
875
|
-
|
|
876
|
-
React.useEffect(() => {
|
|
877
|
-
if (state.context.selectedImage) {
|
|
878
|
-
send({ type: ConversationActions.STOP_LOADING });
|
|
879
|
-
}
|
|
880
|
-
}, [state.context.selectedImage]);
|
|
881
|
-
|
|
882
|
-
useEffect(() => {
|
|
883
|
-
if (data?.messages?.data) {
|
|
884
|
-
console.log('📩 QUERY DATA CHANGED - Messages received:', data.messages.data.length);
|
|
885
|
-
const { data: messages, totalCount: messageTotalCount } = data.messages;
|
|
886
|
-
|
|
887
|
-
if (messages && messages.length > 0) {
|
|
888
|
-
console.log('📩 QUERY DATA - Setting channel messages, count:', messages.length);
|
|
889
|
-
|
|
890
|
-
// Use timeout to ensure this doesn't conflict with other state updates
|
|
891
|
-
setTimeout(() => {
|
|
892
|
-
// Directly update the state machine with new messages
|
|
893
|
-
safeSend({
|
|
894
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
895
|
-
data: {
|
|
896
|
-
messages: uniqBy([...messages, ...state.context.channelMessages], ({ id }) => id),
|
|
897
|
-
totalCount: messageTotalCount,
|
|
898
|
-
},
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
// Verify the update was processed properly
|
|
902
|
-
setTimeout(() => {
|
|
903
|
-
const currentMessagesCount = state.context.channelMessages?.length || 0;
|
|
904
|
-
console.log('📩 STATE UPDATE VERIFICATION - Current message count:', currentMessagesCount);
|
|
905
|
-
|
|
906
|
-
if (currentMessagesCount === 0) {
|
|
907
|
-
console.warn('⚠️ STATE NOT UPDATED after message data received - triggering fallback');
|
|
908
|
-
// Direct fallback approach
|
|
909
|
-
safeSend({
|
|
910
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
911
|
-
data: {
|
|
912
|
-
messages: messages,
|
|
913
|
-
totalCount: messageTotalCount,
|
|
914
|
-
},
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
}, 500);
|
|
918
|
-
}, 0);
|
|
919
|
-
|
|
920
|
-
// Debug: Log the first message to verify data format
|
|
921
|
-
if (messages[0]) {
|
|
922
|
-
const sample = messages[0];
|
|
923
|
-
console.log(
|
|
924
|
-
'📩 SAMPLE MESSAGE:',
|
|
925
|
-
JSON.stringify({
|
|
926
|
-
id: sample.id,
|
|
927
|
-
message: sample.message,
|
|
928
|
-
author: {
|
|
929
|
-
id: sample.author?.id,
|
|
930
|
-
name: `${sample.author?.givenName} ${sample.author?.familyName}`,
|
|
931
|
-
},
|
|
932
|
-
createdAt: sample.createdAt,
|
|
933
|
-
}),
|
|
934
|
-
);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}, [data, safeSend]);
|
|
775
|
+
}, [rest, messageText, addDirectChannel, sendMsg, auth]);
|
|
939
776
|
|
|
940
777
|
// Optimize onFetchOld by adding debounce logic
|
|
941
778
|
const onFetchOld = useCallback(() => {
|
|
@@ -943,49 +780,86 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
943
780
|
if (fetchOldDebounceRef.current) return;
|
|
944
781
|
|
|
945
782
|
// Check if we need to fetch more messages
|
|
946
|
-
if (
|
|
947
|
-
state?.context?.totalCount > state?.context?.channelMessages?.length &&
|
|
948
|
-
!state?.context?.loadingOldMessages
|
|
949
|
-
) {
|
|
783
|
+
if (totalCount > channelMessages.length && !loadingOldMessages) {
|
|
950
784
|
// Set debounce
|
|
951
785
|
fetchOldDebounceRef.current = true;
|
|
952
786
|
|
|
953
|
-
//
|
|
954
|
-
|
|
787
|
+
// Fetch more messages
|
|
788
|
+
fetchMoreMessagesImpl();
|
|
955
789
|
|
|
956
790
|
// Clear debounce after a timeout
|
|
957
791
|
setTimeout(() => {
|
|
958
792
|
fetchOldDebounceRef.current = false;
|
|
959
793
|
}, 1000);
|
|
960
794
|
}
|
|
961
|
-
}, [
|
|
962
|
-
|
|
963
|
-
// Add debounce ref
|
|
964
|
-
const fetchOldDebounceRef = useRef(false);
|
|
795
|
+
}, [totalCount, channelMessages.length, loadingOldMessages, fetchMoreMessagesImpl]);
|
|
965
796
|
|
|
966
797
|
const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
967
798
|
const paddingToTop = 60;
|
|
968
799
|
return contentSize.height - layoutMeasurement.height - paddingToTop <= contentOffset.y;
|
|
969
800
|
};
|
|
970
801
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
802
|
+
// Transform the message data for GiftedChat
|
|
803
|
+
const messageList = useMemo(() => {
|
|
804
|
+
// Short-circuit if no messages to process
|
|
805
|
+
if (!channelMessages || channelMessages.length === 0) {
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Use a more efficient approach - pre-filter messages once
|
|
810
|
+
const filteredMessages = uniqBy(channelMessages, ({ id }) => id);
|
|
811
|
+
|
|
812
|
+
// Skip processing if no filtered messages
|
|
813
|
+
if (filteredMessages.length === 0) {
|
|
814
|
+
return [];
|
|
979
815
|
}
|
|
980
|
-
return new File([u8arr], filename, { type: mime });
|
|
981
|
-
};
|
|
982
816
|
|
|
983
|
-
|
|
817
|
+
// Transform messages only once and return
|
|
818
|
+
return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
819
|
+
const date = new Date(msg.createdAt);
|
|
820
|
+
|
|
821
|
+
// Extract image URL from files data
|
|
822
|
+
let imageUrl = null;
|
|
823
|
+
if (msg.files && typeof msg.files === 'object') {
|
|
824
|
+
// Handle both cases where files might be directly present or via files.data
|
|
825
|
+
const filesData = msg.files.data || (Array.isArray(msg.files) ? msg.files : null);
|
|
826
|
+
|
|
827
|
+
if (filesData && filesData.length > 0) {
|
|
828
|
+
const fileData = filesData[0];
|
|
829
|
+
// Make sure we have valid file data with a URL
|
|
830
|
+
if (fileData && typeof fileData === 'object' && fileData.url) {
|
|
831
|
+
imageUrl = fileData.url;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Create message in a more direct way
|
|
837
|
+
return {
|
|
838
|
+
_id: msg.id,
|
|
839
|
+
text: msg.message,
|
|
840
|
+
createdAt: date,
|
|
841
|
+
user: {
|
|
842
|
+
_id: msg.author?.id || '',
|
|
843
|
+
name: `${msg.author?.givenName || ''} ${msg.author?.familyName || ''}`,
|
|
844
|
+
avatar: msg.author?.picture || '',
|
|
845
|
+
},
|
|
846
|
+
image: imageUrl,
|
|
847
|
+
sent: msg?.isDelivered,
|
|
848
|
+
received: msg?.isRead,
|
|
849
|
+
type: msg?.type,
|
|
850
|
+
propsConfiguration: msg?.propsConfiguration,
|
|
851
|
+
replies: msg?.replies ?? [],
|
|
852
|
+
isShowThreadMessage,
|
|
853
|
+
};
|
|
854
|
+
});
|
|
855
|
+
}, [channelMessages, isShowThreadMessage]);
|
|
856
|
+
|
|
857
|
+
// Render the send button
|
|
984
858
|
const renderSend = useCallback(
|
|
985
859
|
(props) => {
|
|
986
860
|
// Enable the send button if there's text OR we have images
|
|
987
|
-
const hasContent = !!props.text ||
|
|
988
|
-
const canSend = (
|
|
861
|
+
const hasContent = !!props.text || images?.length > 0;
|
|
862
|
+
const canSend = (channelId || rest?.isCreateNewChannel) && hasContent;
|
|
989
863
|
|
|
990
864
|
return (
|
|
991
865
|
<Send
|
|
@@ -1011,157 +885,58 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1011
885
|
</Send>
|
|
1012
886
|
);
|
|
1013
887
|
},
|
|
1014
|
-
[
|
|
888
|
+
[channelId, images, rest?.isCreateNewChannel],
|
|
1015
889
|
);
|
|
1016
890
|
|
|
1017
|
-
//
|
|
891
|
+
// Handle send for messages
|
|
1018
892
|
const handleSend = useCallback(
|
|
1019
893
|
async (messages) => {
|
|
1020
894
|
// Extract message text from GiftedChat messages array
|
|
1021
|
-
const
|
|
1022
|
-
console.log('Sending message:', messageText);
|
|
1023
|
-
console.log('Images:', state.context.images?.length);
|
|
895
|
+
const newMessageText = messages && messages.length > 0 ? messages[0]?.text || ' ' : ' ';
|
|
1024
896
|
|
|
1025
897
|
// Check if we can send a message (channel exists or we're creating one)
|
|
1026
|
-
if (!
|
|
1027
|
-
console.log('Cannot send - no channel');
|
|
898
|
+
if (!channelId && !rest?.isCreateNewChannel) {
|
|
1028
899
|
return;
|
|
1029
900
|
}
|
|
1030
901
|
|
|
1031
902
|
// Allow sending if we have text OR images (image-only messages are valid)
|
|
1032
|
-
const hasText = !!
|
|
1033
|
-
const hasImages =
|
|
903
|
+
const hasText = !!newMessageText && newMessageText !== ' ';
|
|
904
|
+
const hasImages = images && images.length > 0;
|
|
1034
905
|
|
|
1035
906
|
if (!hasText && !hasImages) {
|
|
1036
|
-
console.log('Nothing to send - no text or images');
|
|
1037
907
|
return;
|
|
1038
908
|
}
|
|
1039
909
|
|
|
1040
|
-
//
|
|
1041
|
-
|
|
910
|
+
// Update the message text state - now handled in send functions for better UX
|
|
911
|
+
setMessageText(newMessageText);
|
|
1042
912
|
|
|
1043
913
|
// Handle direct channel creation if needed
|
|
1044
|
-
if (rest?.isCreateNewChannel && !
|
|
914
|
+
if (rest?.isCreateNewChannel && !channelId) {
|
|
1045
915
|
if (rest?.newChannelData?.type === RoomType?.Direct) {
|
|
1046
|
-
|
|
916
|
+
createDirectChannelImpl();
|
|
1047
917
|
}
|
|
1048
918
|
return;
|
|
1049
919
|
}
|
|
1050
920
|
|
|
1051
921
|
// Send message with or without image based on state
|
|
1052
922
|
if (hasImages) {
|
|
1053
|
-
|
|
1054
|
-
safeSend({ type: ConversationActions.SEND_MESSAGE_WITH_FILE });
|
|
923
|
+
sendMessageWithFileImpl();
|
|
1055
924
|
} else {
|
|
1056
|
-
|
|
1057
|
-
safeSend({ type: ConversationActions.SEND_MESSAGE });
|
|
925
|
+
sendMessageImpl();
|
|
1058
926
|
}
|
|
1059
927
|
},
|
|
1060
|
-
[
|
|
928
|
+
[
|
|
929
|
+
channelId,
|
|
930
|
+
images,
|
|
931
|
+
rest?.isCreateNewChannel,
|
|
932
|
+
rest?.newChannelData?.type,
|
|
933
|
+
createDirectChannelImpl,
|
|
934
|
+
sendMessageWithFileImpl,
|
|
935
|
+
sendMessageImpl,
|
|
936
|
+
],
|
|
1061
937
|
);
|
|
1062
938
|
|
|
1063
|
-
//
|
|
1064
|
-
const fetchMessagesWithFallback = useCallback(async () => {
|
|
1065
|
-
if (!state.context.channelId) return;
|
|
1066
|
-
|
|
1067
|
-
try {
|
|
1068
|
-
console.log('🔄 DIRECT FETCH: Using direct approach for channel:', state.context.channelId);
|
|
1069
|
-
|
|
1070
|
-
const response = await refetch({
|
|
1071
|
-
channelId: state.context.channelId?.toString(),
|
|
1072
|
-
parentId: null,
|
|
1073
|
-
limit: MESSAGES_PER_PAGE,
|
|
1074
|
-
skip: 0,
|
|
1075
|
-
});
|
|
1076
|
-
|
|
1077
|
-
if (response?.data?.messages?.data) {
|
|
1078
|
-
const messages = response.data.messages.data;
|
|
1079
|
-
console.log('✅ DIRECT FETCH: Got messages:', messages.length);
|
|
1080
|
-
|
|
1081
|
-
// Skip fallback and send directly to state machine
|
|
1082
|
-
send({
|
|
1083
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
1084
|
-
data: {
|
|
1085
|
-
messages,
|
|
1086
|
-
totalCount: response.data.messages.totalCount,
|
|
1087
|
-
},
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
} catch (error) {
|
|
1091
|
-
console.error('❌ DIRECT FETCH ERROR:', error);
|
|
1092
|
-
}
|
|
1093
|
-
}, [state.context.channelId, refetch]);
|
|
1094
|
-
|
|
1095
|
-
// Auto-trigger fallback if needed
|
|
1096
|
-
useEffect(() => {
|
|
1097
|
-
let timeoutId: NodeJS.Timeout;
|
|
1098
|
-
|
|
1099
|
-
if (state.context.channelId && state.context.channelMessages.length === 0) {
|
|
1100
|
-
timeoutId = setTimeout(() => {
|
|
1101
|
-
console.log('⚠️ ACTIVATING FALLBACK - XState not updating after timeout');
|
|
1102
|
-
fetchMessagesWithFallback();
|
|
1103
|
-
}, 3000); // Wait 3 seconds for normal flow to work
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
return () => {
|
|
1107
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1108
|
-
};
|
|
1109
|
-
}, [state.context.channelId, state.context.channelMessages, fetchMessagesWithFallback]);
|
|
1110
|
-
|
|
1111
|
-
// Optimize the messageList calculation for better performance
|
|
1112
|
-
const messageList = useMemo(() => {
|
|
1113
|
-
// Only recalculate when these dependencies change
|
|
1114
|
-
console.log('🔄 CALCULATING MESSAGE LIST - Optimized version');
|
|
1115
|
-
|
|
1116
|
-
// Short-circuit if no messages to process
|
|
1117
|
-
if (!state?.context?.channelMessages || state.context.channelMessages.length === 0) {
|
|
1118
|
-
console.log('No messages to process');
|
|
1119
|
-
return [];
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
// Use a more efficient approach - pre-filter messages once
|
|
1123
|
-
const filteredMessages = uniqBy(state.context.channelMessages, ({ id }) => id);
|
|
1124
|
-
|
|
1125
|
-
// Skip processing if no filtered messages
|
|
1126
|
-
if (filteredMessages.length === 0) {
|
|
1127
|
-
return [];
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
// Transform messages only once and return
|
|
1131
|
-
return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
1132
|
-
const date = new Date(msg.createdAt);
|
|
1133
|
-
|
|
1134
|
-
// Extract image URL from files data
|
|
1135
|
-
let imageUrl = null;
|
|
1136
|
-
if (msg.files?.data && msg.files.data.length > 0) {
|
|
1137
|
-
const fileData = msg.files.data[0];
|
|
1138
|
-
if (fileData && fileData.url) {
|
|
1139
|
-
imageUrl = fileData.url;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
// Create message in a more direct way
|
|
1144
|
-
return {
|
|
1145
|
-
_id: msg.id,
|
|
1146
|
-
text: msg.message,
|
|
1147
|
-
createdAt: date,
|
|
1148
|
-
user: {
|
|
1149
|
-
_id: msg.author?.id || '',
|
|
1150
|
-
name: `${msg.author?.givenName || ''} ${msg.author?.familyName || ''}`,
|
|
1151
|
-
avatar: msg.author?.picture || '',
|
|
1152
|
-
},
|
|
1153
|
-
image: imageUrl,
|
|
1154
|
-
sent: msg?.isDelivered,
|
|
1155
|
-
received: msg?.isRead,
|
|
1156
|
-
type: msg?.type,
|
|
1157
|
-
propsConfiguration: msg?.propsConfiguration,
|
|
1158
|
-
replies: msg?.replies ?? [],
|
|
1159
|
-
isShowThreadMessage,
|
|
1160
|
-
};
|
|
1161
|
-
});
|
|
1162
|
-
}, [state?.context?.channelMessages, isShowThreadMessage]);
|
|
1163
|
-
|
|
1164
|
-
// Memoize the renderMessageText function
|
|
939
|
+
// Render message text with customizations for alerts and replies
|
|
1165
940
|
const renderMessageText = useCallback(
|
|
1166
941
|
(props: any) => {
|
|
1167
942
|
const { currentMessage } = props;
|
|
@@ -1226,7 +1001,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1226
1001
|
onPress={() => {
|
|
1227
1002
|
if (currentMessage?.isShowThreadMessage)
|
|
1228
1003
|
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1229
|
-
channelId:
|
|
1004
|
+
channelId: channelId,
|
|
1230
1005
|
title: 'Message',
|
|
1231
1006
|
postParentId: currentMessage?._id,
|
|
1232
1007
|
isPostParentIdThread: true,
|
|
@@ -1291,7 +1066,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1291
1066
|
onPress={() => {
|
|
1292
1067
|
if (currentMessage?.isShowThreadMessage)
|
|
1293
1068
|
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1294
|
-
channelId:
|
|
1069
|
+
channelId: channelId,
|
|
1295
1070
|
title: 'Message',
|
|
1296
1071
|
postParentId: currentMessage?._id,
|
|
1297
1072
|
isPostParentIdThread: true,
|
|
@@ -1343,9 +1118,10 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1343
1118
|
);
|
|
1344
1119
|
}
|
|
1345
1120
|
},
|
|
1346
|
-
[navigation,
|
|
1121
|
+
[navigation, channelId, role],
|
|
1347
1122
|
);
|
|
1348
1123
|
|
|
1124
|
+
// Render action buttons (including image upload)
|
|
1349
1125
|
const renderActions = (props) => {
|
|
1350
1126
|
return (
|
|
1351
1127
|
<Actions
|
|
@@ -1379,79 +1155,115 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1379
1155
|
};
|
|
1380
1156
|
|
|
1381
1157
|
// Create a more visible and reliable image preview with cancel button
|
|
1382
|
-
const renderAccessory = useCallback(
|
|
1383
|
-
(
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
if (!selectedImage) {
|
|
1387
|
-
return null;
|
|
1388
|
-
}
|
|
1158
|
+
const renderAccessory = useCallback(() => {
|
|
1159
|
+
if (!selectedImage) {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1389
1162
|
|
|
1390
|
-
|
|
1163
|
+
return (
|
|
1164
|
+
<View
|
|
1165
|
+
style={{
|
|
1166
|
+
height: 70,
|
|
1167
|
+
backgroundColor: 'white',
|
|
1168
|
+
borderTopWidth: 1,
|
|
1169
|
+
borderTopColor: '#e0e0e0',
|
|
1170
|
+
flexDirection: 'row',
|
|
1171
|
+
alignItems: 'center',
|
|
1172
|
+
margin: 0,
|
|
1173
|
+
padding: 0,
|
|
1174
|
+
paddingVertical: 0,
|
|
1175
|
+
position: 'absolute',
|
|
1176
|
+
bottom: Platform.OS === 'ios' ? 105 : 95, // Position well above the input area
|
|
1177
|
+
left: 0,
|
|
1178
|
+
right: 0,
|
|
1179
|
+
zIndex: 1,
|
|
1180
|
+
elevation: 3,
|
|
1181
|
+
shadowColor: '#000',
|
|
1182
|
+
shadowOffset: { width: 0, height: -1 },
|
|
1183
|
+
shadowOpacity: 0.05,
|
|
1184
|
+
shadowRadius: 2,
|
|
1185
|
+
}}
|
|
1186
|
+
>
|
|
1391
1187
|
<View
|
|
1392
1188
|
style={{
|
|
1393
|
-
|
|
1394
|
-
padding: 3,
|
|
1395
|
-
backgroundColor: 'white',
|
|
1396
|
-
borderTopWidth: 1,
|
|
1397
|
-
borderTopColor: '#e0e0e0',
|
|
1189
|
+
flex: 1,
|
|
1398
1190
|
flexDirection: 'row',
|
|
1399
1191
|
alignItems: 'center',
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
paddingTop: 5,
|
|
1403
|
-
position: 'absolute',
|
|
1404
|
-
bottom: 0,
|
|
1405
|
-
left: 0,
|
|
1406
|
-
right: 0,
|
|
1407
|
-
zIndex: 999,
|
|
1192
|
+
paddingLeft: 15,
|
|
1193
|
+
paddingRight: 5,
|
|
1408
1194
|
}}
|
|
1409
1195
|
>
|
|
1410
1196
|
<View
|
|
1411
1197
|
style={{
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1198
|
+
width: 56,
|
|
1199
|
+
height: 56,
|
|
1200
|
+
marginRight: 15,
|
|
1201
|
+
borderRadius: 4,
|
|
1202
|
+
backgroundColor: colors.gray[200],
|
|
1203
|
+
overflow: 'hidden',
|
|
1204
|
+
borderWidth: 1,
|
|
1205
|
+
borderColor: '#e0e0e0',
|
|
1416
1206
|
}}
|
|
1417
1207
|
>
|
|
1418
1208
|
<Image
|
|
1419
|
-
key={
|
|
1209
|
+
key={selectedImage}
|
|
1420
1210
|
alt={'selected image'}
|
|
1421
|
-
source={{ uri:
|
|
1211
|
+
source={{ uri: selectedImage }}
|
|
1422
1212
|
style={{
|
|
1423
|
-
width:
|
|
1424
|
-
height:
|
|
1425
|
-
borderRadius: 5,
|
|
1426
|
-
marginRight: 15,
|
|
1213
|
+
width: '100%',
|
|
1214
|
+
height: '100%',
|
|
1427
1215
|
}}
|
|
1428
|
-
size={'
|
|
1216
|
+
size={'md'}
|
|
1429
1217
|
/>
|
|
1218
|
+
{loading && (
|
|
1219
|
+
<View
|
|
1220
|
+
style={{
|
|
1221
|
+
position: 'absolute',
|
|
1222
|
+
top: 0,
|
|
1223
|
+
left: 0,
|
|
1224
|
+
right: 0,
|
|
1225
|
+
bottom: 0,
|
|
1226
|
+
backgroundColor: 'rgba(255, 255, 255, 0.7)',
|
|
1227
|
+
justifyContent: 'center',
|
|
1228
|
+
alignItems: 'center',
|
|
1229
|
+
}}
|
|
1230
|
+
>
|
|
1231
|
+
<Spinner size="small" color={colors.blue[500]} />
|
|
1232
|
+
</View>
|
|
1233
|
+
)}
|
|
1234
|
+
</View>
|
|
1430
1235
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
borderRadius: 5,
|
|
1439
|
-
marginLeft: 10,
|
|
1440
|
-
elevation: 3,
|
|
1441
|
-
shadowColor: '#000',
|
|
1442
|
-
shadowOffset: { width: 0, height: 1 },
|
|
1443
|
-
shadowOpacity: 0.3,
|
|
1444
|
-
shadowRadius: 2,
|
|
1445
|
-
}}
|
|
1446
|
-
>
|
|
1447
|
-
<Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
|
|
1448
|
-
</TouchableHighlight>
|
|
1236
|
+
<View style={{ flex: 1 }}>
|
|
1237
|
+
<Text style={{ fontSize: 14, fontWeight: '400', color: colors.gray[800] }}>
|
|
1238
|
+
{images[0]?.fileName || 'image_' + new Date().getTime() + '.jpg'}
|
|
1239
|
+
</Text>
|
|
1240
|
+
<Text style={{ fontSize: 12, color: colors.gray[500], marginTop: 2 }}>
|
|
1241
|
+
{loading ? 'Preparing...' : 'Ready to send'}
|
|
1242
|
+
</Text>
|
|
1449
1243
|
</View>
|
|
1244
|
+
|
|
1245
|
+
<TouchableHighlight
|
|
1246
|
+
underlayColor={'rgba(0,0,0,0.1)'}
|
|
1247
|
+
onPress={() => {
|
|
1248
|
+
setSelectedImage('');
|
|
1249
|
+
setImages([]);
|
|
1250
|
+
}}
|
|
1251
|
+
style={{
|
|
1252
|
+
backgroundColor: colors.red[500],
|
|
1253
|
+
borderRadius: 24,
|
|
1254
|
+
width: 36,
|
|
1255
|
+
height: 36,
|
|
1256
|
+
alignItems: 'center',
|
|
1257
|
+
justifyContent: 'center',
|
|
1258
|
+
marginRight: 10,
|
|
1259
|
+
}}
|
|
1260
|
+
>
|
|
1261
|
+
<Ionicons name="close" size={20} color="white" />
|
|
1262
|
+
</TouchableHighlight>
|
|
1450
1263
|
</View>
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
);
|
|
1264
|
+
</View>
|
|
1265
|
+
);
|
|
1266
|
+
}, [selectedImage, loading, images]);
|
|
1455
1267
|
|
|
1456
1268
|
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
1457
1269
|
setImageObject(obj);
|
|
@@ -1459,13 +1271,14 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1459
1271
|
};
|
|
1460
1272
|
|
|
1461
1273
|
const modalContent = React.useMemo(() => {
|
|
1462
|
-
if (!imageObject) return
|
|
1274
|
+
if (!imageObject || !imageObject.image) return null;
|
|
1463
1275
|
const { image, _id } = imageObject;
|
|
1276
|
+
|
|
1464
1277
|
return (
|
|
1465
1278
|
<CachedImage
|
|
1466
1279
|
style={{ width: '100%', height: '100%' }}
|
|
1467
1280
|
resizeMode={'cover'}
|
|
1468
|
-
cacheKey={`${_id}-
|
|
1281
|
+
cacheKey={`${_id}-modal-imageKey`}
|
|
1469
1282
|
source={{
|
|
1470
1283
|
uri: image,
|
|
1471
1284
|
expiresIn: 86400,
|
|
@@ -1475,14 +1288,60 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1475
1288
|
);
|
|
1476
1289
|
}, [imageObject]);
|
|
1477
1290
|
|
|
1291
|
+
// Create a skeleton component for message bubbles with images
|
|
1478
1292
|
const renderMessage = useCallback(
|
|
1479
1293
|
(props: any) => {
|
|
1294
|
+
// Check if this message ID matches the uploading message ID
|
|
1295
|
+
const isUploading = props.currentMessage._id === uploadingMessageId && loading;
|
|
1296
|
+
|
|
1297
|
+
if (isUploading && props.currentMessage.image) {
|
|
1298
|
+
// Return a custom message skeleton during upload
|
|
1299
|
+
return (
|
|
1300
|
+
<View
|
|
1301
|
+
style={{
|
|
1302
|
+
padding: 10,
|
|
1303
|
+
marginBottom: 10,
|
|
1304
|
+
marginRight: 10,
|
|
1305
|
+
alignSelf: 'flex-end',
|
|
1306
|
+
borderRadius: 15,
|
|
1307
|
+
backgroundColor: colors.gray[100],
|
|
1308
|
+
maxWidth: '80%',
|
|
1309
|
+
}}
|
|
1310
|
+
>
|
|
1311
|
+
{props.currentMessage.text && props.currentMessage.text.trim() !== '' && (
|
|
1312
|
+
<Box
|
|
1313
|
+
style={{
|
|
1314
|
+
height: 15,
|
|
1315
|
+
borderRadius: 4,
|
|
1316
|
+
backgroundColor: colors.gray[200],
|
|
1317
|
+
overflow: 'hidden',
|
|
1318
|
+
marginBottom: 8,
|
|
1319
|
+
}}
|
|
1320
|
+
>
|
|
1321
|
+
<Skeleton variant="rounded" style={{ flex: 1 }} />
|
|
1322
|
+
</Box>
|
|
1323
|
+
)}
|
|
1324
|
+
<Box
|
|
1325
|
+
style={{
|
|
1326
|
+
height: 150,
|
|
1327
|
+
width: 150,
|
|
1328
|
+
borderRadius: 10,
|
|
1329
|
+
backgroundColor: colors.gray[200],
|
|
1330
|
+
overflow: 'hidden',
|
|
1331
|
+
}}
|
|
1332
|
+
>
|
|
1333
|
+
<Skeleton variant="rounded" style={{ flex: 1 }} />
|
|
1334
|
+
</Box>
|
|
1335
|
+
</View>
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1480
1339
|
// Use memo to prevent unnecessary re-renders of each message
|
|
1481
1340
|
return (
|
|
1482
1341
|
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
1483
1342
|
);
|
|
1484
1343
|
},
|
|
1485
|
-
[isShowImageViewer],
|
|
1344
|
+
[isShowImageViewer, uploadingMessageId, loading],
|
|
1486
1345
|
);
|
|
1487
1346
|
|
|
1488
1347
|
let onScroll = false;
|
|
@@ -1499,211 +1358,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1499
1358
|
};
|
|
1500
1359
|
|
|
1501
1360
|
const onEndReached = () => {
|
|
1502
|
-
console.log('on end reached');
|
|
1503
1361
|
if (!onScroll) return;
|
|
1504
1362
|
onScroll = false;
|
|
1505
1363
|
};
|
|
1506
1364
|
|
|
1507
|
-
// Add debug logging to help diagnose the issue
|
|
1508
|
-
useEffect(() => {
|
|
1509
|
-
console.log('Current channel ID:', state.context.channelId);
|
|
1510
|
-
console.log('Current state:', state.value);
|
|
1511
|
-
console.log('Channel messages count:', state.context.channelMessages.length);
|
|
1512
|
-
}, [state.context.channelId, state.value, state.context.channelMessages]);
|
|
1513
|
-
|
|
1514
|
-
// Fix the infinite update loop in useEffect monitoring state changes
|
|
1515
|
-
useEffect(() => {
|
|
1516
|
-
// Only trigger effect if we have a specific state to handle
|
|
1517
|
-
// Check if function exists and if we're in a valid state before calling implementation functions
|
|
1518
|
-
if (state && typeof state.matches === 'function') {
|
|
1519
|
-
if (state.matches(BaseState.FetchMessages)) {
|
|
1520
|
-
console.log('In FetchMessages state, attempting to fetch messages');
|
|
1521
|
-
// Use a ref to track if we've already fetched for this state update
|
|
1522
|
-
if (!fetchInProgressRef.current) {
|
|
1523
|
-
fetchInProgressRef.current = true;
|
|
1524
|
-
fetchMessagesDirectly().finally(() => {
|
|
1525
|
-
fetchInProgressRef.current = false;
|
|
1526
|
-
});
|
|
1527
|
-
}
|
|
1528
|
-
} else if (state.matches(MainState.FetchMoreMessages)) {
|
|
1529
|
-
if (!fetchMoreInProgressRef.current) {
|
|
1530
|
-
fetchMoreInProgressRef.current = true;
|
|
1531
|
-
fetchMoreMessagesImpl().then((result) => {
|
|
1532
|
-
if (result.error) {
|
|
1533
|
-
console.error('Error fetching more messages:', result.error);
|
|
1534
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1535
|
-
} else {
|
|
1536
|
-
safeSend({ type: 'FETCH_MORE_MESSAGES_SUCCESS', data: result });
|
|
1537
|
-
}
|
|
1538
|
-
fetchMoreInProgressRef.current = false;
|
|
1539
|
-
});
|
|
1540
|
-
}
|
|
1541
|
-
} else if (state.matches(MainState.SendMessage)) {
|
|
1542
|
-
if (!sendInProgressRef.current) {
|
|
1543
|
-
sendInProgressRef.current = true;
|
|
1544
|
-
sendMessageImpl().then((result) => {
|
|
1545
|
-
if (result.error) {
|
|
1546
|
-
console.error('Error sending message:', result.error);
|
|
1547
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1548
|
-
} else {
|
|
1549
|
-
safeSend({ type: 'SEND_MESSAGE_SUCCESS', data: result });
|
|
1550
|
-
}
|
|
1551
|
-
sendInProgressRef.current = false;
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
} else if (state.matches(MainState.SendMessageWithFile)) {
|
|
1555
|
-
if (!sendFileInProgressRef.current) {
|
|
1556
|
-
sendFileInProgressRef.current = true;
|
|
1557
|
-
sendMessageWithFileImpl().then((result) => {
|
|
1558
|
-
if (result.error) {
|
|
1559
|
-
console.error('Error sending message with file:', result.error);
|
|
1560
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1561
|
-
} else {
|
|
1562
|
-
safeSend({ type: 'SEND_MESSAGE_WITH_FILE_SUCCESS', data: result });
|
|
1563
|
-
}
|
|
1564
|
-
sendFileInProgressRef.current = false;
|
|
1565
|
-
});
|
|
1566
|
-
}
|
|
1567
|
-
} else if (state.matches(MainState.CreateDirectChannel)) {
|
|
1568
|
-
if (!createChannelInProgressRef.current) {
|
|
1569
|
-
createChannelInProgressRef.current = true;
|
|
1570
|
-
createDirectChannelImpl().then((result) => {
|
|
1571
|
-
if (result.error) {
|
|
1572
|
-
console.error('Error creating direct channel:', result.error);
|
|
1573
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1574
|
-
} else {
|
|
1575
|
-
safeSend({ type: 'CREATE_DIRECT_CHANNEL_SUCCESS', data: result });
|
|
1576
|
-
}
|
|
1577
|
-
createChannelInProgressRef.current = false;
|
|
1578
|
-
});
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
}, [
|
|
1583
|
-
state?.value,
|
|
1584
|
-
fetchMessagesDirectly,
|
|
1585
|
-
fetchMoreMessagesImpl,
|
|
1586
|
-
sendMessageImpl,
|
|
1587
|
-
sendMessageWithFileImpl,
|
|
1588
|
-
createDirectChannelImpl,
|
|
1589
|
-
safeSend,
|
|
1590
|
-
]);
|
|
1591
|
-
|
|
1592
|
-
// Add refs to prevent duplicate operations
|
|
1593
|
-
const fetchInProgressRef = useRef(false);
|
|
1594
|
-
const fetchMoreInProgressRef = useRef(false);
|
|
1595
|
-
const sendInProgressRef = useRef(false);
|
|
1596
|
-
const sendFileInProgressRef = useRef(false);
|
|
1597
|
-
const createChannelInProgressRef = useRef(false);
|
|
1598
|
-
|
|
1599
|
-
// Fix subscription handler to prevent infinite updates
|
|
1600
|
-
const renderChatFooter = useCallback(() => {
|
|
1601
|
-
return (
|
|
1602
|
-
<>
|
|
1603
|
-
<ImageViewerModal
|
|
1604
|
-
isVisible={isShowImageViewer}
|
|
1605
|
-
setVisible={setImageViewer}
|
|
1606
|
-
modalContent={modalContent}
|
|
1607
|
-
/>
|
|
1608
|
-
<SubscriptionHandler
|
|
1609
|
-
channelId={state?.context?.channelId?.toString()}
|
|
1610
|
-
subscribeToNewMessages={() =>
|
|
1611
|
-
subscribeToMore({
|
|
1612
|
-
document: CHAT_MESSAGE_ADDED,
|
|
1613
|
-
variables: {
|
|
1614
|
-
channelId: state?.context?.channelId?.toString(),
|
|
1615
|
-
},
|
|
1616
|
-
updateQuery: (prev, { subscriptionData }: any) => {
|
|
1617
|
-
try {
|
|
1618
|
-
// Log for debugging
|
|
1619
|
-
console.log(
|
|
1620
|
-
'🔔 Subscription data received for channel:',
|
|
1621
|
-
state?.context?.channelId,
|
|
1622
|
-
);
|
|
1623
|
-
|
|
1624
|
-
// Check if we have valid subscription data
|
|
1625
|
-
if (!subscriptionData?.data?.chatMessageAdded) {
|
|
1626
|
-
console.log('🔔 No valid message in subscription data');
|
|
1627
|
-
return prev;
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
const newMessage = subscriptionData.data.chatMessageAdded;
|
|
1631
|
-
console.log('🔔 New message received:', newMessage.id);
|
|
1632
|
-
|
|
1633
|
-
// Check if we already have this message to avoid duplicates
|
|
1634
|
-
const currentMessages = prev?.messages?.data || [];
|
|
1635
|
-
if (currentMessages.some((msg) => msg.id === newMessage.id)) {
|
|
1636
|
-
console.log('🔔 Message already in cache, skipping update');
|
|
1637
|
-
return prev;
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
// Capture the new message in state directly
|
|
1641
|
-
setTimeout(() => {
|
|
1642
|
-
try {
|
|
1643
|
-
// Update state machine directly
|
|
1644
|
-
console.log('🔔 Directly updating state machine with new message');
|
|
1645
|
-
safeSend({
|
|
1646
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
1647
|
-
data: {
|
|
1648
|
-
messages: [...state.context.channelMessages, newMessage],
|
|
1649
|
-
totalCount: (state.context.totalCount || 0) + 1,
|
|
1650
|
-
},
|
|
1651
|
-
});
|
|
1652
|
-
} catch (error) {
|
|
1653
|
-
console.error('🔔 Error updating state with subscription data:', error);
|
|
1654
|
-
}
|
|
1655
|
-
}, 0);
|
|
1656
|
-
|
|
1657
|
-
// Update Apollo cache
|
|
1658
|
-
const updatedData = {
|
|
1659
|
-
...prev,
|
|
1660
|
-
messages: {
|
|
1661
|
-
...prev.messages,
|
|
1662
|
-
data: [...currentMessages, newMessage],
|
|
1663
|
-
totalCount: (prev?.messages?.totalCount || 0) + 1,
|
|
1664
|
-
},
|
|
1665
|
-
};
|
|
1666
|
-
|
|
1667
|
-
console.log('🔔 Returning updated query data with new messages');
|
|
1668
|
-
return updatedData;
|
|
1669
|
-
} catch (error) {
|
|
1670
|
-
console.error('🔔 Error in subscription updateQuery:', error);
|
|
1671
|
-
return prev;
|
|
1672
|
-
}
|
|
1673
|
-
},
|
|
1674
|
-
})
|
|
1675
|
-
}
|
|
1676
|
-
/>
|
|
1677
|
-
</>
|
|
1678
|
-
);
|
|
1679
|
-
}, [
|
|
1680
|
-
isShowImageViewer,
|
|
1681
|
-
modalContent,
|
|
1682
|
-
state?.context?.channelId,
|
|
1683
|
-
state?.context?.channelMessages,
|
|
1684
|
-
subscribeToMore,
|
|
1685
|
-
safeSend,
|
|
1686
|
-
]);
|
|
1687
|
-
|
|
1688
|
-
// Add optimized listViewProps to reduce re-renders and improve list performance
|
|
1689
|
-
const listViewProps = useMemo(
|
|
1690
|
-
() => ({
|
|
1691
|
-
onEndReached: onEndReached,
|
|
1692
|
-
onEndReachedThreshold: 0.5,
|
|
1693
|
-
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
1694
|
-
removeClippedSubviews: true, // Improve performance by unmounting components when not visible
|
|
1695
|
-
initialNumToRender: 10, // Reduce initial render amount
|
|
1696
|
-
maxToRenderPerBatch: 7, // Reduce number in each render batch
|
|
1697
|
-
windowSize: 7, // Reduce the window size
|
|
1698
|
-
updateCellsBatchingPeriod: 50, // Batch cell updates to improve scrolling
|
|
1699
|
-
keyExtractor: (item) => item._id, // Add explicit key extractor
|
|
1700
|
-
}),
|
|
1701
|
-
[onEndReached, onMomentumScrollBegin],
|
|
1702
|
-
);
|
|
1703
|
-
|
|
1704
1365
|
// Add a loader for when more messages are being loaded
|
|
1705
1366
|
const renderLoadEarlier = useCallback(() => {
|
|
1706
|
-
return
|
|
1367
|
+
return loadingOldMessages ? (
|
|
1707
1368
|
<View
|
|
1708
1369
|
style={{
|
|
1709
1370
|
padding: 10,
|
|
@@ -1715,7 +1376,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1715
1376
|
<Spinner size="small" color="#3b82f6" />
|
|
1716
1377
|
</View>
|
|
1717
1378
|
) : null;
|
|
1718
|
-
}, [
|
|
1379
|
+
}, [loadingOldMessages]);
|
|
1719
1380
|
|
|
1720
1381
|
// Add renderInputToolbar function
|
|
1721
1382
|
const renderInputToolbar = useCallback((props) => {
|
|
@@ -1739,15 +1400,95 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1739
1400
|
);
|
|
1740
1401
|
}, []);
|
|
1741
1402
|
|
|
1403
|
+
// Create a memoized ImageViewerModal component
|
|
1404
|
+
const imageViewerModal = useMemo(
|
|
1405
|
+
() => (
|
|
1406
|
+
<ImageViewerModal isVisible={isShowImageViewer} setVisible={setImageViewer} modalContent={modalContent} />
|
|
1407
|
+
),
|
|
1408
|
+
[isShowImageViewer, modalContent],
|
|
1409
|
+
);
|
|
1410
|
+
|
|
1411
|
+
// Create a memoized subscription handler component
|
|
1412
|
+
const subscriptionHandler = useMemo(
|
|
1413
|
+
() => (
|
|
1414
|
+
<SubscriptionHandler
|
|
1415
|
+
channelId={channelId?.toString()}
|
|
1416
|
+
subscribeToNewMessages={() =>
|
|
1417
|
+
subscribeToMore({
|
|
1418
|
+
document: CHAT_MESSAGE_ADDED,
|
|
1419
|
+
variables: {
|
|
1420
|
+
channelId: channelId?.toString(),
|
|
1421
|
+
},
|
|
1422
|
+
// Let type policy handle the merge
|
|
1423
|
+
})
|
|
1424
|
+
}
|
|
1425
|
+
/>
|
|
1426
|
+
),
|
|
1427
|
+
[channelId, subscribeToMore, auth?.id],
|
|
1428
|
+
);
|
|
1429
|
+
|
|
1430
|
+
// Create a memoized renderChatFooter function
|
|
1431
|
+
const renderChatFooter = useCallback(() => {
|
|
1432
|
+
return (
|
|
1433
|
+
<>
|
|
1434
|
+
{imageViewerModal}
|
|
1435
|
+
{subscriptionHandler}
|
|
1436
|
+
</>
|
|
1437
|
+
);
|
|
1438
|
+
}, [imageViewerModal, subscriptionHandler]);
|
|
1439
|
+
|
|
1440
|
+
// Add optimized listViewProps to reduce re-renders and improve list performance
|
|
1441
|
+
const listViewProps = useMemo(
|
|
1442
|
+
() => ({
|
|
1443
|
+
onEndReached: onEndReached,
|
|
1444
|
+
onEndReachedThreshold: 0.5,
|
|
1445
|
+
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
1446
|
+
removeClippedSubviews: true, // Improve performance by unmounting components when not visible
|
|
1447
|
+
initialNumToRender: 10, // Reduce initial render amount
|
|
1448
|
+
maxToRenderPerBatch: 7, // Reduce number in each render batch
|
|
1449
|
+
windowSize: 7, // Reduce the window size
|
|
1450
|
+
updateCellsBatchingPeriod: 50, // Batch cell updates to improve scrolling
|
|
1451
|
+
keyExtractor: (item) => item._id, // Add explicit key extractor
|
|
1452
|
+
}),
|
|
1453
|
+
[onEndReached, onMomentumScrollBegin],
|
|
1454
|
+
);
|
|
1455
|
+
|
|
1456
|
+
// Debug helper function to inspect files in messages
|
|
1457
|
+
const debugFileData = useCallback((message: any, prefix: string = 'Message') => {
|
|
1458
|
+
if (__DEV__) {
|
|
1459
|
+
console.log(
|
|
1460
|
+
`${prefix} ID: ${message?.id}, ` +
|
|
1461
|
+
`Has files object: ${!!message?.files}, ` +
|
|
1462
|
+
`Files typename: ${message?.files?.__typename}, ` +
|
|
1463
|
+
`Files data exists: ${!!message?.files?.data}, ` +
|
|
1464
|
+
`Files count: ${message?.files?.data?.length || 0}`,
|
|
1465
|
+
);
|
|
1466
|
+
|
|
1467
|
+
if (message?.files?.data && message?.files?.data?.length > 0) {
|
|
1468
|
+
const file = message?.files?.data[0];
|
|
1469
|
+
console.log(
|
|
1470
|
+
`File[0] ID: ${file?.id}, ` +
|
|
1471
|
+
`URL: ${file?.url?.substring(0, 30)}..., ` +
|
|
1472
|
+
`Name: ${file?.name}, ` +
|
|
1473
|
+
`Type: ${file?.mimeType}`,
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}, []);
|
|
1478
|
+
|
|
1742
1479
|
// Return optimized component with performance improvements
|
|
1743
1480
|
return (
|
|
1744
1481
|
<View
|
|
1745
1482
|
style={{
|
|
1746
1483
|
flex: 1,
|
|
1747
1484
|
backgroundColor: 'white',
|
|
1485
|
+
position: 'relative',
|
|
1748
1486
|
}}
|
|
1749
1487
|
>
|
|
1750
|
-
{
|
|
1488
|
+
{messageLoading && <Spinner color={'#3b82f6'} />}
|
|
1489
|
+
|
|
1490
|
+
{/* Render the image preview directly in the container so it's properly positioned */}
|
|
1491
|
+
{selectedImage ? renderAccessory() : null}
|
|
1751
1492
|
|
|
1752
1493
|
<GiftedChat
|
|
1753
1494
|
ref={messageRootListRef}
|
|
@@ -1757,20 +1498,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1757
1498
|
listViewProps={{
|
|
1758
1499
|
...listViewProps,
|
|
1759
1500
|
contentContainerStyle: {
|
|
1760
|
-
paddingBottom:
|
|
1501
|
+
paddingBottom: selectedImage ? 90 : 0, // Add padding at the bottom when image is selected
|
|
1761
1502
|
},
|
|
1762
|
-
keyboardShouldPersistTaps: 'handled',
|
|
1763
1503
|
}}
|
|
1764
1504
|
onSend={handleSend}
|
|
1765
|
-
text={
|
|
1766
|
-
onInputTextChanged={(text) =>
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1769
|
-
renderFooter={() =>
|
|
1770
|
-
safeContextProperty('loading') || safeContextProperty('imageLoading') ? (
|
|
1771
|
-
<Spinner color={'#3b82f6'} />
|
|
1772
|
-
) : null
|
|
1773
|
-
}
|
|
1505
|
+
text={messageText || ' '}
|
|
1506
|
+
onInputTextChanged={(text) => setMessageText(text)}
|
|
1507
|
+
renderFooter={() => (loading && !images.length ? <Spinner color={'#3b82f6'} /> : null)}
|
|
1774
1508
|
scrollToBottom
|
|
1775
1509
|
user={{
|
|
1776
1510
|
_id: auth?.id || '',
|
|
@@ -1781,14 +1515,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1781
1515
|
renderMessageText={renderMessageText}
|
|
1782
1516
|
renderInputToolbar={renderInputToolbar}
|
|
1783
1517
|
minInputToolbarHeight={50}
|
|
1784
|
-
renderActions={
|
|
1785
|
-
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
1518
|
+
renderActions={channelId && renderActions}
|
|
1786
1519
|
renderMessage={renderMessage}
|
|
1787
1520
|
renderChatFooter={renderChatFooter}
|
|
1788
1521
|
renderLoadEarlier={renderLoadEarlier}
|
|
1789
|
-
loadEarlier={
|
|
1790
|
-
isLoadingEarlier={
|
|
1791
|
-
bottomOffset={Platform.OS === 'ios' ? 10 : 0} //
|
|
1522
|
+
loadEarlier={totalCount > channelMessages.length}
|
|
1523
|
+
isLoadingEarlier={loadingOldMessages}
|
|
1524
|
+
bottomOffset={Platform.OS === 'ios' ? (selectedImage ? 90 : 10) : 0} // Adjust bottom offset based on image preview
|
|
1792
1525
|
textInputProps={{
|
|
1793
1526
|
style: {
|
|
1794
1527
|
borderWidth: 1,
|
|
@@ -1832,12 +1565,9 @@ const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: ISubscriptio
|
|
|
1832
1565
|
useEffect(() => {
|
|
1833
1566
|
// Don't set up subscription if there's no channel ID
|
|
1834
1567
|
if (!channelId) {
|
|
1835
|
-
console.log('⚠️ No channel ID for subscription');
|
|
1836
1568
|
return;
|
|
1837
1569
|
}
|
|
1838
1570
|
|
|
1839
|
-
console.log('🔄 Setting up NEW subscription for channel:', channelId);
|
|
1840
|
-
|
|
1841
1571
|
// Call the subscribe function and store the unsubscribe function
|
|
1842
1572
|
const unsubscribe = subscribeToNewMessages();
|
|
1843
1573
|
|
|
@@ -1847,7 +1577,6 @@ const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: ISubscriptio
|
|
|
1847
1577
|
// Return cleanup function
|
|
1848
1578
|
return () => {
|
|
1849
1579
|
if (unsubscribe && typeof unsubscribe === 'function') {
|
|
1850
|
-
console.log('🔄 Cleaning up subscription for channel:', channelIdRef.current);
|
|
1851
1580
|
unsubscribe();
|
|
1852
1581
|
}
|
|
1853
1582
|
};
|