@messenger-box/platform-mobile 10.0.3-alpha.23 → 10.0.3-alpha.232

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.
Files changed (116) hide show
  1. package/lib/components/messages-container-ui/BuildModeView.js +428 -0
  2. package/lib/components/messages-container-ui/BuildModeView.js.map +1 -0
  3. package/lib/components/messages-container-ui/MessagesContainerUI.js +55 -0
  4. package/lib/components/messages-container-ui/MessagesContainerUI.js.map +1 -0
  5. package/lib/components/messages-container-ui/PlanModeView.js +336 -0
  6. package/lib/components/messages-container-ui/PlanModeView.js.map +1 -0
  7. package/lib/compute.js +2 -3
  8. package/lib/compute.js.map +1 -1
  9. package/lib/index.js +1 -1
  10. package/lib/index.js.map +1 -1
  11. package/lib/module.js.map +1 -1
  12. package/lib/queries/inboxQueries.js +62 -0
  13. package/lib/queries/inboxQueries.js.map +1 -0
  14. package/lib/routes.json +2 -3
  15. package/lib/screens/inbox/DialogMessages.js +8 -3
  16. package/lib/screens/inbox/DialogMessages.js.map +1 -1
  17. package/lib/screens/inbox/DialogThreadMessages.js +6 -11
  18. package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
  19. package/lib/screens/inbox/DialogThreads.js +9 -11
  20. package/lib/screens/inbox/DialogThreads.js.map +1 -1
  21. package/lib/screens/inbox/Inbox.js.map +1 -1
  22. package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
  23. package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
  24. package/lib/screens/inbox/components/CachedImage/index.js +125 -96
  25. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  26. package/lib/screens/inbox/components/DialogItem.js +160 -0
  27. package/lib/screens/inbox/components/DialogItem.js.map +1 -0
  28. package/lib/screens/inbox/components/GiftedChatInboxComponent.js +315 -0
  29. package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
  30. package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js +3 -1
  31. package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js.map +1 -1
  32. package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js +194 -0
  33. package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js.map +1 -0
  34. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +149 -36
  35. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
  36. package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js +4 -5
  37. package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js.map +1 -1
  38. package/lib/screens/inbox/components/SubscriptionHandler.js +22 -0
  39. package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
  40. package/lib/screens/inbox/components/ThreadsViewItem.js +2 -4
  41. package/lib/screens/inbox/components/ThreadsViewItem.js.map +1 -1
  42. package/lib/screens/inbox/config/config.js +4 -2
  43. package/lib/screens/inbox/config/config.js.map +1 -1
  44. package/lib/screens/inbox/containers/ConversationView.js +1093 -1090
  45. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  46. package/lib/screens/inbox/containers/Dialogs.js +130 -577
  47. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  48. package/lib/screens/inbox/containers/ThreadConversationView.js +864 -1408
  49. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  50. package/lib/screens/inbox/containers/ThreadsView.js +9 -15
  51. package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
  52. package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
  53. package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
  54. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js +1 -1
  55. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -1
  56. package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -1
  57. package/package.json +10 -8
  58. package/CHANGELOG.md +0 -172
  59. package/jest.config.js +0 -24
  60. package/lib/screens/inbox/components/DialogsListItem.js +0 -548
  61. package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
  62. package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -489
  63. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
  64. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +0 -175
  65. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +0 -1
  66. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +0 -191
  67. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +0 -1
  68. package/lib/screens/inbox/containers/workflow/conversation-xstate.js +0 -380
  69. package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +0 -1
  70. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +0 -211
  71. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +0 -1
  72. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +0 -438
  73. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +0 -1
  74. package/rollup.config.mjs +0 -45
  75. package/src/components/index.ts +0 -0
  76. package/src/compute.ts +0 -63
  77. package/src/index.ts +0 -7
  78. package/src/module.ts +0 -10
  79. package/src/navigation/InboxNavigation.tsx +0 -102
  80. package/src/navigation/index.ts +0 -1
  81. package/src/screens/inbox/DialogMessages.tsx +0 -21
  82. package/src/screens/inbox/DialogThreadMessages.tsx +0 -97
  83. package/src/screens/inbox/DialogThreads.tsx +0 -125
  84. package/src/screens/inbox/Inbox.tsx +0 -17
  85. package/src/screens/inbox/components/CachedImage/consts.ts +0 -6
  86. package/src/screens/inbox/components/CachedImage/index.tsx +0 -223
  87. package/src/screens/inbox/components/DialogsHeader.tsx +0 -30
  88. package/src/screens/inbox/components/DialogsListItem.tsx +0 -819
  89. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +0 -679
  90. package/src/screens/inbox/components/SlackMessageContainer/ImageViewerModal.tsx +0 -113
  91. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +0 -313
  92. package/src/screens/inbox/components/SlackMessageContainer/SlackMessage.tsx +0 -145
  93. package/src/screens/inbox/components/SlackMessageContainer/index.ts +0 -3
  94. package/src/screens/inbox/components/SmartLoader.tsx +0 -61
  95. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +0 -301
  96. package/src/screens/inbox/components/ThreadsViewItem.tsx +0 -233
  97. package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +0 -145
  98. package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +0 -159
  99. package/src/screens/inbox/config/config.ts +0 -15
  100. package/src/screens/inbox/config/index.ts +0 -1
  101. package/src/screens/inbox/containers/ConversationView.tsx +0 -1784
  102. package/src/screens/inbox/containers/Dialogs.tsx +0 -829
  103. package/src/screens/inbox/containers/SupportServiceDialogs.tsx +0 -119
  104. package/src/screens/inbox/containers/ThreadConversationView.tsx +0 -2295
  105. package/src/screens/inbox/containers/ThreadsView.tsx +0 -224
  106. package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +0 -20
  107. package/src/screens/inbox/containers/workflow/conversation-xstate.ts +0 -313
  108. package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +0 -196
  109. package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +0 -401
  110. package/src/screens/inbox/hooks/useSafeDialogThreadsMachine.ts +0 -136
  111. package/src/screens/inbox/index.ts +0 -37
  112. package/src/screens/inbox/machines/threadsMachine.ts +0 -147
  113. package/src/screens/inbox/workflow/dialog-threads-xstate.ts +0 -163
  114. package/src/screens/index.ts +0 -4
  115. package/tsconfig.json +0 -13
  116. package/webpack.config.js +0 -58
