@messenger-box/platform-mobile 10.0.3-alpha.16 → 10.0.3-alpha.18
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/routes.json +14 -1
- package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/index.js +125 -16
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +32 -21
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/containers/ConversationView.js +1175 -400
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +290 -21
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +858 -351
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js +380 -0
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +1 -0
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +235 -0
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +1 -0
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +438 -0
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +1 -0
- package/package.json +4 -4
- package/src/screens/inbox/components/CachedImage/consts.ts +4 -3
- package/src/screens/inbox/components/CachedImage/index.tsx +137 -17
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +35 -9
- package/src/screens/inbox/containers/ConversationView.tsx +1510 -641
- package/src/screens/inbox/containers/Dialogs.tsx +415 -123
- package/src/screens/inbox/containers/ThreadConversationView.tsx +1053 -288
- package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +20 -0
- package/src/screens/inbox/containers/workflow/conversation-xstate.ts +313 -0
- package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +196 -0
- package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +401 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
Spinner,
|
|
13
13
|
Text,
|
|
14
14
|
} from '@admin-layout/gluestack-ui-mobile';
|
|
15
|
-
import { Platform, TouchableHighlight } from 'react-native';
|
|
15
|
+
import { Platform, TouchableHighlight, SafeAreaView, View } from 'react-native';
|
|
16
16
|
import { useFocusEffect, useIsFocused, useNavigation } from '@react-navigation/native';
|
|
17
17
|
import { navigationRef } from '@common-stack/client-react';
|
|
18
18
|
import { useSelector } from 'react-redux';
|
|
@@ -20,7 +20,7 @@ import { orderBy, startCase, uniqBy } from 'lodash-es';
|
|
|
20
20
|
import * as ImagePicker from 'expo-image-picker';
|
|
21
21
|
import { encode as atob } from 'base-64';
|
|
22
22
|
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
|
|
23
|
-
import { Actions, GiftedChat, IMessage, MessageText, Send } from 'react-native-gifted-chat';
|
|
23
|
+
import { Actions, GiftedChat, IMessage, MessageText, Send, Composer, InputToolbar } from 'react-native-gifted-chat';
|
|
24
24
|
import { PreDefinedRole, RoomType, IExpoNotificationData, IFileInfo } from 'common';
|
|
25
25
|
import {
|
|
26
26
|
OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
|
|
@@ -37,6 +37,20 @@ import { format, isToday, isYesterday } from 'date-fns';
|
|
|
37
37
|
import { ImageViewerModal, SlackMessage } from '../components/SlackMessageContainer';
|
|
38
38
|
import CachedImage from '../components/CachedImage';
|
|
39
39
|
import { config } from '../config';
|
|
40
|
+
import {
|
|
41
|
+
conversationXstate,
|
|
42
|
+
Actions as ConversationActions,
|
|
43
|
+
BaseState,
|
|
44
|
+
MainState,
|
|
45
|
+
} from './workflow/conversation-xstate';
|
|
46
|
+
import colors from 'tailwindcss/colors';
|
|
47
|
+
|
|
48
|
+
// Define an extended interface for ImagePickerAsset with url property
|
|
49
|
+
interface ExtendedImagePickerAsset extends ImagePicker.ImagePickerAsset {
|
|
50
|
+
url?: string;
|
|
51
|
+
fileName?: string;
|
|
52
|
+
mimeType?: string;
|
|
53
|
+
}
|
|
40
54
|
|
|
41
55
|
const {
|
|
42
56
|
MESSAGES_PER_PAGE,
|
|
@@ -76,35 +90,348 @@ export interface AlertMessageAttachmentsInterface {
|
|
|
76
90
|
};
|
|
77
91
|
}
|
|
78
92
|
|
|
93
|
+
// Create a safer version of useMachine to handle potential errors
|
|
94
|
+
function useSafeMachine(machine) {
|
|
95
|
+
// Define the state type
|
|
96
|
+
interface SafeStateType {
|
|
97
|
+
context: {
|
|
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
|
+
|
|
79
317
|
const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMessage, ...rest }: any) => {
|
|
80
318
|
const [channelToTop, setChannelToTop] = useState(0);
|
|
81
|
-
|
|
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]);
|
|
393
|
+
|
|
394
|
+
// Avoid referencing state.context directly in places that might cause undefined errors
|
|
395
|
+
const safeGetContext = useCallback(() => {
|
|
396
|
+
if (stateRef.current && stateRef.current.context) {
|
|
397
|
+
return stateRef.current.context;
|
|
398
|
+
}
|
|
399
|
+
// Return default values if context is undefined
|
|
400
|
+
return {
|
|
401
|
+
channelId: null,
|
|
402
|
+
channelMessages: [],
|
|
403
|
+
totalCount: 0,
|
|
404
|
+
skip: 0,
|
|
405
|
+
loading: false,
|
|
406
|
+
loadingOldMessages: false,
|
|
407
|
+
error: null,
|
|
408
|
+
selectedImage: '',
|
|
409
|
+
files: [],
|
|
410
|
+
images: [],
|
|
411
|
+
messageText: '',
|
|
412
|
+
imageLoading: false,
|
|
413
|
+
};
|
|
414
|
+
}, []);
|
|
415
|
+
|
|
416
|
+
// Use cleanup function to prevent setting state after unmount
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
return () => {
|
|
419
|
+
isMountedRef.current = false;
|
|
420
|
+
};
|
|
421
|
+
}, []);
|
|
422
|
+
|
|
82
423
|
const auth: any = useSelector(userSelector);
|
|
83
|
-
const [totalCount, setTotalCount] = useState<any>(0);
|
|
84
|
-
const [selectedImage, setImage] = useState<string>('');
|
|
85
424
|
const currentRoute = navigationRef.isReady() ? navigationRef?.getCurrentRoute() : null;
|
|
86
|
-
const [channelId, setChannelId] = useState<any>(rest?.isCreateNewChannel ? null : ChannelId);
|
|
87
|
-
const [loadingOldMessages, setLoadingOldMessages] = useState<boolean>(false);
|
|
88
425
|
const navigation = useNavigation<any>();
|
|
89
|
-
const [
|
|
90
|
-
const [images, setImages] = useState<ImagePicker.ImagePickerAsset[]>([]);
|
|
91
|
-
const [msg, setMsg] = useState<string>('');
|
|
92
|
-
const [loading, setLoading] = useState(false);
|
|
93
|
-
const [loadEarlierMsg, setLoadEarlierMsg] = useState(false);
|
|
94
|
-
const [imageLoading, setImageLoading] = useState(false);
|
|
95
|
-
const [expoTokens, setExpoTokens] = useState<any[]>([]);
|
|
426
|
+
const [selectedImage, setImage] = useState<string>('');
|
|
96
427
|
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
97
428
|
const [imageObject, setImageObject] = useState<any>({});
|
|
98
|
-
const [skip, setSkip] = useState(0);
|
|
99
429
|
const messageRootListRef = useRef<any>(null);
|
|
100
430
|
const isFocused = useIsFocused();
|
|
101
431
|
|
|
102
|
-
const [addDirectChannel
|
|
103
|
-
|
|
432
|
+
const [addDirectChannel] = useAddDirectChannelMutation();
|
|
104
433
|
const { startUpload } = useUploadFilesNative();
|
|
105
|
-
|
|
106
434
|
const [sendMsg] = useSendMessagesMutation();
|
|
107
|
-
|
|
108
435
|
const [sendExpoNotificationOnPostMutation] = useSendExpoNotificationOnPostMutation();
|
|
109
436
|
|
|
110
437
|
const {
|
|
@@ -115,119 +442,473 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
115
442
|
subscribeToMore,
|
|
116
443
|
}: any = useMessagesQuery({
|
|
117
444
|
variables: {
|
|
118
|
-
channelId: channelId?.toString(),
|
|
445
|
+
channelId: state.context.channelId?.toString(),
|
|
119
446
|
parentId: null,
|
|
120
447
|
limit: MESSAGES_PER_PAGE,
|
|
121
|
-
skip: skip,
|
|
448
|
+
skip: state.context.skip,
|
|
122
449
|
},
|
|
123
|
-
skip: !channelId,
|
|
450
|
+
skip: !state.context.channelId,
|
|
124
451
|
fetchPolicy: 'cache-and-network',
|
|
125
452
|
nextFetchPolicy: 'cache-first',
|
|
126
453
|
refetchWritePolicy: 'merge',
|
|
454
|
+
onCompleted: (queryData) => {
|
|
455
|
+
console.log('MESSAGE QUERY COMPLETED:', queryData);
|
|
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
|
+
}
|
|
464
|
+
},
|
|
465
|
+
onError: (error) => {
|
|
466
|
+
console.error('MESSAGE QUERY ERROR:', error);
|
|
467
|
+
},
|
|
127
468
|
});
|
|
128
469
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
470
|
+
// Modify the fetchMessagesDirectly function to use safe access
|
|
471
|
+
const fetchMessagesDirectly = useCallback(async () => {
|
|
472
|
+
const channelId = safeGetContext().channelId;
|
|
473
|
+
if (!channelId) {
|
|
474
|
+
console.warn('Cannot fetch messages: No channel ID');
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
console.log('💫 FETCHING messages for channel:', channelId);
|
|
480
|
+
|
|
481
|
+
// Use loading state to prevent duplicate fetches
|
|
482
|
+
send({ type: ConversationActions.START_LOADING });
|
|
483
|
+
|
|
484
|
+
const response = await refetch({
|
|
485
|
+
channelId: channelId.toString(),
|
|
486
|
+
parentId: null,
|
|
487
|
+
limit: MESSAGES_PER_PAGE,
|
|
488
|
+
skip: 0,
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
if (response?.data?.messages) {
|
|
492
|
+
const { data: messages, totalCount } = response.data.messages;
|
|
493
|
+
|
|
494
|
+
if (messages && messages.length > 0) {
|
|
495
|
+
// Batch update to reduce renders
|
|
496
|
+
safeSend({
|
|
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 });
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error('ERROR fetching messages:', error);
|
|
511
|
+
send({ type: ConversationActions.STOP_LOADING });
|
|
512
|
+
}
|
|
513
|
+
}, [safeGetContext, refetch, safeSend]);
|
|
514
|
+
|
|
515
|
+
const fetchMoreMessagesImpl = useCallback(async () => {
|
|
516
|
+
try {
|
|
517
|
+
const response = await fetchMoreMessages({
|
|
518
|
+
variables: {
|
|
519
|
+
channelId: state.context.channelId?.toString(),
|
|
520
|
+
parentId: null,
|
|
521
|
+
skip: state.context.channelMessages.length,
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
if (!response?.data?.messages?.data) {
|
|
526
|
+
return { error: 'No messages returned' };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return { messages: response.data.messages.data };
|
|
530
|
+
} catch (error) {
|
|
531
|
+
return { error: String(error) };
|
|
532
|
+
}
|
|
533
|
+
}, [state.context.channelId, state.context.channelMessages.length, fetchMoreMessages]);
|
|
534
|
+
|
|
535
|
+
const sendMessageImpl = useCallback(async () => {
|
|
536
|
+
try {
|
|
537
|
+
const notificationData: IExpoNotificationData = {
|
|
538
|
+
url: config.INBOX_MESSEGE_PATH,
|
|
539
|
+
params: { channelId: state.context.channelId, hideTabBar: true },
|
|
540
|
+
screen: 'DialogMessages',
|
|
541
|
+
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const response = await sendMsg({
|
|
545
|
+
variables: {
|
|
546
|
+
channelId: state.context.channelId,
|
|
547
|
+
content: state.context.messageText,
|
|
548
|
+
notificationParams: notificationData,
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
return { message: response.data?.sendMessage };
|
|
553
|
+
} catch (error) {
|
|
554
|
+
return { error: String(error) };
|
|
555
|
+
}
|
|
556
|
+
}, [state.context.channelId, state.context.messageText, sendMsg]);
|
|
557
|
+
|
|
558
|
+
// Fix the image selection process to ensure proper format for upload
|
|
559
|
+
const onSelectImages = async () => {
|
|
560
|
+
safeSend({ type: ConversationActions.START_LOADING });
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
console.log('Starting image picker...');
|
|
564
|
+
let imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
565
|
+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
566
|
+
allowsEditing: true,
|
|
567
|
+
aspect: [4, 3],
|
|
568
|
+
quality: 0.8,
|
|
569
|
+
base64: true,
|
|
570
|
+
exif: false,
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
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
|
+
// Get the asset
|
|
586
|
+
const selectedAsset = imageSource?.assets?.[0];
|
|
587
|
+
if (!selectedAsset) {
|
|
588
|
+
console.error('No asset found in selected image');
|
|
589
|
+
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Create a base64 image string for preview
|
|
594
|
+
const base64Data = selectedAsset.base64;
|
|
595
|
+
const previewImage = base64Data ? `data:image/jpeg;base64,${base64Data}` : selectedAsset.uri;
|
|
596
|
+
|
|
597
|
+
// Format the asset for upload service requirements
|
|
598
|
+
const asset: ExtendedImagePickerAsset = {
|
|
599
|
+
...selectedAsset,
|
|
600
|
+
url: selectedAsset.uri,
|
|
601
|
+
fileName: selectedAsset.fileName || `image_${Date.now()}.jpg`,
|
|
602
|
+
mimeType: 'image/jpeg',
|
|
603
|
+
};
|
|
604
|
+
|
|
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
|
+
// Update state with the new image
|
|
613
|
+
safeSend({
|
|
614
|
+
type: ConversationActions.SET_IMAGE,
|
|
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');
|
|
622
|
+
} else {
|
|
623
|
+
console.log('Image selection cancelled');
|
|
624
|
+
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
console.error('Error selecting image:', error);
|
|
628
|
+
safeSend({ type: ConversationActions.STOP_LOADING });
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// Update the sendMessageWithFileImpl function to fix image uploads
|
|
633
|
+
const sendMessageWithFileImpl = useCallback(async () => {
|
|
634
|
+
try {
|
|
635
|
+
console.log('Executing sendMessageWithFileImpl');
|
|
636
|
+
|
|
637
|
+
// Generate a unique post ID for the message
|
|
638
|
+
const postId = objectId();
|
|
639
|
+
console.log('Generated postId for file upload:', postId);
|
|
640
|
+
|
|
641
|
+
// Prepare notification data
|
|
642
|
+
const notificationData: IExpoNotificationData = {
|
|
643
|
+
url: config.INBOX_MESSEGE_PATH,
|
|
644
|
+
params: { channelId: state.context.channelId, hideTabBar: true },
|
|
645
|
+
screen: 'DialogMessages',
|
|
646
|
+
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// Safety check for images
|
|
650
|
+
if (!state.context.images || state.context.images.length === 0) {
|
|
651
|
+
console.error('No images found in state');
|
|
652
|
+
return { error: 'No images available to upload' };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Format the images for upload if needed
|
|
656
|
+
const imagesToUpload = state.context.images.map((img) => {
|
|
657
|
+
// Ensure the image has all required properties
|
|
658
|
+
return {
|
|
659
|
+
...img,
|
|
660
|
+
uri: img.uri || img.url, // Use either uri or url
|
|
661
|
+
type: 'image/jpeg',
|
|
662
|
+
name: img.fileName || `image_${Date.now()}.jpg`,
|
|
663
|
+
};
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
console.log(
|
|
667
|
+
'Formatted images for upload:',
|
|
668
|
+
imagesToUpload.map((img) => ({
|
|
669
|
+
hasUri: !!img.uri,
|
|
670
|
+
hasUrl: !!img.url,
|
|
671
|
+
hasName: !!img.name,
|
|
672
|
+
hasType: !!img.type,
|
|
673
|
+
hasFileName: !!img.fileName,
|
|
674
|
+
uri: img.uri?.substring(0, 30) + '...',
|
|
675
|
+
})),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
// Upload the files
|
|
679
|
+
console.log('Starting file upload...');
|
|
680
|
+
const uploadResponse = await startUpload({
|
|
681
|
+
file: imagesToUpload,
|
|
682
|
+
saveUploadedFile: {
|
|
683
|
+
variables: { postId },
|
|
684
|
+
},
|
|
685
|
+
createUploadLink: {
|
|
686
|
+
variables: { postId },
|
|
687
|
+
},
|
|
688
|
+
});
|
|
689
|
+
|
|
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
|
+
if (uploadResponse?.error) {
|
|
698
|
+
console.error('Upload error:', uploadResponse.error);
|
|
699
|
+
return { error: String(uploadResponse.error) };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Get uploaded file IDs
|
|
703
|
+
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
|
+
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
712
|
+
|
|
713
|
+
console.log('Files uploaded successfully. File IDs:', files);
|
|
714
|
+
|
|
715
|
+
// 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
|
+
const response = await sendMsg({
|
|
725
|
+
variables: {
|
|
726
|
+
postId,
|
|
727
|
+
channelId: state.context.channelId,
|
|
728
|
+
content: state.context.messageText || ' ', // Use a space if no text
|
|
729
|
+
files,
|
|
730
|
+
notificationParams: notificationData,
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
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
|
+
// Clear the images after successful send
|
|
749
|
+
setTimeout(() => {
|
|
750
|
+
safeSend({ type: ConversationActions.CLEAR_IMAGE });
|
|
751
|
+
}, 100);
|
|
752
|
+
} else {
|
|
753
|
+
console.error('Failed to send message with file:', response?.errors);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return { message: response.data?.sendMessage };
|
|
757
|
+
} catch (error) {
|
|
758
|
+
console.error('Error in sendMessageWithFileImpl:', error);
|
|
759
|
+
return { error: String(error) };
|
|
760
|
+
}
|
|
761
|
+
}, [state.context.channelId, state.context.messageText, state.context.images, startUpload, sendMsg, safeSend]);
|
|
762
|
+
|
|
763
|
+
const createDirectChannelImpl = useCallback(async () => {
|
|
764
|
+
try {
|
|
765
|
+
if (
|
|
766
|
+
!rest?.isCreateNewChannel ||
|
|
767
|
+
rest?.newChannelData?.type !== RoomType?.Direct ||
|
|
768
|
+
!rest?.newChannelData?.userIds?.length
|
|
769
|
+
) {
|
|
770
|
+
return { error: 'Invalid channel data' };
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const response = await addDirectChannel({
|
|
774
|
+
variables: {
|
|
775
|
+
receiver: [...(rest?.newChannelData?.userIds ?? [])],
|
|
776
|
+
displayName: 'DIRECT CHANNEL',
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
if (!response?.data?.createDirectChannel?.id) {
|
|
781
|
+
return { error: 'Failed to create channel' };
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const newChannelId = response.data.createDirectChannel.id;
|
|
785
|
+
|
|
786
|
+
const notificationData: IExpoNotificationData = {
|
|
787
|
+
url: config.INBOX_MESSEGE_PATH,
|
|
788
|
+
params: { channelId: newChannelId, hideTabBar: true },
|
|
789
|
+
screen: 'DialogMessages',
|
|
790
|
+
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
await sendMsg({
|
|
794
|
+
variables: {
|
|
795
|
+
channelId: newChannelId,
|
|
796
|
+
content: state.context.messageText,
|
|
797
|
+
notificationParams: notificationData,
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
return { channelId: newChannelId };
|
|
802
|
+
} catch (error) {
|
|
803
|
+
return { error: String(error) };
|
|
804
|
+
}
|
|
805
|
+
}, [rest, state.context.messageText, addDirectChannel, sendMsg]);
|
|
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]);
|
|
139
811
|
|
|
140
812
|
React.useEffect(() => {
|
|
141
813
|
return () => {
|
|
142
|
-
|
|
814
|
+
send({ type: ConversationActions.CLEAR_MESSAGES });
|
|
143
815
|
};
|
|
144
816
|
}, []);
|
|
145
817
|
|
|
146
818
|
useFocusEffect(
|
|
147
819
|
React.useCallback(() => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// refetchChannelDetail({ id: channelId?.toString() });
|
|
151
|
-
if (channelId) {
|
|
152
|
-
refetch({
|
|
153
|
-
channelId: channelId?.toString(),
|
|
154
|
-
parentId: null,
|
|
155
|
-
limit: MESSAGES_PER_PAGE,
|
|
156
|
-
skip: 0,
|
|
157
|
-
}).then(({ data }) => {
|
|
158
|
-
if (!data?.messages) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const { data: messages, totalCount }: any = data.messages;
|
|
162
|
-
setTotalCount(totalCount);
|
|
163
|
-
setChannelMessages(messages);
|
|
164
|
-
});
|
|
820
|
+
if (state.context.channelId) {
|
|
821
|
+
send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: state.context.channelId } });
|
|
165
822
|
}
|
|
166
823
|
return () => {
|
|
167
|
-
|
|
168
|
-
// Useful for cleanup functions
|
|
169
|
-
setChannelId(null);
|
|
170
|
-
setTotalCount(0);
|
|
171
|
-
setSkip(0);
|
|
824
|
+
send({ type: ConversationActions.CLEAR_MESSAGES });
|
|
172
825
|
};
|
|
173
|
-
}, [channelId, isFocused]),
|
|
826
|
+
}, [state.context.channelId, isFocused]),
|
|
174
827
|
);
|
|
175
828
|
|
|
176
829
|
React.useEffect(() => {
|
|
177
830
|
const currentChannelId = ChannelId || currentRoute?.params?.channelId;
|
|
178
|
-
|
|
831
|
+
if (currentChannelId) {
|
|
832
|
+
console.log('Setting initial channel ID:', currentChannelId);
|
|
833
|
+
send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: currentChannelId } });
|
|
834
|
+
}
|
|
179
835
|
}, [ChannelId, currentRoute]);
|
|
180
836
|
|
|
181
837
|
React.useEffect(() => {
|
|
182
|
-
if (selectedImage)
|
|
183
|
-
|
|
838
|
+
if (state.context.selectedImage) {
|
|
839
|
+
send({ type: ConversationActions.STOP_LOADING });
|
|
840
|
+
}
|
|
841
|
+
}, [state.context.selectedImage]);
|
|
184
842
|
|
|
185
843
|
useEffect(() => {
|
|
186
844
|
if (data?.messages?.data) {
|
|
187
|
-
|
|
188
|
-
|
|
845
|
+
console.log('📩 QUERY DATA CHANGED - Messages received:', data.messages.data.length);
|
|
846
|
+
const { data: messages, totalCount: messageTotalCount } = data.messages;
|
|
189
847
|
|
|
190
|
-
if (
|
|
191
|
-
(
|
|
192
|
-
(messages && messages.length > 0 && (loadingOldMessages || channelMessages.length === 0))
|
|
193
|
-
) {
|
|
194
|
-
setChannelMessages((oldMessages: any) => uniqBy([...messages, ...oldMessages], ({ id }) => id));
|
|
195
|
-
setTotalCount(messeageTotalCount);
|
|
196
|
-
}
|
|
848
|
+
if (messages && messages.length > 0) {
|
|
849
|
+
console.log('📩 QUERY DATA - Setting channel messages, count:', messages.length);
|
|
197
850
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
setLoadEarlierMsg(true);
|
|
205
|
-
try {
|
|
206
|
-
const response = await fetchMoreMessages({
|
|
207
|
-
variables: {
|
|
208
|
-
channelId: channelId?.toString(),
|
|
209
|
-
parentId: null,
|
|
210
|
-
skip: channelMessages.length,
|
|
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,
|
|
211
857
|
},
|
|
212
858
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
);
|
|
217
875
|
}
|
|
218
|
-
|
|
219
|
-
|
|
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);
|
|
220
883
|
}
|
|
884
|
+
}
|
|
885
|
+
}, [data]);
|
|
886
|
+
|
|
887
|
+
// Optimize onFetchOld by adding debounce logic
|
|
888
|
+
const onFetchOld = useCallback(() => {
|
|
889
|
+
// Prevent multiple rapid calls
|
|
890
|
+
if (fetchOldDebounceRef.current) return;
|
|
221
891
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
892
|
+
// Check if we need to fetch more messages
|
|
893
|
+
if (
|
|
894
|
+
state?.context?.totalCount > state?.context?.channelMessages?.length &&
|
|
895
|
+
!state?.context?.loadingOldMessages
|
|
896
|
+
) {
|
|
897
|
+
// Set debounce
|
|
898
|
+
fetchOldDebounceRef.current = true;
|
|
899
|
+
|
|
900
|
+
// Send fetch event
|
|
901
|
+
send({ type: ConversationActions.FETCH_MORE_MESSAGES });
|
|
902
|
+
|
|
903
|
+
// Clear debounce after a timeout
|
|
904
|
+
setTimeout(() => {
|
|
905
|
+
fetchOldDebounceRef.current = false;
|
|
906
|
+
}, 1000);
|
|
225
907
|
}
|
|
226
|
-
}, [totalCount, channelMessages]);
|
|
908
|
+
}, [state?.context?.totalCount, state?.context?.channelMessages, state?.context?.loadingOldMessages]);
|
|
227
909
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
// };
|
|
910
|
+
// Add debounce ref
|
|
911
|
+
const fetchOldDebounceRef = useRef(false);
|
|
231
912
|
|
|
232
913
|
const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
233
914
|
const paddingToTop = 60;
|
|
@@ -246,454 +927,492 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
246
927
|
return new File([u8arr], filename, { type: mime });
|
|
247
928
|
};
|
|
248
929
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
quality: 1,
|
|
256
|
-
base64: true,
|
|
257
|
-
});
|
|
258
|
-
// if (!imageSource.cancelled) {
|
|
259
|
-
// const image = 'data:image/jpeg;base64,' + imageSource?.base64;
|
|
260
|
-
// setImage(image);
|
|
261
|
-
// const file = dataURLtoFile(image, 'inputImage.jpg');
|
|
262
|
-
// setFiles((files) => files.concat(file));
|
|
263
|
-
// setImages((images) => images.concat(imageSource as ImagePicker.ImageInfo));
|
|
264
|
-
// }
|
|
265
|
-
// if (imageSource.cancelled) setLoading(false);
|
|
266
|
-
|
|
267
|
-
if (!imageSource.canceled) {
|
|
268
|
-
const image = 'data:image/jpeg;base64,' + imageSource?.assets[0]?.base64;
|
|
269
|
-
setImage(image);
|
|
270
|
-
const file = dataURLtoFile(image, 'inputImage.jpg');
|
|
271
|
-
setFiles((files) => files.concat(file));
|
|
272
|
-
setImages((images) => images.concat(imageSource?.assets[0] as ImagePicker.ImagePickerAsset));
|
|
273
|
-
}
|
|
930
|
+
// Fix the render send function to ensure it works for image-only messages
|
|
931
|
+
const renderSend = useCallback(
|
|
932
|
+
(props) => {
|
|
933
|
+
// Enable the send button if there's text OR we have images
|
|
934
|
+
const hasContent = !!props.text || state?.context?.images?.length > 0;
|
|
935
|
+
const canSend = (state?.context?.channelId || rest?.isCreateNewChannel) && hasContent;
|
|
274
936
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
299
|
-
};
|
|
300
|
-
setLoading(true);
|
|
301
|
-
await sendMsg({
|
|
302
|
-
variables: {
|
|
303
|
-
channelId: res?.data?.createDirectChannel?.id,
|
|
304
|
-
content: msg,
|
|
305
|
-
notificationParams: notificationData,
|
|
306
|
-
},
|
|
307
|
-
update: (cache, { data, errors }: any) => {
|
|
308
|
-
if (!data || errors) {
|
|
309
|
-
setLoading(false);
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
setChannelToTop(channelToTop + 1);
|
|
313
|
-
setLoading(false);
|
|
314
|
-
setMsg('');
|
|
315
|
-
},
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
?.catch((e: any) => console.log('error', JSON.stringify(e)));
|
|
320
|
-
}
|
|
937
|
+
return (
|
|
938
|
+
<Send
|
|
939
|
+
{...props}
|
|
940
|
+
disabled={!canSend}
|
|
941
|
+
containerStyle={{
|
|
942
|
+
justifyContent: 'center',
|
|
943
|
+
alignItems: 'center',
|
|
944
|
+
height: 40,
|
|
945
|
+
width: 44,
|
|
946
|
+
marginRight: 4,
|
|
947
|
+
marginBottom: 0,
|
|
948
|
+
marginLeft: 4,
|
|
949
|
+
}}
|
|
950
|
+
>
|
|
951
|
+
<View style={{ padding: 4 }}>
|
|
952
|
+
<MaterialCommunityIcons
|
|
953
|
+
name="send-circle"
|
|
954
|
+
size={32}
|
|
955
|
+
color={canSend ? colors.blue[500] : colors.gray[400]}
|
|
956
|
+
/>
|
|
957
|
+
</View>
|
|
958
|
+
</Send>
|
|
959
|
+
);
|
|
321
960
|
},
|
|
322
|
-
[rest],
|
|
961
|
+
[state?.context?.channelId, state?.context?.images, rest?.isCreateNewChannel],
|
|
323
962
|
);
|
|
324
963
|
|
|
964
|
+
// Fix the handleSend function to properly handle image-only messages
|
|
325
965
|
const handleSend = useCallback(
|
|
326
|
-
async (
|
|
327
|
-
|
|
328
|
-
|
|
966
|
+
async (messages) => {
|
|
967
|
+
// Extract message text from GiftedChat messages array
|
|
968
|
+
const messageText = messages && messages.length > 0 ? messages[0]?.text || ' ' : ' ';
|
|
969
|
+
console.log('Sending message:', messageText);
|
|
970
|
+
console.log('Images:', state.context.images?.length);
|
|
329
971
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
};
|
|
972
|
+
// Check if we can send a message (channel exists or we're creating one)
|
|
973
|
+
if (!state.context.channelId && !rest?.isCreateNewChannel) {
|
|
974
|
+
console.log('Cannot send - no channel');
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
336
977
|
|
|
337
|
-
if
|
|
338
|
-
|
|
978
|
+
// Allow sending if we have text OR images (image-only messages are valid)
|
|
979
|
+
const hasText = !!messageText && messageText !== ' ';
|
|
980
|
+
const hasImages = state.context.images && state.context.images.length > 0;
|
|
339
981
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
},
|
|
353
|
-
});
|
|
354
|
-
if (uploadResponse?.error) setLoading(false);
|
|
355
|
-
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
356
|
-
|
|
357
|
-
if (uploadResponse.data) {
|
|
358
|
-
setImage('');
|
|
359
|
-
setFiles([]);
|
|
360
|
-
setImages([]);
|
|
361
|
-
//setLoading(false);
|
|
362
|
-
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
363
|
-
await sendMsg({
|
|
364
|
-
variables: {
|
|
365
|
-
postId,
|
|
366
|
-
channelId,
|
|
367
|
-
content: message,
|
|
368
|
-
files,
|
|
369
|
-
notificationParams: notificationData,
|
|
370
|
-
},
|
|
371
|
-
update: (cache, { data, errors }: any) => {
|
|
372
|
-
if (!data || errors) {
|
|
373
|
-
setLoading(false);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
//Temporary fix.....//
|
|
377
|
-
const newMessage: any = data?.sendMessage;
|
|
378
|
-
setChannelMessages((oldMessages: any) =>
|
|
379
|
-
uniqBy([...oldMessages, newMessage], ({ id }) => id),
|
|
380
|
-
);
|
|
381
|
-
setTotalCount((t) => t + 1);
|
|
382
|
-
//Temporary fix.....//
|
|
383
|
-
|
|
384
|
-
setChannelToTop(channelToTop + 1);
|
|
385
|
-
setLoading(false);
|
|
386
|
-
setMsg('');
|
|
387
|
-
},
|
|
388
|
-
});
|
|
982
|
+
if (!hasText && !hasImages) {
|
|
983
|
+
console.log('Nothing to send - no text or images');
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Set the message text in the state (even if empty for image-only messages)
|
|
988
|
+
safeSend({ type: ConversationActions.SET_MESSAGE_TEXT, data: { messageText } });
|
|
989
|
+
|
|
990
|
+
// Handle direct channel creation if needed
|
|
991
|
+
if (rest?.isCreateNewChannel && !state.context.channelId) {
|
|
992
|
+
if (rest?.newChannelData?.type === RoomType?.Direct) {
|
|
993
|
+
safeSend({ type: ConversationActions.CREATE_DIRECT_CHANNEL });
|
|
389
994
|
}
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Send message with or without image based on state
|
|
999
|
+
if (hasImages) {
|
|
1000
|
+
console.log('Sending message with file');
|
|
1001
|
+
safeSend({ type: ConversationActions.SEND_MESSAGE_WITH_FILE });
|
|
390
1002
|
} else {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
variables: {
|
|
394
|
-
channelId,
|
|
395
|
-
content: message,
|
|
396
|
-
notificationParams: notificationData,
|
|
397
|
-
},
|
|
398
|
-
update: (cache, { data, errors }: any) => {
|
|
399
|
-
if (!data || errors) {
|
|
400
|
-
setLoading(false);
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
//Temporary fix.....//
|
|
404
|
-
const newMessage: any = data?.sendMessage;
|
|
405
|
-
setChannelMessages((oldMessages: any) => uniqBy([...oldMessages, newMessage], ({ id }) => id));
|
|
406
|
-
setTotalCount((t) => t + 1);
|
|
407
|
-
//Temporary fix.....//
|
|
408
|
-
|
|
409
|
-
setChannelToTop(channelToTop + 1);
|
|
410
|
-
setLoading(false);
|
|
411
|
-
setMsg('');
|
|
412
|
-
},
|
|
413
|
-
});
|
|
1003
|
+
console.log('Sending text-only message');
|
|
1004
|
+
safeSend({ type: ConversationActions.SEND_MESSAGE });
|
|
414
1005
|
}
|
|
415
1006
|
},
|
|
416
|
-
[
|
|
1007
|
+
[state.context.channelId, state.context.images, rest?.isCreateNewChannel, rest?.newChannelData?.type, safeSend],
|
|
417
1008
|
);
|
|
418
1009
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
1010
|
+
// Update fetchMessagesWithFallback to not use fallback state
|
|
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,
|
|
434
1034
|
},
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
// Log the first message for debugging
|
|
1070
|
+
if (state.context.channelMessages[0]) {
|
|
1071
|
+
const sampleMsg = state.context.channelMessages[0];
|
|
1072
|
+
console.log(
|
|
1073
|
+
'📷 Sample message files:',
|
|
1074
|
+
JSON.stringify({
|
|
1075
|
+
hasFiles: !!sampleMsg.files,
|
|
1076
|
+
fileCount: sampleMsg.files?.data?.length || 0,
|
|
1077
|
+
fileUrl: sampleMsg.files?.data?.[0]?.url || 'none',
|
|
445
1078
|
}),
|
|
446
|
-
|
|
447
|
-
(message.sent = msg?.isDelivered),
|
|
448
|
-
(message.received = msg?.isRead);
|
|
449
|
-
message.type = msg?.type;
|
|
450
|
-
message.propsConfiguration = msg?.propsConfiguration;
|
|
451
|
-
message.replies = msg?.replies ?? [];
|
|
452
|
-
message.isShowThreadMessage = isShowThreadMessage;
|
|
453
|
-
res.push(message);
|
|
454
|
-
});
|
|
1079
|
+
);
|
|
455
1080
|
}
|
|
456
|
-
return res?.length > 0 ? uniqBy([...res], ({ _id }: any) => _id) : [];
|
|
457
|
-
//return res;
|
|
458
|
-
}, [channelMessages, channelId]);
|
|
459
1081
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
<Send {...props} disabled={channelId || rest?.isCreateNewChannel ? false : true}>
|
|
463
|
-
<Box>
|
|
464
|
-
<MaterialCommunityIcons
|
|
465
|
-
name="send-circle"
|
|
466
|
-
style={{ marginBottom: 5, marginRight: 5 }}
|
|
467
|
-
size={32}
|
|
468
|
-
color={channelId || rest?.isCreateNewChannel ? '#2e64e5' : '#b8b2b2'}
|
|
469
|
-
/>
|
|
470
|
-
</Box>
|
|
471
|
-
</Send>
|
|
472
|
-
);
|
|
473
|
-
};
|
|
1082
|
+
// Use a more efficient approach - pre-filter messages once
|
|
1083
|
+
const filteredMessages = uniqBy(state.context.channelMessages, ({ id }) => id);
|
|
474
1084
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
let params: any = {};
|
|
484
|
-
|
|
485
|
-
if (attachment?.callToAction?.extraParams) {
|
|
486
|
-
const extraParams: any = attachment?.callToAction?.extraParams;
|
|
487
|
-
const route: any = extraParams?.route ?? null;
|
|
488
|
-
let path: any = null;
|
|
489
|
-
let param: any = null;
|
|
490
|
-
if (role && role == PreDefinedRole.Guest) {
|
|
491
|
-
path = route?.guest?.name ? route?.guest?.name ?? null : null;
|
|
492
|
-
param = route?.guest?.params ? route?.guest?.params ?? null : null;
|
|
493
|
-
} else if (role && role == PreDefinedRole.Owner) {
|
|
494
|
-
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
495
|
-
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
496
|
-
} else {
|
|
497
|
-
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
498
|
-
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
499
|
-
}
|
|
1085
|
+
// Skip processing if no filtered messages
|
|
1086
|
+
if (filteredMessages.length === 0) {
|
|
1087
|
+
return [];
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Transform messages only once and return
|
|
1091
|
+
return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
1092
|
+
const date = new Date(msg.createdAt);
|
|
500
1093
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
1094
|
+
// Extract image URL from files data
|
|
1095
|
+
let imageUrl = null;
|
|
1096
|
+
if (msg.files?.data && msg.files.data.length > 0) {
|
|
1097
|
+
const fileData = msg.files.data[0];
|
|
1098
|
+
if (fileData && fileData.url) {
|
|
1099
|
+
imageUrl = fileData.url;
|
|
1100
|
+
console.log('📷 Found image URL for message', msg.id, ':', imageUrl);
|
|
1101
|
+
}
|
|
507
1102
|
}
|
|
508
1103
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
1104
|
+
// Create message in a more direct way
|
|
1105
|
+
return {
|
|
1106
|
+
_id: msg.id,
|
|
1107
|
+
text: msg.message,
|
|
1108
|
+
createdAt: date,
|
|
1109
|
+
user: {
|
|
1110
|
+
_id: msg.author?.id || '',
|
|
1111
|
+
name: `${msg.author?.givenName || ''} ${msg.author?.familyName || ''}`,
|
|
1112
|
+
avatar: msg.author?.picture || '',
|
|
1113
|
+
},
|
|
1114
|
+
image: imageUrl,
|
|
1115
|
+
sent: msg?.isDelivered,
|
|
1116
|
+
received: msg?.isRead,
|
|
1117
|
+
type: msg?.type,
|
|
1118
|
+
propsConfiguration: msg?.propsConfiguration,
|
|
1119
|
+
replies: msg?.replies ?? [],
|
|
1120
|
+
isShowThreadMessage,
|
|
1121
|
+
};
|
|
1122
|
+
});
|
|
1123
|
+
}, [state?.context?.channelMessages, state?.context?.channelId, isShowThreadMessage]);
|
|
1124
|
+
|
|
1125
|
+
// Memoize the renderMessageText function
|
|
1126
|
+
const renderMessageText = useCallback(
|
|
1127
|
+
(props: any) => {
|
|
1128
|
+
const { currentMessage } = props;
|
|
1129
|
+
const lastReply: any =
|
|
1130
|
+
currentMessage?.replies?.data?.length > 0 ? currentMessage?.replies?.data?.[0] : null;
|
|
1131
|
+
|
|
1132
|
+
if (currentMessage.type === 'ALERT') {
|
|
1133
|
+
const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
|
|
1134
|
+
let action: string = '';
|
|
1135
|
+
let actionId: any = '';
|
|
1136
|
+
let params: any = {};
|
|
1137
|
+
|
|
1138
|
+
if (attachment?.callToAction?.extraParams) {
|
|
1139
|
+
const extraParams: any = attachment?.callToAction?.extraParams;
|
|
1140
|
+
const route: any = extraParams?.route ?? null;
|
|
1141
|
+
let path: any = null;
|
|
1142
|
+
let param: any = null;
|
|
1143
|
+
if (role && role == PreDefinedRole.Guest) {
|
|
1144
|
+
path = route?.guest?.name ? route?.guest?.name ?? null : null;
|
|
1145
|
+
param = route?.guest?.params ? route?.guest?.params ?? null : null;
|
|
1146
|
+
} else if (role && role == PreDefinedRole.Owner) {
|
|
1147
|
+
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
1148
|
+
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
1149
|
+
} else {
|
|
1150
|
+
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
1151
|
+
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
action = path;
|
|
1155
|
+
params = { ...param };
|
|
1156
|
+
} else if (attachment?.callToAction?.link) {
|
|
1157
|
+
action = CALL_TO_ACTION_PATH;
|
|
1158
|
+
actionId = attachment?.callToAction?.link.split('/').pop();
|
|
1159
|
+
params = { reservationId: actionId };
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
return (
|
|
1163
|
+
<>
|
|
1164
|
+
{attachment?.callToAction && action ? (
|
|
1165
|
+
<Box className={`bg-[${CALL_TO_ACTION_BOX_BGCOLOR}] rounded-[15] pb-2`}>
|
|
1166
|
+
<Button
|
|
1167
|
+
variant={'outline'}
|
|
1168
|
+
size={'sm'}
|
|
1169
|
+
className={`border-[${CALL_TO_ACTION_BUTTON_BORDERCOLOR}]`}
|
|
1170
|
+
onPress={() => action && params && navigation.navigate(action, params)}
|
|
1171
|
+
>
|
|
1172
|
+
<ButtonText className={`color-[${CALL_TO_ACTION_TEXT_COLOR}]`}>
|
|
1173
|
+
{attachment.callToAction.title}
|
|
1174
|
+
</ButtonText>
|
|
1175
|
+
</Button>
|
|
1176
|
+
<MessageText
|
|
1177
|
+
{...props}
|
|
1178
|
+
textStyle={{
|
|
1179
|
+
left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 },
|
|
1180
|
+
}}
|
|
1181
|
+
/>
|
|
1182
|
+
</Box>
|
|
1183
|
+
) : (
|
|
1184
|
+
<TouchableHighlight
|
|
1185
|
+
underlayColor={'#c0c0c0'}
|
|
1186
|
+
style={{ width: '100%' }}
|
|
1187
|
+
onPress={() => {
|
|
1188
|
+
if (currentMessage?.isShowThreadMessage)
|
|
1189
|
+
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1190
|
+
channelId: state?.context?.channelId,
|
|
1191
|
+
title: 'Message',
|
|
1192
|
+
postParentId: currentMessage?._id,
|
|
1193
|
+
isPostParentIdThread: true,
|
|
1194
|
+
});
|
|
528
1195
|
}}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
source={{
|
|
574
|
-
uri: p?.author?.picture,
|
|
575
|
-
}}
|
|
576
|
-
/>
|
|
577
|
-
</Avatar>
|
|
578
|
-
))}
|
|
1196
|
+
>
|
|
1197
|
+
<>
|
|
1198
|
+
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
1199
|
+
{currentMessage?.replies?.data?.length > 0 && (
|
|
1200
|
+
<HStack space={'sm'} className="px-1 items-center">
|
|
1201
|
+
<HStack>
|
|
1202
|
+
{currentMessage?.replies?.data
|
|
1203
|
+
?.filter(
|
|
1204
|
+
(v: any, i: any, a: any) =>
|
|
1205
|
+
a.findIndex((t: any) => t?.author?.id === v?.author?.id) ===
|
|
1206
|
+
i,
|
|
1207
|
+
)
|
|
1208
|
+
?.slice(0, 2)
|
|
1209
|
+
?.reverse()
|
|
1210
|
+
?.map((p: any, i: Number) => (
|
|
1211
|
+
<Avatar
|
|
1212
|
+
key={'conversations-view-key-' + i}
|
|
1213
|
+
size={'sm'}
|
|
1214
|
+
className="bg-transparent"
|
|
1215
|
+
>
|
|
1216
|
+
<AvatarFallbackText>
|
|
1217
|
+
{startCase(p?.author?.username?.charAt(0))}
|
|
1218
|
+
</AvatarFallbackText>
|
|
1219
|
+
<AvatarImage
|
|
1220
|
+
alt="user image"
|
|
1221
|
+
style={{
|
|
1222
|
+
borderRadius: 6,
|
|
1223
|
+
borderWidth: 2,
|
|
1224
|
+
borderColor: '#fff',
|
|
1225
|
+
}}
|
|
1226
|
+
source={{
|
|
1227
|
+
uri: p?.author?.picture,
|
|
1228
|
+
}}
|
|
1229
|
+
/>
|
|
1230
|
+
</Avatar>
|
|
1231
|
+
))}
|
|
1232
|
+
</HStack>
|
|
1233
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
|
|
1234
|
+
{currentMessage?.replies?.totalCount}{' '}
|
|
1235
|
+
{currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
|
|
1236
|
+
</Text>
|
|
1237
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
1238
|
+
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
1239
|
+
</Text>
|
|
579
1240
|
</HStack>
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
</Text>
|
|
584
|
-
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
585
|
-
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
586
|
-
</Text>
|
|
587
|
-
</HStack>
|
|
588
|
-
)}
|
|
589
|
-
</>
|
|
590
|
-
</TouchableHighlight>
|
|
591
|
-
)}
|
|
592
|
-
{/* <MessageText
|
|
593
|
-
{...props}
|
|
594
|
-
textStyle={{ left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 } }}
|
|
595
|
-
/> */}
|
|
596
|
-
</>
|
|
597
|
-
);
|
|
598
|
-
} else {
|
|
599
|
-
return (
|
|
600
|
-
<TouchableHighlight
|
|
601
|
-
underlayColor={'#c0c0c0'}
|
|
602
|
-
style={{ width: '100%' }}
|
|
603
|
-
onPress={() => {
|
|
604
|
-
if (currentMessage?.isShowThreadMessage)
|
|
605
|
-
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
606
|
-
channelId: channelId,
|
|
607
|
-
title: 'Message',
|
|
608
|
-
postParentId: currentMessage?._id,
|
|
609
|
-
isPostParentIdThread: true,
|
|
610
|
-
});
|
|
611
|
-
}}
|
|
612
|
-
>
|
|
613
|
-
<>
|
|
614
|
-
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
615
|
-
{currentMessage?.replies?.data?.length > 0 && (
|
|
616
|
-
<HStack space={'sm'} className="px-1 items-center">
|
|
617
|
-
<HStack>
|
|
618
|
-
{currentMessage?.replies?.data
|
|
619
|
-
?.filter(
|
|
620
|
-
(v: any, i: any, a: any) =>
|
|
621
|
-
a.findIndex((t: any) => t?.author?.id === v?.author?.id) === i,
|
|
622
|
-
)
|
|
623
|
-
?.slice(0, 2)
|
|
624
|
-
?.reverse()
|
|
625
|
-
?.map((p: any, i: Number) => (
|
|
626
|
-
<Avatar
|
|
627
|
-
key={'conversation-replies-key-' + i}
|
|
628
|
-
className="bg-transparent"
|
|
629
|
-
size={'sm'}
|
|
630
|
-
>
|
|
631
|
-
<AvatarFallbackText>
|
|
632
|
-
{startCase(p?.author?.username?.charAt(0))}
|
|
633
|
-
</AvatarFallbackText>
|
|
634
|
-
<AvatarImage
|
|
635
|
-
alt="user image"
|
|
636
|
-
style={{ borderRadius: 6, borderWidth: 2, borderColor: '#fff' }}
|
|
637
|
-
source={{
|
|
638
|
-
uri: p?.author?.picture,
|
|
639
|
-
}}
|
|
640
|
-
/>
|
|
641
|
-
</Avatar>
|
|
642
|
-
))}
|
|
643
|
-
</HStack>
|
|
644
|
-
<Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
|
|
645
|
-
{currentMessage?.replies?.totalCount}{' '}
|
|
646
|
-
{currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
|
|
647
|
-
</Text>
|
|
648
|
-
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
649
|
-
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
650
|
-
</Text>
|
|
651
|
-
</HStack>
|
|
1241
|
+
)}
|
|
1242
|
+
</>
|
|
1243
|
+
</TouchableHighlight>
|
|
652
1244
|
)}
|
|
653
1245
|
</>
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1246
|
+
);
|
|
1247
|
+
} else {
|
|
1248
|
+
return (
|
|
1249
|
+
<TouchableHighlight
|
|
1250
|
+
underlayColor={'#c0c0c0'}
|
|
1251
|
+
style={{ width: '100%' }}
|
|
1252
|
+
onPress={() => {
|
|
1253
|
+
if (currentMessage?.isShowThreadMessage)
|
|
1254
|
+
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1255
|
+
channelId: state?.context?.channelId,
|
|
1256
|
+
title: 'Message',
|
|
1257
|
+
postParentId: currentMessage?._id,
|
|
1258
|
+
isPostParentIdThread: true,
|
|
1259
|
+
});
|
|
1260
|
+
}}
|
|
1261
|
+
>
|
|
1262
|
+
<>
|
|
1263
|
+
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
1264
|
+
{currentMessage?.replies?.data?.length > 0 && (
|
|
1265
|
+
<HStack space={'sm'} className="px-1 items-center">
|
|
1266
|
+
<HStack>
|
|
1267
|
+
{currentMessage?.replies?.data
|
|
1268
|
+
?.filter(
|
|
1269
|
+
(v: any, i: any, a: any) =>
|
|
1270
|
+
a.findIndex((t: any) => t?.author?.id === v?.author?.id) === i,
|
|
1271
|
+
)
|
|
1272
|
+
?.slice(0, 2)
|
|
1273
|
+
?.reverse()
|
|
1274
|
+
?.map((p: any, i: Number) => (
|
|
1275
|
+
<Avatar
|
|
1276
|
+
key={'conversation-replies-key-' + i}
|
|
1277
|
+
className="bg-transparent"
|
|
1278
|
+
size={'sm'}
|
|
1279
|
+
>
|
|
1280
|
+
<AvatarFallbackText>
|
|
1281
|
+
{startCase(p?.author?.username?.charAt(0))}
|
|
1282
|
+
</AvatarFallbackText>
|
|
1283
|
+
<AvatarImage
|
|
1284
|
+
alt="user image"
|
|
1285
|
+
style={{ borderRadius: 6, borderWidth: 2, borderColor: '#fff' }}
|
|
1286
|
+
source={{
|
|
1287
|
+
uri: p?.author?.picture,
|
|
1288
|
+
}}
|
|
1289
|
+
/>
|
|
1290
|
+
</Avatar>
|
|
1291
|
+
))}
|
|
1292
|
+
</HStack>
|
|
1293
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
|
|
1294
|
+
{currentMessage?.replies?.totalCount}{' '}
|
|
1295
|
+
{currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
|
|
1296
|
+
</Text>
|
|
1297
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
1298
|
+
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
1299
|
+
</Text>
|
|
1300
|
+
</HStack>
|
|
1301
|
+
)}
|
|
1302
|
+
</>
|
|
1303
|
+
</TouchableHighlight>
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
[navigation, state?.context?.channelId, role],
|
|
1308
|
+
);
|
|
658
1309
|
|
|
659
1310
|
const renderActions = (props) => {
|
|
660
1311
|
return (
|
|
661
1312
|
<Actions
|
|
662
1313
|
{...props}
|
|
663
|
-
|
|
1314
|
+
options={{
|
|
1315
|
+
['Choose from Library']: onSelectImages,
|
|
1316
|
+
['Cancel']: () => {}, // Add this option to make the sheet dismissible
|
|
1317
|
+
}}
|
|
1318
|
+
optionTintColor="#000000"
|
|
1319
|
+
cancelButtonIndex={1} // Set the Cancel option as the cancel button
|
|
1320
|
+
icon={() => (
|
|
1321
|
+
<Box
|
|
1322
|
+
style={{
|
|
1323
|
+
width: 32,
|
|
1324
|
+
height: 32,
|
|
1325
|
+
alignItems: 'center',
|
|
1326
|
+
justifyContent: 'center',
|
|
1327
|
+
}}
|
|
1328
|
+
>
|
|
1329
|
+
<Ionicons name="image" size={24} color={colors.blue[500]} />
|
|
1330
|
+
</Box>
|
|
1331
|
+
)}
|
|
1332
|
+
containerStyle={{
|
|
1333
|
+
alignItems: 'center',
|
|
1334
|
+
justifyContent: 'center',
|
|
1335
|
+
marginLeft: 8,
|
|
1336
|
+
marginBottom: 0,
|
|
1337
|
+
}}
|
|
664
1338
|
/>
|
|
665
1339
|
);
|
|
666
1340
|
};
|
|
667
1341
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1342
|
+
// Create a more visible and reliable image preview with cancel button
|
|
1343
|
+
const renderAccessory = useCallback(
|
|
1344
|
+
(props) => {
|
|
1345
|
+
const selectedImage = safeContextProperty('selectedImage', '');
|
|
1346
|
+
|
|
1347
|
+
if (!selectedImage) {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
return (
|
|
1352
|
+
<View
|
|
1353
|
+
style={{
|
|
1354
|
+
height: 50,
|
|
1355
|
+
padding: 3,
|
|
1356
|
+
backgroundColor: 'white',
|
|
1357
|
+
borderTopWidth: 1,
|
|
1358
|
+
borderTopColor: '#e0e0e0',
|
|
1359
|
+
flexDirection: 'row',
|
|
1360
|
+
alignItems: 'center',
|
|
1361
|
+
margin: 0,
|
|
1362
|
+
paddingBottom: 0,
|
|
1363
|
+
paddingTop: 5,
|
|
1364
|
+
position: 'absolute',
|
|
1365
|
+
bottom: 0,
|
|
1366
|
+
left: 0,
|
|
1367
|
+
right: 0,
|
|
1368
|
+
zIndex: 999,
|
|
1369
|
+
}}
|
|
1370
|
+
>
|
|
1371
|
+
<View
|
|
1372
|
+
style={{
|
|
1373
|
+
flex: 1,
|
|
1374
|
+
flexDirection: 'row',
|
|
1375
|
+
alignItems: 'center',
|
|
1376
|
+
paddingHorizontal: 15,
|
|
1377
|
+
}}
|
|
1378
|
+
>
|
|
673
1379
|
<Image
|
|
674
|
-
key={selectedImage}
|
|
675
|
-
alt={'image'}
|
|
676
|
-
source={{ uri: selectedImage }}
|
|
1380
|
+
key={state?.context?.selectedImage}
|
|
1381
|
+
alt={'selected image'}
|
|
1382
|
+
source={{ uri: state?.context?.selectedImage }}
|
|
1383
|
+
style={{
|
|
1384
|
+
width: 36,
|
|
1385
|
+
height: 36,
|
|
1386
|
+
borderRadius: 5,
|
|
1387
|
+
marginRight: 15,
|
|
1388
|
+
}}
|
|
677
1389
|
size={'xs'}
|
|
678
|
-
className="ml-3"
|
|
679
1390
|
/>
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1391
|
+
|
|
1392
|
+
<TouchableHighlight
|
|
1393
|
+
underlayColor="#dddddd"
|
|
1394
|
+
onPress={() => safeSend({ type: ConversationActions.CLEAR_IMAGE })}
|
|
1395
|
+
style={{
|
|
1396
|
+
backgroundColor: '#f44336',
|
|
1397
|
+
paddingVertical: 2,
|
|
1398
|
+
paddingHorizontal: 5,
|
|
1399
|
+
borderRadius: 5,
|
|
1400
|
+
marginLeft: 10,
|
|
1401
|
+
elevation: 3,
|
|
1402
|
+
shadowColor: '#000',
|
|
1403
|
+
shadowOffset: { width: 0, height: 1 },
|
|
1404
|
+
shadowOpacity: 0.3,
|
|
1405
|
+
shadowRadius: 2,
|
|
688
1406
|
}}
|
|
689
1407
|
>
|
|
690
|
-
<
|
|
691
|
-
</
|
|
692
|
-
</
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1408
|
+
<Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
|
|
1409
|
+
</TouchableHighlight>
|
|
1410
|
+
</View>
|
|
1411
|
+
</View>
|
|
1412
|
+
);
|
|
1413
|
+
},
|
|
1414
|
+
[state?.context?.selectedImage, safeSend],
|
|
1415
|
+
);
|
|
697
1416
|
|
|
698
1417
|
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
699
1418
|
setImageObject(obj);
|
|
@@ -707,11 +1426,9 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
707
1426
|
<CachedImage
|
|
708
1427
|
style={{ width: '100%', height: '100%' }}
|
|
709
1428
|
resizeMode={'cover'}
|
|
710
|
-
// cacheKey={`${_id}-conversation-modal-image-key`}
|
|
711
1429
|
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
712
1430
|
source={{
|
|
713
1431
|
uri: image,
|
|
714
|
-
//headers: `Authorization: Bearer ${token}`,
|
|
715
1432
|
expiresIn: 86400,
|
|
716
1433
|
}}
|
|
717
1434
|
alt={'image'}
|
|
@@ -721,22 +1438,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
721
1438
|
|
|
722
1439
|
const renderMessage = useCallback(
|
|
723
1440
|
(props: any) => {
|
|
724
|
-
//
|
|
725
|
-
// currentMessage: { text: currText },
|
|
726
|
-
// } = props;
|
|
727
|
-
|
|
728
|
-
//let messageTextStyle: any;
|
|
729
|
-
|
|
730
|
-
// Make "pure emoji" messages much bigger than plain text.
|
|
731
|
-
// if (currText && emojiUtils.isPureEmojiString(currText)) {
|
|
732
|
-
// messageTextStyle = {
|
|
733
|
-
// fontSize: 28,
|
|
734
|
-
// // Emoji get clipped if lineHeight isn't increased; make it consistent across platforms.
|
|
735
|
-
// lineHeight: Platform.OS === 'android' ? 34 : 30,
|
|
736
|
-
// }
|
|
737
|
-
// }
|
|
738
|
-
|
|
739
|
-
// return <SlackMessage {...props} messageTextStyle={messageTextStyle} />;
|
|
1441
|
+
// Use memo to prevent unnecessary re-renders of each message
|
|
740
1442
|
return (
|
|
741
1443
|
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
742
1444
|
);
|
|
@@ -744,164 +1446,331 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
744
1446
|
[isShowImageViewer],
|
|
745
1447
|
);
|
|
746
1448
|
|
|
747
|
-
// const renderMessage = (props: any) => {
|
|
748
|
-
// // const {
|
|
749
|
-
// // currentMessage: { text: currText },
|
|
750
|
-
// // } = props;
|
|
751
|
-
|
|
752
|
-
// //let messageTextStyle: any;
|
|
753
|
-
|
|
754
|
-
// // Make "pure emoji" messages much bigger than plain text.
|
|
755
|
-
// // if (currText && emojiUtils.isPureEmojiString(currText)) {
|
|
756
|
-
// // messageTextStyle = {
|
|
757
|
-
// // fontSize: 28,
|
|
758
|
-
// // // Emoji get clipped if lineHeight isn't increased; make it consistent across platforms.
|
|
759
|
-
// // lineHeight: Platform.OS === 'android' ? 34 : 30,
|
|
760
|
-
// // }
|
|
761
|
-
// // }
|
|
762
|
-
|
|
763
|
-
// // return <SlackMessage {...props} messageTextStyle={messageTextStyle} />;
|
|
764
|
-
// return <SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />;
|
|
765
|
-
// };
|
|
766
|
-
|
|
767
1449
|
let onScroll = false;
|
|
768
1450
|
|
|
1451
|
+
// Optimize onMomentumScrollBegin for better scroll performance
|
|
769
1452
|
const onMomentumScrollBegin = async ({ nativeEvent }: any) => {
|
|
1453
|
+
// Set scroll state
|
|
770
1454
|
onScroll = true;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1455
|
+
|
|
1456
|
+
// Use the debounced fetch function to prevent excessive calls
|
|
1457
|
+
if (isCloseToTop(nativeEvent)) {
|
|
1458
|
+
onFetchOld();
|
|
774
1459
|
}
|
|
775
1460
|
};
|
|
776
1461
|
|
|
777
1462
|
const onEndReached = () => {
|
|
778
1463
|
console.log('on end reached');
|
|
779
1464
|
if (!onScroll) return;
|
|
780
|
-
// load messages, show ActivityIndicator
|
|
781
1465
|
onScroll = false;
|
|
782
|
-
// setLoadingOldMessages(true);
|
|
783
1466
|
};
|
|
784
1467
|
|
|
1468
|
+
// Add debug logging to help diagnose the issue
|
|
1469
|
+
useEffect(() => {
|
|
1470
|
+
console.log('Current channel ID:', state.context.channelId);
|
|
1471
|
+
console.log('Current state:', state.value);
|
|
1472
|
+
console.log('Channel messages count:', state.context.channelMessages.length);
|
|
1473
|
+
}, [state.context.channelId, state.value, state.context.channelMessages]);
|
|
1474
|
+
|
|
1475
|
+
// Fix the infinite update loop in useEffect monitoring state changes
|
|
1476
|
+
useEffect(() => {
|
|
1477
|
+
// Only trigger effect if we have a specific state to handle
|
|
1478
|
+
// Check if function exists and if we're in a valid state before calling implementation functions
|
|
1479
|
+
if (state && typeof state.matches === 'function') {
|
|
1480
|
+
if (state.matches(BaseState.FetchMessages)) {
|
|
1481
|
+
console.log('In FetchMessages state, attempting to fetch messages');
|
|
1482
|
+
// Use a ref to track if we've already fetched for this state update
|
|
1483
|
+
if (!fetchInProgressRef.current) {
|
|
1484
|
+
fetchInProgressRef.current = true;
|
|
1485
|
+
fetchMessagesDirectly().finally(() => {
|
|
1486
|
+
fetchInProgressRef.current = false;
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
} else if (state.matches(MainState.FetchMoreMessages)) {
|
|
1490
|
+
if (!fetchMoreInProgressRef.current) {
|
|
1491
|
+
fetchMoreInProgressRef.current = true;
|
|
1492
|
+
fetchMoreMessagesImpl().then((result) => {
|
|
1493
|
+
if (result.error) {
|
|
1494
|
+
console.error('Error fetching more messages:', result.error);
|
|
1495
|
+
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1496
|
+
} else {
|
|
1497
|
+
safeSend({ type: 'FETCH_MORE_MESSAGES_SUCCESS', data: result });
|
|
1498
|
+
}
|
|
1499
|
+
fetchMoreInProgressRef.current = false;
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
} else if (state.matches(MainState.SendMessage)) {
|
|
1503
|
+
if (!sendInProgressRef.current) {
|
|
1504
|
+
sendInProgressRef.current = true;
|
|
1505
|
+
sendMessageImpl().then((result) => {
|
|
1506
|
+
if (result.error) {
|
|
1507
|
+
console.error('Error sending message:', result.error);
|
|
1508
|
+
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1509
|
+
} else {
|
|
1510
|
+
safeSend({ type: 'SEND_MESSAGE_SUCCESS', data: result });
|
|
1511
|
+
}
|
|
1512
|
+
sendInProgressRef.current = false;
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
} else if (state.matches(MainState.SendMessageWithFile)) {
|
|
1516
|
+
if (!sendFileInProgressRef.current) {
|
|
1517
|
+
sendFileInProgressRef.current = true;
|
|
1518
|
+
sendMessageWithFileImpl().then((result) => {
|
|
1519
|
+
if (result.error) {
|
|
1520
|
+
console.error('Error sending message with file:', result.error);
|
|
1521
|
+
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1522
|
+
} else {
|
|
1523
|
+
safeSend({ type: 'SEND_MESSAGE_WITH_FILE_SUCCESS', data: result });
|
|
1524
|
+
}
|
|
1525
|
+
sendFileInProgressRef.current = false;
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
} else if (state.matches(MainState.CreateDirectChannel)) {
|
|
1529
|
+
if (!createChannelInProgressRef.current) {
|
|
1530
|
+
createChannelInProgressRef.current = true;
|
|
1531
|
+
createDirectChannelImpl().then((result) => {
|
|
1532
|
+
if (result.error) {
|
|
1533
|
+
console.error('Error creating direct channel:', result.error);
|
|
1534
|
+
safeSend({ type: 'ERROR', data: { message: result.error } });
|
|
1535
|
+
} else {
|
|
1536
|
+
safeSend({ type: 'CREATE_DIRECT_CHANNEL_SUCCESS', data: result });
|
|
1537
|
+
}
|
|
1538
|
+
createChannelInProgressRef.current = false;
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}, [
|
|
1544
|
+
state?.value,
|
|
1545
|
+
fetchMessagesDirectly,
|
|
1546
|
+
fetchMoreMessagesImpl,
|
|
1547
|
+
sendMessageImpl,
|
|
1548
|
+
sendMessageWithFileImpl,
|
|
1549
|
+
createDirectChannelImpl,
|
|
1550
|
+
safeSend,
|
|
1551
|
+
]);
|
|
1552
|
+
|
|
1553
|
+
// Add refs to prevent duplicate operations
|
|
1554
|
+
const fetchInProgressRef = useRef(false);
|
|
1555
|
+
const fetchMoreInProgressRef = useRef(false);
|
|
1556
|
+
const sendInProgressRef = useRef(false);
|
|
1557
|
+
const sendFileInProgressRef = useRef(false);
|
|
1558
|
+
const createChannelInProgressRef = useRef(false);
|
|
1559
|
+
|
|
1560
|
+
// Fix subscription handler to prevent infinite updates
|
|
1561
|
+
const renderChatFooter = useCallback(() => {
|
|
1562
|
+
return (
|
|
1563
|
+
<>
|
|
1564
|
+
<ImageViewerModal
|
|
1565
|
+
isVisible={isShowImageViewer}
|
|
1566
|
+
setVisible={setImageViewer}
|
|
1567
|
+
modalContent={modalContent}
|
|
1568
|
+
/>
|
|
1569
|
+
<SubscriptionHandler
|
|
1570
|
+
channelId={state?.context?.channelId?.toString()}
|
|
1571
|
+
subscribeToNewMessages={() =>
|
|
1572
|
+
subscribeToMore({
|
|
1573
|
+
document: CHAT_MESSAGE_ADDED,
|
|
1574
|
+
variables: {
|
|
1575
|
+
channelId: state?.context?.channelId?.toString(),
|
|
1576
|
+
},
|
|
1577
|
+
updateQuery: (prev, { subscriptionData }: any) => {
|
|
1578
|
+
if (!subscriptionData?.data?.chatMessageAdded) return prev;
|
|
1579
|
+
|
|
1580
|
+
const newMessage = subscriptionData.data.chatMessageAdded;
|
|
1581
|
+
const currentMessages = prev?.messages?.data || [];
|
|
1582
|
+
|
|
1583
|
+
// Check if message already exists to prevent duplicates
|
|
1584
|
+
if (currentMessages.some((msg) => msg.id === newMessage.id)) {
|
|
1585
|
+
return prev; // Skip update if message already exists
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// Use a ref to track the last processed message ID to prevent duplicate processing
|
|
1589
|
+
if (lastProcessedMessageRef.current === newMessage.id) {
|
|
1590
|
+
return prev;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
lastProcessedMessageRef.current = newMessage.id;
|
|
1594
|
+
|
|
1595
|
+
// Send update to state machine using a timeout to break the render cycle
|
|
1596
|
+
setTimeout(() => {
|
|
1597
|
+
safeSend({
|
|
1598
|
+
type: ConversationActions.SET_CHANNEL_MESSAGES,
|
|
1599
|
+
data: {
|
|
1600
|
+
messages: uniqBy(
|
|
1601
|
+
[...state.context.channelMessages, newMessage],
|
|
1602
|
+
({ id }) => id,
|
|
1603
|
+
),
|
|
1604
|
+
totalCount: (prev?.messages?.totalCount || 0) + 1,
|
|
1605
|
+
},
|
|
1606
|
+
});
|
|
1607
|
+
}, 0);
|
|
1608
|
+
|
|
1609
|
+
return {
|
|
1610
|
+
...prev,
|
|
1611
|
+
messages: {
|
|
1612
|
+
...prev?.messages,
|
|
1613
|
+
data: [...currentMessages, newMessage],
|
|
1614
|
+
totalCount: (prev?.messages?.totalCount || 0) + 1,
|
|
1615
|
+
},
|
|
1616
|
+
};
|
|
1617
|
+
},
|
|
1618
|
+
})
|
|
1619
|
+
}
|
|
1620
|
+
/>
|
|
1621
|
+
</>
|
|
1622
|
+
);
|
|
1623
|
+
}, [
|
|
1624
|
+
isShowImageViewer,
|
|
1625
|
+
modalContent,
|
|
1626
|
+
state?.context?.channelId,
|
|
1627
|
+
state?.context?.channelMessages,
|
|
1628
|
+
subscribeToMore,
|
|
1629
|
+
safeSend,
|
|
1630
|
+
]);
|
|
1631
|
+
|
|
1632
|
+
// Add ref to track last processed message
|
|
1633
|
+
const lastProcessedMessageRef = useRef(null);
|
|
1634
|
+
|
|
1635
|
+
// Add optimized listViewProps to reduce re-renders
|
|
1636
|
+
const listViewProps = useMemo(
|
|
1637
|
+
() => ({
|
|
1638
|
+
onEndReached: onEndReached,
|
|
1639
|
+
onEndReachedThreshold: 0.5,
|
|
1640
|
+
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
1641
|
+
removeClippedSubviews: true, // Improve performance by unmounting components when not visible
|
|
1642
|
+
initialNumToRender: 10, // Reduce initial render amount
|
|
1643
|
+
maxToRenderPerBatch: 10, // Reduce number in each render batch
|
|
1644
|
+
windowSize: 10, // Reduce the window size
|
|
1645
|
+
}),
|
|
1646
|
+
[onEndReached, onMomentumScrollBegin],
|
|
1647
|
+
);
|
|
1648
|
+
|
|
1649
|
+
// Add a loader for when more messages are being loaded
|
|
1650
|
+
const renderLoadEarlier = useCallback(() => {
|
|
1651
|
+
return state?.context?.loadingOldMessages ? (
|
|
1652
|
+
<View
|
|
1653
|
+
style={{
|
|
1654
|
+
padding: 10,
|
|
1655
|
+
backgroundColor: 'rgba(255,255,255,0.8)',
|
|
1656
|
+
borderRadius: 10,
|
|
1657
|
+
marginTop: 10,
|
|
1658
|
+
}}
|
|
1659
|
+
>
|
|
1660
|
+
<Spinner size="small" color="#3b82f6" />
|
|
1661
|
+
</View>
|
|
1662
|
+
) : null;
|
|
1663
|
+
}, [state?.context?.loadingOldMessages]);
|
|
1664
|
+
|
|
1665
|
+
// Add renderInputToolbar function
|
|
1666
|
+
const renderInputToolbar = useCallback((props) => {
|
|
1667
|
+
return (
|
|
1668
|
+
<InputToolbar
|
|
1669
|
+
{...props}
|
|
1670
|
+
containerStyle={{
|
|
1671
|
+
backgroundColor: 'white',
|
|
1672
|
+
borderTopWidth: 1,
|
|
1673
|
+
borderTopColor: colors.gray[200],
|
|
1674
|
+
paddingHorizontal: 4,
|
|
1675
|
+
paddingVertical: 0,
|
|
1676
|
+
paddingTop: 2,
|
|
1677
|
+
marginBottom: 0,
|
|
1678
|
+
marginTop: 0,
|
|
1679
|
+
}}
|
|
1680
|
+
primaryStyle={{
|
|
1681
|
+
alignItems: 'center',
|
|
1682
|
+
}}
|
|
1683
|
+
/>
|
|
1684
|
+
);
|
|
1685
|
+
}, []);
|
|
1686
|
+
|
|
1687
|
+
// Return optimized component with performance improvements
|
|
785
1688
|
return (
|
|
786
|
-
|
|
787
|
-
|
|
1689
|
+
<View
|
|
1690
|
+
style={{
|
|
1691
|
+
flex: 1,
|
|
1692
|
+
backgroundColor: 'white',
|
|
1693
|
+
}}
|
|
1694
|
+
>
|
|
1695
|
+
{state?.matches && state.matches(BaseState.FetchMessages) && <Spinner color={'#3b82f6'} />}
|
|
788
1696
|
|
|
789
1697
|
<GiftedChat
|
|
790
1698
|
ref={messageRootListRef}
|
|
791
|
-
wrapInSafeArea={
|
|
792
|
-
renderLoading={() => <Spinner color={'
|
|
1699
|
+
wrapInSafeArea={true}
|
|
1700
|
+
renderLoading={() => <Spinner color={'#3b82f6'} />}
|
|
793
1701
|
messages={messageList}
|
|
794
1702
|
listViewProps={{
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1703
|
+
...listViewProps,
|
|
1704
|
+
contentContainerStyle: {
|
|
1705
|
+
paddingBottom: 10,
|
|
1706
|
+
},
|
|
1707
|
+
keyboardShouldPersistTaps: 'handled',
|
|
798
1708
|
}}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
// onFetchOld();
|
|
804
|
-
// }
|
|
805
|
-
// }
|
|
806
|
-
// }}
|
|
807
|
-
onSend={(messages) =>
|
|
808
|
-
rest?.isCreateNewChannel && !channelId
|
|
809
|
-
? rest?.newChannelData?.type === RoomType?.Direct
|
|
810
|
-
? createDirectChannel(messages[0]?.text ?? ' ')
|
|
811
|
-
: null
|
|
812
|
-
: channelId && handleSend(messages[0]?.text ?? ' ')
|
|
1709
|
+
onSend={handleSend}
|
|
1710
|
+
text={safeContextProperty('messageText', ' ') || ' '}
|
|
1711
|
+
onInputTextChanged={(text) =>
|
|
1712
|
+
safeSend({ type: ConversationActions.SET_MESSAGE_TEXT, data: { messageText: text } })
|
|
813
1713
|
}
|
|
814
|
-
text={msg ? msg : ' '}
|
|
815
|
-
onInputTextChanged={(text) => setMsg(text)}
|
|
816
1714
|
renderFooter={() =>
|
|
817
|
-
loading
|
|
1715
|
+
safeContextProperty('loading') ? (
|
|
1716
|
+
<Spinner color={'#3b82f6'} />
|
|
1717
|
+
) : safeContextProperty('imageLoading') ? (
|
|
1718
|
+
<Spinner color={'#3b82f6'} />
|
|
1719
|
+
) : (
|
|
1720
|
+
''
|
|
1721
|
+
)
|
|
818
1722
|
}
|
|
819
1723
|
scrollToBottom
|
|
820
1724
|
user={{
|
|
821
|
-
// _id: currentUser?.id || '',
|
|
822
1725
|
_id: auth?.id || '',
|
|
823
1726
|
}}
|
|
824
|
-
isTyping={
|
|
825
|
-
alwaysShowSend={
|
|
826
|
-
//onLoadEarlier={onFetchOld}
|
|
827
|
-
//infiniteScroll={true}
|
|
1727
|
+
isTyping={false} // Setting to false to reduce animations
|
|
1728
|
+
alwaysShowSend={true} // Always show send button regardless of text content
|
|
828
1729
|
renderSend={renderSend}
|
|
829
|
-
// loadEarlier={data?.messages?.totalCount > channelMessages.length}
|
|
830
|
-
//isLoadingEarlier={loadEarlierMsg}
|
|
831
|
-
//extraData={{ isLoadingEarlier: loadingOldMessages }}
|
|
832
|
-
// renderLoadEarlier={() =>
|
|
833
|
-
// !loadEarlierMsg && (
|
|
834
|
-
// <Center py={2}>
|
|
835
|
-
// <Button
|
|
836
|
-
// onPress={() => onFetchOld()}
|
|
837
|
-
// variant={'outline'}
|
|
838
|
-
// _text={{ color: 'black', fontSize: 15, fontWeight: 'bold' }}
|
|
839
|
-
// >
|
|
840
|
-
// Load earlier messages
|
|
841
|
-
// </Button>
|
|
842
|
-
// </Center>
|
|
843
|
-
// )
|
|
844
|
-
// }
|
|
845
1730
|
renderMessageText={renderMessageText}
|
|
1731
|
+
renderInputToolbar={renderInputToolbar}
|
|
846
1732
|
minInputToolbarHeight={50}
|
|
847
|
-
renderActions={channelId && renderActions}
|
|
848
|
-
renderAccessory={renderAccessory}
|
|
1733
|
+
renderActions={safeContextProperty('channelId') && renderActions}
|
|
1734
|
+
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
849
1735
|
renderMessage={renderMessage}
|
|
850
|
-
renderChatFooter={
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
...prev?.messages,
|
|
881
|
-
data: [...(prev?.messages?.data ?? []), newMessage],
|
|
882
|
-
totalCount: totalMsgCount,
|
|
883
|
-
},
|
|
884
|
-
};
|
|
885
|
-
return merged;
|
|
886
|
-
// return Object.assign({}, prev, {
|
|
887
|
-
// messages: {
|
|
888
|
-
// data: [...prev.messages.data, newMessage],
|
|
889
|
-
// totalCount: prev.messages.totalCount + 1,
|
|
890
|
-
// },
|
|
891
|
-
// });
|
|
892
|
-
},
|
|
893
|
-
})
|
|
894
|
-
}
|
|
895
|
-
/>
|
|
896
|
-
</>
|
|
897
|
-
)}
|
|
1736
|
+
renderChatFooter={renderChatFooter}
|
|
1737
|
+
renderLoadEarlier={renderLoadEarlier}
|
|
1738
|
+
loadEarlier={state?.context?.totalCount > state?.context?.channelMessages?.length}
|
|
1739
|
+
isLoadingEarlier={state?.context?.loadingOldMessages}
|
|
1740
|
+
bottomOffset={Platform.OS === 'ios' ? 10 : 0} // Reduce bottom offset
|
|
1741
|
+
textInputProps={{
|
|
1742
|
+
style: {
|
|
1743
|
+
borderWidth: 1,
|
|
1744
|
+
borderColor: colors.gray[300],
|
|
1745
|
+
backgroundColor: '#f8f8f8',
|
|
1746
|
+
borderRadius: 20,
|
|
1747
|
+
minHeight: 36,
|
|
1748
|
+
maxHeight: 80,
|
|
1749
|
+
color: '#000',
|
|
1750
|
+
padding: 8,
|
|
1751
|
+
paddingHorizontal: 15,
|
|
1752
|
+
fontSize: 16,
|
|
1753
|
+
flex: 1,
|
|
1754
|
+
marginVertical: 2,
|
|
1755
|
+
marginBottom: 0,
|
|
1756
|
+
},
|
|
1757
|
+
multiline: true,
|
|
1758
|
+
returnKeyType: 'default',
|
|
1759
|
+
enablesReturnKeyAutomatically: true,
|
|
1760
|
+
placeholderTextColor: colors.gray[400],
|
|
1761
|
+
}}
|
|
1762
|
+
minComposerHeight={36}
|
|
1763
|
+
maxComposerHeight={100}
|
|
1764
|
+
isKeyboardInternallyHandled={true}
|
|
1765
|
+
placeholder="Type a message..."
|
|
898
1766
|
lightboxProps={{
|
|
899
1767
|
underlayColor: 'transparent',
|
|
900
1768
|
springConfig: { tension: 90000, friction: 90000 },
|
|
901
1769
|
disabled: true,
|
|
902
1770
|
}}
|
|
1771
|
+
infiniteScroll={false} // Disable automatic loading
|
|
903
1772
|
/>
|
|
904
|
-
|
|
1773
|
+
</View>
|
|
905
1774
|
);
|
|
906
1775
|
};
|
|
907
1776
|
|