@messenger-box/platform-mobile 10.0.3-alpha.36 → 10.0.3-alpha.37
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 +4 -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 +75 -271
- 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 +478 -944
- 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 +104 -368
- 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 +660 -1060
- 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,241 @@ 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',
|
|
144
|
+
notifyOnNetworkStatusChange: true,
|
|
479
145
|
onCompleted: (queryData) => {
|
|
480
|
-
|
|
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
|
-
}
|
|
146
|
+
// MESSAGE QUERY COMPLETED
|
|
501
147
|
},
|
|
502
148
|
onError: (error) => {
|
|
503
|
-
|
|
504
|
-
// Report error to state machine
|
|
505
|
-
safeSend({ type: 'ERROR', data: { message: String(error) } });
|
|
149
|
+
setError(String(error));
|
|
506
150
|
},
|
|
507
151
|
});
|
|
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
152
|
|
|
517
|
-
|
|
518
|
-
|
|
153
|
+
// Extract messages from the query data
|
|
154
|
+
const channelMessages = useMemo(() => {
|
|
155
|
+
return data?.messages?.data || [];
|
|
156
|
+
}, [data?.messages?.data]);
|
|
519
157
|
|
|
520
|
-
|
|
521
|
-
|
|
158
|
+
// Get total message count
|
|
159
|
+
const totalCount = useMemo(() => {
|
|
160
|
+
return data?.messages?.totalCount || 0;
|
|
161
|
+
}, [data?.messages?.totalCount]);
|
|
522
162
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
163
|
+
// Clear messages when component unmounts
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
return () => {
|
|
166
|
+
isMounted.current = false;
|
|
167
|
+
};
|
|
168
|
+
}, []);
|
|
529
169
|
|
|
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 });
|
|
170
|
+
// Update channelId from props or navigation params
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
const currentChannelId = initialChannelId || currentRoute?.params?.channelId;
|
|
173
|
+
if (currentChannelId) {
|
|
174
|
+
setChannelId(currentChannelId);
|
|
175
|
+
}
|
|
176
|
+
}, [initialChannelId, currentRoute]);
|
|
177
|
+
|
|
178
|
+
// Focus/unfocus behavior
|
|
179
|
+
useFocusEffect(
|
|
180
|
+
React.useCallback(() => {
|
|
181
|
+
if (channelId) {
|
|
182
|
+
// Refresh messages when screen comes into focus
|
|
183
|
+
refetch();
|
|
547
184
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
185
|
+
return () => {
|
|
186
|
+
// Nothing needed on unfocus
|
|
187
|
+
};
|
|
188
|
+
}, [channelId, isFocused, refetch]),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Loading state for image selection
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (selectedImage) {
|
|
194
|
+
setLoading(false);
|
|
551
195
|
}
|
|
552
|
-
}, [
|
|
196
|
+
}, [selectedImage]);
|
|
553
197
|
|
|
198
|
+
// Fetch more messages function
|
|
554
199
|
const fetchMoreMessagesImpl = useCallback(async () => {
|
|
555
200
|
try {
|
|
201
|
+
setLoadingOldMessages(true);
|
|
556
202
|
const response = await fetchMoreMessages({
|
|
557
203
|
variables: {
|
|
558
|
-
channelId:
|
|
204
|
+
channelId: channelId?.toString(),
|
|
559
205
|
parentId: null,
|
|
560
|
-
skip:
|
|
206
|
+
skip: channelMessages.length,
|
|
561
207
|
},
|
|
208
|
+
// updateQuery: (prev, { fetchMoreResult }) => {
|
|
209
|
+
// if (!fetchMoreResult || !fetchMoreResult.messages) return prev;
|
|
210
|
+
|
|
211
|
+
// // Create a new array of all messages deduped by ID
|
|
212
|
+
// const combinedMessages = [...prev.messages.data, ...fetchMoreResult.messages.data].filter(
|
|
213
|
+
// (message, index, self) => index === self.findIndex((m) => m.id === message.id),
|
|
214
|
+
// );
|
|
215
|
+
|
|
216
|
+
// return {
|
|
217
|
+
// ...prev,
|
|
218
|
+
// messages: {
|
|
219
|
+
// ...prev.messages,
|
|
220
|
+
// data: combinedMessages,
|
|
221
|
+
// totalCount: fetchMoreResult.messages.totalCount,
|
|
222
|
+
// __typename: prev.messages.__typename,
|
|
223
|
+
// },
|
|
224
|
+
// };
|
|
225
|
+
// },
|
|
562
226
|
});
|
|
563
227
|
|
|
228
|
+
setLoadingOldMessages(false);
|
|
564
229
|
if (!response?.data?.messages?.data) {
|
|
565
230
|
return { error: 'No messages returned' };
|
|
566
231
|
}
|
|
567
232
|
|
|
568
233
|
return { messages: response.data.messages.data };
|
|
569
234
|
} catch (error) {
|
|
235
|
+
setLoadingOldMessages(false);
|
|
236
|
+
setError(String(error));
|
|
570
237
|
return { error: String(error) };
|
|
571
238
|
}
|
|
572
|
-
}, [
|
|
239
|
+
}, [channelId, channelMessages.length, fetchMoreMessages]);
|
|
573
240
|
|
|
241
|
+
// Send message function
|
|
574
242
|
const sendMessageImpl = useCallback(async () => {
|
|
575
243
|
try {
|
|
244
|
+
// Store the current message text and clear input immediately for better UX
|
|
245
|
+
const currentMessageText = messageText;
|
|
246
|
+
setMessageText('');
|
|
247
|
+
|
|
576
248
|
const notificationData: IExpoNotificationData = {
|
|
577
249
|
url: config.INBOX_MESSEGE_PATH,
|
|
578
|
-
params: { channelId
|
|
250
|
+
params: { channelId, hideTabBar: true },
|
|
579
251
|
screen: 'DialogMessages',
|
|
580
252
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
581
253
|
};
|
|
582
254
|
|
|
255
|
+
// Create optimistic message with consistent structure
|
|
256
|
+
const messageId = objectId();
|
|
257
|
+
const optimisticMessage = {
|
|
258
|
+
__typename: 'Post' as const,
|
|
259
|
+
id: messageId,
|
|
260
|
+
message: currentMessageText,
|
|
261
|
+
createdAt: new Date().toISOString(),
|
|
262
|
+
updatedAt: new Date().toISOString(),
|
|
263
|
+
author: {
|
|
264
|
+
__typename: 'UserAccount' as const,
|
|
265
|
+
id: auth?.id,
|
|
266
|
+
givenName: auth?.givenName || '',
|
|
267
|
+
familyName: auth?.familyName || '',
|
|
268
|
+
picture: auth?.picture || '',
|
|
269
|
+
username: auth?.username || '',
|
|
270
|
+
email: auth?.email || '',
|
|
271
|
+
alias: [] as string[],
|
|
272
|
+
tokens: [],
|
|
273
|
+
},
|
|
274
|
+
isDelivered: true,
|
|
275
|
+
isRead: false,
|
|
276
|
+
type: 'TEXT' as any,
|
|
277
|
+
parentId: null,
|
|
278
|
+
fromServer: false,
|
|
279
|
+
channel: {
|
|
280
|
+
__typename: 'Channel' as const,
|
|
281
|
+
id: channelId,
|
|
282
|
+
},
|
|
283
|
+
propsConfiguration: {
|
|
284
|
+
__typename: 'MachineConfiguration' as const,
|
|
285
|
+
resource: '' as any, // Cast to any to bypass the URI type check
|
|
286
|
+
},
|
|
287
|
+
props: {},
|
|
288
|
+
files: {
|
|
289
|
+
__typename: 'FilesInfo' as const,
|
|
290
|
+
data: [],
|
|
291
|
+
totalCount: 0,
|
|
292
|
+
},
|
|
293
|
+
replies: {
|
|
294
|
+
__typename: 'Messages' as const,
|
|
295
|
+
data: [],
|
|
296
|
+
totalCount: 0,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
583
300
|
const response = await sendMsg({
|
|
584
301
|
variables: {
|
|
585
|
-
channelId
|
|
586
|
-
content:
|
|
302
|
+
channelId,
|
|
303
|
+
content: currentMessageText,
|
|
587
304
|
notificationParams: notificationData,
|
|
588
305
|
},
|
|
306
|
+
optimisticResponse: {
|
|
307
|
+
__typename: 'Mutation',
|
|
308
|
+
sendMessage: optimisticMessage,
|
|
309
|
+
},
|
|
589
310
|
});
|
|
590
311
|
|
|
591
312
|
return { message: response.data?.sendMessage };
|
|
592
313
|
} catch (error) {
|
|
314
|
+
setLoading(false);
|
|
315
|
+
setError(String(error));
|
|
593
316
|
return { error: String(error) };
|
|
594
317
|
}
|
|
595
|
-
}, [
|
|
318
|
+
}, [channelId, messageText, sendMsg, auth]);
|
|
596
319
|
|
|
597
|
-
//
|
|
320
|
+
// Image selection handler
|
|
598
321
|
const onSelectImages = async () => {
|
|
599
|
-
|
|
322
|
+
setLoading(true);
|
|
600
323
|
|
|
601
324
|
try {
|
|
602
|
-
console.log('Starting image picker...');
|
|
603
325
|
let imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
604
326
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
605
327
|
allowsEditing: true,
|
|
@@ -610,22 +332,10 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
610
332
|
});
|
|
611
333
|
|
|
612
334
|
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
335
|
// Get the asset
|
|
625
336
|
const selectedAsset = imageSource?.assets?.[0];
|
|
626
337
|
if (!selectedAsset) {
|
|
627
|
-
|
|
628
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
338
|
+
setLoading(false);
|
|
629
339
|
return;
|
|
630
340
|
}
|
|
631
341
|
|
|
@@ -641,58 +351,49 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
641
351
|
mimeType: 'image/jpeg',
|
|
642
352
|
};
|
|
643
353
|
|
|
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
354
|
// 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');
|
|
355
|
+
setSelectedImage(previewImage);
|
|
356
|
+
setImages([asset]);
|
|
661
357
|
} else {
|
|
662
|
-
|
|
663
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
358
|
+
setLoading(false);
|
|
664
359
|
}
|
|
665
360
|
} catch (error) {
|
|
666
|
-
|
|
667
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
361
|
+
setLoading(false);
|
|
668
362
|
}
|
|
669
363
|
};
|
|
670
364
|
|
|
671
|
-
//
|
|
365
|
+
// Add a state variable to track which message should show the skeleton
|
|
366
|
+
const [uploadingMessageId, setUploadingMessageId] = useState<string | null>(null);
|
|
367
|
+
|
|
368
|
+
// Send message with file function - update to set and clear uploadingMessageId
|
|
672
369
|
const sendMessageWithFileImpl = useCallback(async () => {
|
|
673
370
|
try {
|
|
674
|
-
|
|
371
|
+
// For file uploads, we still need loading state since we need to wait for the file upload
|
|
372
|
+
setLoading(true);
|
|
675
373
|
|
|
676
374
|
// Generate a unique post ID for the message
|
|
677
375
|
const postId = objectId();
|
|
678
|
-
|
|
376
|
+
|
|
377
|
+
// Set the message ID that should show the skeleton
|
|
378
|
+
setUploadingMessageId(postId);
|
|
679
379
|
|
|
680
380
|
// Prepare notification data
|
|
681
381
|
const notificationData: IExpoNotificationData = {
|
|
682
382
|
url: config.INBOX_MESSEGE_PATH,
|
|
683
|
-
params: { channelId
|
|
383
|
+
params: { channelId, hideTabBar: true },
|
|
684
384
|
screen: 'DialogMessages',
|
|
685
385
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
686
386
|
};
|
|
687
387
|
|
|
688
388
|
// Safety check for images
|
|
689
|
-
if (!
|
|
690
|
-
|
|
389
|
+
if (!images || images.length === 0) {
|
|
390
|
+
setLoading(false);
|
|
391
|
+
setUploadingMessageId(null);
|
|
691
392
|
return { error: 'No images available to upload' };
|
|
692
393
|
}
|
|
693
394
|
|
|
694
395
|
// Format the images for upload if needed
|
|
695
|
-
const imagesToUpload =
|
|
396
|
+
const imagesToUpload = images.map((img) => {
|
|
696
397
|
// Ensure the image has all required properties
|
|
697
398
|
return {
|
|
698
399
|
...img,
|
|
@@ -702,20 +403,82 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
702
403
|
};
|
|
703
404
|
});
|
|
704
405
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
406
|
+
// Store current message text and clear inputs immediately for better UX
|
|
407
|
+
const currentMessageText = messageText;
|
|
408
|
+
setMessageText('');
|
|
409
|
+
|
|
410
|
+
// Create file info for optimistic response
|
|
411
|
+
const optimisticFileInfo = {
|
|
412
|
+
__typename: 'FileInfo' as const,
|
|
413
|
+
id: objectId(),
|
|
414
|
+
url: selectedImage,
|
|
415
|
+
name: imagesToUpload[0]?.name || 'image.jpg',
|
|
416
|
+
extension: 'jpg',
|
|
417
|
+
mimeType: 'image/jpeg',
|
|
418
|
+
size: 0,
|
|
419
|
+
refType: FileRefType.Post,
|
|
420
|
+
height: imagesToUpload[0]?.height || 0,
|
|
421
|
+
width: imagesToUpload[0]?.width || 0,
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Create optimistic message with file
|
|
425
|
+
const optimisticMessage = {
|
|
426
|
+
__typename: 'Post' as const,
|
|
427
|
+
id: postId,
|
|
428
|
+
message: currentMessageText || ' ',
|
|
429
|
+
createdAt: new Date().toISOString(),
|
|
430
|
+
updatedAt: new Date().toISOString(),
|
|
431
|
+
author: {
|
|
432
|
+
__typename: 'UserAccount' as const,
|
|
433
|
+
id: auth?.id,
|
|
434
|
+
givenName: auth?.givenName || '',
|
|
435
|
+
familyName: auth?.familyName || '',
|
|
436
|
+
picture: auth?.picture || '',
|
|
437
|
+
username: auth?.username || '',
|
|
438
|
+
email: auth?.email || '',
|
|
439
|
+
alias: [] as string[],
|
|
440
|
+
tokens: [],
|
|
441
|
+
},
|
|
442
|
+
isDelivered: true,
|
|
443
|
+
isRead: false,
|
|
444
|
+
type: 'TEXT' as any,
|
|
445
|
+
parentId: null,
|
|
446
|
+
fromServer: false,
|
|
447
|
+
channel: {
|
|
448
|
+
__typename: 'Channel' as const,
|
|
449
|
+
id: channelId,
|
|
450
|
+
},
|
|
451
|
+
propsConfiguration: {
|
|
452
|
+
__typename: 'MachineConfiguration' as const,
|
|
453
|
+
resource: '' as any, // Cast to any to bypass the URI type check
|
|
454
|
+
},
|
|
455
|
+
props: {},
|
|
456
|
+
files: {
|
|
457
|
+
__typename: 'FilesInfo' as const,
|
|
458
|
+
data: [
|
|
459
|
+
{
|
|
460
|
+
__typename: 'FileInfo' as const,
|
|
461
|
+
id: objectId(),
|
|
462
|
+
url: selectedImage,
|
|
463
|
+
name: imagesToUpload[0]?.name || 'image.jpg',
|
|
464
|
+
extension: 'jpg',
|
|
465
|
+
mimeType: 'image/jpeg',
|
|
466
|
+
size: 0,
|
|
467
|
+
refType: FileRefType.Post,
|
|
468
|
+
height: imagesToUpload[0]?.height || 0,
|
|
469
|
+
width: imagesToUpload[0]?.width || 0,
|
|
470
|
+
},
|
|
471
|
+
] as any,
|
|
472
|
+
totalCount: 1,
|
|
473
|
+
},
|
|
474
|
+
replies: {
|
|
475
|
+
__typename: 'Messages' as const,
|
|
476
|
+
data: [],
|
|
477
|
+
totalCount: 0,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
716
480
|
|
|
717
481
|
// Upload the files
|
|
718
|
-
console.log('Starting file upload...');
|
|
719
482
|
const uploadResponse = await startUpload({
|
|
720
483
|
file: imagesToUpload,
|
|
721
484
|
saveUploadedFile: {
|
|
@@ -726,89 +489,66 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
726
489
|
},
|
|
727
490
|
});
|
|
728
491
|
|
|
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
492
|
if (uploadResponse?.error) {
|
|
737
|
-
|
|
493
|
+
setLoading(false);
|
|
494
|
+
setUploadingMessageId(null);
|
|
738
495
|
return { error: String(uploadResponse.error) };
|
|
739
496
|
}
|
|
740
497
|
|
|
741
498
|
// Get uploaded file IDs
|
|
742
499
|
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
500
|
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
751
501
|
|
|
752
|
-
console.log('Files uploaded successfully. File IDs:', files);
|
|
753
|
-
|
|
754
502
|
// 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
503
|
const response = await sendMsg({
|
|
764
504
|
variables: {
|
|
765
505
|
postId,
|
|
766
|
-
channelId
|
|
767
|
-
content:
|
|
506
|
+
channelId,
|
|
507
|
+
content: currentMessageText || ' ', // Use a space if no text
|
|
768
508
|
files,
|
|
769
509
|
notificationParams: notificationData,
|
|
770
510
|
},
|
|
511
|
+
optimisticResponse: {
|
|
512
|
+
__typename: 'Mutation',
|
|
513
|
+
sendMessage: optimisticMessage,
|
|
514
|
+
},
|
|
771
515
|
});
|
|
772
516
|
|
|
773
517
|
if (response?.data?.sendMessage) {
|
|
774
|
-
console.log('Message with file sent successfully:', response.data.sendMessage.id);
|
|
775
|
-
|
|
776
|
-
// Log the file data from the response to verify it's being returned correctly
|
|
777
|
-
if (response.data.sendMessage.files?.data) {
|
|
778
|
-
console.log(
|
|
779
|
-
'📷 Message response file data:',
|
|
780
|
-
JSON.stringify({
|
|
781
|
-
fileCount: response.data.sendMessage.files.data.length,
|
|
782
|
-
fileUrl: response.data.sendMessage.files.data[0]?.url?.substring(0, 30) + '...',
|
|
783
|
-
}),
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
518
|
// Clear the images after successful send
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
}, 100);
|
|
791
|
-
} else {
|
|
792
|
-
console.error('Failed to send message with file:', response?.errors);
|
|
519
|
+
setSelectedImage('');
|
|
520
|
+
setImages([]);
|
|
793
521
|
}
|
|
794
522
|
|
|
523
|
+
setLoading(false);
|
|
524
|
+
setUploadingMessageId(null);
|
|
795
525
|
return { message: response.data?.sendMessage };
|
|
796
526
|
} catch (error) {
|
|
797
|
-
|
|
527
|
+
setLoading(false);
|
|
528
|
+
setUploadingMessageId(null);
|
|
529
|
+
setError(String(error));
|
|
798
530
|
return { error: String(error) };
|
|
799
531
|
}
|
|
800
|
-
}, [
|
|
532
|
+
}, [channelId, messageText, images, selectedImage, startUpload, sendMsg, auth]);
|
|
801
533
|
|
|
534
|
+
// Create direct channel implementation
|
|
802
535
|
const createDirectChannelImpl = useCallback(async () => {
|
|
803
536
|
try {
|
|
537
|
+
setLoading(true);
|
|
804
538
|
if (
|
|
805
539
|
!rest?.isCreateNewChannel ||
|
|
806
540
|
rest?.newChannelData?.type !== RoomType?.Direct ||
|
|
807
541
|
!rest?.newChannelData?.userIds?.length
|
|
808
542
|
) {
|
|
543
|
+
setLoading(false);
|
|
809
544
|
return { error: 'Invalid channel data' };
|
|
810
545
|
}
|
|
811
546
|
|
|
547
|
+
// Store current message text
|
|
548
|
+
const currentMessageText = messageText;
|
|
549
|
+
// Clear message text immediately for better UX
|
|
550
|
+
setMessageText('');
|
|
551
|
+
|
|
812
552
|
const response = await addDirectChannel({
|
|
813
553
|
variables: {
|
|
814
554
|
receiver: [...(rest?.newChannelData?.userIds ?? [])],
|
|
@@ -817,10 +557,12 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
817
557
|
});
|
|
818
558
|
|
|
819
559
|
if (!response?.data?.createDirectChannel?.id) {
|
|
560
|
+
setLoading(false);
|
|
820
561
|
return { error: 'Failed to create channel' };
|
|
821
562
|
}
|
|
822
563
|
|
|
823
564
|
const newChannelId = response.data.createDirectChannel.id;
|
|
565
|
+
setChannelId(newChannelId);
|
|
824
566
|
|
|
825
567
|
const notificationData: IExpoNotificationData = {
|
|
826
568
|
url: config.INBOX_MESSEGE_PATH,
|
|
@@ -829,113 +571,74 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
829
571
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
830
572
|
};
|
|
831
573
|
|
|
574
|
+
// Create unique message ID for optimistic response
|
|
575
|
+
const messageId = objectId();
|
|
576
|
+
|
|
577
|
+
// Fix the createDirectChannelImpl optimisticMessage with all required fields
|
|
578
|
+
const optimisticMessage = {
|
|
579
|
+
__typename: 'Post' as const,
|
|
580
|
+
id: messageId,
|
|
581
|
+
message: currentMessageText,
|
|
582
|
+
createdAt: new Date().toISOString(),
|
|
583
|
+
updatedAt: new Date().toISOString(),
|
|
584
|
+
author: {
|
|
585
|
+
__typename: 'UserAccount' as const,
|
|
586
|
+
id: auth?.id,
|
|
587
|
+
givenName: auth?.givenName || '',
|
|
588
|
+
familyName: auth?.familyName || '',
|
|
589
|
+
picture: auth?.picture || '',
|
|
590
|
+
username: auth?.username || '',
|
|
591
|
+
email: auth?.email || '',
|
|
592
|
+
alias: [] as string[],
|
|
593
|
+
tokens: [],
|
|
594
|
+
},
|
|
595
|
+
isDelivered: true,
|
|
596
|
+
isRead: false,
|
|
597
|
+
type: 'TEXT' as any,
|
|
598
|
+
parentId: null,
|
|
599
|
+
fromServer: false,
|
|
600
|
+
channel: {
|
|
601
|
+
__typename: 'Channel' as const,
|
|
602
|
+
id: newChannelId,
|
|
603
|
+
},
|
|
604
|
+
propsConfiguration: {
|
|
605
|
+
__typename: 'MachineConfiguration' as const,
|
|
606
|
+
resource: '' as any, // Cast to any to bypass the URI type check
|
|
607
|
+
},
|
|
608
|
+
props: {},
|
|
609
|
+
files: {
|
|
610
|
+
__typename: 'FilesInfo' as const,
|
|
611
|
+
data: [],
|
|
612
|
+
totalCount: 0,
|
|
613
|
+
},
|
|
614
|
+
replies: {
|
|
615
|
+
__typename: 'Messages' as const,
|
|
616
|
+
data: [],
|
|
617
|
+
totalCount: 0,
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// Send message in the new channel
|
|
832
622
|
await sendMsg({
|
|
833
623
|
variables: {
|
|
834
624
|
channelId: newChannelId,
|
|
835
|
-
content:
|
|
625
|
+
content: currentMessageText,
|
|
836
626
|
notificationParams: notificationData,
|
|
837
627
|
},
|
|
628
|
+
optimisticResponse: {
|
|
629
|
+
__typename: 'Mutation',
|
|
630
|
+
sendMessage: optimisticMessage,
|
|
631
|
+
},
|
|
838
632
|
});
|
|
839
633
|
|
|
634
|
+
setLoading(false);
|
|
840
635
|
return { channelId: newChannelId };
|
|
841
636
|
} catch (error) {
|
|
637
|
+
setLoading(false);
|
|
638
|
+
setError(String(error));
|
|
842
639
|
return { error: String(error) };
|
|
843
640
|
}
|
|
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]);
|
|
641
|
+
}, [rest, messageText, addDirectChannel, sendMsg, auth]);
|
|
939
642
|
|
|
940
643
|
// Optimize onFetchOld by adding debounce logic
|
|
941
644
|
const onFetchOld = useCallback(() => {
|
|
@@ -943,49 +646,80 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
943
646
|
if (fetchOldDebounceRef.current) return;
|
|
944
647
|
|
|
945
648
|
// Check if we need to fetch more messages
|
|
946
|
-
if (
|
|
947
|
-
state?.context?.totalCount > state?.context?.channelMessages?.length &&
|
|
948
|
-
!state?.context?.loadingOldMessages
|
|
949
|
-
) {
|
|
649
|
+
if (totalCount > channelMessages.length && !loadingOldMessages) {
|
|
950
650
|
// Set debounce
|
|
951
651
|
fetchOldDebounceRef.current = true;
|
|
952
652
|
|
|
953
|
-
//
|
|
954
|
-
|
|
653
|
+
// Fetch more messages
|
|
654
|
+
fetchMoreMessagesImpl();
|
|
955
655
|
|
|
956
656
|
// Clear debounce after a timeout
|
|
957
657
|
setTimeout(() => {
|
|
958
658
|
fetchOldDebounceRef.current = false;
|
|
959
659
|
}, 1000);
|
|
960
660
|
}
|
|
961
|
-
}, [
|
|
962
|
-
|
|
963
|
-
// Add debounce ref
|
|
964
|
-
const fetchOldDebounceRef = useRef(false);
|
|
661
|
+
}, [totalCount, channelMessages.length, loadingOldMessages, fetchMoreMessagesImpl]);
|
|
965
662
|
|
|
966
663
|
const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
967
664
|
const paddingToTop = 60;
|
|
968
665
|
return contentSize.height - layoutMeasurement.height - paddingToTop <= contentOffset.y;
|
|
969
666
|
};
|
|
970
667
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
u8arr = new Uint8Array(n);
|
|
977
|
-
while (n--) {
|
|
978
|
-
u8arr[n] = bstr.charCodeAt(n);
|
|
668
|
+
// Transform the message data for GiftedChat
|
|
669
|
+
const messageList = useMemo(() => {
|
|
670
|
+
// Short-circuit if no messages to process
|
|
671
|
+
if (!channelMessages || channelMessages.length === 0) {
|
|
672
|
+
return [];
|
|
979
673
|
}
|
|
980
|
-
return new File([u8arr], filename, { type: mime });
|
|
981
|
-
};
|
|
982
674
|
|
|
983
|
-
|
|
675
|
+
// Use a more efficient approach - pre-filter messages once
|
|
676
|
+
const filteredMessages = uniqBy(channelMessages, ({ id }) => id);
|
|
677
|
+
|
|
678
|
+
// Skip processing if no filtered messages
|
|
679
|
+
if (filteredMessages.length === 0) {
|
|
680
|
+
return [];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Transform messages only once and return
|
|
684
|
+
return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
685
|
+
const date = new Date(msg.createdAt);
|
|
686
|
+
|
|
687
|
+
// Extract image URL from files data
|
|
688
|
+
let imageUrl = null;
|
|
689
|
+
if (msg.files?.data && msg.files.data.length > 0) {
|
|
690
|
+
const fileData = msg.files.data[0];
|
|
691
|
+
if (fileData && fileData.url) {
|
|
692
|
+
imageUrl = fileData.url;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Create message in a more direct way
|
|
697
|
+
return {
|
|
698
|
+
_id: msg.id,
|
|
699
|
+
text: msg.message,
|
|
700
|
+
createdAt: date,
|
|
701
|
+
user: {
|
|
702
|
+
_id: msg.author?.id || '',
|
|
703
|
+
name: `${msg.author?.givenName || ''} ${msg.author?.familyName || ''}`,
|
|
704
|
+
avatar: msg.author?.picture || '',
|
|
705
|
+
},
|
|
706
|
+
image: imageUrl,
|
|
707
|
+
sent: msg?.isDelivered,
|
|
708
|
+
received: msg?.isRead,
|
|
709
|
+
type: msg?.type,
|
|
710
|
+
propsConfiguration: msg?.propsConfiguration,
|
|
711
|
+
replies: msg?.replies ?? [],
|
|
712
|
+
isShowThreadMessage,
|
|
713
|
+
};
|
|
714
|
+
});
|
|
715
|
+
}, [channelMessages, isShowThreadMessage]);
|
|
716
|
+
|
|
717
|
+
// Render the send button
|
|
984
718
|
const renderSend = useCallback(
|
|
985
719
|
(props) => {
|
|
986
720
|
// Enable the send button if there's text OR we have images
|
|
987
|
-
const hasContent = !!props.text ||
|
|
988
|
-
const canSend = (
|
|
721
|
+
const hasContent = !!props.text || images?.length > 0;
|
|
722
|
+
const canSend = (channelId || rest?.isCreateNewChannel) && hasContent;
|
|
989
723
|
|
|
990
724
|
return (
|
|
991
725
|
<Send
|
|
@@ -1011,157 +745,58 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1011
745
|
</Send>
|
|
1012
746
|
);
|
|
1013
747
|
},
|
|
1014
|
-
[
|
|
748
|
+
[channelId, images, rest?.isCreateNewChannel],
|
|
1015
749
|
);
|
|
1016
750
|
|
|
1017
|
-
//
|
|
751
|
+
// Handle send for messages
|
|
1018
752
|
const handleSend = useCallback(
|
|
1019
753
|
async (messages) => {
|
|
1020
754
|
// Extract message text from GiftedChat messages array
|
|
1021
|
-
const
|
|
1022
|
-
console.log('Sending message:', messageText);
|
|
1023
|
-
console.log('Images:', state.context.images?.length);
|
|
755
|
+
const newMessageText = messages && messages.length > 0 ? messages[0]?.text || ' ' : ' ';
|
|
1024
756
|
|
|
1025
757
|
// Check if we can send a message (channel exists or we're creating one)
|
|
1026
|
-
if (!
|
|
1027
|
-
console.log('Cannot send - no channel');
|
|
758
|
+
if (!channelId && !rest?.isCreateNewChannel) {
|
|
1028
759
|
return;
|
|
1029
760
|
}
|
|
1030
761
|
|
|
1031
762
|
// Allow sending if we have text OR images (image-only messages are valid)
|
|
1032
|
-
const hasText = !!
|
|
1033
|
-
const hasImages =
|
|
763
|
+
const hasText = !!newMessageText && newMessageText !== ' ';
|
|
764
|
+
const hasImages = images && images.length > 0;
|
|
1034
765
|
|
|
1035
766
|
if (!hasText && !hasImages) {
|
|
1036
|
-
console.log('Nothing to send - no text or images');
|
|
1037
767
|
return;
|
|
1038
768
|
}
|
|
1039
769
|
|
|
1040
|
-
//
|
|
1041
|
-
|
|
770
|
+
// Update the message text state - now handled in send functions for better UX
|
|
771
|
+
setMessageText(newMessageText);
|
|
1042
772
|
|
|
1043
773
|
// Handle direct channel creation if needed
|
|
1044
|
-
if (rest?.isCreateNewChannel && !
|
|
774
|
+
if (rest?.isCreateNewChannel && !channelId) {
|
|
1045
775
|
if (rest?.newChannelData?.type === RoomType?.Direct) {
|
|
1046
|
-
|
|
776
|
+
createDirectChannelImpl();
|
|
1047
777
|
}
|
|
1048
778
|
return;
|
|
1049
779
|
}
|
|
1050
780
|
|
|
1051
781
|
// Send message with or without image based on state
|
|
1052
782
|
if (hasImages) {
|
|
1053
|
-
|
|
1054
|
-
safeSend({ type: ConversationActions.SEND_MESSAGE_WITH_FILE });
|
|
783
|
+
sendMessageWithFileImpl();
|
|
1055
784
|
} else {
|
|
1056
|
-
|
|
1057
|
-
safeSend({ type: ConversationActions.SEND_MESSAGE });
|
|
785
|
+
sendMessageImpl();
|
|
1058
786
|
}
|
|
1059
787
|
},
|
|
1060
|
-
[
|
|
788
|
+
[
|
|
789
|
+
channelId,
|
|
790
|
+
images,
|
|
791
|
+
rest?.isCreateNewChannel,
|
|
792
|
+
rest?.newChannelData?.type,
|
|
793
|
+
createDirectChannelImpl,
|
|
794
|
+
sendMessageWithFileImpl,
|
|
795
|
+
sendMessageImpl,
|
|
796
|
+
],
|
|
1061
797
|
);
|
|
1062
798
|
|
|
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
|
|
799
|
+
// Render message text with customizations for alerts and replies
|
|
1165
800
|
const renderMessageText = useCallback(
|
|
1166
801
|
(props: any) => {
|
|
1167
802
|
const { currentMessage } = props;
|
|
@@ -1226,7 +861,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1226
861
|
onPress={() => {
|
|
1227
862
|
if (currentMessage?.isShowThreadMessage)
|
|
1228
863
|
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1229
|
-
channelId:
|
|
864
|
+
channelId: channelId,
|
|
1230
865
|
title: 'Message',
|
|
1231
866
|
postParentId: currentMessage?._id,
|
|
1232
867
|
isPostParentIdThread: true,
|
|
@@ -1291,7 +926,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1291
926
|
onPress={() => {
|
|
1292
927
|
if (currentMessage?.isShowThreadMessage)
|
|
1293
928
|
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1294
|
-
channelId:
|
|
929
|
+
channelId: channelId,
|
|
1295
930
|
title: 'Message',
|
|
1296
931
|
postParentId: currentMessage?._id,
|
|
1297
932
|
isPostParentIdThread: true,
|
|
@@ -1343,9 +978,10 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1343
978
|
);
|
|
1344
979
|
}
|
|
1345
980
|
},
|
|
1346
|
-
[navigation,
|
|
981
|
+
[navigation, channelId, role],
|
|
1347
982
|
);
|
|
1348
983
|
|
|
984
|
+
// Render action buttons (including image upload)
|
|
1349
985
|
const renderActions = (props) => {
|
|
1350
986
|
return (
|
|
1351
987
|
<Actions
|
|
@@ -1379,79 +1015,115 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1379
1015
|
};
|
|
1380
1016
|
|
|
1381
1017
|
// 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
|
-
}
|
|
1018
|
+
const renderAccessory = useCallback(() => {
|
|
1019
|
+
if (!selectedImage) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1389
1022
|
|
|
1390
|
-
|
|
1023
|
+
return (
|
|
1024
|
+
<View
|
|
1025
|
+
style={{
|
|
1026
|
+
height: 70,
|
|
1027
|
+
backgroundColor: 'white',
|
|
1028
|
+
borderTopWidth: 1,
|
|
1029
|
+
borderTopColor: '#e0e0e0',
|
|
1030
|
+
flexDirection: 'row',
|
|
1031
|
+
alignItems: 'center',
|
|
1032
|
+
margin: 0,
|
|
1033
|
+
padding: 0,
|
|
1034
|
+
paddingVertical: 0,
|
|
1035
|
+
position: 'absolute',
|
|
1036
|
+
bottom: Platform.OS === 'ios' ? 105 : 95, // Position well above the input area
|
|
1037
|
+
left: 0,
|
|
1038
|
+
right: 0,
|
|
1039
|
+
zIndex: 1,
|
|
1040
|
+
elevation: 3,
|
|
1041
|
+
shadowColor: '#000',
|
|
1042
|
+
shadowOffset: { width: 0, height: -1 },
|
|
1043
|
+
shadowOpacity: 0.05,
|
|
1044
|
+
shadowRadius: 2,
|
|
1045
|
+
}}
|
|
1046
|
+
>
|
|
1391
1047
|
<View
|
|
1392
1048
|
style={{
|
|
1393
|
-
|
|
1394
|
-
padding: 3,
|
|
1395
|
-
backgroundColor: 'white',
|
|
1396
|
-
borderTopWidth: 1,
|
|
1397
|
-
borderTopColor: '#e0e0e0',
|
|
1049
|
+
flex: 1,
|
|
1398
1050
|
flexDirection: 'row',
|
|
1399
1051
|
alignItems: 'center',
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
paddingTop: 5,
|
|
1403
|
-
position: 'absolute',
|
|
1404
|
-
bottom: 0,
|
|
1405
|
-
left: 0,
|
|
1406
|
-
right: 0,
|
|
1407
|
-
zIndex: 999,
|
|
1052
|
+
paddingLeft: 15,
|
|
1053
|
+
paddingRight: 5,
|
|
1408
1054
|
}}
|
|
1409
1055
|
>
|
|
1410
1056
|
<View
|
|
1411
1057
|
style={{
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1058
|
+
width: 56,
|
|
1059
|
+
height: 56,
|
|
1060
|
+
marginRight: 15,
|
|
1061
|
+
borderRadius: 4,
|
|
1062
|
+
backgroundColor: colors.gray[200],
|
|
1063
|
+
overflow: 'hidden',
|
|
1064
|
+
borderWidth: 1,
|
|
1065
|
+
borderColor: '#e0e0e0',
|
|
1416
1066
|
}}
|
|
1417
1067
|
>
|
|
1418
1068
|
<Image
|
|
1419
|
-
key={
|
|
1069
|
+
key={selectedImage}
|
|
1420
1070
|
alt={'selected image'}
|
|
1421
|
-
source={{ uri:
|
|
1071
|
+
source={{ uri: selectedImage }}
|
|
1422
1072
|
style={{
|
|
1423
|
-
width:
|
|
1424
|
-
height:
|
|
1425
|
-
borderRadius: 5,
|
|
1426
|
-
marginRight: 15,
|
|
1073
|
+
width: '100%',
|
|
1074
|
+
height: '100%',
|
|
1427
1075
|
}}
|
|
1428
|
-
size={'
|
|
1076
|
+
size={'md'}
|
|
1429
1077
|
/>
|
|
1078
|
+
{loading && (
|
|
1079
|
+
<View
|
|
1080
|
+
style={{
|
|
1081
|
+
position: 'absolute',
|
|
1082
|
+
top: 0,
|
|
1083
|
+
left: 0,
|
|
1084
|
+
right: 0,
|
|
1085
|
+
bottom: 0,
|
|
1086
|
+
backgroundColor: 'rgba(255, 255, 255, 0.7)',
|
|
1087
|
+
justifyContent: 'center',
|
|
1088
|
+
alignItems: 'center',
|
|
1089
|
+
}}
|
|
1090
|
+
>
|
|
1091
|
+
<Spinner size="small" color={colors.blue[500]} />
|
|
1092
|
+
</View>
|
|
1093
|
+
)}
|
|
1094
|
+
</View>
|
|
1430
1095
|
|
|
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>
|
|
1096
|
+
<View style={{ flex: 1 }}>
|
|
1097
|
+
<Text style={{ fontSize: 14, fontWeight: '400', color: colors.gray[800] }}>
|
|
1098
|
+
{images[0]?.fileName || 'image_' + new Date().getTime() + '.jpg'}
|
|
1099
|
+
</Text>
|
|
1100
|
+
<Text style={{ fontSize: 12, color: colors.gray[500], marginTop: 2 }}>
|
|
1101
|
+
{loading ? 'Preparing...' : 'Ready to send'}
|
|
1102
|
+
</Text>
|
|
1449
1103
|
</View>
|
|
1104
|
+
|
|
1105
|
+
<TouchableHighlight
|
|
1106
|
+
underlayColor={'rgba(0,0,0,0.1)'}
|
|
1107
|
+
onPress={() => {
|
|
1108
|
+
setSelectedImage('');
|
|
1109
|
+
setImages([]);
|
|
1110
|
+
}}
|
|
1111
|
+
style={{
|
|
1112
|
+
backgroundColor: colors.red[500],
|
|
1113
|
+
borderRadius: 24,
|
|
1114
|
+
width: 36,
|
|
1115
|
+
height: 36,
|
|
1116
|
+
alignItems: 'center',
|
|
1117
|
+
justifyContent: 'center',
|
|
1118
|
+
marginRight: 10,
|
|
1119
|
+
}}
|
|
1120
|
+
>
|
|
1121
|
+
<Ionicons name="close" size={20} color="white" />
|
|
1122
|
+
</TouchableHighlight>
|
|
1450
1123
|
</View>
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
);
|
|
1124
|
+
</View>
|
|
1125
|
+
);
|
|
1126
|
+
}, [selectedImage, loading, images]);
|
|
1455
1127
|
|
|
1456
1128
|
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
1457
1129
|
setImageObject(obj);
|
|
@@ -1459,13 +1131,14 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1459
1131
|
};
|
|
1460
1132
|
|
|
1461
1133
|
const modalContent = React.useMemo(() => {
|
|
1462
|
-
if (!imageObject) return
|
|
1134
|
+
if (!imageObject || !imageObject.image) return null;
|
|
1463
1135
|
const { image, _id } = imageObject;
|
|
1136
|
+
|
|
1464
1137
|
return (
|
|
1465
1138
|
<CachedImage
|
|
1466
1139
|
style={{ width: '100%', height: '100%' }}
|
|
1467
1140
|
resizeMode={'cover'}
|
|
1468
|
-
cacheKey={`${_id}-
|
|
1141
|
+
cacheKey={`${_id}-modal-imageKey`}
|
|
1469
1142
|
source={{
|
|
1470
1143
|
uri: image,
|
|
1471
1144
|
expiresIn: 86400,
|
|
@@ -1475,14 +1148,60 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1475
1148
|
);
|
|
1476
1149
|
}, [imageObject]);
|
|
1477
1150
|
|
|
1151
|
+
// Create a skeleton component for message bubbles with images
|
|
1478
1152
|
const renderMessage = useCallback(
|
|
1479
1153
|
(props: any) => {
|
|
1154
|
+
// Check if this message ID matches the uploading message ID
|
|
1155
|
+
const isUploading = props.currentMessage._id === uploadingMessageId && loading;
|
|
1156
|
+
|
|
1157
|
+
if (isUploading && props.currentMessage.image) {
|
|
1158
|
+
// Return a custom message skeleton during upload
|
|
1159
|
+
return (
|
|
1160
|
+
<View
|
|
1161
|
+
style={{
|
|
1162
|
+
padding: 10,
|
|
1163
|
+
marginBottom: 10,
|
|
1164
|
+
marginRight: 10,
|
|
1165
|
+
alignSelf: 'flex-end',
|
|
1166
|
+
borderRadius: 15,
|
|
1167
|
+
backgroundColor: colors.gray[100],
|
|
1168
|
+
maxWidth: '80%',
|
|
1169
|
+
}}
|
|
1170
|
+
>
|
|
1171
|
+
{props.currentMessage.text && props.currentMessage.text.trim() !== '' && (
|
|
1172
|
+
<Box
|
|
1173
|
+
style={{
|
|
1174
|
+
height: 15,
|
|
1175
|
+
borderRadius: 4,
|
|
1176
|
+
backgroundColor: colors.gray[200],
|
|
1177
|
+
overflow: 'hidden',
|
|
1178
|
+
marginBottom: 8,
|
|
1179
|
+
}}
|
|
1180
|
+
>
|
|
1181
|
+
<Skeleton variant="rounded" style={{ flex: 1 }} />
|
|
1182
|
+
</Box>
|
|
1183
|
+
)}
|
|
1184
|
+
<Box
|
|
1185
|
+
style={{
|
|
1186
|
+
height: 150,
|
|
1187
|
+
width: 150,
|
|
1188
|
+
borderRadius: 10,
|
|
1189
|
+
backgroundColor: colors.gray[200],
|
|
1190
|
+
overflow: 'hidden',
|
|
1191
|
+
}}
|
|
1192
|
+
>
|
|
1193
|
+
<Skeleton variant="rounded" style={{ flex: 1 }} />
|
|
1194
|
+
</Box>
|
|
1195
|
+
</View>
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1480
1199
|
// Use memo to prevent unnecessary re-renders of each message
|
|
1481
1200
|
return (
|
|
1482
1201
|
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
1483
1202
|
);
|
|
1484
1203
|
},
|
|
1485
|
-
[isShowImageViewer],
|
|
1204
|
+
[isShowImageViewer, uploadingMessageId, loading],
|
|
1486
1205
|
);
|
|
1487
1206
|
|
|
1488
1207
|
let onScroll = false;
|
|
@@ -1499,211 +1218,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1499
1218
|
};
|
|
1500
1219
|
|
|
1501
1220
|
const onEndReached = () => {
|
|
1502
|
-
console.log('on end reached');
|
|
1503
1221
|
if (!onScroll) return;
|
|
1504
1222
|
onScroll = false;
|
|
1505
1223
|
};
|
|
1506
1224
|
|
|
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
1225
|
// Add a loader for when more messages are being loaded
|
|
1705
1226
|
const renderLoadEarlier = useCallback(() => {
|
|
1706
|
-
return
|
|
1227
|
+
return loadingOldMessages ? (
|
|
1707
1228
|
<View
|
|
1708
1229
|
style={{
|
|
1709
1230
|
padding: 10,
|
|
@@ -1715,7 +1236,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1715
1236
|
<Spinner size="small" color="#3b82f6" />
|
|
1716
1237
|
</View>
|
|
1717
1238
|
) : null;
|
|
1718
|
-
}, [
|
|
1239
|
+
}, [loadingOldMessages]);
|
|
1719
1240
|
|
|
1720
1241
|
// Add renderInputToolbar function
|
|
1721
1242
|
const renderInputToolbar = useCallback((props) => {
|
|
@@ -1739,15 +1260,106 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1739
1260
|
);
|
|
1740
1261
|
}, []);
|
|
1741
1262
|
|
|
1263
|
+
// Create a memoized ImageViewerModal component
|
|
1264
|
+
const imageViewerModal = useMemo(
|
|
1265
|
+
() => (
|
|
1266
|
+
<ImageViewerModal isVisible={isShowImageViewer} setVisible={setImageViewer} modalContent={modalContent} />
|
|
1267
|
+
),
|
|
1268
|
+
[isShowImageViewer, modalContent],
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
// Create a memoized subscription handler component
|
|
1272
|
+
const subscriptionHandler = useMemo(
|
|
1273
|
+
() => (
|
|
1274
|
+
<SubscriptionHandler
|
|
1275
|
+
channelId={channelId?.toString()}
|
|
1276
|
+
subscribeToNewMessages={() =>
|
|
1277
|
+
subscribeToMore({
|
|
1278
|
+
document: CHAT_MESSAGE_ADDED,
|
|
1279
|
+
variables: {
|
|
1280
|
+
channelId: channelId?.toString(),
|
|
1281
|
+
},
|
|
1282
|
+
// updateQuery: (prev, { subscriptionData }: any) => {
|
|
1283
|
+
// try {
|
|
1284
|
+
// // Check if we have valid subscription data
|
|
1285
|
+
// if (!subscriptionData?.data?.chatMessageAdded) {
|
|
1286
|
+
// return prev;
|
|
1287
|
+
// }
|
|
1288
|
+
|
|
1289
|
+
// const newMessage = subscriptionData.data.chatMessageAdded;
|
|
1290
|
+
|
|
1291
|
+
// // Check if message is from current user - skip update as it's handled by optimistic UI
|
|
1292
|
+
// if (newMessage.author?.id === auth?.id) {
|
|
1293
|
+
// return prev;
|
|
1294
|
+
// }
|
|
1295
|
+
|
|
1296
|
+
// // Check if we already have this message to avoid duplicates
|
|
1297
|
+
// const currentMessages = prev?.messages?.data || [];
|
|
1298
|
+
// if (currentMessages.some((msg) => msg.id === newMessage.id)) {
|
|
1299
|
+
// return prev;
|
|
1300
|
+
// }
|
|
1301
|
+
|
|
1302
|
+
// // Update Apollo cache
|
|
1303
|
+
// const updatedData = {
|
|
1304
|
+
// ...prev,
|
|
1305
|
+
// messages: {
|
|
1306
|
+
// ...prev.messages,
|
|
1307
|
+
// data: [newMessage, ...currentMessages],
|
|
1308
|
+
// totalCount: (prev?.messages?.totalCount || 0) + 1,
|
|
1309
|
+
// },
|
|
1310
|
+
// };
|
|
1311
|
+
|
|
1312
|
+
// return updatedData;
|
|
1313
|
+
// } catch (error) {
|
|
1314
|
+
// return prev;
|
|
1315
|
+
// }
|
|
1316
|
+
// },
|
|
1317
|
+
})
|
|
1318
|
+
}
|
|
1319
|
+
/>
|
|
1320
|
+
),
|
|
1321
|
+
[channelId, subscribeToMore, auth?.id],
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
// Create a memoized renderChatFooter function
|
|
1325
|
+
const renderChatFooter = useCallback(() => {
|
|
1326
|
+
return (
|
|
1327
|
+
<>
|
|
1328
|
+
{imageViewerModal}
|
|
1329
|
+
{subscriptionHandler}
|
|
1330
|
+
</>
|
|
1331
|
+
);
|
|
1332
|
+
}, [imageViewerModal, subscriptionHandler]);
|
|
1333
|
+
|
|
1334
|
+
// Add optimized listViewProps to reduce re-renders and improve list performance
|
|
1335
|
+
const listViewProps = useMemo(
|
|
1336
|
+
() => ({
|
|
1337
|
+
onEndReached: onEndReached,
|
|
1338
|
+
onEndReachedThreshold: 0.5,
|
|
1339
|
+
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
1340
|
+
removeClippedSubviews: true, // Improve performance by unmounting components when not visible
|
|
1341
|
+
initialNumToRender: 10, // Reduce initial render amount
|
|
1342
|
+
maxToRenderPerBatch: 7, // Reduce number in each render batch
|
|
1343
|
+
windowSize: 7, // Reduce the window size
|
|
1344
|
+
updateCellsBatchingPeriod: 50, // Batch cell updates to improve scrolling
|
|
1345
|
+
keyExtractor: (item) => item._id, // Add explicit key extractor
|
|
1346
|
+
}),
|
|
1347
|
+
[onEndReached, onMomentumScrollBegin],
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1742
1350
|
// Return optimized component with performance improvements
|
|
1743
1351
|
return (
|
|
1744
1352
|
<View
|
|
1745
1353
|
style={{
|
|
1746
1354
|
flex: 1,
|
|
1747
1355
|
backgroundColor: 'white',
|
|
1356
|
+
position: 'relative',
|
|
1748
1357
|
}}
|
|
1749
1358
|
>
|
|
1750
|
-
{
|
|
1359
|
+
{messageLoading && <Spinner color={'#3b82f6'} />}
|
|
1360
|
+
|
|
1361
|
+
{/* Render the image preview directly in the container so it's properly positioned */}
|
|
1362
|
+
{selectedImage ? renderAccessory() : null}
|
|
1751
1363
|
|
|
1752
1364
|
<GiftedChat
|
|
1753
1365
|
ref={messageRootListRef}
|
|
@@ -1757,20 +1369,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1757
1369
|
listViewProps={{
|
|
1758
1370
|
...listViewProps,
|
|
1759
1371
|
contentContainerStyle: {
|
|
1760
|
-
paddingBottom:
|
|
1372
|
+
paddingBottom: selectedImage ? 90 : 0, // Add padding at the bottom when image is selected
|
|
1761
1373
|
},
|
|
1762
|
-
keyboardShouldPersistTaps: 'handled',
|
|
1763
1374
|
}}
|
|
1764
1375
|
onSend={handleSend}
|
|
1765
|
-
text={
|
|
1766
|
-
onInputTextChanged={(text) =>
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1769
|
-
renderFooter={() =>
|
|
1770
|
-
safeContextProperty('loading') || safeContextProperty('imageLoading') ? (
|
|
1771
|
-
<Spinner color={'#3b82f6'} />
|
|
1772
|
-
) : null
|
|
1773
|
-
}
|
|
1376
|
+
text={messageText || ' '}
|
|
1377
|
+
onInputTextChanged={(text) => setMessageText(text)}
|
|
1378
|
+
renderFooter={() => (loading && !images.length ? <Spinner color={'#3b82f6'} /> : null)}
|
|
1774
1379
|
scrollToBottom
|
|
1775
1380
|
user={{
|
|
1776
1381
|
_id: auth?.id || '',
|
|
@@ -1781,14 +1386,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1781
1386
|
renderMessageText={renderMessageText}
|
|
1782
1387
|
renderInputToolbar={renderInputToolbar}
|
|
1783
1388
|
minInputToolbarHeight={50}
|
|
1784
|
-
renderActions={
|
|
1785
|
-
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
1389
|
+
renderActions={channelId && renderActions}
|
|
1786
1390
|
renderMessage={renderMessage}
|
|
1787
1391
|
renderChatFooter={renderChatFooter}
|
|
1788
1392
|
renderLoadEarlier={renderLoadEarlier}
|
|
1789
|
-
loadEarlier={
|
|
1790
|
-
isLoadingEarlier={
|
|
1791
|
-
bottomOffset={Platform.OS === 'ios' ? 10 : 0} //
|
|
1393
|
+
loadEarlier={totalCount > channelMessages.length}
|
|
1394
|
+
isLoadingEarlier={loadingOldMessages}
|
|
1395
|
+
bottomOffset={Platform.OS === 'ios' ? (selectedImage ? 90 : 10) : 0} // Adjust bottom offset based on image preview
|
|
1792
1396
|
textInputProps={{
|
|
1793
1397
|
style: {
|
|
1794
1398
|
borderWidth: 1,
|
|
@@ -1832,12 +1436,9 @@ const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: ISubscriptio
|
|
|
1832
1436
|
useEffect(() => {
|
|
1833
1437
|
// Don't set up subscription if there's no channel ID
|
|
1834
1438
|
if (!channelId) {
|
|
1835
|
-
console.log('⚠️ No channel ID for subscription');
|
|
1836
1439
|
return;
|
|
1837
1440
|
}
|
|
1838
1441
|
|
|
1839
|
-
console.log('🔄 Setting up NEW subscription for channel:', channelId);
|
|
1840
|
-
|
|
1841
1442
|
// Call the subscribe function and store the unsubscribe function
|
|
1842
1443
|
const unsubscribe = subscribeToNewMessages();
|
|
1843
1444
|
|
|
@@ -1847,7 +1448,6 @@ const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: ISubscriptio
|
|
|
1847
1448
|
// Return cleanup function
|
|
1848
1449
|
return () => {
|
|
1849
1450
|
if (unsubscribe && typeof unsubscribe === 'function') {
|
|
1850
|
-
console.log('🔄 Cleaning up subscription for channel:', channelIdRef.current);
|
|
1851
1451
|
unsubscribe();
|
|
1852
1452
|
}
|
|
1853
1453
|
};
|