@@ -1,1784 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import {
3
- Avatar,
4
- AvatarFallbackText,
5
- AvatarImage,
6
- Box,
7
- Button,
8
- ButtonText,
9
- HStack,
10
- Icon,
11
- Image,
12
- Spinner,
13
- Text,
14
- } from '@admin-layout/gluestack-ui-mobile';
15
- import { Platform, TouchableHighlight, SafeAreaView, View } from 'react-native';
16
- import { useFocusEffect, useIsFocused, useNavigation } from '@react-navigation/native';
17
- import { navigationRef } from '@common-stack/client-react';
18
- import { useSelector } from 'react-redux';
19
- import { orderBy, startCase, uniqBy } from 'lodash-es';
20
- import * as ImagePicker from 'expo-image-picker';
21
- import { encode as atob } from 'base-64';
22
- import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
23
- import { Actions, GiftedChat, IMessage, MessageText, Send, Composer, InputToolbar } from 'react-native-gifted-chat';
24
- import { PreDefinedRole, RoomType, IExpoNotificationData, IFileInfo } from 'common';
25
- import {
26
- OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
27
- useMessagesQuery,
28
- useSendExpoNotificationOnPostMutation,
29
- useSendMessagesMutation,
30
- useViewChannelDetailQuery,
31
- useAddDirectChannelMutation,
32
- } from 'common/graphql';
33
- import { useUploadFilesNative } from '@messenger-box/platform-client';
34
- import { objectId } from '@messenger-box/core';
35
- import { userSelector } from '@adminide-stack/user-auth0-client';
36
- import { format, isToday, isYesterday } from 'date-fns';
37
- import { ImageViewerModal, SlackMessage } from '../components/SlackMessageContainer';
38
- import CachedImage from '../components/CachedImage';
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
- }
54
-
55
- const {
56
- MESSAGES_PER_PAGE,
57
- CALL_TO_ACTION_BOX_BGCOLOR,
58
- CALL_TO_ACTION_PATH,
59
- CALL_TO_ACTION_BUTTON_BORDERCOLOR,
60
- CALL_TO_ACTION_TEXT_COLOR,
61
- } = config;
62
-
63
- const createdAtText = (value: string) => {
64
- if (!value) return '';
65
- let date = new Date(value);
66
- if (isToday(date)) return 'Today';
67
- if (isYesterday(date)) return 'Yesterday';
68
- return format(new Date(value), 'MMM dd, yyyy');
69
- };
70
-
71
- interface ISubscriptionHandlerProps {
72
- subscribeToNewMessages: () => any;
73
- channelId: string;
74
- }
75
-
76
- interface IMessageProps extends IMessage {
77
- type: string;
78
- propsConfiguration?: any;
79
- replies?: any;
80
- isShowThreadMessage?: boolean;
81
- }
82
-
83
- export interface AlertMessageAttachmentsInterface {
84
- title: string;
85
- isTitleHtml: boolean;
86
- icon: string;
87
- callToAction: {
88
- title: string;
89
- link: string;
90
- };
91
- }
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
-
317
- const ConversationViewComponent = ({ channelId: ChannelId, role, isShowThreadMessage, ...rest }: any) => {
318
- const [channelToTop, setChannelToTop] = useState(0);
319
-
320
- // Create a ref to track if component is mounted
321
- const isMountedRef = useRef(true);
322
-
323
- // Use our safer custom implementation instead of the problematic useMachine
324
- const [state, send] = useSafeMachine(conversationXstate);
325
-
326
- // Define safe functions first to avoid "used before declaration" errors
327
- const safeContext = useCallback(() => {
328
- try {
329
- return state?.context || {};
330
- } catch (error) {
331
- console.error('Error accessing state.context:', error);
332
- return {};
333
- }
334
- }, [state]);
335
-
336
- const safeContextProperty = useCallback(
337
- (property, defaultValue = null) => {
338
- try {
339
- return state?.context?.[property] ?? defaultValue;
340
- } catch (error) {
341
- console.error(`Error accessing state.context.${property}:`, error);
342
- return defaultValue;
343
- }
344
- },
345
- [state],
346
- );
347
-
348
- const safeMatches = useCallback(
349
- (stateValue) => {
350
- try {
351
- return state?.matches?.(stateValue) || false;
352
- } catch (error) {
353
- console.error(`Error calling state.matches with ${stateValue}:`, error);
354
- return false;
355
- }
356
- },
357
- [state],
358
- );
359
-
360
- const safeSend = useCallback(
361
- (event) => {
362
- try {
363
- send(event);
364
- } catch (error) {
365
- console.error('Error sending event to state machine:', error, event);
366
- }
367
- },
368
- [send],
369
- );
370
-
371
- // Immediately set initial context if needed
372
- useEffect(() => {
373
- if (ChannelId) {
374
- console.log('Setting initial channel ID on mount:', ChannelId);
375
- try {
376
- send({
377
- type: ConversationActions.INITIAL_CONTEXT,
378
- data: { channelId: ChannelId },
379
- });
380
- } catch (error) {
381
- console.error('Error sending initial context:', error);
382
- }
383
- }
384
- }, []);
385
-
386
- // Use a ref to track the current machine snapshot for safer access
387
- const stateRef = useRef(state);
388
-
389
- // Keep the ref updated with the latest snapshot
390
- useEffect(() => {
391
- stateRef.current = state;
392
- }, [state]);
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
-
423
- const auth: any = useSelector(userSelector);
424
- const currentRoute = navigationRef.isReady() ? navigationRef?.getCurrentRoute() : null;
425
- const navigation = useNavigation<any>();
426
- const [selectedImage, setImage] = useState<string>('');
427
- const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
428
- const [imageObject, setImageObject] = useState<any>({});
429
- const messageRootListRef = useRef<any>(null);
430
- const isFocused = useIsFocused();
431
-
432
- const [addDirectChannel] = useAddDirectChannelMutation();
433
- const { startUpload } = useUploadFilesNative();
434
- const [sendMsg] = useSendMessagesMutation();
435
- const [sendExpoNotificationOnPostMutation] = useSendExpoNotificationOnPostMutation();
436
-
437
- const {
438
- data,
439
- loading: messageLoading,
440
- refetch,
441
- fetchMore: fetchMoreMessages,
442
- subscribeToMore,
443
- }: any = useMessagesQuery({
444
- variables: {
445
- channelId: state.context.channelId?.toString(),
446
- parentId: null,
447
- limit: MESSAGES_PER_PAGE,
448
- skip: state.context.skip,
449
- },
450
- skip: !state.context.channelId,
451
- fetchPolicy: 'cache-and-network',
452
- nextFetchPolicy: 'cache-first',
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
- },
468
- });
469
-
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]);
811
-
812
- React.useEffect(() => {
813
- return () => {
814
- send({ type: ConversationActions.CLEAR_MESSAGES });
815
- };
816
- }, []);
817
-
818
- useFocusEffect(
819
- React.useCallback(() => {
820
- if (state.context.channelId) {
821
- send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: state.context.channelId } });
822
- }
823
- return () => {
824
- send({ type: ConversationActions.CLEAR_MESSAGES });
825
- };
826
- }, [state.context.channelId, isFocused]),
827
- );
828
-
829
- React.useEffect(() => {
830
- const currentChannelId = ChannelId || currentRoute?.params?.channelId;
831
- if (currentChannelId) {
832
- console.log('Setting initial channel ID:', currentChannelId);
833
- send({ type: ConversationActions.INITIAL_CONTEXT, data: { channelId: currentChannelId } });
834
- }
835
- }, [ChannelId, currentRoute]);
836
-
837
- React.useEffect(() => {
838
- if (state.context.selectedImage) {
839
- send({ type: ConversationActions.STOP_LOADING });
840
- }
841
- }, [state.context.selectedImage]);
842
-
843
- useEffect(() => {
844
- if (data?.messages?.data) {
845
- console.log('📩 QUERY DATA CHANGED - Messages received:', data.messages.data.length);
846
- const { data: messages, totalCount: messageTotalCount } = data.messages;
847
-
848
- if (messages && messages.length > 0) {
849
- console.log('📩 QUERY DATA - Setting channel messages, count:', messages.length);
850
-
851
- // First try dispatching the update through XState
852
- send({
853
- type: ConversationActions.SET_CHANNEL_MESSAGES,
854
- data: {
855
- messages: uniqBy([...messages, ...state.context.channelMessages], ({ id }) => id),
856
- totalCount: messageTotalCount,
857
- },
858
- });
859
-
860
- // Debug: Log the first message to verify data format
861
- if (messages[0]) {
862
- const sample = messages[0];
863
- console.log(
864
- '📩 SAMPLE MESSAGE:',
865
- JSON.stringify({
866
- id: sample.id,
867
- message: sample.message,
868
- author: {
869
- id: sample.author?.id,
870
- name: `${sample.author?.givenName} ${sample.author?.familyName}`,
871
- },
872
- createdAt: sample.createdAt,
873
- }),
874
- );
875
- }
876
-
877
- // Check if the state machine actually updated (debug only)
878
- setTimeout(() => {
879
- if (state.context.channelMessages?.length === 0) {
880
- console.warn('⚠️ STATE NOT UPDATED after message data received - may need fallback');
881
- }
882
- }, 500);
883
- }
884
- }
885
- }, [data]);
886
-
887
- // Optimize onFetchOld by adding debounce logic
888
- const onFetchOld = useCallback(() => {
889
- // Prevent multiple rapid calls
890
- if (fetchOldDebounceRef.current) return;
891
-
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);
907
- }
908
- }, [state?.context?.totalCount, state?.context?.channelMessages, state?.context?.loadingOldMessages]);
909
-
910
- // Add debounce ref
911
- const fetchOldDebounceRef = useRef(false);
912
-
913
- const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
914
- const paddingToTop = 60;
915
- return contentSize.height - layoutMeasurement.height - paddingToTop <= contentOffset.y;
916
- };
917
-
918
- const dataURLtoFile = (dataurl: any, filename: any) => {
919
- var arr = dataurl.split(','),
920
- mime = arr[0].match(/:(.*?);/)[1],
921
- bstr = atob(arr[1]),
922
- n = bstr.length,
923
- u8arr = new Uint8Array(n);
924
- while (n--) {
925
- u8arr[n] = bstr.charCodeAt(n);
926
- }
927
- return new File([u8arr], filename, { type: mime });
928
- };
929
-
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;
936
-
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
- );
960
- },
961
- [state?.context?.channelId, state?.context?.images, rest?.isCreateNewChannel],
962
- );
963
-
964
- // Fix the handleSend function to properly handle image-only messages
965
- const handleSend = useCallback(
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);
971
-
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
- }
977
-
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;
981
-
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 });
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 });
1002
- } else {
1003
- console.log('Sending text-only message');
1004
- safeSend({ type: ConversationActions.SEND_MESSAGE });
1005
- }
1006
- },
1007
- [state.context.channelId, state.context.images, rest?.isCreateNewChannel, rest?.newChannelData?.type, safeSend],
1008
- );
1009
-
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,
1034
- },
1035
- });
1036
- }
1037
- } catch (error) {
1038
- console.error('❌ DIRECT FETCH ERROR:', error);
1039
- }
1040
- }, [state.context.channelId, refetch]);
1041
-
1042
- // Auto-trigger fallback if needed
1043
- useEffect(() => {
1044
- let timeoutId: NodeJS.Timeout;
1045
-
1046
- if (state.context.channelId && state.context.channelMessages.length === 0) {
1047
- timeoutId = setTimeout(() => {
1048
- console.log('⚠️ ACTIVATING FALLBACK - XState not updating after timeout');
1049
- fetchMessagesWithFallback();
1050
- }, 3000); // Wait 3 seconds for normal flow to work
1051
- }
1052
-
1053
- return () => {
1054
- if (timeoutId) clearTimeout(timeoutId);
1055
- };
1056
- }, [state.context.channelId, state.context.channelMessages, fetchMessagesWithFallback]);
1057
-
1058
- // Optimize the messageList calculation for better performance
1059
- const messageList = useMemo(() => {
1060
- // Only recalculate when these dependencies change
1061
- console.log('🔄 CALCULATING MESSAGE LIST - Optimized version');
1062
-
1063
- // Short-circuit if no messages to process
1064
- if (!state?.context?.channelMessages || state.context.channelMessages.length === 0) {
1065
- console.log('No messages to process');
1066
- return [];
1067
- }
1068
-
1069
- // Use a more efficient approach - pre-filter messages once
1070
- const filteredMessages = uniqBy(state.context.channelMessages, ({ id }) => id);
1071
-
1072
- // Skip processing if no filtered messages
1073
- if (filteredMessages.length === 0) {
1074
- return [];
1075
- }
1076
-
1077
- // Transform messages only once and return
1078
- return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
1079
- const date = new Date(msg.createdAt);
1080
-
1081
- // Extract image URL from files data
1082
- let imageUrl = null;
1083
- if (msg.files?.data && msg.files.data.length > 0) {
1084
- const fileData = msg.files.data[0];
1085
- if (fileData && fileData.url) {
1086
- imageUrl = fileData.url;
1087
- }
1088
- }
1089
-
1090
- // Create message in a more direct way
1091
- return {
1092
- _id: msg.id,
1093
- text: msg.message,
1094
- createdAt: date,
1095
- user: {
1096
- _id: msg.author?.id || '',
1097
- name: `${msg.author?.givenName || ''} ${msg.author?.familyName || ''}`,
1098
- avatar: msg.author?.picture || '',
1099
- },
1100
- image: imageUrl,
1101
- sent: msg?.isDelivered,
1102
- received: msg?.isRead,
1103
- type: msg?.type,
1104
- propsConfiguration: msg?.propsConfiguration,
1105
- replies: msg?.replies ?? [],
1106
- isShowThreadMessage,
1107
- };
1108
- });
1109
- }, [state?.context?.channelMessages, isShowThreadMessage]);
1110
-
1111
- // Memoize the renderMessageText function
1112
- const renderMessageText = useCallback(
1113
- (props: any) => {
1114
- const { currentMessage } = props;
1115
- const lastReply: any =
1116
- currentMessage?.replies?.data?.length > 0 ? currentMessage?.replies?.data?.[0] : null;
1117
-
1118
- if (currentMessage.type === 'ALERT') {
1119
- const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
1120
- let action: string = '';
1121
- let actionId: any = '';
1122
- let params: any = {};
1123
-
1124
- if (attachment?.callToAction?.extraParams) {
1125
- const extraParams: any = attachment?.callToAction?.extraParams;
1126
- const route: any = extraParams?.route ?? null;
1127
- let path: any = null;
1128
- let param: any = null;
1129
- if (role && role == PreDefinedRole.Guest) {
1130
- path = route?.guest?.name ? route?.guest?.name ?? null : null;
1131
- param = route?.guest?.params ? route?.guest?.params ?? null : null;
1132
- } else if (role && role == PreDefinedRole.Owner) {
1133
- path = route?.host?.name ? route?.host?.name ?? null : null;
1134
- param = route?.host?.params ? route?.host?.params ?? null : null;
1135
- } else {
1136
- path = route?.host?.name ? route?.host?.name ?? null : null;
1137
- param = route?.host?.params ? route?.host?.params ?? null : null;
1138
- }
1139
-
1140
- action = path;
1141
- params = { ...param };
1142
- } else if (attachment?.callToAction?.link) {
1143
- action = CALL_TO_ACTION_PATH;
1144
- actionId = attachment?.callToAction?.link.split('/').pop();
1145
- params = { reservationId: actionId };
1146
- }
1147
-
1148
- return (
1149
- <>
1150
- {attachment?.callToAction && action ? (
1151
- <Box className={`bg-[${CALL_TO_ACTION_BOX_BGCOLOR}] rounded-[15] pb-2`}>
1152
- <Button
1153
- variant={'outline'}
1154
- size={'sm'}
1155
- className={`border-[${CALL_TO_ACTION_BUTTON_BORDERCOLOR}]`}
1156
- onPress={() => action && params && navigation.navigate(action, params)}
1157
- >
1158
- <ButtonText className={`color-[${CALL_TO_ACTION_TEXT_COLOR}]`}>
1159
- {attachment.callToAction.title}
1160
- </ButtonText>
1161
- </Button>
1162
- <MessageText
1163
- {...props}
1164
- textStyle={{
1165
- left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 },
1166
- }}
1167
- />
1168
- </Box>
1169
- ) : (
1170
- <TouchableHighlight
1171
- underlayColor={'#c0c0c0'}
1172
- style={{ width: '100%' }}
1173
- onPress={() => {
1174
- if (currentMessage?.isShowThreadMessage)
1175
- navigation.navigate(config.THREAD_MESSEGE_PATH, {
1176
- channelId: state?.context?.channelId,
1177
- title: 'Message',
1178
- postParentId: currentMessage?._id,
1179
- isPostParentIdThread: true,
1180
- });
1181
- }}
1182
- >
1183
- <>
1184
- <MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
1185
- {currentMessage?.replies?.data?.length > 0 && (
1186
- <HStack space={'sm'} className="px-1 items-center">
1187
- <HStack>
1188
- {currentMessage?.replies?.data
1189
- ?.filter(
1190
- (v: any, i: any, a: any) =>
1191
- a.findIndex((t: any) => t?.author?.id === v?.author?.id) ===
1192
- i,
1193
- )
1194
- ?.slice(0, 2)
1195
- ?.reverse()
1196
- ?.map((p: any, i: Number) => (
1197
- <Avatar
1198
- key={'conversations-view-key-' + i}
1199
- size={'sm'}
1200
- className="bg-transparent"
1201
- >
1202
- <AvatarFallbackText>
1203
- {startCase(p?.author?.username?.charAt(0))}
1204
- </AvatarFallbackText>
1205
- <AvatarImage
1206
- alt="user image"
1207
- style={{
1208
- borderRadius: 6,
1209
- borderWidth: 2,
1210
- borderColor: '#fff',
1211
- }}
1212
- source={{
1213
- uri: p?.author?.picture,
1214
- }}
1215
- />
1216
- </Avatar>
1217
- ))}
1218
- </HStack>
1219
- <Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
1220
- {currentMessage?.replies?.totalCount}{' '}
1221
- {currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
1222
- </Text>
1223
- <Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
1224
- {lastReply ? createdAtText(lastReply?.createdAt) : ''}
1225
- </Text>
1226
- </HStack>
1227
- )}
1228
- </>
1229
- </TouchableHighlight>
1230
- )}
1231
- </>
1232
- );
1233
- } else {
1234
- return (
1235
- <TouchableHighlight
1236
- underlayColor={'#c0c0c0'}
1237
- style={{ width: '100%' }}
1238
- onPress={() => {
1239
- if (currentMessage?.isShowThreadMessage)
1240
- navigation.navigate(config.THREAD_MESSEGE_PATH, {
1241
- channelId: state?.context?.channelId,
1242
- title: 'Message',
1243
- postParentId: currentMessage?._id,
1244
- isPostParentIdThread: true,
1245
- });
1246
- }}
1247
- >
1248
- <>
1249
- <MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
1250
- {currentMessage?.replies?.data?.length > 0 && (
1251
- <HStack space={'sm'} className="px-1 items-center">
1252
- <HStack>
1253
- {currentMessage?.replies?.data
1254
- ?.filter(
1255
- (v: any, i: any, a: any) =>
1256
- a.findIndex((t: any) => t?.author?.id === v?.author?.id) === i,
1257
- )
1258
- ?.slice(0, 2)
1259
- ?.reverse()
1260
- ?.map((p: any, i: Number) => (
1261
- <Avatar
1262
- key={'conversation-replies-key-' + i}
1263
- className="bg-transparent"
1264
- size={'sm'}
1265
- >
1266
- <AvatarFallbackText>
1267
- {startCase(p?.author?.username?.charAt(0))}
1268
- </AvatarFallbackText>
1269
- <AvatarImage
1270
- alt="user image"
1271
- style={{ borderRadius: 6, borderWidth: 2, borderColor: '#fff' }}
1272
- source={{
1273
- uri: p?.author?.picture,
1274
- }}
1275
- />
1276
- </Avatar>
1277
- ))}
1278
- </HStack>
1279
- <Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
1280
- {currentMessage?.replies?.totalCount}{' '}
1281
- {currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
1282
- </Text>
1283
- <Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
1284
- {lastReply ? createdAtText(lastReply?.createdAt) : ''}
1285
- </Text>
1286
- </HStack>
1287
- )}
1288
- </>
1289
- </TouchableHighlight>
1290
- );
1291
- }
1292
- },
1293
- [navigation, state?.context?.channelId, role],
1294
- );
1295
-
1296
- const renderActions = (props) => {
1297
- return (
1298
- <Actions
1299
- {...props}
1300
- options={{
1301
- ['Choose from Library']: onSelectImages,
1302
- ['Cancel']: () => {}, // Add this option to make the sheet dismissible
1303
- }}
1304
- optionTintColor="#000000"
1305
- cancelButtonIndex={1} // Set the Cancel option as the cancel button
1306
- icon={() => (
1307
- <Box
1308
- style={{
1309
- width: 32,
1310
- height: 32,
1311
- alignItems: 'center',
1312
- justifyContent: 'center',
1313
- }}
1314
- >
1315
- <Ionicons name="image" size={24} color={colors.blue[500]} />
1316
- </Box>
1317
- )}
1318
- containerStyle={{
1319
- alignItems: 'center',
1320
- justifyContent: 'center',
1321
- marginLeft: 8,
1322
- marginBottom: 0,
1323
- }}
1324
- />
1325
- );
1326
- };
1327
-
1328
- // Create a more visible and reliable image preview with cancel button
1329
- const renderAccessory = useCallback(
1330
- (props) => {
1331
- const selectedImage = safeContextProperty('selectedImage', '');
1332
-
1333
- if (!selectedImage) {
1334
- return null;
1335
- }
1336
-
1337
- return (
1338
- <View
1339
- style={{
1340
- height: 50,
1341
- padding: 3,
1342
- backgroundColor: 'white',
1343
- borderTopWidth: 1,
1344
- borderTopColor: '#e0e0e0',
1345
- flexDirection: 'row',
1346
- alignItems: 'center',
1347
- margin: 0,
1348
- paddingBottom: 0,
1349
- paddingTop: 5,
1350
- position: 'absolute',
1351
- bottom: 0,
1352
- left: 0,
1353
- right: 0,
1354
- zIndex: 999,
1355
- }}
1356
- >
1357
- <View
1358
- style={{
1359
- flex: 1,
1360
- flexDirection: 'row',
1361
- alignItems: 'center',
1362
- paddingHorizontal: 15,
1363
- }}
1364
- >
1365
- <Image
1366
- key={state?.context?.selectedImage}
1367
- alt={'selected image'}
1368
- source={{ uri: state?.context?.selectedImage }}
1369
- style={{
1370
- width: 36,
1371
- height: 36,
1372
- borderRadius: 5,
1373
- marginRight: 15,
1374
- }}
1375
- size={'xs'}
1376
- />
1377
-
1378
- <TouchableHighlight
1379
- underlayColor="#dddddd"
1380
- onPress={() => safeSend({ type: ConversationActions.CLEAR_IMAGE })}
1381
- style={{
1382
- backgroundColor: '#f44336',
1383
- paddingVertical: 2,
1384
- paddingHorizontal: 5,
1385
- borderRadius: 5,
1386
- marginLeft: 10,
1387
- elevation: 3,
1388
- shadowColor: '#000',
1389
- shadowOffset: { width: 0, height: 1 },
1390
- shadowOpacity: 0.3,
1391
- shadowRadius: 2,
1392
- }}
1393
- >
1394
- <Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
1395
- </TouchableHighlight>
1396
- </View>
1397
- </View>
1398
- );
1399
- },
1400
- [state?.context?.selectedImage, safeSend],
1401
- );
1402
-
1403
- const setImageViewerObject = (obj: any, v: boolean) => {
1404
- setImageObject(obj);
1405
- setImageViewer(v);
1406
- };
1407
-
1408
- const modalContent = React.useMemo(() => {
1409
- if (!imageObject) return <></>;
1410
- const { image, _id } = imageObject;
1411
- return (
1412
- <CachedImage
1413
- style={{ width: '100%', height: '100%' }}
1414
- resizeMode={'cover'}
1415
- cacheKey={`${_id}-slack-bubble-imageKey`}
1416
- source={{
1417
- uri: image,
1418
- expiresIn: 86400,
1419
- }}
1420
- alt={'image'}
1421
- />
1422
- );
1423
- }, [imageObject]);
1424
-
1425
- const renderMessage = useCallback(
1426
- (props: any) => {
1427
- // Use memo to prevent unnecessary re-renders of each message
1428
- return (
1429
- <SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
1430
- );
1431
- },
1432
- [isShowImageViewer],
1433
- );
1434
-
1435
- let onScroll = false;
1436
-
1437
- // Optimize onMomentumScrollBegin for better scroll performance
1438
- const onMomentumScrollBegin = async ({ nativeEvent }: any) => {
1439
- // Set scroll state
1440
- onScroll = true;
1441
-
1442
- // Use the debounced fetch function to prevent excessive calls
1443
- if (isCloseToTop(nativeEvent)) {
1444
- onFetchOld();
1445
- }
1446
- };
1447
-
1448
- const onEndReached = () => {
1449
- console.log('on end reached');
1450
- if (!onScroll) return;
1451
- onScroll = false;
1452
- };
1453
-
1454
- // Add debug logging to help diagnose the issue
1455
- useEffect(() => {
1456
- console.log('Current channel ID:', state.context.channelId);
1457
- console.log('Current state:', state.value);
1458
- console.log('Channel messages count:', state.context.channelMessages.length);
1459
- }, [state.context.channelId, state.value, state.context.channelMessages]);
1460
-
1461
- // Fix the infinite update loop in useEffect monitoring state changes
1462
- useEffect(() => {
1463
- // Only trigger effect if we have a specific state to handle
1464
- // Check if function exists and if we're in a valid state before calling implementation functions
1465
- if (state && typeof state.matches === 'function') {
1466
- if (state.matches(BaseState.FetchMessages)) {
1467
- console.log('In FetchMessages state, attempting to fetch messages');
1468
- // Use a ref to track if we've already fetched for this state update
1469
- if (!fetchInProgressRef.current) {
1470
- fetchInProgressRef.current = true;
1471
- fetchMessagesDirectly().finally(() => {
1472
- fetchInProgressRef.current = false;
1473
- });
1474
- }
1475
- } else if (state.matches(MainState.FetchMoreMessages)) {
1476
- if (!fetchMoreInProgressRef.current) {
1477
- fetchMoreInProgressRef.current = true;
1478
- fetchMoreMessagesImpl().then((result) => {
1479
- if (result.error) {
1480
- console.error('Error fetching more messages:', result.error);
1481
- safeSend({ type: 'ERROR', data: { message: result.error } });
1482
- } else {
1483
- safeSend({ type: 'FETCH_MORE_MESSAGES_SUCCESS', data: result });
1484
- }
1485
- fetchMoreInProgressRef.current = false;
1486
- });
1487
- }
1488
- } else if (state.matches(MainState.SendMessage)) {
1489
- if (!sendInProgressRef.current) {
1490
- sendInProgressRef.current = true;
1491
- sendMessageImpl().then((result) => {
1492
- if (result.error) {
1493
- console.error('Error sending message:', result.error);
1494
- safeSend({ type: 'ERROR', data: { message: result.error } });
1495
- } else {
1496
- safeSend({ type: 'SEND_MESSAGE_SUCCESS', data: result });
1497
- }
1498
- sendInProgressRef.current = false;
1499
- });
1500
- }
1501
- } else if (state.matches(MainState.SendMessageWithFile)) {
1502
- if (!sendFileInProgressRef.current) {
1503
- sendFileInProgressRef.current = true;
1504
- sendMessageWithFileImpl().then((result) => {
1505
- if (result.error) {
1506
- console.error('Error sending message with file:', result.error);
1507
- safeSend({ type: 'ERROR', data: { message: result.error } });
1508
- } else {
1509
- safeSend({ type: 'SEND_MESSAGE_WITH_FILE_SUCCESS', data: result });
1510
- }
1511
- sendFileInProgressRef.current = false;
1512
- });
1513
- }
1514
- } else if (state.matches(MainState.CreateDirectChannel)) {
1515
- if (!createChannelInProgressRef.current) {
1516
- createChannelInProgressRef.current = true;
1517
- createDirectChannelImpl().then((result) => {
1518
- if (result.error) {
1519
- console.error('Error creating direct channel:', result.error);
1520
- safeSend({ type: 'ERROR', data: { message: result.error } });
1521
- } else {
1522
- safeSend({ type: 'CREATE_DIRECT_CHANNEL_SUCCESS', data: result });
1523
- }
1524
- createChannelInProgressRef.current = false;
1525
- });
1526
- }
1527
- }
1528
- }
1529
- }, [
1530
- state?.value,
1531
- fetchMessagesDirectly,
1532
- fetchMoreMessagesImpl,
1533
- sendMessageImpl,
1534
- sendMessageWithFileImpl,
1535
- createDirectChannelImpl,
1536
- safeSend,
1537
- ]);
1538
-
1539
- // Add refs to prevent duplicate operations
1540
- const fetchInProgressRef = useRef(false);
1541
- const fetchMoreInProgressRef = useRef(false);
1542
- const sendInProgressRef = useRef(false);
1543
- const sendFileInProgressRef = useRef(false);
1544
- const createChannelInProgressRef = useRef(false);
1545
-
1546
- // Fix subscription handler to prevent infinite updates
1547
- const renderChatFooter = useCallback(() => {
1548
- return (
1549
- <>
1550
- <ImageViewerModal
1551
- isVisible={isShowImageViewer}
1552
- setVisible={setImageViewer}
1553
- modalContent={modalContent}
1554
- />
1555
- <SubscriptionHandler
1556
- channelId={state?.context?.channelId?.toString()}
1557
- subscribeToNewMessages={() =>
1558
- subscribeToMore({
1559
- document: CHAT_MESSAGE_ADDED,
1560
- variables: {
1561
- channelId: state?.context?.channelId?.toString(),
1562
- },
1563
- updateQuery: (prev, { subscriptionData }: any) => {
1564
- if (!subscriptionData?.data?.chatMessageAdded) return prev;
1565
-
1566
- const newMessage = subscriptionData.data.chatMessageAdded;
1567
- const currentMessages = prev?.messages?.data || [];
1568
-
1569
- // Check if message already exists to prevent duplicates
1570
- if (currentMessages.some((msg) => msg.id === newMessage.id)) {
1571
- return prev; // Skip update if message already exists
1572
- }
1573
-
1574
- // Use a ref to track the last processed message ID to prevent duplicate processing
1575
- if (lastProcessedMessageRef.current === newMessage.id) {
1576
- return prev;
1577
- }
1578
-
1579
- lastProcessedMessageRef.current = newMessage.id;
1580
-
1581
- // Use a batch update strategy to avoid frequent re-renders
1582
- queueMicrotask(() => {
1583
- safeSend({
1584
- type: ConversationActions.SET_CHANNEL_MESSAGES,
1585
- data: {
1586
- messages: uniqBy(
1587
- [...state.context.channelMessages, newMessage],
1588
- ({ id }) => id,
1589
- ),
1590
- totalCount: (prev?.messages?.totalCount || 0) + 1,
1591
- },
1592
- });
1593
- });
1594
-
1595
- return {
1596
- ...prev,
1597
- messages: {
1598
- ...prev?.messages,
1599
- data: [...currentMessages, newMessage],
1600
- totalCount: (prev?.messages?.totalCount || 0) + 1,
1601
- },
1602
- };
1603
- },
1604
- })
1605
- }
1606
- />
1607
- </>
1608
- );
1609
- }, [
1610
- isShowImageViewer,
1611
- modalContent,
1612
- state?.context?.channelId,
1613
- state?.context?.channelMessages,
1614
- subscribeToMore,
1615
- safeSend,
1616
- ]);
1617
-
1618
- // Add ref to track last processed message
1619
- const lastProcessedMessageRef = useRef(null);
1620
-
1621
- // Add optimized listViewProps to reduce re-renders and improve list performance
1622
- const listViewProps = useMemo(
1623
- () => ({
1624
- onEndReached: onEndReached,
1625
- onEndReachedThreshold: 0.5,
1626
- onMomentumScrollBegin: onMomentumScrollBegin,
1627
- removeClippedSubviews: true, // Improve performance by unmounting components when not visible
1628
- initialNumToRender: 10, // Reduce initial render amount
1629
- maxToRenderPerBatch: 7, // Reduce number in each render batch
1630
- windowSize: 7, // Reduce the window size
1631
- updateCellsBatchingPeriod: 50, // Batch cell updates to improve scrolling
1632
- keyExtractor: (item) => item._id, // Add explicit key extractor
1633
- }),
1634
- [onEndReached, onMomentumScrollBegin],
1635
- );
1636
-
1637
- // Add a loader for when more messages are being loaded
1638
- const renderLoadEarlier = useCallback(() => {
1639
- return state?.context?.loadingOldMessages ? (
1640
- <View
1641
- style={{
1642
- padding: 10,
1643
- backgroundColor: 'rgba(255,255,255,0.8)',
1644
- borderRadius: 10,
1645
- marginTop: 10,
1646
- }}
1647
- >
1648
- <Spinner size="small" color="#3b82f6" />
1649
- </View>
1650
- ) : null;
1651
- }, [state?.context?.loadingOldMessages]);
1652
-
1653
- // Add renderInputToolbar function
1654
- const renderInputToolbar = useCallback((props) => {
1655
- return (
1656
- <InputToolbar
1657
- {...props}
1658
- containerStyle={{
1659
- backgroundColor: 'white',
1660
- borderTopWidth: 1,
1661
- borderTopColor: colors.gray[200],
1662
- paddingHorizontal: 4,
1663
- paddingVertical: 0,
1664
- paddingTop: 2,
1665
- marginBottom: 0,
1666
- marginTop: 0,
1667
- }}
1668
- primaryStyle={{
1669
- alignItems: 'center',
1670
- }}
1671
- />
1672
- );
1673
- }, []);
1674
-
1675
- // Return optimized component with performance improvements
1676
- return (
1677
- <View
1678
- style={{
1679
- flex: 1,
1680
- backgroundColor: 'white',
1681
- }}
1682
- >
1683
- {state?.matches && state.matches(BaseState.FetchMessages) && <Spinner color={'#3b82f6'} />}
1684
-
1685
- <GiftedChat
1686
- ref={messageRootListRef}
1687
- wrapInSafeArea={true}
1688
- renderLoading={() => <Spinner color={'#3b82f6'} />}
1689
- messages={messageList}
1690
- listViewProps={{
1691
- ...listViewProps,
1692
- contentContainerStyle: {
1693
- paddingBottom: 10,
1694
- },
1695
- keyboardShouldPersistTaps: 'handled',
1696
- }}
1697
- onSend={handleSend}
1698
- text={safeContextProperty('messageText', ' ') || ' '}
1699
- onInputTextChanged={(text) =>
1700
- safeSend({ type: ConversationActions.SET_MESSAGE_TEXT, data: { messageText: text } })
1701
- }
1702
- renderFooter={() =>
1703
- safeContextProperty('loading') || safeContextProperty('imageLoading') ? (
1704
- <Spinner color={'#3b82f6'} />
1705
- ) : null
1706
- }
1707
- scrollToBottom
1708
- user={{
1709
- _id: auth?.id || '',
1710
- }}
1711
- isTyping={false} // Setting to false to reduce animations
1712
- alwaysShowSend={true} // Always show send button regardless of text content
1713
- renderSend={renderSend}
1714
- renderMessageText={renderMessageText}
1715
- renderInputToolbar={renderInputToolbar}
1716
- minInputToolbarHeight={50}
1717
- renderActions={safeContextProperty('channelId') && renderActions}
1718
- renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
1719
- renderMessage={renderMessage}
1720
- renderChatFooter={renderChatFooter}
1721
- renderLoadEarlier={renderLoadEarlier}
1722
- loadEarlier={state?.context?.totalCount > state?.context?.channelMessages?.length}
1723
- isLoadingEarlier={state?.context?.loadingOldMessages}
1724
- bottomOffset={Platform.OS === 'ios' ? 10 : 0} // Reduce bottom offset
1725
- textInputProps={{
1726
- style: {
1727
- borderWidth: 1,
1728
- borderColor: colors.gray[300],
1729
- backgroundColor: '#f8f8f8',
1730
- borderRadius: 20,
1731
- minHeight: 36,
1732
- maxHeight: 80,
1733
- color: '#000',
1734
- padding: 8,
1735
- paddingHorizontal: 15,
1736
- fontSize: 16,
1737
- flex: 1,
1738
- marginVertical: 2,
1739
- marginBottom: 0,
1740
- },
1741
- multiline: true,
1742
- returnKeyType: 'default',
1743
- enablesReturnKeyAutomatically: true,
1744
- placeholderTextColor: colors.gray[400],
1745
- }}
1746
- minComposerHeight={36}
1747
- maxComposerHeight={100}
1748
- isKeyboardInternallyHandled={true}
1749
- placeholder="Type a message..."
1750
- lightboxProps={{
1751
- underlayColor: 'transparent',
1752
- springConfig: { tension: 90000, friction: 90000 },
1753
- disabled: true,
1754
- }}
1755
- infiniteScroll={false} // Disable automatic loading
1756
- />
1757
- </View>
1758
- );
1759
- };
1760
-
1761
- const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: ISubscriptionHandlerProps) => {
1762
- // Use memo for the channelId dependency to prevent unnecessary subscriptions
1763
- const channelIdRef = useRef(channelId);
1764
-
1765
- useEffect(() => {
1766
- if (channelId && channelId !== channelIdRef.current) {
1767
- channelIdRef.current = channelId;
1768
- console.log('Setting up subscription for channel:', channelId);
1769
- return subscribeToNewMessages();
1770
- }
1771
- }, [channelId, subscribeToNewMessages]);
1772
-
1773
- return null;
1774
- };
1775
-
1776
- // Export with React.memo to prevent unnecessary re-renders
1777
- export const ConversationView = React.memo(ConversationViewComponent, (prevProps, nextProps) => {
1778
- // Only re-render if these critical props change
1779
- return (
1780
- prevProps.channelId === nextProps.channelId &&
1781
- prevProps.role === nextProps.role &&
1782
- prevProps.isShowThreadMessage === nextProps.isShowThreadMessage
1783
- );
1784
- });