@messenger-box/platform-mobile 10.0.3-alpha.17 → 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 +4 -0
- 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 +1174 -403
- 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 +2 -2
- 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 +1512 -647
- 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, SafeAreaView } 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
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
(messages && messages.length > 0 && messeageTotalCount > totalCount) ||
|
|
192
|
-
(messages && messages.length > 0 && (loadingOldMessages || channelMessages.length === 0))
|
|
193
|
-
) {
|
|
194
|
-
setChannelMessages((oldMessages: any) => uniqBy([...messages, ...oldMessages], ({ id }) => id));
|
|
195
|
-
setTotalCount(messeageTotalCount);
|
|
196
|
-
}
|
|
845
|
+
console.log('📩 QUERY DATA CHANGED - Messages received:', data.messages.data.length);
|
|
846
|
+
const { data: messages, totalCount: messageTotalCount } = data.messages;
|
|
197
847
|
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
}, [data, loadingOldMessages, channelMessages, totalCount]);
|
|
848
|
+
if (messages && messages.length > 0) {
|
|
849
|
+
console.log('📩 QUERY DATA - Setting channel messages, count:', messages.length);
|
|
201
850
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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,458 +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
|
-
aspect: [4, 3],
|
|
256
|
-
quality: 1,
|
|
257
|
-
base64: true,
|
|
258
|
-
});
|
|
259
|
-
// if (!imageSource.cancelled) {
|
|
260
|
-
// const image = 'data:image/jpeg;base64,' + imageSource?.base64;
|
|
261
|
-
// setImage(image);
|
|
262
|
-
// const file = dataURLtoFile(image, 'inputImage.jpg');
|
|
263
|
-
// setFiles((files) => files.concat(file));
|
|
264
|
-
// setImages((images) => images.concat(imageSource as ImagePicker.ImageInfo));
|
|
265
|
-
// }
|
|
266
|
-
// if (imageSource.cancelled) setLoading(false);
|
|
267
|
-
if (!imageSource.canceled) {
|
|
268
|
-
setImages((images) => images.concat(imageSource?.assets[0] as ImagePicker.ImagePickerAsset));
|
|
269
|
-
const image = 'data:image/jpeg;base64,' + imageSource?.assets[0]?.base64;
|
|
270
|
-
setImage(image);
|
|
271
|
-
const file = dataURLtoFile(image, 'inputImage.jpg');
|
|
272
|
-
setFiles((files) => files.concat(file));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (imageSource.canceled) setLoading(false);
|
|
276
|
-
};
|
|
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;
|
|
277
936
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
329
|
-
|
|
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);
|
|
330
971
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
};
|
|
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
|
+
}
|
|
337
977
|
|
|
338
|
-
if
|
|
339
|
-
|
|
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;
|
|
340
981
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
if (uploadResponse?.error) setLoading(false);
|
|
356
|
-
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
357
|
-
|
|
358
|
-
if (uploadResponse.data) {
|
|
359
|
-
setImage('');
|
|
360
|
-
setFiles([]);
|
|
361
|
-
setImages([]);
|
|
362
|
-
//setLoading(false);
|
|
363
|
-
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
364
|
-
await sendMsg({
|
|
365
|
-
variables: {
|
|
366
|
-
postId,
|
|
367
|
-
channelId,
|
|
368
|
-
content: message,
|
|
369
|
-
files,
|
|
370
|
-
notificationParams: notificationData,
|
|
371
|
-
},
|
|
372
|
-
update: (cache, { data, errors }: any) => {
|
|
373
|
-
if (!data || errors) {
|
|
374
|
-
setLoading(false);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
//Temporary fix.....//
|
|
378
|
-
const newMessage: any = data?.sendMessage;
|
|
379
|
-
setChannelMessages((oldMessages: any) =>
|
|
380
|
-
uniqBy([...oldMessages, newMessage], ({ id }) => id),
|
|
381
|
-
);
|
|
382
|
-
setTotalCount((t) => t + 1);
|
|
383
|
-
//Temporary fix.....//
|
|
384
|
-
|
|
385
|
-
setChannelToTop(channelToTop + 1);
|
|
386
|
-
setLoading(false);
|
|
387
|
-
setMsg('');
|
|
388
|
-
},
|
|
389
|
-
});
|
|
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 });
|
|
390
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 });
|
|
391
1002
|
} else {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
variables: {
|
|
395
|
-
channelId,
|
|
396
|
-
content: message,
|
|
397
|
-
notificationParams: notificationData,
|
|
398
|
-
},
|
|
399
|
-
update: (cache, { data, errors }: any) => {
|
|
400
|
-
if (!data || errors) {
|
|
401
|
-
setLoading(false);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
//Temporary fix.....//
|
|
405
|
-
const newMessage: any = data?.sendMessage;
|
|
406
|
-
setChannelMessages((oldMessages: any) => uniqBy([...oldMessages, newMessage], ({ id }) => id));
|
|
407
|
-
setTotalCount((t) => t + 1);
|
|
408
|
-
//Temporary fix.....//
|
|
409
|
-
|
|
410
|
-
setChannelToTop(channelToTop + 1);
|
|
411
|
-
setLoading(false);
|
|
412
|
-
setMsg('');
|
|
413
|
-
},
|
|
414
|
-
});
|
|
1003
|
+
console.log('Sending text-only message');
|
|
1004
|
+
safeSend({ type: ConversationActions.SEND_MESSAGE });
|
|
415
1005
|
}
|
|
416
1006
|
},
|
|
417
|
-
[
|
|
1007
|
+
[state.context.channelId, state.context.images, rest?.isCreateNewChannel, rest?.newChannelData?.type, safeSend],
|
|
418
1008
|
);
|
|
419
1009
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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,
|
|
435
1034
|
},
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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',
|
|
446
1078
|
}),
|
|
447
|
-
|
|
448
|
-
(message.sent = msg?.isDelivered),
|
|
449
|
-
(message.received = msg?.isRead);
|
|
450
|
-
message.type = msg?.type;
|
|
451
|
-
message.propsConfiguration = msg?.propsConfiguration;
|
|
452
|
-
message.replies = msg?.replies ?? [];
|
|
453
|
-
message.isShowThreadMessage = isShowThreadMessage;
|
|
454
|
-
res.push(message);
|
|
455
|
-
});
|
|
1079
|
+
);
|
|
456
1080
|
}
|
|
457
|
-
return res?.length > 0 ? uniqBy([...res], ({ _id }: any) => _id) : [];
|
|
458
|
-
//return res;
|
|
459
|
-
}, [channelMessages, channelId]);
|
|
460
1081
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
<Send {...props} disabled={channelId || rest?.isCreateNewChannel ? false : true}>
|
|
464
|
-
<Box>
|
|
465
|
-
<MaterialCommunityIcons
|
|
466
|
-
name="send-circle"
|
|
467
|
-
style={{ marginBottom: 5, marginRight: 5 }}
|
|
468
|
-
size={32}
|
|
469
|
-
color={channelId || rest?.isCreateNewChannel ? '#2e64e5' : '#b8b2b2'}
|
|
470
|
-
/>
|
|
471
|
-
</Box>
|
|
472
|
-
</Send>
|
|
473
|
-
);
|
|
474
|
-
};
|
|
1082
|
+
// Use a more efficient approach - pre-filter messages once
|
|
1083
|
+
const filteredMessages = uniqBy(state.context.channelMessages, ({ id }) => id);
|
|
475
1084
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (currentMessage.type === 'ALERT') {
|
|
481
|
-
const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
|
|
482
|
-
let action: string = '';
|
|
483
|
-
let actionId: any = '';
|
|
484
|
-
let params: any = {};
|
|
485
|
-
|
|
486
|
-
if (attachment?.callToAction?.extraParams) {
|
|
487
|
-
const extraParams: any = attachment?.callToAction?.extraParams;
|
|
488
|
-
const route: any = extraParams?.route ?? null;
|
|
489
|
-
let path: any = null;
|
|
490
|
-
let param: any = null;
|
|
491
|
-
if (role && role == PreDefinedRole.Guest) {
|
|
492
|
-
path = route?.guest?.name ? route?.guest?.name ?? null : null;
|
|
493
|
-
param = route?.guest?.params ? route?.guest?.params ?? null : null;
|
|
494
|
-
} else if (role && role == PreDefinedRole.Owner) {
|
|
495
|
-
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
496
|
-
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
497
|
-
} else {
|
|
498
|
-
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
499
|
-
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
500
|
-
}
|
|
1085
|
+
// Skip processing if no filtered messages
|
|
1086
|
+
if (filteredMessages.length === 0) {
|
|
1087
|
+
return [];
|
|
1088
|
+
}
|
|
501
1089
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
1090
|
+
// Transform messages only once and return
|
|
1091
|
+
return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
1092
|
+
const date = new Date(msg.createdAt);
|
|
1093
|
+
|
|
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
|
+
}
|
|
508
1102
|
}
|
|
509
1103
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
+
});
|
|
529
1195
|
}}
|
|
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
|
-
|
|
574
|
-
source={{
|
|
575
|
-
uri: p?.author?.picture,
|
|
576
|
-
}}
|
|
577
|
-
/>
|
|
578
|
-
</Avatar>
|
|
579
|
-
))}
|
|
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>
|
|
580
1240
|
</HStack>
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
</Text>
|
|
585
|
-
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
586
|
-
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
587
|
-
</Text>
|
|
588
|
-
</HStack>
|
|
589
|
-
)}
|
|
590
|
-
</>
|
|
591
|
-
</TouchableHighlight>
|
|
592
|
-
)}
|
|
593
|
-
{/* <MessageText
|
|
594
|
-
{...props}
|
|
595
|
-
textStyle={{ left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 } }}
|
|
596
|
-
/> */}
|
|
597
|
-
</>
|
|
598
|
-
);
|
|
599
|
-
} else {
|
|
600
|
-
return (
|
|
601
|
-
<TouchableHighlight
|
|
602
|
-
underlayColor={'#c0c0c0'}
|
|
603
|
-
style={{ width: '100%' }}
|
|
604
|
-
onPress={() => {
|
|
605
|
-
if (currentMessage?.isShowThreadMessage)
|
|
606
|
-
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
607
|
-
channelId: channelId,
|
|
608
|
-
title: 'Message',
|
|
609
|
-
postParentId: currentMessage?._id,
|
|
610
|
-
isPostParentIdThread: true,
|
|
611
|
-
});
|
|
612
|
-
}}
|
|
613
|
-
>
|
|
614
|
-
<>
|
|
615
|
-
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
616
|
-
{currentMessage?.replies?.data?.length > 0 && (
|
|
617
|
-
<HStack space={'sm'} className="px-1 items-center">
|
|
618
|
-
<HStack>
|
|
619
|
-
{currentMessage?.replies?.data
|
|
620
|
-
?.filter(
|
|
621
|
-
(v: any, i: any, a: any) =>
|
|
622
|
-
a.findIndex((t: any) => t?.author?.id === v?.author?.id) === i,
|
|
623
|
-
)
|
|
624
|
-
?.slice(0, 2)
|
|
625
|
-
?.reverse()
|
|
626
|
-
?.map((p: any, i: Number) => (
|
|
627
|
-
<Avatar
|
|
628
|
-
key={'conversation-replies-key-' + i}
|
|
629
|
-
className="bg-transparent"
|
|
630
|
-
size={'sm'}
|
|
631
|
-
>
|
|
632
|
-
<AvatarFallbackText>
|
|
633
|
-
{startCase(p?.author?.username?.charAt(0))}
|
|
634
|
-
</AvatarFallbackText>
|
|
635
|
-
<AvatarImage
|
|
636
|
-
alt="user image"
|
|
637
|
-
style={{ borderRadius: 6, borderWidth: 2, borderColor: '#fff' }}
|
|
638
|
-
source={{
|
|
639
|
-
uri: p?.author?.picture,
|
|
640
|
-
}}
|
|
641
|
-
/>
|
|
642
|
-
</Avatar>
|
|
643
|
-
))}
|
|
644
|
-
</HStack>
|
|
645
|
-
<Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
|
|
646
|
-
{currentMessage?.replies?.totalCount}{' '}
|
|
647
|
-
{currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
|
|
648
|
-
</Text>
|
|
649
|
-
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
650
|
-
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
651
|
-
</Text>
|
|
652
|
-
</HStack>
|
|
1241
|
+
)}
|
|
1242
|
+
</>
|
|
1243
|
+
</TouchableHighlight>
|
|
653
1244
|
)}
|
|
654
1245
|
</>
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
+
);
|
|
659
1309
|
|
|
660
1310
|
const renderActions = (props) => {
|
|
661
1311
|
return (
|
|
662
|
-
<
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1312
|
+
<Actions
|
|
1313
|
+
{...props}
|
|
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
|
+
}}
|
|
1338
|
+
/>
|
|
668
1339
|
);
|
|
669
1340
|
};
|
|
670
1341
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
+
>
|
|
676
1379
|
<Image
|
|
677
|
-
key={selectedImage}
|
|
678
|
-
alt={'image'}
|
|
679
|
-
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
|
+
}}
|
|
680
1389
|
size={'xs'}
|
|
681
|
-
className="ml-3"
|
|
682
1390
|
/>
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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,
|
|
692
1406
|
}}
|
|
693
1407
|
>
|
|
694
|
-
<
|
|
695
|
-
</
|
|
696
|
-
</
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1408
|
+
<Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
|
|
1409
|
+
</TouchableHighlight>
|
|
1410
|
+
</View>
|
|
1411
|
+
</View>
|
|
1412
|
+
);
|
|
1413
|
+
},
|
|
1414
|
+
[state?.context?.selectedImage, safeSend],
|
|
1415
|
+
);
|
|
701
1416
|
|
|
702
1417
|
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
703
1418
|
setImageObject(obj);
|
|
@@ -711,11 +1426,9 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
711
1426
|
<CachedImage
|
|
712
1427
|
style={{ width: '100%', height: '100%' }}
|
|
713
1428
|
resizeMode={'cover'}
|
|
714
|
-
// cacheKey={`${_id}-conversation-modal-image-key`}
|
|
715
1429
|
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
716
1430
|
source={{
|
|
717
1431
|
uri: image,
|
|
718
|
-
//headers: `Authorization: Bearer ${token}`,
|
|
719
1432
|
expiresIn: 86400,
|
|
720
1433
|
}}
|
|
721
1434
|
alt={'image'}
|
|
@@ -725,22 +1438,7 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
725
1438
|
|
|
726
1439
|
const renderMessage = useCallback(
|
|
727
1440
|
(props: any) => {
|
|
728
|
-
//
|
|
729
|
-
// currentMessage: { text: currText },
|
|
730
|
-
// } = props;
|
|
731
|
-
|
|
732
|
-
//let messageTextStyle: any;
|
|
733
|
-
|
|
734
|
-
// Make "pure emoji" messages much bigger than plain text.
|
|
735
|
-
// if (currText && emojiUtils.isPureEmojiString(currText)) {
|
|
736
|
-
// messageTextStyle = {
|
|
737
|
-
// fontSize: 28,
|
|
738
|
-
// // Emoji get clipped if lineHeight isn't increased; make it consistent across platforms.
|
|
739
|
-
// lineHeight: Platform.OS === 'android' ? 34 : 30,
|
|
740
|
-
// }
|
|
741
|
-
// }
|
|
742
|
-
|
|
743
|
-
// return <SlackMessage {...props} messageTextStyle={messageTextStyle} />;
|
|
1441
|
+
// Use memo to prevent unnecessary re-renders of each message
|
|
744
1442
|
return (
|
|
745
1443
|
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
746
1444
|
);
|
|
@@ -748,164 +1446,331 @@ const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMes
|
|
|
748
1446
|
[isShowImageViewer],
|
|
749
1447
|
);
|
|
750
1448
|
|
|
751
|
-
// const renderMessage = (props: any) => {
|
|
752
|
-
// // const {
|
|
753
|
-
// // currentMessage: { text: currText },
|
|
754
|
-
// // } = props;
|
|
755
|
-
|
|
756
|
-
// //let messageTextStyle: any;
|
|
757
|
-
|
|
758
|
-
// // Make "pure emoji" messages much bigger than plain text.
|
|
759
|
-
// // if (currText && emojiUtils.isPureEmojiString(currText)) {
|
|
760
|
-
// // messageTextStyle = {
|
|
761
|
-
// // fontSize: 28,
|
|
762
|
-
// // // Emoji get clipped if lineHeight isn't increased; make it consistent across platforms.
|
|
763
|
-
// // lineHeight: Platform.OS === 'android' ? 34 : 30,
|
|
764
|
-
// // }
|
|
765
|
-
// // }
|
|
766
|
-
|
|
767
|
-
// // return <SlackMessage {...props} messageTextStyle={messageTextStyle} />;
|
|
768
|
-
// return <SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />;
|
|
769
|
-
// };
|
|
770
|
-
|
|
771
1449
|
let onScroll = false;
|
|
772
1450
|
|
|
1451
|
+
// Optimize onMomentumScrollBegin for better scroll performance
|
|
773
1452
|
const onMomentumScrollBegin = async ({ nativeEvent }: any) => {
|
|
1453
|
+
// Set scroll state
|
|
774
1454
|
onScroll = true;
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1455
|
+
|
|
1456
|
+
// Use the debounced fetch function to prevent excessive calls
|
|
1457
|
+
if (isCloseToTop(nativeEvent)) {
|
|
1458
|
+
onFetchOld();
|
|
778
1459
|
}
|
|
779
1460
|
};
|
|
780
1461
|
|
|
781
1462
|
const onEndReached = () => {
|
|
782
1463
|
console.log('on end reached');
|
|
783
1464
|
if (!onScroll) return;
|
|
784
|
-
// load messages, show ActivityIndicator
|
|
785
1465
|
onScroll = false;
|
|
786
|
-
// setLoadingOldMessages(true);
|
|
787
1466
|
};
|
|
788
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
|
|
789
1688
|
return (
|
|
790
|
-
<
|
|
791
|
-
|
|
1689
|
+
<View
|
|
1690
|
+
style={{
|
|
1691
|
+
flex: 1,
|
|
1692
|
+
backgroundColor: 'white',
|
|
1693
|
+
}}
|
|
1694
|
+
>
|
|
1695
|
+
{state?.matches && state.matches(BaseState.FetchMessages) && <Spinner color={'#3b82f6'} />}
|
|
792
1696
|
|
|
793
1697
|
<GiftedChat
|
|
794
1698
|
ref={messageRootListRef}
|
|
795
|
-
wrapInSafeArea={
|
|
1699
|
+
wrapInSafeArea={true}
|
|
796
1700
|
renderLoading={() => <Spinner color={'#3b82f6'} />}
|
|
797
1701
|
messages={messageList}
|
|
798
1702
|
listViewProps={{
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1703
|
+
...listViewProps,
|
|
1704
|
+
contentContainerStyle: {
|
|
1705
|
+
paddingBottom: 10,
|
|
1706
|
+
},
|
|
1707
|
+
keyboardShouldPersistTaps: 'handled',
|
|
802
1708
|
}}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
// onFetchOld();
|
|
808
|
-
// }
|
|
809
|
-
// }
|
|
810
|
-
// }}
|
|
811
|
-
onSend={(messages) =>
|
|
812
|
-
rest?.isCreateNewChannel && !channelId
|
|
813
|
-
? rest?.newChannelData?.type === RoomType?.Direct
|
|
814
|
-
? createDirectChannel(messages[0]?.text ?? ' ')
|
|
815
|
-
: null
|
|
816
|
-
: 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 } })
|
|
817
1713
|
}
|
|
818
|
-
text={msg ? msg : ' '}
|
|
819
|
-
onInputTextChanged={(text) => setMsg(text)}
|
|
820
1714
|
renderFooter={() =>
|
|
821
|
-
loading
|
|
1715
|
+
safeContextProperty('loading') ? (
|
|
1716
|
+
<Spinner color={'#3b82f6'} />
|
|
1717
|
+
) : safeContextProperty('imageLoading') ? (
|
|
1718
|
+
<Spinner color={'#3b82f6'} />
|
|
1719
|
+
) : (
|
|
1720
|
+
''
|
|
1721
|
+
)
|
|
822
1722
|
}
|
|
823
1723
|
scrollToBottom
|
|
824
1724
|
user={{
|
|
825
|
-
// _id: currentUser?.id || '',
|
|
826
1725
|
_id: auth?.id || '',
|
|
827
1726
|
}}
|
|
828
|
-
isTyping={
|
|
829
|
-
alwaysShowSend={
|
|
830
|
-
//onLoadEarlier={onFetchOld}
|
|
831
|
-
//infiniteScroll={true}
|
|
1727
|
+
isTyping={false} // Setting to false to reduce animations
|
|
1728
|
+
alwaysShowSend={true} // Always show send button regardless of text content
|
|
832
1729
|
renderSend={renderSend}
|
|
833
|
-
// loadEarlier={data?.messages?.totalCount > channelMessages.length}
|
|
834
|
-
//isLoadingEarlier={loadEarlierMsg}
|
|
835
|
-
//extraData={{ isLoadingEarlier: loadingOldMessages }}
|
|
836
|
-
// renderLoadEarlier={() =>
|
|
837
|
-
// !loadEarlierMsg && (
|
|
838
|
-
// <Center py={2}>
|
|
839
|
-
// <Button
|
|
840
|
-
// onPress={() => onFetchOld()}
|
|
841
|
-
// variant={'outline'}
|
|
842
|
-
// _text={{ color: 'black', fontSize: 15, fontWeight: 'bold' }}
|
|
843
|
-
// >
|
|
844
|
-
// Load earlier messages
|
|
845
|
-
// </Button>
|
|
846
|
-
// </Center>
|
|
847
|
-
// )
|
|
848
|
-
// }
|
|
849
1730
|
renderMessageText={renderMessageText}
|
|
1731
|
+
renderInputToolbar={renderInputToolbar}
|
|
850
1732
|
minInputToolbarHeight={50}
|
|
851
|
-
renderActions={channelId && renderActions}
|
|
852
|
-
renderAccessory={renderAccessory}
|
|
1733
|
+
renderActions={safeContextProperty('channelId') && renderActions}
|
|
1734
|
+
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
853
1735
|
renderMessage={renderMessage}
|
|
854
|
-
renderChatFooter={
|
|
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
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
...prev?.messages,
|
|
885
|
-
data: [...(prev?.messages?.data ?? []), newMessage],
|
|
886
|
-
totalCount: totalMsgCount,
|
|
887
|
-
},
|
|
888
|
-
};
|
|
889
|
-
return merged;
|
|
890
|
-
// return Object.assign({}, prev, {
|
|
891
|
-
// messages: {
|
|
892
|
-
// data: [...prev.messages.data, newMessage],
|
|
893
|
-
// totalCount: prev.messages.totalCount + 1,
|
|
894
|
-
// },
|
|
895
|
-
// });
|
|
896
|
-
},
|
|
897
|
-
})
|
|
898
|
-
}
|
|
899
|
-
/>
|
|
900
|
-
</>
|
|
901
|
-
)}
|
|
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..."
|
|
902
1766
|
lightboxProps={{
|
|
903
1767
|
underlayColor: 'transparent',
|
|
904
1768
|
springConfig: { tension: 90000, friction: 90000 },
|
|
905
1769
|
disabled: true,
|
|
906
1770
|
}}
|
|
1771
|
+
infiniteScroll={false} // Disable automatic loading
|
|
907
1772
|
/>
|
|
908
|
-
</
|
|
1773
|
+
</View>
|
|
909
1774
|
);
|
|
910
1775
|
};
|
|
911
1776
|
|