@messenger-box/platform-mobile 10.0.3-alpha.34 → 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 +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 +80 -256
- package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +222 -324
- 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 +487 -888
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +243 -547
- 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 +4 -4
- package/src/screens/inbox/components/CachedImage/index.tsx +191 -140
- package/src/screens/inbox/components/DialogsListItem.tsx +112 -345
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +316 -437
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +2 -4
- package/src/screens/inbox/containers/ConversationView.tsx +676 -993
- package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
- package/src/screens/inbox/containers/Dialogs.tsx +345 -636
- 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,477 +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
|
-
send(event);
|
|
364
|
-
} catch (error) {
|
|
365
|
-
console.error('Error sending event to state machine:', error, event);
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
[send],
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
// Immediately set initial context if needed
|
|
372
|
-
useEffect(() => {
|
|
373
|
-
if (ChannelId) {
|
|
374
|
-
console.log('Setting initial channel ID on mount:', ChannelId);
|
|
375
|
-
try {
|
|
376
|
-
send({
|
|
377
|
-
type: ConversationActions.INITIAL_CONTEXT,
|
|
378
|
-
data: { channelId: ChannelId },
|
|
379
|
-
});
|
|
380
|
-
} catch (error) {
|
|
381
|
-
console.error('Error sending initial context:', error);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}, []);
|
|
385
|
-
|
|
386
|
-
// Use a ref to track the current machine snapshot for safer access
|
|
387
|
-
const stateRef = useRef(state);
|
|
388
|
-
|
|
389
|
-
// Keep the ref updated with the latest snapshot
|
|
390
|
-
useEffect(() => {
|
|
391
|
-
stateRef.current = state;
|
|
392
|
-
}, [state]);
|
|
90
|
+
// Fix for the optimistic response types
|
|
91
|
+
type OptimisticPropsConfig = {
|
|
92
|
+
__typename: 'MachineConfiguration';
|
|
93
|
+
resource: string;
|
|
94
|
+
};
|
|
393
95
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
loadingOldMessages: false,
|
|
407
|
-
error: null,
|
|
408
|
-
selectedImage: '',
|
|
409
|
-
files: [],
|
|
410
|
-
images: [],
|
|
411
|
-
messageText: '',
|
|
412
|
-
imageLoading: false,
|
|
413
|
-
};
|
|
414
|
-
}, []);
|
|
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>({});
|
|
415
108
|
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
};
|
|
421
|
-
}, []);
|
|
109
|
+
// Create refs for various operations
|
|
110
|
+
const messageRootListRef = useRef<any>(null);
|
|
111
|
+
const isMounted = useRef(true);
|
|
112
|
+
const fetchOldDebounceRef = useRef(false);
|
|
422
113
|
|
|
114
|
+
// Navigation and auth
|
|
423
115
|
const auth: any = useSelector(userSelector);
|
|
424
116
|
const currentRoute = navigationRef.isReady() ? navigationRef?.getCurrentRoute() : null;
|
|
425
117
|
const navigation = useNavigation<any>();
|
|
426
|
-
const [selectedImage, setImage] = useState<string>('');
|
|
427
|
-
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
428
|
-
const [imageObject, setImageObject] = useState<any>({});
|
|
429
|
-
const messageRootListRef = useRef<any>(null);
|
|
430
118
|
const isFocused = useIsFocused();
|
|
431
119
|
|
|
120
|
+
// Apollo mutations
|
|
432
121
|
const [addDirectChannel] = useAddDirectChannelMutation();
|
|
433
122
|
const { startUpload } = useUploadFilesNative();
|
|
434
123
|
const [sendMsg] = useSendMessagesMutation();
|
|
435
124
|
const [sendExpoNotificationOnPostMutation] = useSendExpoNotificationOnPostMutation();
|
|
436
125
|
|
|
126
|
+
// Apollo query for messages
|
|
437
127
|
const {
|
|
438
128
|
data,
|
|
439
129
|
loading: messageLoading,
|
|
440
130
|
refetch,
|
|
441
131
|
fetchMore: fetchMoreMessages,
|
|
442
132
|
subscribeToMore,
|
|
443
|
-
}
|
|
133
|
+
} = useMessagesQuery({
|
|
444
134
|
variables: {
|
|
445
|
-
channelId:
|
|
135
|
+
channelId: channelId?.toString(),
|
|
446
136
|
parentId: null,
|
|
447
137
|
limit: MESSAGES_PER_PAGE,
|
|
448
|
-
skip:
|
|
138
|
+
skip: skip,
|
|
449
139
|
},
|
|
450
|
-
skip: !
|
|
140
|
+
skip: !channelId,
|
|
451
141
|
fetchPolicy: 'cache-and-network',
|
|
452
142
|
nextFetchPolicy: 'cache-first',
|
|
453
143
|
refetchWritePolicy: 'merge',
|
|
144
|
+
notifyOnNetworkStatusChange: true,
|
|
454
145
|
onCompleted: (queryData) => {
|
|
455
|
-
|
|
456
|
-
if (queryData?.messages?.data) {
|
|
457
|
-
console.log(
|
|
458
|
-
'Raw message data from query:',
|
|
459
|
-
JSON.stringify(queryData.messages.data).substring(0, 100) + '...',
|
|
460
|
-
);
|
|
461
|
-
console.log('Message count from query:', queryData.messages.data.length);
|
|
462
|
-
console.log('Total count from query:', queryData.messages.totalCount);
|
|
463
|
-
}
|
|
146
|
+
// MESSAGE QUERY COMPLETED
|
|
464
147
|
},
|
|
465
148
|
onError: (error) => {
|
|
466
|
-
|
|
149
|
+
setError(String(error));
|
|
467
150
|
},
|
|
468
151
|
});
|
|
469
152
|
|
|
470
|
-
//
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
console.warn('Cannot fetch messages: No channel ID');
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
153
|
+
// Extract messages from the query data
|
|
154
|
+
const channelMessages = useMemo(() => {
|
|
155
|
+
return data?.messages?.data || [];
|
|
156
|
+
}, [data?.messages?.data]);
|
|
477
157
|
|
|
478
|
-
|
|
479
|
-
|
|
158
|
+
// Get total message count
|
|
159
|
+
const totalCount = useMemo(() => {
|
|
160
|
+
return data?.messages?.totalCount || 0;
|
|
161
|
+
}, [data?.messages?.totalCount]);
|
|
480
162
|
|
|
481
|
-
|
|
482
|
-
|
|
163
|
+
// Clear messages when component unmounts
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
return () => {
|
|
166
|
+
isMounted.current = false;
|
|
167
|
+
};
|
|
168
|
+
}, []);
|
|
483
169
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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]);
|
|
490
177
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
498
|
-
data: { messages, totalCount },
|
|
499
|
-
});
|
|
500
|
-
} else {
|
|
501
|
-
console.warn('No messages found for channel', channelId);
|
|
502
|
-
// Still clear loading state
|
|
503
|
-
send({ type: ConversationActions.STOP_LOADING });
|
|
504
|
-
}
|
|
505
|
-
} else {
|
|
506
|
-
console.warn('Query returned no messages data');
|
|
507
|
-
send({ type: ConversationActions.STOP_LOADING });
|
|
178
|
+
// Focus/unfocus behavior
|
|
179
|
+
useFocusEffect(
|
|
180
|
+
React.useCallback(() => {
|
|
181
|
+
if (channelId) {
|
|
182
|
+
// Refresh messages when screen comes into focus
|
|
183
|
+
refetch();
|
|
508
184
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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);
|
|
512
195
|
}
|
|
513
|
-
}, [
|
|
196
|
+
}, [selectedImage]);
|
|
514
197
|
|
|
198
|
+
// Fetch more messages function
|
|
515
199
|
const fetchMoreMessagesImpl = useCallback(async () => {
|
|
516
200
|
try {
|
|
201
|
+
setLoadingOldMessages(true);
|
|
517
202
|
const response = await fetchMoreMessages({
|
|
518
203
|
variables: {
|
|
519
|
-
channelId:
|
|
204
|
+
channelId: channelId?.toString(),
|
|
520
205
|
parentId: null,
|
|
521
|
-
skip:
|
|
206
|
+
skip: channelMessages.length,
|
|
522
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
|
+
// },
|
|
523
226
|
});
|
|
524
227
|
|
|
228
|
+
setLoadingOldMessages(false);
|
|
525
229
|
if (!response?.data?.messages?.data) {
|
|
526
230
|
return { error: 'No messages returned' };
|
|
527
231
|
}
|
|
528
232
|
|
|
529
233
|
return { messages: response.data.messages.data };
|
|
530
234
|
} catch (error) {
|
|
235
|
+
setLoadingOldMessages(false);
|
|
236
|
+
setError(String(error));
|
|
531
237
|
return { error: String(error) };
|
|
532
238
|
}
|
|
533
|
-
}, [
|
|
239
|
+
}, [channelId, channelMessages.length, fetchMoreMessages]);
|
|
534
240
|
|
|
241
|
+
// Send message function
|
|
535
242
|
const sendMessageImpl = useCallback(async () => {
|
|
536
243
|
try {
|
|
244
|
+
// Store the current message text and clear input immediately for better UX
|
|
245
|
+
const currentMessageText = messageText;
|
|
246
|
+
setMessageText('');
|
|
247
|
+
|
|
537
248
|
const notificationData: IExpoNotificationData = {
|
|
538
249
|
url: config.INBOX_MESSEGE_PATH,
|
|
539
|
-
params: { channelId
|
|
250
|
+
params: { channelId, hideTabBar: true },
|
|
540
251
|
screen: 'DialogMessages',
|
|
541
252
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
542
253
|
};
|
|
543
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
|
+
|
|
544
300
|
const response = await sendMsg({
|
|
545
301
|
variables: {
|
|
546
|
-
channelId
|
|
547
|
-
content:
|
|
302
|
+
channelId,
|
|
303
|
+
content: currentMessageText,
|
|
548
304
|
notificationParams: notificationData,
|
|
549
305
|
},
|
|
306
|
+
optimisticResponse: {
|
|
307
|
+
__typename: 'Mutation',
|
|
308
|
+
sendMessage: optimisticMessage,
|
|
309
|
+
},
|
|
550
310
|
});
|
|
551
311
|
|
|
552
312
|
return { message: response.data?.sendMessage };
|
|
553
313
|
} catch (error) {
|
|
314
|
+
setLoading(false);
|
|
315
|
+
setError(String(error));
|
|
554
316
|
return { error: String(error) };
|
|
555
317
|
}
|
|
556
|
-
}, [
|
|
318
|
+
}, [channelId, messageText, sendMsg, auth]);
|
|
557
319
|
|
|
558
|
-
//
|
|
320
|
+
// Image selection handler
|
|
559
321
|
const onSelectImages = async () => {
|
|
560
|
-
|
|
322
|
+
setLoading(true);
|
|
561
323
|
|
|
562
324
|
try {
|
|
563
|
-
console.log('Starting image picker...');
|
|
564
325
|
let imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
565
326
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
566
327
|
allowsEditing: true,
|
|
@@ -571,22 +332,10 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
571
332
|
});
|
|
572
333
|
|
|
573
334
|
if (!imageSource?.canceled) {
|
|
574
|
-
console.log(
|
|
575
|
-
'Image selected. Asset details:',
|
|
576
|
-
JSON.stringify({
|
|
577
|
-
uri: imageSource?.assets?.[0]?.uri?.substring(0, 30) + '...',
|
|
578
|
-
width: imageSource?.assets?.[0]?.width,
|
|
579
|
-
height: imageSource?.assets?.[0]?.height,
|
|
580
|
-
hasBase64: !!imageSource?.assets?.[0]?.base64,
|
|
581
|
-
hasUri: !!imageSource?.assets?.[0]?.uri,
|
|
582
|
-
}),
|
|
583
|
-
);
|
|
584
|
-
|
|
585
335
|
// Get the asset
|
|
586
336
|
const selectedAsset = imageSource?.assets?.[0];
|
|
587
337
|
if (!selectedAsset) {
|
|
588
|
-
|
|
589
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
338
|
+
setLoading(false);
|
|
590
339
|
return;
|
|
591
340
|
}
|
|
592
341
|
|
|
@@ -602,58 +351,49 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
602
351
|
mimeType: 'image/jpeg',
|
|
603
352
|
};
|
|
604
353
|
|
|
605
|
-
console.log('Prepared image asset for upload:', {
|
|
606
|
-
hasUrl: !!asset.url,
|
|
607
|
-
hasFileName: !!asset.fileName,
|
|
608
|
-
hasMimeType: !!asset.mimeType,
|
|
609
|
-
previewAvailable: !!previewImage,
|
|
610
|
-
});
|
|
611
|
-
|
|
612
354
|
// Update state with the new image
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
data: {
|
|
616
|
-
image: previewImage,
|
|
617
|
-
images: [asset], // Replace existing images with the new one
|
|
618
|
-
},
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
console.log('Image state updated successfully');
|
|
355
|
+
setSelectedImage(previewImage);
|
|
356
|
+
setImages([asset]);
|
|
622
357
|
} else {
|
|
623
|
-
|
|
624
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
358
|
+
setLoading(false);
|
|
625
359
|
}
|
|
626
360
|
} catch (error) {
|
|
627
|
-
|
|
628
|
-
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
361
|
+
setLoading(false);
|
|
629
362
|
}
|
|
630
363
|
};
|
|
631
364
|
|
|
632
|
-
//
|
|
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
|
|
633
369
|
const sendMessageWithFileImpl = useCallback(async () => {
|
|
634
370
|
try {
|
|
635
|
-
|
|
371
|
+
// For file uploads, we still need loading state since we need to wait for the file upload
|
|
372
|
+
setLoading(true);
|
|
636
373
|
|
|
637
374
|
// Generate a unique post ID for the message
|
|
638
375
|
const postId = objectId();
|
|
639
|
-
|
|
376
|
+
|
|
377
|
+
// Set the message ID that should show the skeleton
|
|
378
|
+
setUploadingMessageId(postId);
|
|
640
379
|
|
|
641
380
|
// Prepare notification data
|
|
642
381
|
const notificationData: IExpoNotificationData = {
|
|
643
382
|
url: config.INBOX_MESSEGE_PATH,
|
|
644
|
-
params: { channelId
|
|
383
|
+
params: { channelId, hideTabBar: true },
|
|
645
384
|
screen: 'DialogMessages',
|
|
646
385
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
647
386
|
};
|
|
648
387
|
|
|
649
388
|
// Safety check for images
|
|
650
|
-
if (!
|
|
651
|
-
|
|
389
|
+
if (!images || images.length === 0) {
|
|
390
|
+
setLoading(false);
|
|
391
|
+
setUploadingMessageId(null);
|
|
652
392
|
return { error: 'No images available to upload' };
|
|
653
393
|
}
|
|
654
394
|
|
|
655
395
|
// Format the images for upload if needed
|
|
656
|
-
const imagesToUpload =
|
|
396
|
+
const imagesToUpload = images.map((img) => {
|
|
657
397
|
// Ensure the image has all required properties
|
|
658
398
|
return {
|
|
659
399
|
...img,
|
|
@@ -663,20 +403,82 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
663
403
|
};
|
|
664
404
|
});
|
|
665
405
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
+
};
|
|
677
480
|
|
|
678
481
|
// Upload the files
|
|
679
|
-
console.log('Starting file upload...');
|
|
680
482
|
const uploadResponse = await startUpload({
|
|
681
483
|
file: imagesToUpload,
|
|
682
484
|
saveUploadedFile: {
|
|
@@ -687,89 +489,66 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
687
489
|
},
|
|
688
490
|
});
|
|
689
491
|
|
|
690
|
-
console.log(
|
|
691
|
-
'Upload response received:',
|
|
692
|
-
uploadResponse?.data ? 'Has data' : 'No data',
|
|
693
|
-
'Error:',
|
|
694
|
-
uploadResponse?.error ? uploadResponse.error : 'None',
|
|
695
|
-
);
|
|
696
|
-
|
|
697
492
|
if (uploadResponse?.error) {
|
|
698
|
-
|
|
493
|
+
setLoading(false);
|
|
494
|
+
setUploadingMessageId(null);
|
|
699
495
|
return { error: String(uploadResponse.error) };
|
|
700
496
|
}
|
|
701
497
|
|
|
702
498
|
// Get uploaded file IDs
|
|
703
499
|
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
704
|
-
console.log(
|
|
705
|
-
'Uploaded files:',
|
|
706
|
-
uploadedFiles
|
|
707
|
-
? JSON.stringify(uploadedFiles.map((f) => ({ id: f.id, url: f.url?.substring(0, 30) + '...' })))
|
|
708
|
-
: 'null',
|
|
709
|
-
);
|
|
710
|
-
|
|
711
500
|
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
712
501
|
|
|
713
|
-
console.log('Files uploaded successfully. File IDs:', files);
|
|
714
|
-
|
|
715
502
|
// Send the message with the uploaded files
|
|
716
|
-
console.log('Sending message with files:', {
|
|
717
|
-
postId,
|
|
718
|
-
channelId: state.context.channelId,
|
|
719
|
-
content: state.context.messageText || ' ',
|
|
720
|
-
hasFiles: !!files,
|
|
721
|
-
fileCount: files?.length || 0,
|
|
722
|
-
});
|
|
723
|
-
|
|
724
503
|
const response = await sendMsg({
|
|
725
504
|
variables: {
|
|
726
505
|
postId,
|
|
727
|
-
channelId
|
|
728
|
-
content:
|
|
506
|
+
channelId,
|
|
507
|
+
content: currentMessageText || ' ', // Use a space if no text
|
|
729
508
|
files,
|
|
730
509
|
notificationParams: notificationData,
|
|
731
510
|
},
|
|
511
|
+
optimisticResponse: {
|
|
512
|
+
__typename: 'Mutation',
|
|
513
|
+
sendMessage: optimisticMessage,
|
|
514
|
+
},
|
|
732
515
|
});
|
|
733
516
|
|
|
734
517
|
if (response?.data?.sendMessage) {
|
|
735
|
-
console.log('Message with file sent successfully:', response.data.sendMessage.id);
|
|
736
|
-
|
|
737
|
-
// Log the file data from the response to verify it's being returned correctly
|
|
738
|
-
if (response.data.sendMessage.files?.data) {
|
|
739
|
-
console.log(
|
|
740
|
-
'📷 Message response file data:',
|
|
741
|
-
JSON.stringify({
|
|
742
|
-
fileCount: response.data.sendMessage.files.data.length,
|
|
743
|
-
fileUrl: response.data.sendMessage.files.data[0]?.url?.substring(0, 30) + '...',
|
|
744
|
-
}),
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
518
|
// Clear the images after successful send
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}, 100);
|
|
752
|
-
} else {
|
|
753
|
-
console.error('Failed to send message with file:', response?.errors);
|
|
519
|
+
setSelectedImage('');
|
|
520
|
+
setImages([]);
|
|
754
521
|
}
|
|
755
522
|
|
|
523
|
+
setLoading(false);
|
|
524
|
+
setUploadingMessageId(null);
|
|
756
525
|
return { message: response.data?.sendMessage };
|
|
757
526
|
} catch (error) {
|
|
758
|
-
|
|
527
|
+
setLoading(false);
|
|
528
|
+
setUploadingMessageId(null);
|
|
529
|
+
setError(String(error));
|
|
759
530
|
return { error: String(error) };
|
|
760
531
|
}
|
|
761
|
-
}, [
|
|
532
|
+
}, [channelId, messageText, images, selectedImage, startUpload, sendMsg, auth]);
|
|
762
533
|
|
|
534
|
+
// Create direct channel implementation
|
|
763
535
|
const createDirectChannelImpl = useCallback(async () => {
|
|
764
536
|
try {
|
|
537
|
+
setLoading(true);
|
|
765
538
|
if (
|
|
766
539
|
!rest?.isCreateNewChannel ||
|
|
767
540
|
rest?.newChannelData?.type !== RoomType?.Direct ||
|
|
768
541
|
!rest?.newChannelData?.userIds?.length
|
|
769
542
|
) {
|
|
543
|
+
setLoading(false);
|
|
770
544
|
return { error: 'Invalid channel data' };
|
|
771
545
|
}
|
|
772
546
|
|
|
547
|
+
// Store current message text
|
|
548
|
+
const currentMessageText = messageText;
|
|
549
|
+
// Clear message text immediately for better UX
|
|
550
|
+
setMessageText('');
|
|
551
|
+
|
|
773
552
|
const response = await addDirectChannel({
|
|
774
553
|
variables: {
|
|
775
554
|
receiver: [...(rest?.newChannelData?.userIds ?? [])],
|
|
@@ -778,10 +557,12 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
778
557
|
});
|
|
779
558
|
|
|
780
559
|
if (!response?.data?.createDirectChannel?.id) {
|
|
560
|
+
setLoading(false);
|
|
781
561
|
return { error: 'Failed to create channel' };
|
|
782
562
|
}
|
|
783
563
|
|
|
784
564
|
const newChannelId = response.data.createDirectChannel.id;
|
|
565
|
+
setChannelId(newChannelId);
|
|
785
566
|
|
|
786
567
|
const notificationData: IExpoNotificationData = {
|
|
787
568
|
url: config.INBOX_MESSEGE_PATH,
|
|
@@ -790,99 +571,74 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
790
571
|
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
791
572
|
};
|
|
792
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
|
|
793
622
|
await sendMsg({
|
|
794
623
|
variables: {
|
|
795
624
|
channelId: newChannelId,
|
|
796
|
-
content:
|
|
625
|
+
content: currentMessageText,
|
|
797
626
|
notificationParams: notificationData,
|
|
798
627
|
},
|
|
628
|
+
optimisticResponse: {
|
|
629
|
+
__typename: 'Mutation',
|
|
630
|
+
sendMessage: optimisticMessage,
|
|
631
|
+
},
|
|
799
632
|
});
|
|
800
633
|
|
|
634
|
+
setLoading(false);
|
|
801
635
|
return { channelId: newChannelId };
|
|
802
636
|
} catch (error) {
|
|
637
|
+
setLoading(false);
|
|
638
|
+
setError(String(error));
|
|
803
639
|
return { error: String(error) };
|
|
804
640
|
}
|
|
805
|
-
}, [rest,
|
|
806
|
-
|
|
807
|
-
// Remove the implementation inside this effect
|
|
808
|
-
useEffect(() => {
|
|
809
|
-
// We've moved these implementations to useCallback hooks above
|
|
810
|
-
}, [state.value, sendMsg, refetch, fetchMoreMessages, addDirectChannel, startUpload, rest, state.context]);
|
|
811
|
-
|
|
812
|
-
React.useEffect(() => {
|
|
813
|
-
return () => {
|
|
814
|
-
send({ type: ConversationActions.CLEAR_MESSAGES });
|
|
815
|
-
};
|
|
816
|
-
}, []);
|
|
817
|
-
|
|
818
|
-
useFocusEffect(
|
|
819
|
-
React.useCallback(() => {
|
|
820
|
-
if (state.context.channelId) {
|
|
821
|
-
send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: state.context.channelId } });
|
|
822
|
-
}
|
|
823
|
-
return () => {
|
|
824
|
-
send({ type: ConversationActions.CLEAR_MESSAGES });
|
|
825
|
-
};
|
|
826
|
-
}, [state.context.channelId, isFocused]),
|
|
827
|
-
);
|
|
828
|
-
|
|
829
|
-
React.useEffect(() => {
|
|
830
|
-
const currentChannelId = ChannelId || currentRoute?.params?.channelId;
|
|
831
|
-
if (currentChannelId) {
|
|
832
|
-
console.log('Setting initial channel ID:', currentChannelId);
|
|
833
|
-
send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: currentChannelId } });
|
|
834
|
-
}
|
|
835
|
-
}, [ChannelId, currentRoute]);
|
|
836
|
-
|
|
837
|
-
React.useEffect(() => {
|
|
838
|
-
if (state.context.selectedImage) {
|
|
839
|
-
send({ type: ConversationActions.STOP_LOADING });
|
|
840
|
-
}
|
|
841
|
-
}, [state.context.selectedImage]);
|
|
842
|
-
|
|
843
|
-
useEffect(() => {
|
|
844
|
-
if (data?.messages?.data) {
|
|
845
|
-
console.log('📩 QUERY DATA CHANGED - Messages received:', data.messages.data.length);
|
|
846
|
-
const { data: messages, totalCount: messageTotalCount } = data.messages;
|
|
847
|
-
|
|
848
|
-
if (messages && messages.length > 0) {
|
|
849
|
-
console.log('📩 QUERY DATA - Setting channel messages, count:', messages.length);
|
|
850
|
-
|
|
851
|
-
// First try dispatching the update through XState
|
|
852
|
-
send({
|
|
853
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
854
|
-
data: {
|
|
855
|
-
messages: uniqBy([...messages, ...state.context.channelMessages], ({ id }) => id),
|
|
856
|
-
totalCount: messageTotalCount,
|
|
857
|
-
},
|
|
858
|
-
});
|
|
859
|
-
|
|
860
|
-
// Debug: Log the first message to verify data format
|
|
861
|
-
if (messages[0]) {
|
|
862
|
-
const sample = messages[0];
|
|
863
|
-
console.log(
|
|
864
|
-
'📩 SAMPLE MESSAGE:',
|
|
865
|
-
JSON.stringify({
|
|
866
|
-
id: sample.id,
|
|
867
|
-
message: sample.message,
|
|
868
|
-
author: {
|
|
869
|
-
id: sample.author?.id,
|
|
870
|
-
name: `${sample.author?.givenName} ${sample.author?.familyName}`,
|
|
871
|
-
},
|
|
872
|
-
createdAt: sample.createdAt,
|
|
873
|
-
}),
|
|
874
|
-
);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Check if the state machine actually updated (debug only)
|
|
878
|
-
setTimeout(() => {
|
|
879
|
-
if (state.context.channelMessages?.length === 0) {
|
|
880
|
-
console.warn('⚠️ STATE NOT UPDATED after message data received - may need fallback');
|
|
881
|
-
}
|
|
882
|
-
}, 500);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}, [data]);
|
|
641
|
+
}, [rest, messageText, addDirectChannel, sendMsg, auth]);
|
|
886
642
|
|
|
887
643
|
// Optimize onFetchOld by adding debounce logic
|
|
888
644
|
const onFetchOld = useCallback(() => {
|
|
@@ -890,49 +646,80 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
890
646
|
if (fetchOldDebounceRef.current) return;
|
|
891
647
|
|
|
892
648
|
// Check if we need to fetch more messages
|
|
893
|
-
if (
|
|
894
|
-
state?.context?.totalCount > state?.context?.channelMessages?.length &&
|
|
895
|
-
!state?.context?.loadingOldMessages
|
|
896
|
-
) {
|
|
649
|
+
if (totalCount > channelMessages.length && !loadingOldMessages) {
|
|
897
650
|
// Set debounce
|
|
898
651
|
fetchOldDebounceRef.current = true;
|
|
899
652
|
|
|
900
|
-
//
|
|
901
|
-
|
|
653
|
+
// Fetch more messages
|
|
654
|
+
fetchMoreMessagesImpl();
|
|
902
655
|
|
|
903
656
|
// Clear debounce after a timeout
|
|
904
657
|
setTimeout(() => {
|
|
905
658
|
fetchOldDebounceRef.current = false;
|
|
906
659
|
}, 1000);
|
|
907
660
|
}
|
|
908
|
-
}, [
|
|
909
|
-
|
|
910
|
-
// Add debounce ref
|
|
911
|
-
const fetchOldDebounceRef = useRef(false);
|
|
661
|
+
}, [totalCount, channelMessages.length, loadingOldMessages, fetchMoreMessagesImpl]);
|
|
912
662
|
|
|
913
663
|
const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
914
664
|
const paddingToTop = 60;
|
|
915
665
|
return contentSize.height - layoutMeasurement.height - paddingToTop <= contentOffset.y;
|
|
916
666
|
};
|
|
917
667
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
u8arr = new Uint8Array(n);
|
|
924
|
-
while (n--) {
|
|
925
|
-
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 [];
|
|
926
673
|
}
|
|
927
|
-
return new File([u8arr], filename, { type: mime });
|
|
928
|
-
};
|
|
929
674
|
|
|
930
|
-
|
|
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
|
|
931
718
|
const renderSend = useCallback(
|
|
932
719
|
(props) => {
|
|
933
720
|
// Enable the send button if there's text OR we have images
|
|
934
|
-
const hasContent = !!props.text ||
|
|
935
|
-
const canSend = (
|
|
721
|
+
const hasContent = !!props.text || images?.length > 0;
|
|
722
|
+
const canSend = (channelId || rest?.isCreateNewChannel) && hasContent;
|
|
936
723
|
|
|
937
724
|
return (
|
|
938
725
|
<Send
|
|
@@ -958,157 +745,58 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
958
745
|
</Send>
|
|
959
746
|
);
|
|
960
747
|
},
|
|
961
|
-
[
|
|
748
|
+
[channelId, images, rest?.isCreateNewChannel],
|
|
962
749
|
);
|
|
963
750
|
|
|
964
|
-
//
|
|
751
|
+
// Handle send for messages
|
|
965
752
|
const handleSend = useCallback(
|
|
966
753
|
async (messages) => {
|
|
967
754
|
// Extract message text from GiftedChat messages array
|
|
968
|
-
const
|
|
969
|
-
console.log('Sending message:', messageText);
|
|
970
|
-
console.log('Images:', state.context.images?.length);
|
|
755
|
+
const newMessageText = messages && messages.length > 0 ? messages[0]?.text || ' ' : ' ';
|
|
971
756
|
|
|
972
757
|
// Check if we can send a message (channel exists or we're creating one)
|
|
973
|
-
if (!
|
|
974
|
-
console.log('Cannot send - no channel');
|
|
758
|
+
if (!channelId && !rest?.isCreateNewChannel) {
|
|
975
759
|
return;
|
|
976
760
|
}
|
|
977
761
|
|
|
978
762
|
// Allow sending if we have text OR images (image-only messages are valid)
|
|
979
|
-
const hasText = !!
|
|
980
|
-
const hasImages =
|
|
763
|
+
const hasText = !!newMessageText && newMessageText !== ' ';
|
|
764
|
+
const hasImages = images && images.length > 0;
|
|
981
765
|
|
|
982
766
|
if (!hasText && !hasImages) {
|
|
983
|
-
console.log('Nothing to send - no text or images');
|
|
984
767
|
return;
|
|
985
768
|
}
|
|
986
769
|
|
|
987
|
-
//
|
|
988
|
-
|
|
770
|
+
// Update the message text state - now handled in send functions for better UX
|
|
771
|
+
setMessageText(newMessageText);
|
|
989
772
|
|
|
990
773
|
// Handle direct channel creation if needed
|
|
991
|
-
if (rest?.isCreateNewChannel && !
|
|
774
|
+
if (rest?.isCreateNewChannel && !channelId) {
|
|
992
775
|
if (rest?.newChannelData?.type === RoomType?.Direct) {
|
|
993
|
-
|
|
776
|
+
createDirectChannelImpl();
|
|
994
777
|
}
|
|
995
778
|
return;
|
|
996
779
|
}
|
|
997
780
|
|
|
998
781
|
// Send message with or without image based on state
|
|
999
782
|
if (hasImages) {
|
|
1000
|
-
|
|
1001
|
-
safeSend({ type: ConversationActions.SEND_MESSAGE_WITH_FILE });
|
|
783
|
+
sendMessageWithFileImpl();
|
|
1002
784
|
} else {
|
|
1003
|
-
|
|
1004
|
-
safeSend({ type: ConversationActions.SEND_MESSAGE });
|
|
785
|
+
sendMessageImpl();
|
|
1005
786
|
}
|
|
1006
787
|
},
|
|
1007
|
-
[
|
|
788
|
+
[
|
|
789
|
+
channelId,
|
|
790
|
+
images,
|
|
791
|
+
rest?.isCreateNewChannel,
|
|
792
|
+
rest?.newChannelData?.type,
|
|
793
|
+
createDirectChannelImpl,
|
|
794
|
+
sendMessageWithFileImpl,
|
|
795
|
+
sendMessageImpl,
|
|
796
|
+
],
|
|
1008
797
|
);
|
|
1009
798
|
|
|
1010
|
-
//
|
|
1011
|
-
const fetchMessagesWithFallback = useCallback(async () => {
|
|
1012
|
-
if (!state.context.channelId) return;
|
|
1013
|
-
|
|
1014
|
-
try {
|
|
1015
|
-
console.log('🔄 DIRECT FETCH: Using direct approach for channel:', state.context.channelId);
|
|
1016
|
-
|
|
1017
|
-
const response = await refetch({
|
|
1018
|
-
channelId: state.context.channelId?.toString(),
|
|
1019
|
-
parentId: null,
|
|
1020
|
-
limit: MESSAGES_PER_PAGE,
|
|
1021
|
-
skip: 0,
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
if (response?.data?.messages?.data) {
|
|
1025
|
-
const messages = response.data.messages.data;
|
|
1026
|
-
console.log('✅ DIRECT FETCH: Got messages:', messages.length);
|
|
1027
|
-
|
|
1028
|
-
// Skip fallback and send directly to state machine
|
|
1029
|
-
send({
|
|
1030
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
1031
|
-
data: {
|
|
1032
|
-
messages,
|
|
1033
|
-
totalCount: response.data.messages.totalCount,
|
|
1034
|
-
},
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
console.error('❌ DIRECT FETCH ERROR:', error);
|
|
1039
|
-
}
|
|
1040
|
-
}, [state.context.channelId, refetch]);
|
|
1041
|
-
|
|
1042
|
-
// Auto-trigger fallback if needed
|
|
1043
|
-
useEffect(() => {
|
|
1044
|
-
let timeoutId: NodeJS.Timeout;
|
|
1045
|
-
|
|
1046
|
-
if (state.context.channelId && state.context.channelMessages.length === 0) {
|
|
1047
|
-
timeoutId = setTimeout(() => {
|
|
1048
|
-
console.log('⚠️ ACTIVATING FALLBACK - XState not updating after timeout');
|
|
1049
|
-
fetchMessagesWithFallback();
|
|
1050
|
-
}, 3000); // Wait 3 seconds for normal flow to work
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
return () => {
|
|
1054
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1055
|
-
};
|
|
1056
|
-
}, [state.context.channelId, state.context.channelMessages, fetchMessagesWithFallback]);
|
|
1057
|
-
|
|
1058
|
-
// Optimize the messageList calculation for better performance
|
|
1059
|
-
const messageList = useMemo(() => {
|
|
1060
|
-
// Only recalculate when these dependencies change
|
|
1061
|
-
console.log('🔄 CALCULATING MESSAGE LIST - Optimized version');
|
|
1062
|
-
|
|
1063
|
-
// Short-circuit if no messages to process
|
|
1064
|
-
if (!state?.context?.channelMessages || state.context.channelMessages.length === 0) {
|
|
1065
|
-
console.log('No messages to process');
|
|
1066
|
-
return [];
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// Use a more efficient approach - pre-filter messages once
|
|
1070
|
-
const filteredMessages = uniqBy(state.context.channelMessages, ({ id }) => id);
|
|
1071
|
-
|
|
1072
|
-
// Skip processing if no filtered messages
|
|
1073
|
-
if (filteredMessages.length === 0) {
|
|
1074
|
-
return [];
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
// Transform messages only once and return
|
|
1078
|
-
return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
1079
|
-
const date = new Date(msg.createdAt);
|
|
1080
|
-
|
|
1081
|
-
// Extract image URL from files data
|
|
1082
|
-
let imageUrl = null;
|
|
1083
|
-
if (msg.files?.data && msg.files.data.length > 0) {
|
|
1084
|
-
const fileData = msg.files.data[0];
|
|
1085
|
-
if (fileData && fileData.url) {
|
|
1086
|
-
imageUrl = fileData.url;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// Create message in a more direct way
|
|
1091
|
-
return {
|
|
1092
|
-
_id: msg.id,
|
|
1093
|
-
text: msg.message,
|
|
1094
|
-
createdAt: date,
|
|
1095
|
-
user: {
|
|
1096
|
-
_id: msg.author?.id || '',
|
|
1097
|
-
name: `${msg.author?.givenName || ''} ${msg.author?.familyName || ''}`,
|
|
1098
|
-
avatar: msg.author?.picture || '',
|
|
1099
|
-
},
|
|
1100
|
-
image: imageUrl,
|
|
1101
|
-
sent: msg?.isDelivered,
|
|
1102
|
-
received: msg?.isRead,
|
|
1103
|
-
type: msg?.type,
|
|
1104
|
-
propsConfiguration: msg?.propsConfiguration,
|
|
1105
|
-
replies: msg?.replies ?? [],
|
|
1106
|
-
isShowThreadMessage,
|
|
1107
|
-
};
|
|
1108
|
-
});
|
|
1109
|
-
}, [state?.context?.channelMessages, isShowThreadMessage]);
|
|
1110
|
-
|
|
1111
|
-
// Memoize the renderMessageText function
|
|
799
|
+
// Render message text with customizations for alerts and replies
|
|
1112
800
|
const renderMessageText = useCallback(
|
|
1113
801
|
(props: any) => {
|
|
1114
802
|
const { currentMessage } = props;
|
|
@@ -1173,7 +861,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1173
861
|
onPress={() => {
|
|
1174
862
|
if (currentMessage?.isShowThreadMessage)
|
|
1175
863
|
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1176
|
-
channelId:
|
|
864
|
+
channelId: channelId,
|
|
1177
865
|
title: 'Message',
|
|
1178
866
|
postParentId: currentMessage?._id,
|
|
1179
867
|
isPostParentIdThread: true,
|
|
@@ -1238,7 +926,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1238
926
|
onPress={() => {
|
|
1239
927
|
if (currentMessage?.isShowThreadMessage)
|
|
1240
928
|
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1241
|
-
channelId:
|
|
929
|
+
channelId: channelId,
|
|
1242
930
|
title: 'Message',
|
|
1243
931
|
postParentId: currentMessage?._id,
|
|
1244
932
|
isPostParentIdThread: true,
|
|
@@ -1290,9 +978,10 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1290
978
|
);
|
|
1291
979
|
}
|
|
1292
980
|
},
|
|
1293
|
-
[navigation,
|
|
981
|
+
[navigation, channelId, role],
|
|
1294
982
|
);
|
|
1295
983
|
|
|
984
|
+
// Render action buttons (including image upload)
|
|
1296
985
|
const renderActions = (props) => {
|
|
1297
986
|
return (
|
|
1298
987
|
<Actions
|
|
@@ -1326,79 +1015,115 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1326
1015
|
};
|
|
1327
1016
|
|
|
1328
1017
|
// Create a more visible and reliable image preview with cancel button
|
|
1329
|
-
const renderAccessory = useCallback(
|
|
1330
|
-
(
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
if (!selectedImage) {
|
|
1334
|
-
return null;
|
|
1335
|
-
}
|
|
1018
|
+
const renderAccessory = useCallback(() => {
|
|
1019
|
+
if (!selectedImage) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1336
1022
|
|
|
1337
|
-
|
|
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
|
+
>
|
|
1338
1047
|
<View
|
|
1339
1048
|
style={{
|
|
1340
|
-
|
|
1341
|
-
padding: 3,
|
|
1342
|
-
backgroundColor: 'white',
|
|
1343
|
-
borderTopWidth: 1,
|
|
1344
|
-
borderTopColor: '#e0e0e0',
|
|
1049
|
+
flex: 1,
|
|
1345
1050
|
flexDirection: 'row',
|
|
1346
1051
|
alignItems: 'center',
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
paddingTop: 5,
|
|
1350
|
-
position: 'absolute',
|
|
1351
|
-
bottom: 0,
|
|
1352
|
-
left: 0,
|
|
1353
|
-
right: 0,
|
|
1354
|
-
zIndex: 999,
|
|
1052
|
+
paddingLeft: 15,
|
|
1053
|
+
paddingRight: 5,
|
|
1355
1054
|
}}
|
|
1356
1055
|
>
|
|
1357
1056
|
<View
|
|
1358
1057
|
style={{
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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',
|
|
1363
1066
|
}}
|
|
1364
1067
|
>
|
|
1365
1068
|
<Image
|
|
1366
|
-
key={
|
|
1069
|
+
key={selectedImage}
|
|
1367
1070
|
alt={'selected image'}
|
|
1368
|
-
source={{ uri:
|
|
1071
|
+
source={{ uri: selectedImage }}
|
|
1369
1072
|
style={{
|
|
1370
|
-
width:
|
|
1371
|
-
height:
|
|
1372
|
-
borderRadius: 5,
|
|
1373
|
-
marginRight: 15,
|
|
1073
|
+
width: '100%',
|
|
1074
|
+
height: '100%',
|
|
1374
1075
|
}}
|
|
1375
|
-
size={'
|
|
1076
|
+
size={'md'}
|
|
1376
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>
|
|
1377
1095
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
borderRadius: 5,
|
|
1386
|
-
marginLeft: 10,
|
|
1387
|
-
elevation: 3,
|
|
1388
|
-
shadowColor: '#000',
|
|
1389
|
-
shadowOffset: { width: 0, height: 1 },
|
|
1390
|
-
shadowOpacity: 0.3,
|
|
1391
|
-
shadowRadius: 2,
|
|
1392
|
-
}}
|
|
1393
|
-
>
|
|
1394
|
-
<Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
|
|
1395
|
-
</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>
|
|
1396
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>
|
|
1397
1123
|
</View>
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
);
|
|
1124
|
+
</View>
|
|
1125
|
+
);
|
|
1126
|
+
}, [selectedImage, loading, images]);
|
|
1402
1127
|
|
|
1403
1128
|
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
1404
1129
|
setImageObject(obj);
|
|
@@ -1406,13 +1131,14 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1406
1131
|
};
|
|
1407
1132
|
|
|
1408
1133
|
const modalContent = React.useMemo(() => {
|
|
1409
|
-
if (!imageObject) return
|
|
1134
|
+
if (!imageObject || !imageObject.image) return null;
|
|
1410
1135
|
const { image, _id } = imageObject;
|
|
1136
|
+
|
|
1411
1137
|
return (
|
|
1412
1138
|
<CachedImage
|
|
1413
1139
|
style={{ width: '100%', height: '100%' }}
|
|
1414
1140
|
resizeMode={'cover'}
|
|
1415
|
-
cacheKey={`${_id}-
|
|
1141
|
+
cacheKey={`${_id}-modal-imageKey`}
|
|
1416
1142
|
source={{
|
|
1417
1143
|
uri: image,
|
|
1418
1144
|
expiresIn: 86400,
|
|
@@ -1422,14 +1148,60 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1422
1148
|
);
|
|
1423
1149
|
}, [imageObject]);
|
|
1424
1150
|
|
|
1151
|
+
// Create a skeleton component for message bubbles with images
|
|
1425
1152
|
const renderMessage = useCallback(
|
|
1426
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
|
+
|
|
1427
1199
|
// Use memo to prevent unnecessary re-renders of each message
|
|
1428
1200
|
return (
|
|
1429
1201
|
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
1430
1202
|
);
|
|
1431
1203
|
},
|
|
1432
|
-
[isShowImageViewer],
|
|
1204
|
+
[isShowImageViewer, uploadingMessageId, loading],
|
|
1433
1205
|
);
|
|
1434
1206
|
|
|
1435
1207
|
let onScroll = false;
|
|
@@ -1446,197 +1218,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1446
1218
|
};
|
|
1447
1219
|
|
|
1448
1220
|
const onEndReached = () => {
|
|
1449
|
-
console.log('on end reached');
|
|
1450
1221
|
if (!onScroll) return;
|
|
1451
1222
|
onScroll = false;
|
|
1452
1223
|
};
|
|
1453
1224
|
|
|
1454
|
-
// Add debug logging to help diagnose the issue
|
|
1455
|
-
useEffect(() => {
|
|
1456
|
-
console.log('Current channel ID:', state.context.channelId);
|
|
1457
|
-
console.log('Current state:', state.value);
|
|
1458
|
-
console.log('Channel messages count:', state.context.channelMessages.length);
|
|
1459
|
-
}, [state.context.channelId, state.value, state.context.channelMessages]);
|
|
1460
|
-
|
|
1461
|
-
// Fix the infinite update loop in useEffect monitoring state changes
|
|
1462
|
-
useEffect(() => {
|
|
1463
|
-
// Only trigger effect if we have a specific state to handle
|
|
1464
|
-
// Check if function exists and if we're in a valid state before calling implementation functions
|
|
1465
|
-
if (state && typeof state.matches === 'function') {
|
|
1466
|
-
if (state.matches(BaseState.FetchMessages)) {
|
|
1467
|
-
console.log('In FetchMessages state, attempting to fetch messages');
|
|
1468
|
-
// Use a ref to track if we've already fetched for this state update
|
|
1469
|
-
if (!fetchInProgressRef.current) {
|
|
1470
|
-
fetchInProgressRef.current = true;
|
|
1471
|
-
fetchMessagesDirectly().finally(() => {
|
|
1472
|
-
fetchInProgressRef.current = false;
|
|
1473
|
-
});
|
|
1474
|
-
}
|
|
1475
|
-
} else if (state.matches(MainState.FetchMoreMessages)) {
|
|
1476
|
-
if (!fetchMoreInProgressRef.current) {
|
|
1477
|
-
fetchMoreInProgressRef.current = true;
|
|
1478
|
-
fetchMoreMessagesImpl().then((result) => {
|
|
1479
|
-
if (result.error) {
|
|
1480
|
-
console.error('Error fetching more messages:', result.error);
|
|
1481
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1482
|
-
} else {
|
|
1483
|
-
safeSend({ type: 'FETCH_MORE_MESSAGES_SUCCESS', data: result });
|
|
1484
|
-
}
|
|
1485
|
-
fetchMoreInProgressRef.current = false;
|
|
1486
|
-
});
|
|
1487
|
-
}
|
|
1488
|
-
} else if (state.matches(MainState.SendMessage)) {
|
|
1489
|
-
if (!sendInProgressRef.current) {
|
|
1490
|
-
sendInProgressRef.current = true;
|
|
1491
|
-
sendMessageImpl().then((result) => {
|
|
1492
|
-
if (result.error) {
|
|
1493
|
-
console.error('Error sending message:', result.error);
|
|
1494
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1495
|
-
} else {
|
|
1496
|
-
safeSend({ type: 'SEND_MESSAGE_SUCCESS', data: result });
|
|
1497
|
-
}
|
|
1498
|
-
sendInProgressRef.current = false;
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
|
-
} else if (state.matches(MainState.SendMessageWithFile)) {
|
|
1502
|
-
if (!sendFileInProgressRef.current) {
|
|
1503
|
-
sendFileInProgressRef.current = true;
|
|
1504
|
-
sendMessageWithFileImpl().then((result) => {
|
|
1505
|
-
if (result.error) {
|
|
1506
|
-
console.error('Error sending message with file:', result.error);
|
|
1507
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1508
|
-
} else {
|
|
1509
|
-
safeSend({ type: 'SEND_MESSAGE_WITH_FILE_SUCCESS', data: result });
|
|
1510
|
-
}
|
|
1511
|
-
sendFileInProgressRef.current = false;
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
} else if (state.matches(MainState.CreateDirectChannel)) {
|
|
1515
|
-
if (!createChannelInProgressRef.current) {
|
|
1516
|
-
createChannelInProgressRef.current = true;
|
|
1517
|
-
createDirectChannelImpl().then((result) => {
|
|
1518
|
-
if (result.error) {
|
|
1519
|
-
console.error('Error creating direct channel:', result.error);
|
|
1520
|
-
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1521
|
-
} else {
|
|
1522
|
-
safeSend({ type: 'CREATE_DIRECT_CHANNEL_SUCCESS', data: result });
|
|
1523
|
-
}
|
|
1524
|
-
createChannelInProgressRef.current = false;
|
|
1525
|
-
});
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
}, [
|
|
1530
|
-
state?.value,
|
|
1531
|
-
fetchMessagesDirectly,
|
|
1532
|
-
fetchMoreMessagesImpl,
|
|
1533
|
-
sendMessageImpl,
|
|
1534
|
-
sendMessageWithFileImpl,
|
|
1535
|
-
createDirectChannelImpl,
|
|
1536
|
-
safeSend,
|
|
1537
|
-
]);
|
|
1538
|
-
|
|
1539
|
-
// Add refs to prevent duplicate operations
|
|
1540
|
-
const fetchInProgressRef = useRef(false);
|
|
1541
|
-
const fetchMoreInProgressRef = useRef(false);
|
|
1542
|
-
const sendInProgressRef = useRef(false);
|
|
1543
|
-
const sendFileInProgressRef = useRef(false);
|
|
1544
|
-
const createChannelInProgressRef = useRef(false);
|
|
1545
|
-
|
|
1546
|
-
// Fix subscription handler to prevent infinite updates
|
|
1547
|
-
const renderChatFooter = useCallback(() => {
|
|
1548
|
-
return (
|
|
1549
|
-
<>
|
|
1550
|
-
<ImageViewerModal
|
|
1551
|
-
isVisible={isShowImageViewer}
|
|
1552
|
-
setVisible={setImageViewer}
|
|
1553
|
-
modalContent={modalContent}
|
|
1554
|
-
/>
|
|
1555
|
-
<SubscriptionHandler
|
|
1556
|
-
channelId={state?.context?.channelId?.toString()}
|
|
1557
|
-
subscribeToNewMessages={() =>
|
|
1558
|
-
subscribeToMore({
|
|
1559
|
-
document: CHAT_MESSAGE_ADDED,
|
|
1560
|
-
variables: {
|
|
1561
|
-
channelId: state?.context?.channelId?.toString(),
|
|
1562
|
-
},
|
|
1563
|
-
updateQuery: (prev, { subscriptionData }: any) => {
|
|
1564
|
-
if (!subscriptionData?.data?.chatMessageAdded) return prev;
|
|
1565
|
-
|
|
1566
|
-
const newMessage = subscriptionData.data.chatMessageAdded;
|
|
1567
|
-
const currentMessages = prev?.messages?.data || [];
|
|
1568
|
-
|
|
1569
|
-
// Check if message already exists to prevent duplicates
|
|
1570
|
-
if (currentMessages.some((msg) => msg.id === newMessage.id)) {
|
|
1571
|
-
return prev; // Skip update if message already exists
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
// Use a ref to track the last processed message ID to prevent duplicate processing
|
|
1575
|
-
if (lastProcessedMessageRef.current === newMessage.id) {
|
|
1576
|
-
return prev;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
lastProcessedMessageRef.current = newMessage.id;
|
|
1580
|
-
|
|
1581
|
-
// Use a batch update strategy to avoid frequent re-renders
|
|
1582
|
-
queueMicrotask(() => {
|
|
1583
|
-
safeSend({
|
|
1584
|
-
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
1585
|
-
data: {
|
|
1586
|
-
messages: uniqBy(
|
|
1587
|
-
[...state.context.channelMessages, newMessage],
|
|
1588
|
-
({ id }) => id,
|
|
1589
|
-
),
|
|
1590
|
-
totalCount: (prev?.messages?.totalCount || 0) + 1,
|
|
1591
|
-
},
|
|
1592
|
-
});
|
|
1593
|
-
});
|
|
1594
|
-
|
|
1595
|
-
return {
|
|
1596
|
-
...prev,
|
|
1597
|
-
messages: {
|
|
1598
|
-
...prev?.messages,
|
|
1599
|
-
data: [...currentMessages, newMessage],
|
|
1600
|
-
totalCount: (prev?.messages?.totalCount || 0) + 1,
|
|
1601
|
-
},
|
|
1602
|
-
};
|
|
1603
|
-
},
|
|
1604
|
-
})
|
|
1605
|
-
}
|
|
1606
|
-
/>
|
|
1607
|
-
</>
|
|
1608
|
-
);
|
|
1609
|
-
}, [
|
|
1610
|
-
isShowImageViewer,
|
|
1611
|
-
modalContent,
|
|
1612
|
-
state?.context?.channelId,
|
|
1613
|
-
state?.context?.channelMessages,
|
|
1614
|
-
subscribeToMore,
|
|
1615
|
-
safeSend,
|
|
1616
|
-
]);
|
|
1617
|
-
|
|
1618
|
-
// Add ref to track last processed message
|
|
1619
|
-
const lastProcessedMessageRef = useRef(null);
|
|
1620
|
-
|
|
1621
|
-
// Add optimized listViewProps to reduce re-renders and improve list performance
|
|
1622
|
-
const listViewProps = useMemo(
|
|
1623
|
-
() => ({
|
|
1624
|
-
onEndReached: onEndReached,
|
|
1625
|
-
onEndReachedThreshold: 0.5,
|
|
1626
|
-
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
1627
|
-
removeClippedSubviews: true, // Improve performance by unmounting components when not visible
|
|
1628
|
-
initialNumToRender: 10, // Reduce initial render amount
|
|
1629
|
-
maxToRenderPerBatch: 7, // Reduce number in each render batch
|
|
1630
|
-
windowSize: 7, // Reduce the window size
|
|
1631
|
-
updateCellsBatchingPeriod: 50, // Batch cell updates to improve scrolling
|
|
1632
|
-
keyExtractor: (item) => item._id, // Add explicit key extractor
|
|
1633
|
-
}),
|
|
1634
|
-
[onEndReached, onMomentumScrollBegin],
|
|
1635
|
-
);
|
|
1636
|
-
|
|
1637
1225
|
// Add a loader for when more messages are being loaded
|
|
1638
1226
|
const renderLoadEarlier = useCallback(() => {
|
|
1639
|
-
return
|
|
1227
|
+
return loadingOldMessages ? (
|
|
1640
1228
|
<View
|
|
1641
1229
|
style={{
|
|
1642
1230
|
padding: 10,
|
|
@@ -1648,7 +1236,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1648
1236
|
<Spinner size="small" color="#3b82f6" />
|
|
1649
1237
|
</View>
|
|
1650
1238
|
) : null;
|
|
1651
|
-
}, [
|
|
1239
|
+
}, [loadingOldMessages]);
|
|
1652
1240
|
|
|
1653
1241
|
// Add renderInputToolbar function
|
|
1654
1242
|
const renderInputToolbar = useCallback((props) => {
|
|
@@ -1672,15 +1260,106 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1672
1260
|
);
|
|
1673
1261
|
}, []);
|
|
1674
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
|
+
|
|
1675
1350
|
// Return optimized component with performance improvements
|
|
1676
1351
|
return (
|
|
1677
1352
|
<View
|
|
1678
1353
|
style={{
|
|
1679
1354
|
flex: 1,
|
|
1680
1355
|
backgroundColor: 'white',
|
|
1356
|
+
position: 'relative',
|
|
1681
1357
|
}}
|
|
1682
1358
|
>
|
|
1683
|
-
{
|
|
1359
|
+
{messageLoading && <Spinner color={'#3b82f6'} />}
|
|
1360
|
+
|
|
1361
|
+
{/* Render the image preview directly in the container so it's properly positioned */}
|
|
1362
|
+
{selectedImage ? renderAccessory() : null}
|
|
1684
1363
|
|
|
1685
1364
|
<GiftedChat
|
|
1686
1365
|
ref={messageRootListRef}
|
|
@@ -1690,20 +1369,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1690
1369
|
listViewProps={{
|
|
1691
1370
|
...listViewProps,
|
|
1692
1371
|
contentContainerStyle: {
|
|
1693
|
-
paddingBottom:
|
|
1372
|
+
paddingBottom: selectedImage ? 90 : 0, // Add padding at the bottom when image is selected
|
|
1694
1373
|
},
|
|
1695
|
-
keyboardShouldPersistTaps: 'handled',
|
|
1696
1374
|
}}
|
|
1697
1375
|
onSend={handleSend}
|
|
1698
|
-
text={
|
|
1699
|
-
onInputTextChanged={(text) =>
|
|
1700
|
-
|
|
1701
|
-
}
|
|
1702
|
-
renderFooter={() =>
|
|
1703
|
-
safeContextProperty('loading') || safeContextProperty('imageLoading') ? (
|
|
1704
|
-
<Spinner color={'#3b82f6'} />
|
|
1705
|
-
) : null
|
|
1706
|
-
}
|
|
1376
|
+
text={messageText || ' '}
|
|
1377
|
+
onInputTextChanged={(text) => setMessageText(text)}
|
|
1378
|
+
renderFooter={() => (loading && !images.length ? <Spinner color={'#3b82f6'} /> : null)}
|
|
1707
1379
|
scrollToBottom
|
|
1708
1380
|
user={{
|
|
1709
1381
|
_id: auth?.id || '',
|
|
@@ -1714,14 +1386,13 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1714
1386
|
renderMessageText={renderMessageText}
|
|
1715
1387
|
renderInputToolbar={renderInputToolbar}
|
|
1716
1388
|
minInputToolbarHeight={50}
|
|
1717
|
-
renderActions={
|
|
1718
|
-
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
1389
|
+
renderActions={channelId && renderActions}
|
|
1719
1390
|
renderMessage={renderMessage}
|
|
1720
1391
|
renderChatFooter={renderChatFooter}
|
|
1721
1392
|
renderLoadEarlier={renderLoadEarlier}
|
|
1722
|
-
loadEarlier={
|
|
1723
|
-
isLoadingEarlier={
|
|
1724
|
-
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
|
|
1725
1396
|
textInputProps={{
|
|
1726
1397
|
style: {
|
|
1727
1398
|
borderWidth: 1,
|
|
@@ -1759,15 +1430,27 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
1759
1430
|
};
|
|
1760
1431
|
|
|
1761
1432
|
const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: ISubscriptionHandlerProps) => {
|
|
1762
|
-
//
|
|
1433
|
+
// Store the channelId in a ref to track changes
|
|
1763
1434
|
const channelIdRef = useRef(channelId);
|
|
1764
1435
|
|
|
1765
1436
|
useEffect(() => {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
return subscribeToNewMessages();
|
|
1437
|
+
// Don't set up subscription if there's no channel ID
|
|
1438
|
+
if (!channelId) {
|
|
1439
|
+
return;
|
|
1770
1440
|
}
|
|
1441
|
+
|
|
1442
|
+
// Call the subscribe function and store the unsubscribe function
|
|
1443
|
+
const unsubscribe = subscribeToNewMessages();
|
|
1444
|
+
|
|
1445
|
+
// Update the ref with the current channelId
|
|
1446
|
+
channelIdRef.current = channelId;
|
|
1447
|
+
|
|
1448
|
+
// Return cleanup function
|
|
1449
|
+
return () => {
|
|
1450
|
+
if (unsubscribe && typeof unsubscribe === 'function') {
|
|
1451
|
+
unsubscribe();
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1771
1454
|
}, [channelId, subscribeToNewMessages]);
|
|
1772
1455
|
|
|
1773
1456
|
return null;
|