@messenger-box/platform-mobile 10.0.3-alpha.20 → 10.0.3-alpha.201
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/lib/components/messages-container-ui/BuildModeView.js +428 -0
- package/lib/components/messages-container-ui/BuildModeView.js.map +1 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.js +55 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.js.map +1 -0
- package/lib/components/messages-container-ui/PlanModeView.js +336 -0
- package/lib/components/messages-container-ui/PlanModeView.js.map +1 -0
- package/lib/compute.js +2 -3
- package/lib/compute.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/module.js.map +1 -1
- package/lib/queries/inboxQueries.js +62 -0
- package/lib/queries/inboxQueries.js.map +1 -0
- package/lib/routes.json +2 -3
- package/lib/screens/inbox/DialogMessages.js +8 -3
- package/lib/screens/inbox/DialogMessages.js.map +1 -1
- package/lib/screens/inbox/DialogThreadMessages.js +6 -11
- package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
- package/lib/screens/inbox/DialogThreads.js +58 -20
- package/lib/screens/inbox/DialogThreads.js.map +1 -1
- package/lib/screens/inbox/Inbox.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/index.js +125 -115
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/DialogItem.js +160 -0
- package/lib/screens/inbox/components/DialogItem.js.map +1 -0
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js +315 -0
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
- package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js +3 -1
- package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js +194 -0
- package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js.map +1 -0
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +149 -36
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js +4 -5
- package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js.map +1 -1
- package/lib/screens/inbox/components/SubscriptionHandler.js +22 -0
- package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
- package/lib/screens/inbox/components/ThreadsViewItem.js +67 -47
- package/lib/screens/inbox/components/ThreadsViewItem.js.map +1 -1
- package/lib/screens/inbox/config/config.js +4 -2
- package/lib/screens/inbox/config/config.js.map +1 -1
- package/lib/screens/inbox/containers/ConversationView.js +1099 -1094
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +132 -534
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +876 -1357
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadsView.js +81 -54
- package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
- package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
- package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
- package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js +108 -0
- package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -0
- package/lib/screens/inbox/workflow/dialog-threads-xstate.js +151 -0
- package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -0
- package/package.json +9 -7
- package/CHANGELOG.md +0 -164
- package/jest.config.js +0 -24
- package/lib/screens/inbox/components/DialogsListItem.js +0 -548
- package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -489
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +0 -175
- package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +0 -1
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +0 -191
- package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +0 -1
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js +0 -380
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +0 -1
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +0 -211
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +0 -1
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +0 -438
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +0 -1
- package/rollup.config.mjs +0 -45
- package/src/components/index.ts +0 -0
- package/src/compute.ts +0 -63
- package/src/index.ts +0 -7
- package/src/module.ts +0 -10
- package/src/navigation/InboxNavigation.tsx +0 -102
- package/src/navigation/index.ts +0 -1
- package/src/screens/inbox/DialogMessages.tsx +0 -21
- package/src/screens/inbox/DialogThreadMessages.tsx +0 -97
- package/src/screens/inbox/DialogThreads.tsx +0 -129
- package/src/screens/inbox/Inbox.tsx +0 -17
- package/src/screens/inbox/components/CachedImage/consts.ts +0 -6
- package/src/screens/inbox/components/CachedImage/index.tsx +0 -223
- package/src/screens/inbox/components/DialogsHeader.tsx +0 -30
- package/src/screens/inbox/components/DialogsListItem.tsx +0 -819
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +0 -679
- package/src/screens/inbox/components/SlackMessageContainer/ImageViewerModal.tsx +0 -113
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +0 -313
- package/src/screens/inbox/components/SlackMessageContainer/SlackMessage.tsx +0 -145
- package/src/screens/inbox/components/SlackMessageContainer/index.ts +0 -3
- package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +0 -301
- package/src/screens/inbox/components/ThreadsViewItem.tsx +0 -321
- package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +0 -145
- package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +0 -159
- package/src/screens/inbox/config/config.ts +0 -15
- package/src/screens/inbox/config/index.ts +0 -1
- package/src/screens/inbox/containers/ConversationView.tsx +0 -1782
- package/src/screens/inbox/containers/Dialogs.tsx +0 -794
- package/src/screens/inbox/containers/SupportServiceDialogs.tsx +0 -119
- package/src/screens/inbox/containers/ThreadConversationView.tsx +0 -2312
- package/src/screens/inbox/containers/ThreadsView.tsx +0 -305
- package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +0 -20
- package/src/screens/inbox/containers/workflow/conversation-xstate.ts +0 -313
- package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +0 -196
- package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +0 -401
- package/src/screens/index.ts +0 -4
- package/tsconfig.json +0 -13
- package/webpack.config.js +0 -58
|
@@ -1,1782 +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
|
-
// 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',
|
|
1078
|
-
}),
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// Use a more efficient approach - pre-filter messages once
|
|
1083
|
-
const filteredMessages = uniqBy(state.context.channelMessages, ({ id }) => id);
|
|
1084
|
-
|
|
1085
|
-
// Skip processing if no filtered messages
|
|
1086
|
-
if (filteredMessages.length === 0) {
|
|
1087
|
-
return [];
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// Transform messages only once and return
|
|
1091
|
-
return orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
1092
|
-
const date = new Date(msg.createdAt);
|
|
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
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
|
|
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
|
-
});
|
|
1195
|
-
}}
|
|
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>
|
|
1240
|
-
</HStack>
|
|
1241
|
-
)}
|
|
1242
|
-
</>
|
|
1243
|
-
</TouchableHighlight>
|
|
1244
|
-
)}
|
|
1245
|
-
</>
|
|
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
|
-
);
|
|
1309
|
-
|
|
1310
|
-
const renderActions = (props) => {
|
|
1311
|
-
return (
|
|
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
|
-
/>
|
|
1339
|
-
);
|
|
1340
|
-
};
|
|
1341
|
-
|
|
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
|
-
>
|
|
1379
|
-
<Image
|
|
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
|
-
}}
|
|
1389
|
-
size={'xs'}
|
|
1390
|
-
/>
|
|
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,
|
|
1406
|
-
}}
|
|
1407
|
-
>
|
|
1408
|
-
<Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
|
|
1409
|
-
</TouchableHighlight>
|
|
1410
|
-
</View>
|
|
1411
|
-
</View>
|
|
1412
|
-
);
|
|
1413
|
-
},
|
|
1414
|
-
[state?.context?.selectedImage, safeSend],
|
|
1415
|
-
);
|
|
1416
|
-
|
|
1417
|
-
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
1418
|
-
setImageObject(obj);
|
|
1419
|
-
setImageViewer(v);
|
|
1420
|
-
};
|
|
1421
|
-
|
|
1422
|
-
const modalContent = React.useMemo(() => {
|
|
1423
|
-
if (!imageObject) return <></>;
|
|
1424
|
-
const { image, _id } = imageObject;
|
|
1425
|
-
return (
|
|
1426
|
-
<CachedImage
|
|
1427
|
-
style={{ width: '100%', height: '100%' }}
|
|
1428
|
-
resizeMode={'cover'}
|
|
1429
|
-
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
1430
|
-
source={{
|
|
1431
|
-
uri: image,
|
|
1432
|
-
expiresIn: 86400,
|
|
1433
|
-
}}
|
|
1434
|
-
alt={'image'}
|
|
1435
|
-
/>
|
|
1436
|
-
);
|
|
1437
|
-
}, [imageObject]);
|
|
1438
|
-
|
|
1439
|
-
const renderMessage = useCallback(
|
|
1440
|
-
(props: any) => {
|
|
1441
|
-
// Use memo to prevent unnecessary re-renders of each message
|
|
1442
|
-
return (
|
|
1443
|
-
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
1444
|
-
);
|
|
1445
|
-
},
|
|
1446
|
-
[isShowImageViewer],
|
|
1447
|
-
);
|
|
1448
|
-
|
|
1449
|
-
let onScroll = false;
|
|
1450
|
-
|
|
1451
|
-
// Optimize onMomentumScrollBegin for better scroll performance
|
|
1452
|
-
const onMomentumScrollBegin = async ({ nativeEvent }: any) => {
|
|
1453
|
-
// Set scroll state
|
|
1454
|
-
onScroll = true;
|
|
1455
|
-
|
|
1456
|
-
// Use the debounced fetch function to prevent excessive calls
|
|
1457
|
-
if (isCloseToTop(nativeEvent)) {
|
|
1458
|
-
onFetchOld();
|
|
1459
|
-
}
|
|
1460
|
-
};
|
|
1461
|
-
|
|
1462
|
-
const onEndReached = () => {
|
|
1463
|
-
console.log('on end reached');
|
|
1464
|
-
if (!onScroll) return;
|
|
1465
|
-
onScroll = false;
|
|
1466
|
-
};
|
|
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
|
|
1688
|
-
return (
|
|
1689
|
-
<View
|
|
1690
|
-
style={{
|
|
1691
|
-
flex: 1,
|
|
1692
|
-
backgroundColor: 'white',
|
|
1693
|
-
}}
|
|
1694
|
-
>
|
|
1695
|
-
{state?.matches && state.matches(BaseState.FetchMessages) && <Spinner color={'#3b82f6'} />}
|
|
1696
|
-
|
|
1697
|
-
<GiftedChat
|
|
1698
|
-
ref={messageRootListRef}
|
|
1699
|
-
wrapInSafeArea={true}
|
|
1700
|
-
renderLoading={() => <Spinner color={'#3b82f6'} />}
|
|
1701
|
-
messages={messageList}
|
|
1702
|
-
listViewProps={{
|
|
1703
|
-
...listViewProps,
|
|
1704
|
-
contentContainerStyle: {
|
|
1705
|
-
paddingBottom: 10,
|
|
1706
|
-
},
|
|
1707
|
-
keyboardShouldPersistTaps: 'handled',
|
|
1708
|
-
}}
|
|
1709
|
-
onSend={handleSend}
|
|
1710
|
-
text={safeContextProperty('messageText', ' ') || ' '}
|
|
1711
|
-
onInputTextChanged={(text) =>
|
|
1712
|
-
safeSend({ type: ConversationActions.SET_MESSAGE_TEXT, data: { messageText: text } })
|
|
1713
|
-
}
|
|
1714
|
-
renderFooter={() =>
|
|
1715
|
-
safeContextProperty('loading') ? (
|
|
1716
|
-
<Spinner color={'#3b82f6'} />
|
|
1717
|
-
) : safeContextProperty('imageLoading') ? (
|
|
1718
|
-
<Spinner color={'#3b82f6'} />
|
|
1719
|
-
) : (
|
|
1720
|
-
''
|
|
1721
|
-
)
|
|
1722
|
-
}
|
|
1723
|
-
scrollToBottom
|
|
1724
|
-
user={{
|
|
1725
|
-
_id: auth?.id || '',
|
|
1726
|
-
}}
|
|
1727
|
-
isTyping={false} // Setting to false to reduce animations
|
|
1728
|
-
alwaysShowSend={true} // Always show send button regardless of text content
|
|
1729
|
-
renderSend={renderSend}
|
|
1730
|
-
renderMessageText={renderMessageText}
|
|
1731
|
-
renderInputToolbar={renderInputToolbar}
|
|
1732
|
-
minInputToolbarHeight={50}
|
|
1733
|
-
renderActions={safeContextProperty('channelId') && renderActions}
|
|
1734
|
-
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
1735
|
-
renderMessage={renderMessage}
|
|
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..."
|
|
1766
|
-
lightboxProps={{
|
|
1767
|
-
underlayColor: 'transparent',
|
|
1768
|
-
springConfig: { tension: 90000, friction: 90000 },
|
|
1769
|
-
disabled: true,
|
|
1770
|
-
}}
|
|
1771
|
-
infiniteScroll={false} // Disable automatic loading
|
|
1772
|
-
/>
|
|
1773
|
-
</View>
|
|
1774
|
-
);
|
|
1775
|
-
};
|
|
1776
|
-
|
|
1777
|
-
const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: ISubscriptionHandlerProps) => {
|
|
1778
|
-
useEffect(() => subscribeToNewMessages(), [channelId]);
|
|
1779
|
-
return <></>;
|
|
1780
|
-
};
|
|
1781
|
-
|
|
1782
|
-
export const ConversationView = React.memo(ConversationViewComponent);
|