@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.
- 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 +9 -11
- 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 -96
- 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 +2 -4
- 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 +1093 -1090
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +130 -577
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +864 -1408
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadsView.js +9 -15
- 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 +1 -1
- package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -1
- package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -1
- package/package.json +10 -8
- package/CHANGELOG.md +0 -172
- 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 -125
- 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/SmartLoader.tsx +0 -61
- package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +0 -301
- package/src/screens/inbox/components/ThreadsViewItem.tsx +0 -233
- 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 -1784
- package/src/screens/inbox/containers/Dialogs.tsx +0 -829
- package/src/screens/inbox/containers/SupportServiceDialogs.tsx +0 -119
- package/src/screens/inbox/containers/ThreadConversationView.tsx +0 -2295
- package/src/screens/inbox/containers/ThreadsView.tsx +0 -224
- 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/inbox/hooks/useSafeDialogThreadsMachine.ts +0 -136
- package/src/screens/inbox/index.ts +0 -37
- package/src/screens/inbox/machines/threadsMachine.ts +0 -147
- package/src/screens/inbox/workflow/dialog-threads-xstate.ts +0 -163
- package/src/screens/index.ts +0 -4
- package/tsconfig.json +0 -13
- package/webpack.config.js +0 -58
|
@@ -1,2295 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Center,
|
|
4
|
-
Avatar,
|
|
5
|
-
AvatarFallbackText,
|
|
6
|
-
AvatarImage,
|
|
7
|
-
Box,
|
|
8
|
-
Button,
|
|
9
|
-
ButtonText,
|
|
10
|
-
VStack,
|
|
11
|
-
HStack,
|
|
12
|
-
Icon,
|
|
13
|
-
Image,
|
|
14
|
-
Spinner,
|
|
15
|
-
Text,
|
|
16
|
-
} from '@admin-layout/gluestack-ui-mobile';
|
|
17
|
-
import { Platform, Linking, SafeAreaView, View, TouchableHighlight, Alert } from 'react-native';
|
|
18
|
-
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
|
19
|
-
import { useSelector } from 'react-redux';
|
|
20
|
-
import { orderBy, startCase, uniqBy } from 'lodash-es';
|
|
21
|
-
import * as ImagePicker from 'expo-image-picker';
|
|
22
|
-
import { encode as atob } from 'base-64';
|
|
23
|
-
import { Ionicons, MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons';
|
|
24
|
-
import { Actions, GiftedChat, IMessage, MessageText, Send, Composer, InputToolbar } from 'react-native-gifted-chat';
|
|
25
|
-
import { IPost, IPostThread, PreDefinedRole, IExpoNotificationData, IFileInfo } from 'common';
|
|
26
|
-
import {
|
|
27
|
-
OnThreadChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
|
|
28
|
-
useCreatePostThreadMutation,
|
|
29
|
-
useOnThreadChatMessageAddedSubscription,
|
|
30
|
-
useSendExpoNotificationOnPostMutation,
|
|
31
|
-
useSendThreadMessageMutation,
|
|
32
|
-
useThreadMessagesLazyQuery,
|
|
33
|
-
useGetPostThreadLazyQuery,
|
|
34
|
-
} from 'common/graphql';
|
|
35
|
-
import { useUploadFilesNative } from '@messenger-box/platform-client';
|
|
36
|
-
import { objectId } from '@messenger-box/core';
|
|
37
|
-
import { format, isToday, isYesterday } from 'date-fns';
|
|
38
|
-
import { userSelector } from '@adminide-stack/user-auth0-client';
|
|
39
|
-
import { config } from '../config';
|
|
40
|
-
import { ImageViewerModal, SlackMessage } from '../components/SlackMessageContainer';
|
|
41
|
-
import CachedImage from '../components/CachedImage';
|
|
42
|
-
import colors from 'tailwindcss/colors';
|
|
43
|
-
import {
|
|
44
|
-
threadConversationXstate,
|
|
45
|
-
Actions as ThreadActions,
|
|
46
|
-
BaseState,
|
|
47
|
-
MainState,
|
|
48
|
-
} from './workflow/thread-conversation-xstate';
|
|
49
|
-
|
|
50
|
-
// Define an extended interface for ImagePickerAsset with url property
|
|
51
|
-
interface ExtendedImagePickerAsset extends ImagePicker.ImagePickerAsset {
|
|
52
|
-
url?: string;
|
|
53
|
-
fileName?: string;
|
|
54
|
-
mimeType?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const {
|
|
58
|
-
MESSAGES_PER_PAGE,
|
|
59
|
-
CALL_TO_ACTION_BOX_BGCOLOR,
|
|
60
|
-
CALL_TO_ACTION_PATH,
|
|
61
|
-
CALL_TO_ACTION_BUTTON_BORDERCOLOR,
|
|
62
|
-
CALL_TO_ACTION_TEXT_COLOR,
|
|
63
|
-
} = config;
|
|
64
|
-
|
|
65
|
-
const createdAtText = (value: string) => {
|
|
66
|
-
if (!value) return '';
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
// Validate the date before processing
|
|
70
|
-
const timestamp = new Date(value).getTime();
|
|
71
|
-
if (isNaN(timestamp)) {
|
|
72
|
-
console.warn(`Invalid date value in createdAtText: ${value}`);
|
|
73
|
-
return 'Unknown date';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let date = new Date(value);
|
|
77
|
-
if (isToday(date)) return 'Today';
|
|
78
|
-
if (isYesterday(date)) return 'Yesterday';
|
|
79
|
-
return format(date, 'MMM dd, yyyy');
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error(`Error processing date in createdAtText: ${value}`, error);
|
|
82
|
-
return 'Unknown date';
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Create a safer version of useMachine to handle potential errors
|
|
87
|
-
function useSafeMachine(machine) {
|
|
88
|
-
// Define the state type
|
|
89
|
-
interface SafeStateType {
|
|
90
|
-
context: {
|
|
91
|
-
channelId: any;
|
|
92
|
-
postParentId: any;
|
|
93
|
-
role: any;
|
|
94
|
-
threadMessages: any[];
|
|
95
|
-
totalCount: number;
|
|
96
|
-
skip: number;
|
|
97
|
-
loading: boolean;
|
|
98
|
-
loadingOldMessages: boolean;
|
|
99
|
-
error: any;
|
|
100
|
-
selectedImage: string;
|
|
101
|
-
files: any[];
|
|
102
|
-
images: any[];
|
|
103
|
-
messageText: string;
|
|
104
|
-
imageLoading: boolean;
|
|
105
|
-
postThread: any;
|
|
106
|
-
threadPost: any[];
|
|
107
|
-
isScrollToBottom: boolean;
|
|
108
|
-
};
|
|
109
|
-
value: string;
|
|
110
|
-
matches?: (stateValue: string) => boolean;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Initialize with default state
|
|
114
|
-
const [state, setState] = useState<SafeStateType>({
|
|
115
|
-
context: {
|
|
116
|
-
channelId: null,
|
|
117
|
-
postParentId: null,
|
|
118
|
-
role: null,
|
|
119
|
-
threadMessages: [],
|
|
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
|
-
postThread: null,
|
|
131
|
-
threadPost: [],
|
|
132
|
-
isScrollToBottom: false,
|
|
133
|
-
},
|
|
134
|
-
value: 'idle',
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Create a safe send function
|
|
138
|
-
const send = useCallback((event) => {
|
|
139
|
-
try {
|
|
140
|
-
// Log event for debugging
|
|
141
|
-
console.log('Thread Event received:', event.type);
|
|
142
|
-
|
|
143
|
-
// Handle specific events manually
|
|
144
|
-
if (event.type === ThreadActions.INITIAL_CONTEXT) {
|
|
145
|
-
setState((prev) => ({
|
|
146
|
-
...prev,
|
|
147
|
-
context: {
|
|
148
|
-
...prev.context,
|
|
149
|
-
channelId: event.data?.channelId || null,
|
|
150
|
-
postParentId: event.data?.postParentId || null,
|
|
151
|
-
role: event.data?.role || null,
|
|
152
|
-
},
|
|
153
|
-
value: BaseState.FetchThreadMessages,
|
|
154
|
-
}));
|
|
155
|
-
} else if (event.type === ThreadActions.SET_THREAD_MESSAGES) {
|
|
156
|
-
setState((prev) => ({
|
|
157
|
-
...prev,
|
|
158
|
-
context: {
|
|
159
|
-
...prev.context,
|
|
160
|
-
threadMessages: event.data?.messages || [],
|
|
161
|
-
totalCount: event.data?.totalCount || 0,
|
|
162
|
-
loading: false,
|
|
163
|
-
loadingOldMessages: false,
|
|
164
|
-
threadPost: event.data?.threadPost || [],
|
|
165
|
-
postThread: event.data?.postThread || null,
|
|
166
|
-
},
|
|
167
|
-
value: 'active',
|
|
168
|
-
}));
|
|
169
|
-
} else if (event.type === ThreadActions.CLEAR_MESSAGES) {
|
|
170
|
-
setState((prev) => ({
|
|
171
|
-
...prev,
|
|
172
|
-
context: {
|
|
173
|
-
...prev.context,
|
|
174
|
-
threadMessages: [],
|
|
175
|
-
totalCount: 0,
|
|
176
|
-
},
|
|
177
|
-
}));
|
|
178
|
-
} else if (event.type === ThreadActions.SET_MESSAGE_TEXT) {
|
|
179
|
-
setState((prev) => ({
|
|
180
|
-
...prev,
|
|
181
|
-
context: {
|
|
182
|
-
...prev.context,
|
|
183
|
-
messageText: event.data?.messageText || '',
|
|
184
|
-
},
|
|
185
|
-
}));
|
|
186
|
-
} else if (event.type === ThreadActions.FETCH_MORE_MESSAGES) {
|
|
187
|
-
setState((prev) => ({
|
|
188
|
-
...prev,
|
|
189
|
-
context: {
|
|
190
|
-
...prev.context,
|
|
191
|
-
loadingOldMessages: true,
|
|
192
|
-
},
|
|
193
|
-
value: MainState.FetchMoreMessages,
|
|
194
|
-
}));
|
|
195
|
-
} else if (event.type === ThreadActions.SET_IMAGE) {
|
|
196
|
-
setState((prev) => ({
|
|
197
|
-
...prev,
|
|
198
|
-
context: {
|
|
199
|
-
...prev.context,
|
|
200
|
-
selectedImage: event.data?.image || '',
|
|
201
|
-
images: event.data?.images || [],
|
|
202
|
-
files: event.data?.files || [],
|
|
203
|
-
imageLoading: false,
|
|
204
|
-
},
|
|
205
|
-
}));
|
|
206
|
-
} else if (event.type === ThreadActions.CLEAR_IMAGE) {
|
|
207
|
-
setState((prev) => ({
|
|
208
|
-
...prev,
|
|
209
|
-
context: {
|
|
210
|
-
...prev.context,
|
|
211
|
-
selectedImage: '',
|
|
212
|
-
images: [],
|
|
213
|
-
files: [],
|
|
214
|
-
},
|
|
215
|
-
}));
|
|
216
|
-
} else if (event.type === ThreadActions.START_LOADING) {
|
|
217
|
-
setState((prev) => ({
|
|
218
|
-
...prev,
|
|
219
|
-
context: {
|
|
220
|
-
...prev.context,
|
|
221
|
-
loading: true,
|
|
222
|
-
},
|
|
223
|
-
}));
|
|
224
|
-
} else if (event.type === ThreadActions.STOP_LOADING) {
|
|
225
|
-
setState((prev) => ({
|
|
226
|
-
...prev,
|
|
227
|
-
context: {
|
|
228
|
-
...prev.context,
|
|
229
|
-
loading: false,
|
|
230
|
-
loadingOldMessages:
|
|
231
|
-
event.data?.loadingOldMessages === false ? false : prev.context.loadingOldMessages,
|
|
232
|
-
},
|
|
233
|
-
}));
|
|
234
|
-
} else if (event.type === ThreadActions.SEND_THREAD_MESSAGE) {
|
|
235
|
-
console.log('Sending message event with text:', event.data?.messageText);
|
|
236
|
-
setState((prev) => ({
|
|
237
|
-
...prev,
|
|
238
|
-
context: {
|
|
239
|
-
...prev.context,
|
|
240
|
-
loading: true,
|
|
241
|
-
// Keep the message text until we're done sending
|
|
242
|
-
messageText: event.data?.messageText || prev.context.messageText,
|
|
243
|
-
},
|
|
244
|
-
value: MainState.SendThreadMessage,
|
|
245
|
-
}));
|
|
246
|
-
} else if (event.type === ThreadActions.SEND_THREAD_MESSAGE_WITH_FILE) {
|
|
247
|
-
console.log('Sending message with file event, text:', event.data?.messageText);
|
|
248
|
-
setState((prev) => ({
|
|
249
|
-
...prev,
|
|
250
|
-
context: {
|
|
251
|
-
...prev.context,
|
|
252
|
-
loading: true,
|
|
253
|
-
// Keep the message text until we're done sending
|
|
254
|
-
messageText: event.data?.messageText || prev.context.messageText,
|
|
255
|
-
},
|
|
256
|
-
value: MainState.SendThreadMessageWithFile,
|
|
257
|
-
}));
|
|
258
|
-
} else if (
|
|
259
|
-
event.type === 'SEND_THREAD_MESSAGE_SUCCESS' ||
|
|
260
|
-
event.type === 'SEND_THREAD_MESSAGE_WITH_FILE_SUCCESS'
|
|
261
|
-
) {
|
|
262
|
-
console.log('Handling send success event:', event.type, 'with message:', event.data?.message?.id);
|
|
263
|
-
setState((prev) => {
|
|
264
|
-
// Make sure we have the message data
|
|
265
|
-
if (!event.data?.message) {
|
|
266
|
-
console.warn('Send success event without message data');
|
|
267
|
-
return {
|
|
268
|
-
...prev,
|
|
269
|
-
context: {
|
|
270
|
-
...prev.context,
|
|
271
|
-
loading: false,
|
|
272
|
-
messageText: '', // Clear input
|
|
273
|
-
images: [],
|
|
274
|
-
selectedImage: '',
|
|
275
|
-
files: [],
|
|
276
|
-
},
|
|
277
|
-
value: 'active',
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Add the new message to our threadMessages
|
|
282
|
-
const newMessage = event.data.message;
|
|
283
|
-
const updatedMessages = [newMessage, ...prev.context.threadMessages];
|
|
284
|
-
|
|
285
|
-
console.log('Updated thread messages list after send, now has', updatedMessages.length, 'messages');
|
|
286
|
-
|
|
287
|
-
return {
|
|
288
|
-
...prev,
|
|
289
|
-
context: {
|
|
290
|
-
...prev.context,
|
|
291
|
-
loading: false,
|
|
292
|
-
messageText: '', // Always clear input text after sending
|
|
293
|
-
images: [],
|
|
294
|
-
selectedImage: '',
|
|
295
|
-
files: [],
|
|
296
|
-
threadMessages: updatedMessages,
|
|
297
|
-
totalCount: prev.context.totalCount + 1,
|
|
298
|
-
},
|
|
299
|
-
value: 'active',
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
} else if (event.type === 'FETCH_MORE_MESSAGES_SUCCESS') {
|
|
303
|
-
setState((prev) => {
|
|
304
|
-
const newMessages = event.data?.messages || [];
|
|
305
|
-
const apiTotalCount = event.data?.totalCount;
|
|
306
|
-
console.log(
|
|
307
|
-
`Merging ${newMessages.length} older messages with ${prev.context.threadMessages.length} existing messages`,
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
if (newMessages.length > 0) {
|
|
311
|
-
try {
|
|
312
|
-
console.log(
|
|
313
|
-
'First new message date:',
|
|
314
|
-
!isNaN(new Date(newMessages[0].createdAt).getTime())
|
|
315
|
-
? new Date(newMessages[0].createdAt).toISOString()
|
|
316
|
-
: 'Invalid date',
|
|
317
|
-
);
|
|
318
|
-
console.log(
|
|
319
|
-
'Last new message date:',
|
|
320
|
-
!isNaN(new Date(newMessages[newMessages.length - 1].createdAt).getTime())
|
|
321
|
-
? new Date(newMessages[newMessages.length - 1].createdAt).toISOString()
|
|
322
|
-
: 'Invalid date',
|
|
323
|
-
);
|
|
324
|
-
} catch (error) {
|
|
325
|
-
console.error('Error logging new message dates:', error);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (prev.context.threadMessages.length > 0) {
|
|
330
|
-
try {
|
|
331
|
-
console.log(
|
|
332
|
-
'First existing message date:',
|
|
333
|
-
!isNaN(new Date(prev.context.threadMessages[0].createdAt).getTime())
|
|
334
|
-
? new Date(prev.context.threadMessages[0].createdAt).toISOString()
|
|
335
|
-
: 'Invalid date',
|
|
336
|
-
);
|
|
337
|
-
console.log(
|
|
338
|
-
'Last existing message date:',
|
|
339
|
-
!isNaN(
|
|
340
|
-
new Date(
|
|
341
|
-
prev.context.threadMessages[prev.context.threadMessages.length - 1].createdAt,
|
|
342
|
-
).getTime(),
|
|
343
|
-
)
|
|
344
|
-
? new Date(
|
|
345
|
-
prev.context.threadMessages[prev.context.threadMessages.length - 1].createdAt,
|
|
346
|
-
).toISOString()
|
|
347
|
-
: 'Invalid date',
|
|
348
|
-
);
|
|
349
|
-
} catch (error) {
|
|
350
|
-
console.error('Error logging existing message dates:', error);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// If no new messages returned but total count says there should be more
|
|
355
|
-
if (newMessages.length === 0 && prev.context.totalCount > prev.context.threadMessages.length) {
|
|
356
|
-
console.log('No new messages found despite totalCount indicating more should exist');
|
|
357
|
-
|
|
358
|
-
// Adjust totalCount to match reality
|
|
359
|
-
return {
|
|
360
|
-
...prev,
|
|
361
|
-
context: {
|
|
362
|
-
...prev.context,
|
|
363
|
-
loadingOldMessages: false,
|
|
364
|
-
totalCount: prev.context.threadMessages.length,
|
|
365
|
-
},
|
|
366
|
-
value: 'active',
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Make sure we don't add duplicate messages
|
|
371
|
-
const combinedMessages = uniqBy([...prev.context.threadMessages, ...newMessages], 'id');
|
|
372
|
-
|
|
373
|
-
// GiftedChat expects messages sorted by date (newest first)
|
|
374
|
-
const sortedMessages = orderBy(
|
|
375
|
-
combinedMessages,
|
|
376
|
-
[
|
|
377
|
-
(msg) => {
|
|
378
|
-
try {
|
|
379
|
-
// Safely access and convert date
|
|
380
|
-
return !isNaN(new Date(msg.createdAt).getTime())
|
|
381
|
-
? new Date(msg.createdAt).getTime()
|
|
382
|
-
: 0; // Default to oldest if invalid
|
|
383
|
-
} catch (error) {
|
|
384
|
-
console.error(`Error sorting message by date: ${msg.id}`, error);
|
|
385
|
-
return 0; // Default to oldest if error
|
|
386
|
-
}
|
|
387
|
-
},
|
|
388
|
-
],
|
|
389
|
-
['desc'],
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
// Use the total count from the API response if available, otherwise keep the current count
|
|
393
|
-
const newTotalCount =
|
|
394
|
-
typeof apiTotalCount === 'number'
|
|
395
|
-
? apiTotalCount
|
|
396
|
-
: Math.max(sortedMessages.length, prev.context.totalCount);
|
|
397
|
-
|
|
398
|
-
console.log(
|
|
399
|
-
`Total messages after merge and sort: ${sortedMessages.length}, totalCount: ${newTotalCount}`,
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
return {
|
|
403
|
-
...prev,
|
|
404
|
-
context: {
|
|
405
|
-
...prev.context,
|
|
406
|
-
loadingOldMessages: false,
|
|
407
|
-
threadMessages: sortedMessages,
|
|
408
|
-
totalCount: newTotalCount,
|
|
409
|
-
},
|
|
410
|
-
value: 'active',
|
|
411
|
-
};
|
|
412
|
-
});
|
|
413
|
-
} else if (event.type === 'ERROR') {
|
|
414
|
-
setState((prev) => ({
|
|
415
|
-
...prev,
|
|
416
|
-
context: {
|
|
417
|
-
...prev.context,
|
|
418
|
-
loading: false,
|
|
419
|
-
loadingOldMessages: false,
|
|
420
|
-
error: event.data?.message || 'Unknown error',
|
|
421
|
-
},
|
|
422
|
-
value: 'error',
|
|
423
|
-
}));
|
|
424
|
-
}
|
|
425
|
-
} catch (error) {
|
|
426
|
-
console.error('Error in thread conversation send function:', error);
|
|
427
|
-
}
|
|
428
|
-
}, []);
|
|
429
|
-
|
|
430
|
-
// Add a custom matches function to the state
|
|
431
|
-
const stateWithMatches = useMemo(() => {
|
|
432
|
-
return {
|
|
433
|
-
...state,
|
|
434
|
-
matches: (checkState) => {
|
|
435
|
-
return state.value === checkState;
|
|
436
|
-
},
|
|
437
|
-
};
|
|
438
|
-
}, [state]);
|
|
439
|
-
|
|
440
|
-
// Return as a tuple to match useMachine API
|
|
441
|
-
return [stateWithMatches, send] as const;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
interface IMessageProps extends IMessage {
|
|
445
|
-
type: string;
|
|
446
|
-
propsConfiguration?: any;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
export interface AlertMessageAttachmentsInterface {
|
|
450
|
-
title: string;
|
|
451
|
-
isTitleHtml: boolean;
|
|
452
|
-
icon: string;
|
|
453
|
-
callToAction: {
|
|
454
|
-
title: string;
|
|
455
|
-
link: string;
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
interface IThreadSubscriptionHandlerProps {
|
|
460
|
-
subscribeToNewMessages: () => any;
|
|
461
|
-
channelId: string;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParentIdThread, role }: any): JSX.Element => {
|
|
465
|
-
const { params } = useRoute<any>();
|
|
466
|
-
const [channelToTop, setChannelToTop] = useState(0);
|
|
467
|
-
|
|
468
|
-
// Create a ref to track if component is mounted
|
|
469
|
-
const isMountedRef = useRef(true);
|
|
470
|
-
|
|
471
|
-
// Use our safer custom implementation instead of the problematic useMachine
|
|
472
|
-
const [state, send] = useSafeMachine(threadConversationXstate);
|
|
473
|
-
|
|
474
|
-
// Define safe functions first to avoid "used before declaration" errors
|
|
475
|
-
const safeContext = useCallback(() => {
|
|
476
|
-
try {
|
|
477
|
-
return state?.context || {};
|
|
478
|
-
} catch (error) {
|
|
479
|
-
console.error('Error accessing state.context:', error);
|
|
480
|
-
return {};
|
|
481
|
-
}
|
|
482
|
-
}, [state]);
|
|
483
|
-
|
|
484
|
-
const safeContextProperty = useCallback(
|
|
485
|
-
(property, defaultValue = null) => {
|
|
486
|
-
try {
|
|
487
|
-
return state?.context?.[property] ?? defaultValue;
|
|
488
|
-
} catch (error) {
|
|
489
|
-
console.error(`Error accessing state.context.${property}:`, error);
|
|
490
|
-
return defaultValue;
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
[state],
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
const safeMatches = useCallback(
|
|
497
|
-
(stateValue) => {
|
|
498
|
-
try {
|
|
499
|
-
return state?.matches?.(stateValue) || false;
|
|
500
|
-
} catch (error) {
|
|
501
|
-
console.error(`Error calling state.matches with ${stateValue}:`, error);
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
[state],
|
|
506
|
-
);
|
|
507
|
-
|
|
508
|
-
const safeSend = useCallback(
|
|
509
|
-
(event) => {
|
|
510
|
-
try {
|
|
511
|
-
send(event);
|
|
512
|
-
} catch (error) {
|
|
513
|
-
console.error('Error sending event to state machine:', error, event);
|
|
514
|
-
}
|
|
515
|
-
},
|
|
516
|
-
[send],
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
// Use a ref to track the current machine snapshot for safer access
|
|
520
|
-
const stateRef = useRef(state);
|
|
521
|
-
|
|
522
|
-
// Keep the ref updated with the latest snapshot
|
|
523
|
-
useEffect(() => {
|
|
524
|
-
stateRef.current = state;
|
|
525
|
-
}, [state]);
|
|
526
|
-
|
|
527
|
-
const auth: any = useSelector(userSelector);
|
|
528
|
-
const [selectedImage, setImage] = useState<string>('');
|
|
529
|
-
const navigation = useNavigation<any>();
|
|
530
|
-
const [files, setFiles] = useState<File[]>([]);
|
|
531
|
-
const [images, setImages] = useState<ImagePicker.ImagePickerAsset[]>([]);
|
|
532
|
-
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
533
|
-
const [imageObject, setImageObject] = useState<any>({});
|
|
534
|
-
const [parentId, setParentId] = useState<any>(postParentId);
|
|
535
|
-
const [expoTokens, setExpoTokens] = useState<any[]>([]);
|
|
536
|
-
const threadMessageListRef = useRef<any>(null);
|
|
537
|
-
|
|
538
|
-
// const [sendThreadMessage] = useSendThreadMessageMutation();
|
|
539
|
-
const [sendThreadMessage] = useCreatePostThreadMutation();
|
|
540
|
-
const [sendExpoNotificationOnPostMutation] = useSendExpoNotificationOnPostMutation();
|
|
541
|
-
const { startUpload } = useUploadFilesNative();
|
|
542
|
-
|
|
543
|
-
const [
|
|
544
|
-
getThreadMessages,
|
|
545
|
-
{ data, loading: threadLoading, fetchMore: fetchMoreMessages, refetch: refetchThreadMessages, subscribeToMore },
|
|
546
|
-
] = useGetPostThreadLazyQuery({ fetchPolicy: 'cache-and-network' });
|
|
547
|
-
|
|
548
|
-
// Add a function to force refresh all messages
|
|
549
|
-
const forceRefreshMessages = useCallback(() => {
|
|
550
|
-
console.log('Force refreshing all messages');
|
|
551
|
-
|
|
552
|
-
// Clear the current messages first
|
|
553
|
-
safeSend({
|
|
554
|
-
type: ThreadActions.CLEAR_MESSAGES,
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// Then trigger a refetch
|
|
558
|
-
if (channelId && parentId) {
|
|
559
|
-
safeSend({
|
|
560
|
-
type: ThreadActions.START_LOADING,
|
|
561
|
-
data: { loading: true },
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
getThreadMessages({
|
|
565
|
-
variables: {
|
|
566
|
-
channelId: channelId?.toString(),
|
|
567
|
-
role: role?.toString(),
|
|
568
|
-
postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
|
|
569
|
-
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
570
|
-
limit: 50, // Use larger limit that proved to work
|
|
571
|
-
},
|
|
572
|
-
})
|
|
573
|
-
.then(({ data }) => {
|
|
574
|
-
if (data?.getPostThread) {
|
|
575
|
-
const threads: any = data.getPostThread;
|
|
576
|
-
const threadPost = threads?.post ?? [];
|
|
577
|
-
const threadReplies = threads?.replies ?? [];
|
|
578
|
-
const messageTotalCount = threads?.replyCount ?? 0;
|
|
579
|
-
const messages = [...threadReplies];
|
|
580
|
-
|
|
581
|
-
console.log(
|
|
582
|
-
`Force refresh complete. Got ${messages.length} messages of ${messageTotalCount} total`,
|
|
583
|
-
);
|
|
584
|
-
|
|
585
|
-
safeSend({
|
|
586
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
587
|
-
data: {
|
|
588
|
-
messages,
|
|
589
|
-
totalCount: messageTotalCount,
|
|
590
|
-
threadPost: threadPost,
|
|
591
|
-
postThread: threads,
|
|
592
|
-
},
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
})
|
|
596
|
-
.catch((error) => {
|
|
597
|
-
console.error('Error during force refresh:', error);
|
|
598
|
-
safeSend({
|
|
599
|
-
type: 'ERROR',
|
|
600
|
-
data: { message: error.message },
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
}, [channelId, parentId, getThreadMessages, safeSend]);
|
|
605
|
-
|
|
606
|
-
useFocusEffect(
|
|
607
|
-
React.useCallback(() => {
|
|
608
|
-
// navigation?.setOptions({ title: params?.title ?? 'Thread' });
|
|
609
|
-
|
|
610
|
-
navigation.setOptions({
|
|
611
|
-
title: params?.title ?? 'Thread',
|
|
612
|
-
headerLeft: (props: any) => (
|
|
613
|
-
<Button className="bg-transparent active:bg-gray-200 " onPress={() => navigation.goBack()}>
|
|
614
|
-
<MaterialIcons size={20} name="arrow-back-ios" color={'black'} />
|
|
615
|
-
</Button>
|
|
616
|
-
),
|
|
617
|
-
headerRight: () => (
|
|
618
|
-
<Button className="bg-transparent active:bg-gray-200 mr-2" onPress={forceRefreshMessages}>
|
|
619
|
-
<MaterialIcons size={20} name="refresh" color={'black'} />
|
|
620
|
-
</Button>
|
|
621
|
-
),
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
// Set initial context when focused
|
|
625
|
-
if (channelId && postParentId) {
|
|
626
|
-
safeSend({
|
|
627
|
-
type: ThreadActions.INITIAL_CONTEXT,
|
|
628
|
-
data: {
|
|
629
|
-
channelId,
|
|
630
|
-
postParentId,
|
|
631
|
-
role,
|
|
632
|
-
},
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
setParentId(postParentId);
|
|
637
|
-
|
|
638
|
-
return () => {
|
|
639
|
-
safeSend({ type: ThreadActions.CLEAR_MESSAGES });
|
|
640
|
-
};
|
|
641
|
-
}, [postParentId, forceRefreshMessages]),
|
|
642
|
-
);
|
|
643
|
-
|
|
644
|
-
// Effect for when in FetchThreadMessages state
|
|
645
|
-
useEffect(() => {
|
|
646
|
-
if (safeMatches(BaseState.FetchThreadMessages)) {
|
|
647
|
-
fetchThreadMessages();
|
|
648
|
-
}
|
|
649
|
-
}, [state.value]);
|
|
650
|
-
|
|
651
|
-
// Effect for when in FetchMoreMessages state
|
|
652
|
-
useEffect(() => {
|
|
653
|
-
if (safeMatches(MainState.FetchMoreMessages)) {
|
|
654
|
-
onFetchOld();
|
|
655
|
-
}
|
|
656
|
-
}, [state.value]);
|
|
657
|
-
|
|
658
|
-
// Effect for when in SendThreadMessage state
|
|
659
|
-
useEffect(() => {
|
|
660
|
-
if (safeMatches(MainState.SendThreadMessage)) {
|
|
661
|
-
const messageText = safeContextProperty('messageText', '');
|
|
662
|
-
console.log('Sending message from state transition, text:', messageText);
|
|
663
|
-
sendThreadMessageHandler(messageText);
|
|
664
|
-
}
|
|
665
|
-
}, [state.value]);
|
|
666
|
-
|
|
667
|
-
// Effect for when in SendThreadMessageWithFile state
|
|
668
|
-
useEffect(() => {
|
|
669
|
-
if (safeMatches(MainState.SendThreadMessageWithFile)) {
|
|
670
|
-
const messageText = safeContextProperty('messageText', '');
|
|
671
|
-
const images = safeContextProperty('images', []);
|
|
672
|
-
sendThreadMessageWithFileHandler(messageText, images);
|
|
673
|
-
}
|
|
674
|
-
}, [state.value]);
|
|
675
|
-
|
|
676
|
-
// Fetch thread messages function
|
|
677
|
-
const fetchThreadMessages = useCallback(() => {
|
|
678
|
-
if (channelId && parentId) {
|
|
679
|
-
if (__DEV__) console.log('Initial fetch of thread messages using larger limit (50)');
|
|
680
|
-
|
|
681
|
-
// Set loading state
|
|
682
|
-
safeSend({
|
|
683
|
-
type: ThreadActions.START_LOADING,
|
|
684
|
-
data: { loading: true },
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
getThreadMessages({
|
|
688
|
-
variables: {
|
|
689
|
-
channelId: channelId?.toString(),
|
|
690
|
-
role: role?.toString(),
|
|
691
|
-
postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
|
|
692
|
-
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
693
|
-
limit: 50, // Use larger limit that proved to work
|
|
694
|
-
},
|
|
695
|
-
})
|
|
696
|
-
.then(({ data }) => {
|
|
697
|
-
if (data?.getPostThread) {
|
|
698
|
-
const threads: any = data.getPostThread;
|
|
699
|
-
const threadPost = threads?.post ?? [];
|
|
700
|
-
const threadReplies = threads?.replies ?? [];
|
|
701
|
-
const messageTotalCount = threads?.replyCount ?? 0;
|
|
702
|
-
const messages = [...threadReplies];
|
|
703
|
-
|
|
704
|
-
if (__DEV__)
|
|
705
|
-
console.log(
|
|
706
|
-
`Initial fetch complete. Got ${messages.length} messages of ${messageTotalCount} total`,
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
// Use batch updates to reduce render cycles
|
|
710
|
-
safeSend({
|
|
711
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
712
|
-
data: {
|
|
713
|
-
messages,
|
|
714
|
-
totalCount: messageTotalCount,
|
|
715
|
-
threadPost: threadPost,
|
|
716
|
-
postThread: threads,
|
|
717
|
-
},
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
})
|
|
721
|
-
.catch((error) => {
|
|
722
|
-
safeSend({
|
|
723
|
-
type: 'ERROR',
|
|
724
|
-
data: { message: error.message },
|
|
725
|
-
});
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}, [channelId, parentId, role, getThreadMessages, safeSend]);
|
|
729
|
-
|
|
730
|
-
React.useEffect(() => {
|
|
731
|
-
if (data?.getPostThread) {
|
|
732
|
-
const threads: any = data.getPostThread;
|
|
733
|
-
const threadPost = threads?.post ?? [];
|
|
734
|
-
const threadReplies = threads?.replies ?? [];
|
|
735
|
-
const messeageTotalCount = threads?.replyCount ?? 0;
|
|
736
|
-
const messages = [...threadReplies];
|
|
737
|
-
|
|
738
|
-
safeSend({
|
|
739
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
740
|
-
data: {
|
|
741
|
-
messages,
|
|
742
|
-
totalCount: messeageTotalCount,
|
|
743
|
-
threadPost: threadPost,
|
|
744
|
-
postThread: threads,
|
|
745
|
-
},
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
}, [data]);
|
|
749
|
-
|
|
750
|
-
React.useEffect(() => {
|
|
751
|
-
if (safeContextProperty('selectedImage')) {
|
|
752
|
-
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
753
|
-
}
|
|
754
|
-
}, [safeContextProperty('selectedImage')]);
|
|
755
|
-
|
|
756
|
-
// Add a safety timeout to clear loading state if it gets stuck
|
|
757
|
-
React.useEffect(() => {
|
|
758
|
-
const isLoading = safeContextProperty('loadingOldMessages', false);
|
|
759
|
-
|
|
760
|
-
if (isLoading) {
|
|
761
|
-
console.log('Message loading timeout safety started');
|
|
762
|
-
const timeoutId = setTimeout(() => {
|
|
763
|
-
// Check if we're still loading after 10 seconds
|
|
764
|
-
if (safeContextProperty('loadingOldMessages', false)) {
|
|
765
|
-
console.log('Message loading timed out - resetting state');
|
|
766
|
-
safeSend({
|
|
767
|
-
type: ThreadActions.STOP_LOADING,
|
|
768
|
-
data: { loadingOldMessages: false },
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
}, 10000); // 10 second timeout
|
|
772
|
-
|
|
773
|
-
return () => clearTimeout(timeoutId);
|
|
774
|
-
}
|
|
775
|
-
}, [safeContextProperty('loadingOldMessages')]);
|
|
776
|
-
|
|
777
|
-
// Add a safety counter to detect and fix incorrect message counts automatically
|
|
778
|
-
const failedLoadAttemptsRef = useRef(0);
|
|
779
|
-
|
|
780
|
-
// Track failed attempts to load more messages
|
|
781
|
-
const registerLoadAttemptResult = useCallback((success: boolean) => {
|
|
782
|
-
if (!success) {
|
|
783
|
-
failedLoadAttemptsRef.current += 1;
|
|
784
|
-
console.log(`Failed load attempt registered, count: ${failedLoadAttemptsRef.current}`);
|
|
785
|
-
} else {
|
|
786
|
-
// Reset counter on successful load
|
|
787
|
-
failedLoadAttemptsRef.current = 0;
|
|
788
|
-
}
|
|
789
|
-
}, []);
|
|
790
|
-
|
|
791
|
-
// Watch for failed load attempts and auto-fix the count after 3 consecutive failures
|
|
792
|
-
React.useEffect(() => {
|
|
793
|
-
const isLoading = safeContextProperty('loadingOldMessages', false);
|
|
794
|
-
const totalCount = safeContextProperty('totalCount', 0);
|
|
795
|
-
const messagesCount = safeContextProperty('threadMessages', []).length;
|
|
796
|
-
|
|
797
|
-
// If we're not loading and there's a discrepancy in message counts
|
|
798
|
-
if (!isLoading && totalCount > messagesCount) {
|
|
799
|
-
// If we've had 3 consecutive failed attempts, fix the count
|
|
800
|
-
if (failedLoadAttemptsRef.current >= 2) {
|
|
801
|
-
console.log(
|
|
802
|
-
`Auto-fixing incorrect message count after ${
|
|
803
|
-
failedLoadAttemptsRef.current + 1
|
|
804
|
-
} failed load attempts`,
|
|
805
|
-
);
|
|
806
|
-
console.log(`Adjusting totalCount from ${totalCount} to ${messagesCount}`);
|
|
807
|
-
|
|
808
|
-
safeSend({
|
|
809
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
810
|
-
data: {
|
|
811
|
-
messages: safeContextProperty('threadMessages', []),
|
|
812
|
-
totalCount: messagesCount,
|
|
813
|
-
threadPost: safeContextProperty('threadPost', []),
|
|
814
|
-
postThread: safeContextProperty('postThread', null),
|
|
815
|
-
},
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
// Reset the counter
|
|
819
|
-
failedLoadAttemptsRef.current = 0;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}, [safeContextProperty('loadingOldMessages'), safeContextProperty('threadMessages')]);
|
|
823
|
-
|
|
824
|
-
const scrollToBottom = React.useCallback(() => {
|
|
825
|
-
if (threadMessageListRef?.current) {
|
|
826
|
-
threadMessageListRef.current.scrollToBottom();
|
|
827
|
-
}
|
|
828
|
-
}, [threadMessageListRef]);
|
|
829
|
-
|
|
830
|
-
const isCloseToTop = useCallback(({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
831
|
-
// We consider being "close to top" when scrolled to top 15% of visible area
|
|
832
|
-
const visibleHeight = layoutMeasurement.height;
|
|
833
|
-
const topThreshold = Math.min(80, visibleHeight * 0.15);
|
|
834
|
-
return contentOffset.y <= topThreshold;
|
|
835
|
-
}, []);
|
|
836
|
-
|
|
837
|
-
const handleScrollToTop = useCallback(
|
|
838
|
-
({ nativeEvent }: any) => {
|
|
839
|
-
// Check if we're near the top of the list
|
|
840
|
-
if (isCloseToTop(nativeEvent)) {
|
|
841
|
-
const isLoading = safeContextProperty('loadingOldMessages', false);
|
|
842
|
-
const totalCount = safeContextProperty('totalCount', 0);
|
|
843
|
-
const currentCount = safeContextProperty('threadMessages', []).length;
|
|
844
|
-
const hasMoreMessages = totalCount > currentCount;
|
|
845
|
-
|
|
846
|
-
if (__DEV__)
|
|
847
|
-
console.log(
|
|
848
|
-
`Scroll near top - Loading state: ${isLoading}, Messages: ${currentCount}/${totalCount}, Has more: ${hasMoreMessages}`,
|
|
849
|
-
);
|
|
850
|
-
|
|
851
|
-
if (!isLoading && hasMoreMessages) {
|
|
852
|
-
if (__DEV__) console.log('Near top of list - loading older messages');
|
|
853
|
-
safeSend({ type: ThreadActions.FETCH_MORE_MESSAGES });
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
},
|
|
857
|
-
[isCloseToTop, safeContextProperty, safeSend],
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
const handleEndReached = () => {
|
|
861
|
-
// This triggers when scrolled to the bottom
|
|
862
|
-
console.log('Reached end of message list');
|
|
863
|
-
};
|
|
864
|
-
|
|
865
|
-
const onSelectImages = async () => {
|
|
866
|
-
try {
|
|
867
|
-
safeSend({ type: ThreadActions.START_LOADING });
|
|
868
|
-
|
|
869
|
-
const imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
870
|
-
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
871
|
-
allowsEditing: true,
|
|
872
|
-
aspect: [4, 3],
|
|
873
|
-
quality: 0.8, // Reduced from 1 for better performance
|
|
874
|
-
base64: true,
|
|
875
|
-
allowsMultipleSelection: false, // Set to true if you want to support multiple images
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
if (imageSource.canceled) {
|
|
879
|
-
console.log('Image selection was canceled');
|
|
880
|
-
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
if (!imageSource.assets || imageSource.assets.length === 0 || !imageSource.assets[0]?.base64) {
|
|
885
|
-
console.error('No valid image data received');
|
|
886
|
-
safeSend({
|
|
887
|
-
type: 'ERROR',
|
|
888
|
-
data: { message: 'No valid image data received' },
|
|
889
|
-
});
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// Get the first asset
|
|
894
|
-
const asset = imageSource.assets[0];
|
|
895
|
-
|
|
896
|
-
// Derive file extension from mime type or default to jpg
|
|
897
|
-
const fileExtension = asset.mimeType ? asset.mimeType.split('/').pop() || 'jpg' : 'jpg';
|
|
898
|
-
|
|
899
|
-
// Create a more descriptive filename with timestamp
|
|
900
|
-
const filename = `image_${Date.now()}.${fileExtension}`;
|
|
901
|
-
|
|
902
|
-
// Create data URL with proper mime type
|
|
903
|
-
const mimeType = asset.mimeType || 'image/jpeg';
|
|
904
|
-
const image = `data:${mimeType};base64,${asset.base64}`;
|
|
905
|
-
|
|
906
|
-
// Create file-like object suitable for React Native
|
|
907
|
-
const fileData = {
|
|
908
|
-
uri: asset.uri,
|
|
909
|
-
type: mimeType,
|
|
910
|
-
name: filename,
|
|
911
|
-
base64: asset.base64,
|
|
912
|
-
};
|
|
913
|
-
|
|
914
|
-
console.log(`Selected image: ${filename}, type: ${mimeType}`);
|
|
915
|
-
|
|
916
|
-
safeSend({
|
|
917
|
-
type: ThreadActions.SET_IMAGE,
|
|
918
|
-
data: {
|
|
919
|
-
image,
|
|
920
|
-
files: [fileData],
|
|
921
|
-
images: [asset as ImagePicker.ImagePickerAsset],
|
|
922
|
-
},
|
|
923
|
-
});
|
|
924
|
-
} catch (error) {
|
|
925
|
-
console.error('Error selecting image:', error);
|
|
926
|
-
safeSend({
|
|
927
|
-
type: 'ERROR',
|
|
928
|
-
data: { message: error.message || 'Failed to select image' },
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
// Define message sending handlers
|
|
934
|
-
const sendThreadMessageHandler = useCallback(
|
|
935
|
-
async (message: string) => {
|
|
936
|
-
console.log('Sending message:', message);
|
|
937
|
-
|
|
938
|
-
if (!channelId) {
|
|
939
|
-
console.error('No channelId provided');
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Allow empty messages with spaces or blank content - GiftedChat sometimes sends these
|
|
944
|
-
// But use the actual message if available
|
|
945
|
-
const messageContent = message?.trim() || ' ';
|
|
946
|
-
console.log('Using message content for sending:', messageContent);
|
|
947
|
-
|
|
948
|
-
const postId = objectId();
|
|
949
|
-
console.log('Generated postId:', postId);
|
|
950
|
-
|
|
951
|
-
safeSend({ type: ThreadActions.START_LOADING });
|
|
952
|
-
|
|
953
|
-
try {
|
|
954
|
-
console.log('Sending mutation with variables:', {
|
|
955
|
-
channelId,
|
|
956
|
-
postThreadId: safeContextProperty('postThread')?.id,
|
|
957
|
-
postParentId: !parentId || parentId == 0 ? null : parentId,
|
|
958
|
-
message: messageContent,
|
|
959
|
-
});
|
|
960
|
-
|
|
961
|
-
const result = await sendThreadMessage({
|
|
962
|
-
variables: {
|
|
963
|
-
channelId,
|
|
964
|
-
postThreadId: safeContextProperty('postThread') && safeContextProperty('postThread')?.id,
|
|
965
|
-
postParentId: !parentId || parentId == 0 ? null : parentId,
|
|
966
|
-
threadMessageInput: {
|
|
967
|
-
content: messageContent,
|
|
968
|
-
role,
|
|
969
|
-
},
|
|
970
|
-
},
|
|
971
|
-
update: (cache, { data, errors }: any) => {
|
|
972
|
-
console.log('Send message update callback - data:', data, 'errors:', errors);
|
|
973
|
-
|
|
974
|
-
if (!data || errors) {
|
|
975
|
-
console.error('Send message failed:', errors);
|
|
976
|
-
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
console.log('Message sent successfully:', data?.createPostThread?.lastMessage);
|
|
981
|
-
|
|
982
|
-
// Add the new message to our local state
|
|
983
|
-
const newMessage = data?.createPostThread?.lastMessage;
|
|
984
|
-
|
|
985
|
-
// Reset the message text and add the new message
|
|
986
|
-
safeSend({
|
|
987
|
-
type: 'SEND_THREAD_MESSAGE_SUCCESS',
|
|
988
|
-
data: {
|
|
989
|
-
message: newMessage,
|
|
990
|
-
messageText: '', // Clear the message text now
|
|
991
|
-
},
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
if (!parentId || parentId == 0) {
|
|
995
|
-
console.log('Setting new parentId:', data?.createPostThread?.lastMessage?.id);
|
|
996
|
-
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
setChannelToTop(channelToTop + 1);
|
|
1000
|
-
|
|
1001
|
-
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
1002
|
-
sendPushNotification(lastMessageId, channelId, parentId, data?.createPostThread?.data?.id);
|
|
1003
|
-
},
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
console.log('Send mutation result:', result);
|
|
1007
|
-
} catch (error) {
|
|
1008
|
-
console.error('Error sending message:', error);
|
|
1009
|
-
safeSend({
|
|
1010
|
-
type: 'ERROR',
|
|
1011
|
-
data: { message: error.message || 'Failed to send message' },
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
},
|
|
1015
|
-
[channelId, parentId, state.context, role],
|
|
1016
|
-
);
|
|
1017
|
-
|
|
1018
|
-
const sendThreadMessageWithFileHandler = useCallback(
|
|
1019
|
-
async (message: string, images: any[]) => {
|
|
1020
|
-
console.log('Sending message with file:', message, 'Images:', images.length);
|
|
1021
|
-
|
|
1022
|
-
if (!channelId) {
|
|
1023
|
-
console.error('No channelId provided');
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
if (images.length === 0) {
|
|
1028
|
-
console.error('No images to send');
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// Allow empty message content for file uploads
|
|
1033
|
-
// But use the actual message if available
|
|
1034
|
-
const messageContent = message?.trim() || ' ';
|
|
1035
|
-
console.log('Using message content for file send:', messageContent);
|
|
1036
|
-
|
|
1037
|
-
const postId = objectId();
|
|
1038
|
-
console.log('Generated postId for file upload:', postId);
|
|
1039
|
-
|
|
1040
|
-
try {
|
|
1041
|
-
// Prepare image assets in the format expected by the upload service
|
|
1042
|
-
const preparedImages = images.map((img) => ({
|
|
1043
|
-
uri: img.uri,
|
|
1044
|
-
type: img.mimeType || 'image/jpeg',
|
|
1045
|
-
name: img.fileName || `image_${Date.now()}.jpg`,
|
|
1046
|
-
base64: img.base64,
|
|
1047
|
-
width: img.width || 0,
|
|
1048
|
-
height: img.height || 0,
|
|
1049
|
-
})) as ImagePicker.ImagePickerAsset[];
|
|
1050
|
-
|
|
1051
|
-
console.log('Starting file upload with prepared images:', preparedImages.length);
|
|
1052
|
-
|
|
1053
|
-
const uploadResponse = await startUpload({
|
|
1054
|
-
file: preparedImages,
|
|
1055
|
-
saveUploadedFile: {
|
|
1056
|
-
variables: {
|
|
1057
|
-
postId,
|
|
1058
|
-
},
|
|
1059
|
-
},
|
|
1060
|
-
createUploadLink: {
|
|
1061
|
-
variables: {
|
|
1062
|
-
postId,
|
|
1063
|
-
},
|
|
1064
|
-
},
|
|
1065
|
-
});
|
|
1066
|
-
|
|
1067
|
-
if (uploadResponse?.error) {
|
|
1068
|
-
console.error('File upload failed:', uploadResponse.error);
|
|
1069
|
-
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
1070
|
-
return;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
1074
|
-
console.log('Files uploaded successfully:', uploadedFiles?.length);
|
|
1075
|
-
|
|
1076
|
-
if (uploadResponse.data) {
|
|
1077
|
-
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
1078
|
-
console.log('File IDs for message:', files);
|
|
1079
|
-
|
|
1080
|
-
console.log('Sending message with attached files');
|
|
1081
|
-
const result = await sendThreadMessage({
|
|
1082
|
-
variables: {
|
|
1083
|
-
postId,
|
|
1084
|
-
channelId,
|
|
1085
|
-
postThreadId: safeContextProperty('postThread') && safeContextProperty('postThread')?.id,
|
|
1086
|
-
postParentId: !parentId || parentId == 0 ? null : parentId,
|
|
1087
|
-
threadMessageInput: {
|
|
1088
|
-
content: messageContent,
|
|
1089
|
-
files,
|
|
1090
|
-
role,
|
|
1091
|
-
},
|
|
1092
|
-
},
|
|
1093
|
-
update: (cache, { data, errors }: any) => {
|
|
1094
|
-
console.log('Send message with file update callback - data:', data, 'errors:', errors);
|
|
1095
|
-
|
|
1096
|
-
if (!data || errors) {
|
|
1097
|
-
console.error('Send message with file failed:', errors);
|
|
1098
|
-
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
console.log('Message with file sent successfully:', data?.createPostThread?.lastMessage);
|
|
1103
|
-
|
|
1104
|
-
// Add the new message to our local state
|
|
1105
|
-
const newMessage = data?.createPostThread?.lastMessage;
|
|
1106
|
-
|
|
1107
|
-
safeSend({
|
|
1108
|
-
type: 'SEND_THREAD_MESSAGE_WITH_FILE_SUCCESS',
|
|
1109
|
-
data: {
|
|
1110
|
-
message: newMessage,
|
|
1111
|
-
messageText: '', // Clear the message text now
|
|
1112
|
-
},
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
if (!parentId || parentId == 0) {
|
|
1116
|
-
console.log('Setting new parentId:', data?.createPostThread?.lastMessage?.id);
|
|
1117
|
-
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
setChannelToTop(channelToTop + 1);
|
|
1121
|
-
|
|
1122
|
-
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
1123
|
-
sendPushNotification(lastMessageId, channelId, parentId, data?.createPostThread?.data?.id);
|
|
1124
|
-
},
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
console.log('Send with file mutation result:', result);
|
|
1128
|
-
}
|
|
1129
|
-
} catch (error) {
|
|
1130
|
-
console.error('Error sending message with file:', error);
|
|
1131
|
-
safeSend({
|
|
1132
|
-
type: 'ERROR',
|
|
1133
|
-
data: { message: error.message || 'Failed to send message with file' },
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
},
|
|
1137
|
-
[channelId, parentId, state.context, role, startUpload],
|
|
1138
|
-
);
|
|
1139
|
-
|
|
1140
|
-
const sendPushNotification = async (messageId: string, channelId: string, parentId: any, threadId: string) => {
|
|
1141
|
-
const notificationData: IExpoNotificationData = {
|
|
1142
|
-
url: config.THREAD_MESSEGE_PATH,
|
|
1143
|
-
params: { channelId, title: params?.title ?? 'Thread', postParentId: parentId, hideTabBar: true },
|
|
1144
|
-
screen: 'DialogThreadMessages',
|
|
1145
|
-
thread: { id: threadId },
|
|
1146
|
-
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
1147
|
-
};
|
|
1148
|
-
if (parentId || parentId == 0) {
|
|
1149
|
-
await sendExpoNotificationOnPostMutation({
|
|
1150
|
-
variables: {
|
|
1151
|
-
postId: messageId?.toString(),
|
|
1152
|
-
notificationData,
|
|
1153
|
-
},
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
|
|
1158
|
-
// Optimize messageList memo to avoid unnecessary recalculations
|
|
1159
|
-
const messageList = useMemo(() => {
|
|
1160
|
-
const threadMessages = safeContextProperty('threadMessages', []);
|
|
1161
|
-
// Avoid excessive logging in production
|
|
1162
|
-
if (__DEV__) console.log(`Creating message list from ${threadMessages.length} thread messages`);
|
|
1163
|
-
|
|
1164
|
-
if (!threadMessages?.length) return [];
|
|
1165
|
-
|
|
1166
|
-
// We need to convert the threadMessages into the format expected by GiftedChat
|
|
1167
|
-
// Use a Set to track IDs and prevent duplicates
|
|
1168
|
-
const messageIds = new Set();
|
|
1169
|
-
|
|
1170
|
-
const res = threadMessages
|
|
1171
|
-
.filter((msg) => {
|
|
1172
|
-
// Skip duplicate IDs
|
|
1173
|
-
if (!msg.id || messageIds.has(msg.id)) return false;
|
|
1174
|
-
messageIds.add(msg.id);
|
|
1175
|
-
return true;
|
|
1176
|
-
})
|
|
1177
|
-
.map((msg) => {
|
|
1178
|
-
// Generate a unique _id if needed by combining id and createdAt
|
|
1179
|
-
const uniqueId = msg.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1180
|
-
|
|
1181
|
-
// Safely create a Date object with validation
|
|
1182
|
-
let messageDate;
|
|
1183
|
-
try {
|
|
1184
|
-
// Check if createdAt is a valid date string
|
|
1185
|
-
if (msg.createdAt && !isNaN(new Date(msg.createdAt).getTime())) {
|
|
1186
|
-
messageDate = new Date(msg.createdAt);
|
|
1187
|
-
} else {
|
|
1188
|
-
// Use current time as fallback if date is invalid
|
|
1189
|
-
messageDate = new Date();
|
|
1190
|
-
}
|
|
1191
|
-
} catch (error) {
|
|
1192
|
-
messageDate = new Date(); // Fallback to current time
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
// Extract image URL from files data
|
|
1196
|
-
let imageUrl = null;
|
|
1197
|
-
if (msg.files?.data && msg.files.data.length > 0) {
|
|
1198
|
-
const fileData = msg.files.data[0];
|
|
1199
|
-
if (fileData && fileData.url) {
|
|
1200
|
-
imageUrl = fileData.url;
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
// Get the message text without adding "File attachment" for image-only messages
|
|
1205
|
-
let messageText = msg.message || '';
|
|
1206
|
-
|
|
1207
|
-
return {
|
|
1208
|
-
_id: uniqueId,
|
|
1209
|
-
text: messageText,
|
|
1210
|
-
createdAt: messageDate,
|
|
1211
|
-
user: {
|
|
1212
|
-
_id: msg?.author?.id ?? auth?.profile?.id,
|
|
1213
|
-
name:
|
|
1214
|
-
msg?.author?.givenName ??
|
|
1215
|
-
auth?.profile?.given_name + ' ' + msg?.author?.familyName ??
|
|
1216
|
-
auth?.profile?.family_name,
|
|
1217
|
-
avatar: msg?.author?.picture ?? auth?.profile?.picture,
|
|
1218
|
-
},
|
|
1219
|
-
type: msg?.type || '',
|
|
1220
|
-
image: imageUrl,
|
|
1221
|
-
sent: msg?.isDelivered || true,
|
|
1222
|
-
received: msg?.isRead || false,
|
|
1223
|
-
propsConfiguration: msg?.propsConfiguration,
|
|
1224
|
-
};
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
// Sort messages by date (newest first as required by GiftedChat)
|
|
1228
|
-
// Use a safer getTime() approach with error handling
|
|
1229
|
-
return orderBy(
|
|
1230
|
-
res,
|
|
1231
|
-
[
|
|
1232
|
-
(msg) => {
|
|
1233
|
-
try {
|
|
1234
|
-
return msg.createdAt instanceof Date ? msg.createdAt.getTime() : new Date().getTime();
|
|
1235
|
-
} catch (error) {
|
|
1236
|
-
return 0; // Fallback to a default value
|
|
1237
|
-
}
|
|
1238
|
-
},
|
|
1239
|
-
],
|
|
1240
|
-
['desc'],
|
|
1241
|
-
);
|
|
1242
|
-
}, [safeContextProperty('threadMessages'), auth]);
|
|
1243
|
-
|
|
1244
|
-
// Optimize render functions with memoization
|
|
1245
|
-
const renderSend = useCallback(
|
|
1246
|
-
(props) => {
|
|
1247
|
-
// Check if there's an image selected
|
|
1248
|
-
const hasImage = safeContextProperty('selectedImage', '') !== '';
|
|
1249
|
-
|
|
1250
|
-
// Enable send button if there's text OR an image
|
|
1251
|
-
const isDisabled = !hasImage && (!props.text || props.text.trim().length === 0);
|
|
1252
|
-
|
|
1253
|
-
return (
|
|
1254
|
-
<Send
|
|
1255
|
-
{...props}
|
|
1256
|
-
containerStyle={{
|
|
1257
|
-
alignItems: 'center',
|
|
1258
|
-
justifyContent: 'center',
|
|
1259
|
-
marginHorizontal: 4,
|
|
1260
|
-
marginBottom: 0,
|
|
1261
|
-
}}
|
|
1262
|
-
disabled={isDisabled}
|
|
1263
|
-
>
|
|
1264
|
-
<Box
|
|
1265
|
-
style={{
|
|
1266
|
-
width: 32,
|
|
1267
|
-
height: 32,
|
|
1268
|
-
alignItems: 'center',
|
|
1269
|
-
justifyContent: 'center',
|
|
1270
|
-
}}
|
|
1271
|
-
>
|
|
1272
|
-
<MaterialCommunityIcons
|
|
1273
|
-
name="send-circle"
|
|
1274
|
-
size={30}
|
|
1275
|
-
color={isDisabled ? colors.gray[400] : colors.blue[500]}
|
|
1276
|
-
/>
|
|
1277
|
-
</Box>
|
|
1278
|
-
</Send>
|
|
1279
|
-
);
|
|
1280
|
-
},
|
|
1281
|
-
[safeContextProperty],
|
|
1282
|
-
);
|
|
1283
|
-
|
|
1284
|
-
const renderActions = useCallback(
|
|
1285
|
-
(props) => {
|
|
1286
|
-
return (
|
|
1287
|
-
<Actions
|
|
1288
|
-
{...props}
|
|
1289
|
-
options={{
|
|
1290
|
-
['Choose from Library']: onSelectImages,
|
|
1291
|
-
['Cancel']: () => {}, // Add this option to make the sheet dismissible
|
|
1292
|
-
}}
|
|
1293
|
-
optionTintColor="#000000"
|
|
1294
|
-
cancelButtonIndex={1} // Set the Cancel option as the cancel button
|
|
1295
|
-
icon={() => (
|
|
1296
|
-
<Box
|
|
1297
|
-
style={{
|
|
1298
|
-
width: 32,
|
|
1299
|
-
height: 32,
|
|
1300
|
-
alignItems: 'center',
|
|
1301
|
-
justifyContent: 'center',
|
|
1302
|
-
}}
|
|
1303
|
-
>
|
|
1304
|
-
<Ionicons name="image" size={24} color={colors.blue[500]} />
|
|
1305
|
-
</Box>
|
|
1306
|
-
)}
|
|
1307
|
-
containerStyle={{
|
|
1308
|
-
alignItems: 'center',
|
|
1309
|
-
justifyContent: 'center',
|
|
1310
|
-
marginLeft: 8,
|
|
1311
|
-
marginBottom: 0,
|
|
1312
|
-
}}
|
|
1313
|
-
/>
|
|
1314
|
-
);
|
|
1315
|
-
},
|
|
1316
|
-
[onSelectImages],
|
|
1317
|
-
);
|
|
1318
|
-
|
|
1319
|
-
const renderAccessory = useCallback(
|
|
1320
|
-
(props) => {
|
|
1321
|
-
const selectedImage = safeContextProperty('selectedImage', '');
|
|
1322
|
-
|
|
1323
|
-
if (!selectedImage) {
|
|
1324
|
-
return null;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
return (
|
|
1328
|
-
<View
|
|
1329
|
-
style={{
|
|
1330
|
-
height: 80,
|
|
1331
|
-
padding: 10,
|
|
1332
|
-
backgroundColor: 'white',
|
|
1333
|
-
borderTopWidth: 1,
|
|
1334
|
-
borderTopColor: '#e0e0e0',
|
|
1335
|
-
flexDirection: 'row',
|
|
1336
|
-
alignItems: 'center',
|
|
1337
|
-
}}
|
|
1338
|
-
>
|
|
1339
|
-
<View
|
|
1340
|
-
style={{
|
|
1341
|
-
flex: 1,
|
|
1342
|
-
flexDirection: 'row',
|
|
1343
|
-
alignItems: 'center',
|
|
1344
|
-
paddingHorizontal: 20,
|
|
1345
|
-
}}
|
|
1346
|
-
>
|
|
1347
|
-
<Image
|
|
1348
|
-
key={state?.context?.selectedImage}
|
|
1349
|
-
alt={'selected image'}
|
|
1350
|
-
source={{ uri: state?.context?.selectedImage }}
|
|
1351
|
-
size={'xs'}
|
|
1352
|
-
style={{
|
|
1353
|
-
width: 5,
|
|
1354
|
-
height: 5,
|
|
1355
|
-
borderRadius: 5,
|
|
1356
|
-
marginRight: 20,
|
|
1357
|
-
}}
|
|
1358
|
-
/>
|
|
1359
|
-
|
|
1360
|
-
<TouchableHighlight
|
|
1361
|
-
underlayColor="#dddddd"
|
|
1362
|
-
onPress={() => safeSend({ type: ThreadActions.CLEAR_IMAGE })}
|
|
1363
|
-
style={{
|
|
1364
|
-
backgroundColor: '#f44336',
|
|
1365
|
-
paddingVertical: 2,
|
|
1366
|
-
paddingHorizontal: 5,
|
|
1367
|
-
borderRadius: 5,
|
|
1368
|
-
marginLeft: 10,
|
|
1369
|
-
elevation: 3,
|
|
1370
|
-
shadowColor: '#000',
|
|
1371
|
-
shadowOffset: { width: 0, height: 1 },
|
|
1372
|
-
shadowOpacity: 0.3,
|
|
1373
|
-
shadowRadius: 2,
|
|
1374
|
-
}}
|
|
1375
|
-
>
|
|
1376
|
-
<Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
|
|
1377
|
-
</TouchableHighlight>
|
|
1378
|
-
</View>
|
|
1379
|
-
</View>
|
|
1380
|
-
);
|
|
1381
|
-
},
|
|
1382
|
-
[state?.context?.selectedImage, safeSend],
|
|
1383
|
-
);
|
|
1384
|
-
|
|
1385
|
-
const renderInputToolbar = useCallback((props) => {
|
|
1386
|
-
return (
|
|
1387
|
-
<InputToolbar
|
|
1388
|
-
{...props}
|
|
1389
|
-
containerStyle={{
|
|
1390
|
-
backgroundColor: 'white',
|
|
1391
|
-
borderTopWidth: 1,
|
|
1392
|
-
borderTopColor: colors.gray[200],
|
|
1393
|
-
paddingHorizontal: 4,
|
|
1394
|
-
paddingVertical: 4,
|
|
1395
|
-
}}
|
|
1396
|
-
primaryStyle={{
|
|
1397
|
-
alignItems: 'center',
|
|
1398
|
-
}}
|
|
1399
|
-
/>
|
|
1400
|
-
);
|
|
1401
|
-
}, []);
|
|
1402
|
-
|
|
1403
|
-
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
1404
|
-
setImageObject(obj);
|
|
1405
|
-
setImageViewer(v);
|
|
1406
|
-
};
|
|
1407
|
-
|
|
1408
|
-
// Add back the renderMessageText function (memoized)
|
|
1409
|
-
const renderMessageText = useCallback(
|
|
1410
|
-
(props: any) => {
|
|
1411
|
-
const { currentMessage } = props;
|
|
1412
|
-
|
|
1413
|
-
// For ALERT type messages with call to action
|
|
1414
|
-
if (currentMessage.type === 'ALERT') {
|
|
1415
|
-
const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
|
|
1416
|
-
let action: string = '';
|
|
1417
|
-
let actionId: any = '';
|
|
1418
|
-
let params: any = {};
|
|
1419
|
-
if (attachment?.callToAction?.extraParams) {
|
|
1420
|
-
const extraParams: any = attachment?.callToAction?.extraParams;
|
|
1421
|
-
const route: any = extraParams?.route ?? null;
|
|
1422
|
-
let path: any = null;
|
|
1423
|
-
let param: any = null;
|
|
1424
|
-
if (role && role == PreDefinedRole.Guest) {
|
|
1425
|
-
path = route?.guest?.name ? route?.guest?.name ?? null : null;
|
|
1426
|
-
param = route?.guest?.params ? route?.guest?.params ?? null : null;
|
|
1427
|
-
} else if (role && role == PreDefinedRole.Owner) {
|
|
1428
|
-
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
1429
|
-
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
1430
|
-
} else {
|
|
1431
|
-
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
1432
|
-
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
action = path;
|
|
1436
|
-
params = { ...param };
|
|
1437
|
-
} else if (attachment?.callToAction?.link) {
|
|
1438
|
-
action = CALL_TO_ACTION_PATH;
|
|
1439
|
-
actionId = attachment?.callToAction?.link.split('/').pop();
|
|
1440
|
-
params = { reservationId: actionId };
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
return (
|
|
1444
|
-
<>
|
|
1445
|
-
{attachment?.callToAction && action ? (
|
|
1446
|
-
<Box className={`bg-[${CALL_TO_ACTION_BOX_BGCOLOR}] rounded-[15] pb-2`}>
|
|
1447
|
-
<Button
|
|
1448
|
-
variant={'outline'}
|
|
1449
|
-
size={'sm'}
|
|
1450
|
-
className={`border-[${CALL_TO_ACTION_BUTTON_BORDERCOLOR}]`}
|
|
1451
|
-
onPress={() => action && params && navigation.navigate(action, params)}
|
|
1452
|
-
>
|
|
1453
|
-
<ButtonText className={`color-[${CALL_TO_ACTION_TEXT_COLOR}]`}>
|
|
1454
|
-
{attachment.callToAction.title}
|
|
1455
|
-
</ButtonText>
|
|
1456
|
-
</Button>
|
|
1457
|
-
<MessageText
|
|
1458
|
-
{...props}
|
|
1459
|
-
textStyle={{
|
|
1460
|
-
left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 },
|
|
1461
|
-
}}
|
|
1462
|
-
/>
|
|
1463
|
-
</Box>
|
|
1464
|
-
) : (
|
|
1465
|
-
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
1466
|
-
)}
|
|
1467
|
-
</>
|
|
1468
|
-
);
|
|
1469
|
-
}
|
|
1470
|
-
// For file attachment messages, don't show the "File attachment" text
|
|
1471
|
-
else if (currentMessage.text === '📎 File attachment') {
|
|
1472
|
-
// Return null to not render any text for these messages
|
|
1473
|
-
return null;
|
|
1474
|
-
}
|
|
1475
|
-
// Default text rendering
|
|
1476
|
-
else {
|
|
1477
|
-
return <MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />;
|
|
1478
|
-
}
|
|
1479
|
-
},
|
|
1480
|
-
[role, navigation],
|
|
1481
|
-
);
|
|
1482
|
-
|
|
1483
|
-
const renderMessage = useCallback(
|
|
1484
|
-
(props: any) => {
|
|
1485
|
-
return (
|
|
1486
|
-
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
1487
|
-
);
|
|
1488
|
-
},
|
|
1489
|
-
[isShowImageViewer, setImageViewerObject],
|
|
1490
|
-
);
|
|
1491
|
-
|
|
1492
|
-
const modalContent = React.useMemo(() => {
|
|
1493
|
-
if (!imageObject) return <></>;
|
|
1494
|
-
const { image, _id } = imageObject;
|
|
1495
|
-
return (
|
|
1496
|
-
<CachedImage
|
|
1497
|
-
style={{ width: '100%', height: '100%' }}
|
|
1498
|
-
resizeMode={'cover'}
|
|
1499
|
-
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
1500
|
-
source={{
|
|
1501
|
-
uri: image,
|
|
1502
|
-
expiresIn: 86400,
|
|
1503
|
-
}}
|
|
1504
|
-
alt={'image'}
|
|
1505
|
-
/>
|
|
1506
|
-
);
|
|
1507
|
-
}, [imageObject]);
|
|
1508
|
-
|
|
1509
|
-
// Define a memo that provides the current message text from state
|
|
1510
|
-
const currentMessageText = useMemo(() => {
|
|
1511
|
-
const text = safeContextProperty('messageText', '') || ' ';
|
|
1512
|
-
return text;
|
|
1513
|
-
}, [safeContextProperty('messageText')]);
|
|
1514
|
-
|
|
1515
|
-
// Add a function to load messages with specific skip value for debugging
|
|
1516
|
-
const forceLoadMessages = useCallback(
|
|
1517
|
-
(skipValue: number) => {
|
|
1518
|
-
console.log(`Force loading messages with explicit skip=${skipValue}, limit=50`);
|
|
1519
|
-
|
|
1520
|
-
if (channelId && parentId) {
|
|
1521
|
-
safeSend({
|
|
1522
|
-
type: ThreadActions.START_LOADING,
|
|
1523
|
-
data: { loading: true },
|
|
1524
|
-
});
|
|
1525
|
-
|
|
1526
|
-
getThreadMessages({
|
|
1527
|
-
variables: {
|
|
1528
|
-
channelId: channelId?.toString(),
|
|
1529
|
-
role: role?.toString(),
|
|
1530
|
-
postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
|
|
1531
|
-
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
1532
|
-
skip: skipValue,
|
|
1533
|
-
limit: 50, // Use larger limit that proved to work
|
|
1534
|
-
},
|
|
1535
|
-
})
|
|
1536
|
-
.then(({ data }) => {
|
|
1537
|
-
if (data?.getPostThread) {
|
|
1538
|
-
const threads: any = data.getPostThread;
|
|
1539
|
-
const threadPost = threads?.post ?? [];
|
|
1540
|
-
const threadReplies = threads?.replies ?? [];
|
|
1541
|
-
const messageTotalCount = threads?.replyCount ?? 0;
|
|
1542
|
-
const messages = [...threadReplies];
|
|
1543
|
-
|
|
1544
|
-
console.log(
|
|
1545
|
-
`Force load with skip=${skipValue} complete. Got ${messages.length} messages of ${messageTotalCount} total`,
|
|
1546
|
-
);
|
|
1547
|
-
|
|
1548
|
-
safeSend({
|
|
1549
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1550
|
-
data: {
|
|
1551
|
-
messages,
|
|
1552
|
-
totalCount: messageTotalCount,
|
|
1553
|
-
threadPost: threadPost,
|
|
1554
|
-
postThread: threads,
|
|
1555
|
-
},
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
})
|
|
1559
|
-
.catch((error) => {
|
|
1560
|
-
console.error('Error during force load:', error);
|
|
1561
|
-
safeSend({
|
|
1562
|
-
type: 'ERROR',
|
|
1563
|
-
data: { message: error.message },
|
|
1564
|
-
});
|
|
1565
|
-
});
|
|
1566
|
-
}
|
|
1567
|
-
},
|
|
1568
|
-
[channelId, parentId, getThreadMessages, safeSend],
|
|
1569
|
-
);
|
|
1570
|
-
|
|
1571
|
-
// Optimize the onFetchOld function to be more efficient
|
|
1572
|
-
const onFetchOld = useCallback(() => {
|
|
1573
|
-
const totalCount = safeContextProperty('totalCount', 0);
|
|
1574
|
-
const threadMessages = safeContextProperty('threadMessages', []);
|
|
1575
|
-
const isLoading = safeContextProperty('loadingOldMessages', false);
|
|
1576
|
-
|
|
1577
|
-
if (totalCount <= threadMessages.length || isLoading) {
|
|
1578
|
-
// Make sure we're not stuck in loading state
|
|
1579
|
-
if (isLoading) {
|
|
1580
|
-
safeSend({
|
|
1581
|
-
type: ThreadActions.STOP_LOADING,
|
|
1582
|
-
data: { loadingOldMessages: false },
|
|
1583
|
-
});
|
|
1584
|
-
}
|
|
1585
|
-
if (__DEV__) console.log('No more messages to load or already loading');
|
|
1586
|
-
return;
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
if (__DEV__) console.log('Loading more messages - current count:', threadMessages.length, 'of', totalCount);
|
|
1590
|
-
|
|
1591
|
-
// Set the loading state specifically for old messages
|
|
1592
|
-
safeSend({
|
|
1593
|
-
type: ThreadActions.FETCH_MORE_MESSAGES,
|
|
1594
|
-
data: { loadingOldMessages: true },
|
|
1595
|
-
});
|
|
1596
|
-
|
|
1597
|
-
// Use simple, consistent approach: Skip=0, Limit=50
|
|
1598
|
-
if (__DEV__) console.log('Using proven approach: Skip=0, Limit=50');
|
|
1599
|
-
|
|
1600
|
-
// Create query variables once
|
|
1601
|
-
const queryVariables = {
|
|
1602
|
-
channelId: channelId?.toString(),
|
|
1603
|
-
role: role?.toString(),
|
|
1604
|
-
postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
|
|
1605
|
-
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
1606
|
-
limit: 50,
|
|
1607
|
-
skip: 0,
|
|
1608
|
-
};
|
|
1609
|
-
|
|
1610
|
-
if (__DEV__) console.log('Query variables:', JSON.stringify(queryVariables));
|
|
1611
|
-
|
|
1612
|
-
fetchMoreMessages({
|
|
1613
|
-
variables: queryVariables,
|
|
1614
|
-
})
|
|
1615
|
-
.then((res: any) => {
|
|
1616
|
-
if (__DEV__) console.log('API response received with status:', res ? 'success' : 'empty');
|
|
1617
|
-
|
|
1618
|
-
if (res?.data?.getPostThread) {
|
|
1619
|
-
const threads: any = res?.data?.getPostThread;
|
|
1620
|
-
const threadReplies = threads?.replies ?? [];
|
|
1621
|
-
const actualTotalCount = threads?.replyCount ?? 0;
|
|
1622
|
-
|
|
1623
|
-
if (__DEV__) {
|
|
1624
|
-
console.log('API response details:');
|
|
1625
|
-
console.log('- replyCount:', threads?.replyCount);
|
|
1626
|
-
console.log('- replies array length:', threadReplies.length);
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
if (threadReplies.length > 0) {
|
|
1630
|
-
// Compare with our existing messages to find the ones we're missing
|
|
1631
|
-
const existingIds = new Set(threadMessages.map((msg) => msg.id));
|
|
1632
|
-
const newUniqueMessages = threadReplies.filter((msg) => !existingIds.has(msg.id));
|
|
1633
|
-
|
|
1634
|
-
if (__DEV__)
|
|
1635
|
-
console.log(
|
|
1636
|
-
`Found ${newUniqueMessages.length} unique new messages out of ${threadReplies.length} received`,
|
|
1637
|
-
);
|
|
1638
|
-
|
|
1639
|
-
// If no new unique messages, it means we already have everything
|
|
1640
|
-
if (newUniqueMessages.length === 0) {
|
|
1641
|
-
if (__DEV__) console.log('No new unique messages found, adjusting total count');
|
|
1642
|
-
|
|
1643
|
-
// Register this as a failed load attempt
|
|
1644
|
-
registerLoadAttemptResult(false);
|
|
1645
|
-
|
|
1646
|
-
// Adjust total count to match what we have
|
|
1647
|
-
safeSend({
|
|
1648
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1649
|
-
data: {
|
|
1650
|
-
messages: threadMessages,
|
|
1651
|
-
totalCount: threadMessages.length,
|
|
1652
|
-
threadPost: safeContextProperty('threadPost', []),
|
|
1653
|
-
postThread: safeContextProperty('postThread', null),
|
|
1654
|
-
},
|
|
1655
|
-
});
|
|
1656
|
-
return;
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
// Register success since we found new messages
|
|
1660
|
-
registerLoadAttemptResult(true);
|
|
1661
|
-
|
|
1662
|
-
if (__DEV__) console.log(`Adding ${newUniqueMessages.length} new messages to thread`);
|
|
1663
|
-
|
|
1664
|
-
safeSend({
|
|
1665
|
-
type: 'FETCH_MORE_MESSAGES_SUCCESS',
|
|
1666
|
-
data: {
|
|
1667
|
-
messages: newUniqueMessages,
|
|
1668
|
-
totalCount: actualTotalCount,
|
|
1669
|
-
loadingOldMessages: false,
|
|
1670
|
-
},
|
|
1671
|
-
});
|
|
1672
|
-
} else {
|
|
1673
|
-
if (__DEV__) console.log('No thread replies returned when loading more messages');
|
|
1674
|
-
registerLoadAttemptResult(false);
|
|
1675
|
-
|
|
1676
|
-
// Adjust total count to match what we have, since server says there are no more messages
|
|
1677
|
-
safeSend({
|
|
1678
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1679
|
-
data: {
|
|
1680
|
-
messages: threadMessages,
|
|
1681
|
-
totalCount: threadMessages.length,
|
|
1682
|
-
threadPost: safeContextProperty('threadPost', []),
|
|
1683
|
-
postThread: safeContextProperty('postThread', null),
|
|
1684
|
-
},
|
|
1685
|
-
});
|
|
1686
|
-
}
|
|
1687
|
-
} else {
|
|
1688
|
-
if (__DEV__) console.log('No thread data returned when loading more messages');
|
|
1689
|
-
registerLoadAttemptResult(false);
|
|
1690
|
-
safeSend({
|
|
1691
|
-
type: ThreadActions.STOP_LOADING,
|
|
1692
|
-
data: { loadingOldMessages: false },
|
|
1693
|
-
});
|
|
1694
|
-
}
|
|
1695
|
-
})
|
|
1696
|
-
.catch((error: any) => {
|
|
1697
|
-
console.error('Error fetching more messages:', error);
|
|
1698
|
-
registerLoadAttemptResult(false);
|
|
1699
|
-
safeSend({
|
|
1700
|
-
type: 'ERROR',
|
|
1701
|
-
data: {
|
|
1702
|
-
message: error.message,
|
|
1703
|
-
loadingOldMessages: false,
|
|
1704
|
-
},
|
|
1705
|
-
});
|
|
1706
|
-
});
|
|
1707
|
-
}, [parentId, channelId, role, safeContextProperty, safeSend, fetchMoreMessages, registerLoadAttemptResult]);
|
|
1708
|
-
|
|
1709
|
-
return (
|
|
1710
|
-
<SafeAreaView style={{ flex: 1 }}>
|
|
1711
|
-
{safeContextProperty('loadingOldMessages', false) === true && (
|
|
1712
|
-
<Box className="absolute top-10 left-0 right-0 z-10 items-center">
|
|
1713
|
-
<Box className="bg-blue-500/20 rounded-full px-4 py-2 flex-row items-center">
|
|
1714
|
-
<Spinner color={colors.blue[500]} size="small" />
|
|
1715
|
-
<Text className="text-sm font-medium color-blue-600 ml-2">Loading messages...</Text>
|
|
1716
|
-
</Box>
|
|
1717
|
-
</Box>
|
|
1718
|
-
)}
|
|
1719
|
-
{!safeContextProperty('loadingOldMessages', false) &&
|
|
1720
|
-
safeContextProperty('totalCount', 0) > safeContextProperty('threadMessages', []).length && (
|
|
1721
|
-
<Box className="absolute top-10 left-0 right-0 z-10 items-center">
|
|
1722
|
-
<HStack space={2} className="px-2">
|
|
1723
|
-
<TouchableHighlight
|
|
1724
|
-
onPress={() => {
|
|
1725
|
-
console.log('Manual load more pressed');
|
|
1726
|
-
Alert.alert('Load Options', 'Choose loading method', [
|
|
1727
|
-
{
|
|
1728
|
-
text: 'Normal Load',
|
|
1729
|
-
onPress: () => safeSend({ type: ThreadActions.FETCH_MORE_MESSAGES }),
|
|
1730
|
-
},
|
|
1731
|
-
{
|
|
1732
|
-
text: 'Try Skip=0',
|
|
1733
|
-
onPress: () => forceLoadMessages(0),
|
|
1734
|
-
},
|
|
1735
|
-
{
|
|
1736
|
-
text: 'Try Skip=0, Limit=50',
|
|
1737
|
-
onPress: () => {
|
|
1738
|
-
// Try with a larger limit
|
|
1739
|
-
console.log('Force loading with explicit skip=0, limit=50');
|
|
1740
|
-
|
|
1741
|
-
safeSend({
|
|
1742
|
-
type: ThreadActions.START_LOADING,
|
|
1743
|
-
data: { loadingOldMessages: true },
|
|
1744
|
-
});
|
|
1745
|
-
|
|
1746
|
-
fetchMoreMessages({
|
|
1747
|
-
variables: {
|
|
1748
|
-
channelId: channelId?.toString(),
|
|
1749
|
-
role: role?.toString(),
|
|
1750
|
-
postParentId:
|
|
1751
|
-
!parentId || parentId == 0 ? null : parentId?.toString(),
|
|
1752
|
-
selectedFields:
|
|
1753
|
-
'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
1754
|
-
limit: 50, // Try a larger limit
|
|
1755
|
-
skip: 0,
|
|
1756
|
-
},
|
|
1757
|
-
})
|
|
1758
|
-
.then((res: any) => {
|
|
1759
|
-
console.log(
|
|
1760
|
-
'LARGE LIMIT response:',
|
|
1761
|
-
JSON.stringify(res?.data, null, 2),
|
|
1762
|
-
);
|
|
1763
|
-
|
|
1764
|
-
if (res?.data?.getPostThread) {
|
|
1765
|
-
const threads: any = res?.data?.getPostThread;
|
|
1766
|
-
const threadReplies = threads?.replies ?? [];
|
|
1767
|
-
const actualTotalCount = threads?.replyCount ?? 0;
|
|
1768
|
-
|
|
1769
|
-
console.log(
|
|
1770
|
-
`Large limit load complete. Got ${threadReplies.length} messages of ${actualTotalCount} total`,
|
|
1771
|
-
);
|
|
1772
|
-
|
|
1773
|
-
if (threadReplies.length > 0) {
|
|
1774
|
-
// Reset our message list with all available messages
|
|
1775
|
-
safeSend({
|
|
1776
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1777
|
-
data: {
|
|
1778
|
-
messages: threadReplies,
|
|
1779
|
-
totalCount: actualTotalCount,
|
|
1780
|
-
threadPost: safeContextProperty(
|
|
1781
|
-
'threadPost',
|
|
1782
|
-
[],
|
|
1783
|
-
),
|
|
1784
|
-
postThread: threads,
|
|
1785
|
-
},
|
|
1786
|
-
});
|
|
1787
|
-
} else {
|
|
1788
|
-
// Reset count if no messages found
|
|
1789
|
-
safeSend({
|
|
1790
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1791
|
-
data: {
|
|
1792
|
-
messages: safeContextProperty(
|
|
1793
|
-
'threadMessages',
|
|
1794
|
-
[],
|
|
1795
|
-
),
|
|
1796
|
-
totalCount: safeContextProperty(
|
|
1797
|
-
'threadMessages',
|
|
1798
|
-
[],
|
|
1799
|
-
).length,
|
|
1800
|
-
threadPost: safeContextProperty(
|
|
1801
|
-
'threadPost',
|
|
1802
|
-
[],
|
|
1803
|
-
),
|
|
1804
|
-
postThread: safeContextProperty(
|
|
1805
|
-
'postThread',
|
|
1806
|
-
null,
|
|
1807
|
-
),
|
|
1808
|
-
},
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
} else {
|
|
1812
|
-
safeSend({
|
|
1813
|
-
type: ThreadActions.STOP_LOADING,
|
|
1814
|
-
data: { loadingOldMessages: false },
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1817
|
-
})
|
|
1818
|
-
.catch((error) => {
|
|
1819
|
-
console.error('Error in large limit load:', error);
|
|
1820
|
-
safeSend({
|
|
1821
|
-
type: ThreadActions.STOP_LOADING,
|
|
1822
|
-
data: { loadingOldMessages: false },
|
|
1823
|
-
});
|
|
1824
|
-
});
|
|
1825
|
-
},
|
|
1826
|
-
},
|
|
1827
|
-
{
|
|
1828
|
-
text: 'Try Direct Fetch',
|
|
1829
|
-
onPress: () => {
|
|
1830
|
-
// Get current info
|
|
1831
|
-
const currentCount = safeContextProperty('threadMessages', []).length;
|
|
1832
|
-
const totalCount = safeContextProperty('totalCount', 0);
|
|
1833
|
-
const missingCount = totalCount - currentCount;
|
|
1834
|
-
|
|
1835
|
-
if (missingCount <= 0) {
|
|
1836
|
-
Alert.alert('Info', 'No missing messages to fetch');
|
|
1837
|
-
return;
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
console.log(
|
|
1841
|
-
`Attempting direct fetch of missing ${missingCount} messages`,
|
|
1842
|
-
);
|
|
1843
|
-
|
|
1844
|
-
// Try explicit query with exact parameters
|
|
1845
|
-
safeSend({
|
|
1846
|
-
type: ThreadActions.START_LOADING,
|
|
1847
|
-
data: { loadingOldMessages: true },
|
|
1848
|
-
});
|
|
1849
|
-
|
|
1850
|
-
// Special query directly for the missing messages
|
|
1851
|
-
getThreadMessages({
|
|
1852
|
-
variables: {
|
|
1853
|
-
channelId: channelId?.toString(),
|
|
1854
|
-
role: role?.toString(),
|
|
1855
|
-
postParentId:
|
|
1856
|
-
!parentId || parentId == 0 ? null : parentId?.toString(),
|
|
1857
|
-
selectedFields:
|
|
1858
|
-
'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
1859
|
-
limit: missingCount, // Only get the exact number we need
|
|
1860
|
-
skip: 0, // Start from the beginning
|
|
1861
|
-
},
|
|
1862
|
-
})
|
|
1863
|
-
.then(({ data }) => {
|
|
1864
|
-
console.log(
|
|
1865
|
-
'DIRECT FETCH response:',
|
|
1866
|
-
JSON.stringify(data, null, 2),
|
|
1867
|
-
);
|
|
1868
|
-
|
|
1869
|
-
if (data?.getPostThread) {
|
|
1870
|
-
const threads: any = data.getPostThread;
|
|
1871
|
-
const threadReplies = threads?.replies ?? [];
|
|
1872
|
-
const actualTotalCount = threads?.replyCount ?? 0;
|
|
1873
|
-
|
|
1874
|
-
console.log(
|
|
1875
|
-
`Direct fetch complete. Got ${threadReplies.length} messages of ${actualTotalCount} total`,
|
|
1876
|
-
);
|
|
1877
|
-
console.log(
|
|
1878
|
-
'Message IDs:',
|
|
1879
|
-
threadReplies.map((msg) => msg.id).join(', '),
|
|
1880
|
-
);
|
|
1881
|
-
|
|
1882
|
-
// Compare with our existing messages to find the ones we're missing
|
|
1883
|
-
const existingIds = new Set(
|
|
1884
|
-
safeContextProperty('threadMessages', []).map(
|
|
1885
|
-
(msg) => msg.id,
|
|
1886
|
-
),
|
|
1887
|
-
);
|
|
1888
|
-
const newMessages = threadReplies.filter(
|
|
1889
|
-
(msg) => !existingIds.has(msg.id),
|
|
1890
|
-
);
|
|
1891
|
-
|
|
1892
|
-
console.log(
|
|
1893
|
-
`Found ${newMessages.length} new messages that we didn't have before`,
|
|
1894
|
-
);
|
|
1895
|
-
|
|
1896
|
-
if (newMessages.length > 0) {
|
|
1897
|
-
safeSend({
|
|
1898
|
-
type: 'FETCH_MORE_MESSAGES_SUCCESS',
|
|
1899
|
-
data: {
|
|
1900
|
-
messages: newMessages,
|
|
1901
|
-
totalCount: actualTotalCount,
|
|
1902
|
-
loadingOldMessages: false,
|
|
1903
|
-
},
|
|
1904
|
-
});
|
|
1905
|
-
|
|
1906
|
-
// Show the results
|
|
1907
|
-
Alert.alert(
|
|
1908
|
-
'Success',
|
|
1909
|
-
`Found ${newMessages.length} new messages`,
|
|
1910
|
-
);
|
|
1911
|
-
} else {
|
|
1912
|
-
// If we didn't find any new messages, adjust the total count
|
|
1913
|
-
console.log('No new messages found, adjusting count');
|
|
1914
|
-
safeSend({
|
|
1915
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1916
|
-
data: {
|
|
1917
|
-
messages: safeContextProperty(
|
|
1918
|
-
'threadMessages',
|
|
1919
|
-
[],
|
|
1920
|
-
),
|
|
1921
|
-
totalCount: safeContextProperty(
|
|
1922
|
-
'threadMessages',
|
|
1923
|
-
[],
|
|
1924
|
-
).length,
|
|
1925
|
-
threadPost: safeContextProperty(
|
|
1926
|
-
'threadPost',
|
|
1927
|
-
[],
|
|
1928
|
-
),
|
|
1929
|
-
postThread: safeContextProperty(
|
|
1930
|
-
'postThread',
|
|
1931
|
-
null,
|
|
1932
|
-
),
|
|
1933
|
-
},
|
|
1934
|
-
});
|
|
1935
|
-
|
|
1936
|
-
// Notify the user
|
|
1937
|
-
Alert.alert(
|
|
1938
|
-
'Info',
|
|
1939
|
-
'No new messages found. Count has been adjusted.',
|
|
1940
|
-
);
|
|
1941
|
-
}
|
|
1942
|
-
} else {
|
|
1943
|
-
Alert.alert('Error', 'Could not fetch thread messages');
|
|
1944
|
-
safeSend({
|
|
1945
|
-
type: ThreadActions.STOP_LOADING,
|
|
1946
|
-
data: { loadingOldMessages: false },
|
|
1947
|
-
});
|
|
1948
|
-
}
|
|
1949
|
-
})
|
|
1950
|
-
.catch((error) => {
|
|
1951
|
-
console.error('Error in direct fetch:', error);
|
|
1952
|
-
Alert.alert(
|
|
1953
|
-
'Error',
|
|
1954
|
-
'Failed to fetch messages: ' + error.message,
|
|
1955
|
-
);
|
|
1956
|
-
safeSend({
|
|
1957
|
-
type: ThreadActions.STOP_LOADING,
|
|
1958
|
-
data: { loadingOldMessages: false },
|
|
1959
|
-
});
|
|
1960
|
-
});
|
|
1961
|
-
},
|
|
1962
|
-
},
|
|
1963
|
-
{
|
|
1964
|
-
text: 'Reset Count',
|
|
1965
|
-
style: 'destructive',
|
|
1966
|
-
onPress: () => {
|
|
1967
|
-
console.log('Resetting message count to match reality');
|
|
1968
|
-
safeSend({
|
|
1969
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1970
|
-
data: {
|
|
1971
|
-
messages: safeContextProperty('threadMessages', []),
|
|
1972
|
-
totalCount: safeContextProperty('threadMessages', []).length,
|
|
1973
|
-
threadPost: safeContextProperty('threadPost', []),
|
|
1974
|
-
postThread: safeContextProperty('postThread', null),
|
|
1975
|
-
},
|
|
1976
|
-
});
|
|
1977
|
-
},
|
|
1978
|
-
},
|
|
1979
|
-
{
|
|
1980
|
-
text: 'Cancel',
|
|
1981
|
-
style: 'cancel',
|
|
1982
|
-
},
|
|
1983
|
-
]);
|
|
1984
|
-
}}
|
|
1985
|
-
underlayColor="#e6e6e6"
|
|
1986
|
-
>
|
|
1987
|
-
<Box className="bg-gray-200 rounded-full px-4 py-2 flex-row items-center">
|
|
1988
|
-
<MaterialIcons name="arrow-upward" size={16} color={colors.blue[500]} />
|
|
1989
|
-
<Text className="text-sm font-medium color-blue-600 ml-2">
|
|
1990
|
-
Load More (
|
|
1991
|
-
{safeContextProperty('totalCount', 0) -
|
|
1992
|
-
safeContextProperty('threadMessages', []).length}
|
|
1993
|
-
)
|
|
1994
|
-
</Text>
|
|
1995
|
-
</Box>
|
|
1996
|
-
</TouchableHighlight>
|
|
1997
|
-
|
|
1998
|
-
<TouchableHighlight onPress={forceRefreshMessages} underlayColor="#e6e6e6">
|
|
1999
|
-
<Box className="bg-gray-200 rounded-full px-4 py-2 flex-row items-center">
|
|
2000
|
-
<MaterialIcons name="refresh" size={16} color={colors.blue[500]} />
|
|
2001
|
-
<Text className="text-sm font-medium color-blue-600 ml-2">Refresh All</Text>
|
|
2002
|
-
</Box>
|
|
2003
|
-
</TouchableHighlight>
|
|
2004
|
-
</HStack>
|
|
2005
|
-
</Box>
|
|
2006
|
-
)}
|
|
2007
|
-
{isPostParentIdThread && (
|
|
2008
|
-
<>
|
|
2009
|
-
{safeContextProperty('threadPost', [])?.length > 0 && (
|
|
2010
|
-
<>
|
|
2011
|
-
<VStack className="px-2 pt-2 pb-0" space={'sm'}>
|
|
2012
|
-
<HStack space={'sm'} className="items-center">
|
|
2013
|
-
<Avatar className="bg-transparent" size={'md'}>
|
|
2014
|
-
<AvatarFallbackText>
|
|
2015
|
-
{startCase(
|
|
2016
|
-
safeContextProperty('threadPost')[0]?.author?.username?.charAt(0),
|
|
2017
|
-
)}
|
|
2018
|
-
</AvatarFallbackText>
|
|
2019
|
-
<AvatarImage
|
|
2020
|
-
alt="image"
|
|
2021
|
-
style={{
|
|
2022
|
-
borderRadius: 6,
|
|
2023
|
-
borderWidth: 2,
|
|
2024
|
-
borderColor: '#fff',
|
|
2025
|
-
}}
|
|
2026
|
-
source={{
|
|
2027
|
-
uri: safeContextProperty('threadPost')[0]?.author?.picture,
|
|
2028
|
-
}}
|
|
2029
|
-
/>
|
|
2030
|
-
</Avatar>
|
|
2031
|
-
<Box>
|
|
2032
|
-
<Text className="font-bold color-black">
|
|
2033
|
-
{safeContextProperty('threadPost')[0]?.author?.givenName ?? ''}{' '}
|
|
2034
|
-
{safeContextProperty('threadPost')[0]?.author?.familyName ?? ''}
|
|
2035
|
-
</Text>
|
|
2036
|
-
<Text className="pl-0 color-gray-500">
|
|
2037
|
-
{createdAtText(safeContextProperty('threadPost')[0]?.createdAt)} at{' '}
|
|
2038
|
-
{(() => {
|
|
2039
|
-
try {
|
|
2040
|
-
const createdAt = safeContextProperty('threadPost')[0]?.createdAt;
|
|
2041
|
-
if (createdAt && !isNaN(new Date(createdAt).getTime())) {
|
|
2042
|
-
return format(new Date(createdAt), 'hh:mm:a');
|
|
2043
|
-
} else {
|
|
2044
|
-
return 'unknown time';
|
|
2045
|
-
}
|
|
2046
|
-
} catch (error) {
|
|
2047
|
-
console.error('Error formatting thread post time:', error);
|
|
2048
|
-
return 'unknown time';
|
|
2049
|
-
}
|
|
2050
|
-
})()}
|
|
2051
|
-
</Text>
|
|
2052
|
-
</Box>
|
|
2053
|
-
</HStack>
|
|
2054
|
-
<HStack space={'sm'} className="px-2 items-center">
|
|
2055
|
-
<Text>{safeContextProperty('threadPost')[0]?.message ?? ''}</Text>
|
|
2056
|
-
</HStack>
|
|
2057
|
-
</VStack>
|
|
2058
|
-
|
|
2059
|
-
<Box className="py-4">
|
|
2060
|
-
<Box className="px-4 py-2 border-t border-b border-gray-200">
|
|
2061
|
-
<Text className="font-bold color-gray-600">
|
|
2062
|
-
{safeContextProperty('threadPost')[0]?.replies?.totalCount}{' '}
|
|
2063
|
-
{safeContextProperty('threadPost')[0]?.replies?.totalCount > 0
|
|
2064
|
-
? 'replies'
|
|
2065
|
-
: 'reply'}
|
|
2066
|
-
</Text>
|
|
2067
|
-
</Box>
|
|
2068
|
-
</Box>
|
|
2069
|
-
</>
|
|
2070
|
-
)}
|
|
2071
|
-
</>
|
|
2072
|
-
)}
|
|
2073
|
-
<GiftedChat
|
|
2074
|
-
ref={threadMessageListRef}
|
|
2075
|
-
wrapInSafeArea={false}
|
|
2076
|
-
renderLoading={() => <Spinner color={colors.blue[500]} />}
|
|
2077
|
-
messages={messageList}
|
|
2078
|
-
listViewProps={{
|
|
2079
|
-
onScroll: handleScrollToTop,
|
|
2080
|
-
onEndReached: handleEndReached,
|
|
2081
|
-
onEndReachedThreshold: 0.2,
|
|
2082
|
-
contentContainerStyle: {
|
|
2083
|
-
paddingBottom: 10,
|
|
2084
|
-
},
|
|
2085
|
-
maintainVisibleContentPosition: {
|
|
2086
|
-
minIndexForVisible: 0,
|
|
2087
|
-
autoscrollToTopThreshold: 100,
|
|
2088
|
-
},
|
|
2089
|
-
scrollEventThrottle: 16,
|
|
2090
|
-
keyboardDismissMode: 'on-drag',
|
|
2091
|
-
keyboardShouldPersistTaps: 'handled',
|
|
2092
|
-
// removeClippedSubviews={true}
|
|
2093
|
-
}}
|
|
2094
|
-
onSend={(messages) => {
|
|
2095
|
-
if (!messages || messages.length === 0) {
|
|
2096
|
-
console.log('No messages to send');
|
|
2097
|
-
return;
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
// Use the actual message text from the state, not the one from GiftedChat
|
|
2101
|
-
// GiftedChat sometimes sends blank messages even when there's text in the input
|
|
2102
|
-
const currentInputText = currentMessageText;
|
|
2103
|
-
const messageToSend = currentInputText?.trim() || messages[0]?.text?.trim() || ' ';
|
|
2104
|
-
|
|
2105
|
-
if (__DEV__) console.log('GiftedChat onSend triggered with text from state:', messageToSend);
|
|
2106
|
-
|
|
2107
|
-
// Make sure we update the message text in state
|
|
2108
|
-
safeSend({
|
|
2109
|
-
type: ThreadActions.SET_MESSAGE_TEXT,
|
|
2110
|
-
data: { messageText: '' },
|
|
2111
|
-
});
|
|
2112
|
-
|
|
2113
|
-
// Then send the message
|
|
2114
|
-
if (safeContextProperty('images', []).length > 0) {
|
|
2115
|
-
if (__DEV__)
|
|
2116
|
-
console.log(
|
|
2117
|
-
'Sending message with file:',
|
|
2118
|
-
messageToSend,
|
|
2119
|
-
'Images:',
|
|
2120
|
-
safeContextProperty('images', []).length,
|
|
2121
|
-
);
|
|
2122
|
-
safeSend({
|
|
2123
|
-
type: ThreadActions.SEND_THREAD_MESSAGE_WITH_FILE,
|
|
2124
|
-
data: { messageText: messageToSend },
|
|
2125
|
-
});
|
|
2126
|
-
} else {
|
|
2127
|
-
if (__DEV__) console.log('Sending text message:', messageToSend);
|
|
2128
|
-
safeSend({
|
|
2129
|
-
type: ThreadActions.SEND_THREAD_MESSAGE,
|
|
2130
|
-
data: { messageText: messageToSend },
|
|
2131
|
-
});
|
|
2132
|
-
}
|
|
2133
|
-
}}
|
|
2134
|
-
text={currentMessageText}
|
|
2135
|
-
onInputTextChanged={(text) => {
|
|
2136
|
-
// Set the text in the state without excessive logging
|
|
2137
|
-
safeSend({
|
|
2138
|
-
type: ThreadActions.SET_MESSAGE_TEXT,
|
|
2139
|
-
data: { messageText: text },
|
|
2140
|
-
});
|
|
2141
|
-
}}
|
|
2142
|
-
renderFooter={() =>
|
|
2143
|
-
safeContextProperty('loading', false) && !safeContextProperty('loadingOldMessages', false) ? (
|
|
2144
|
-
<Box className="w-full py-2 items-center">
|
|
2145
|
-
<Spinner color={colors.blue[500]} />
|
|
2146
|
-
</Box>
|
|
2147
|
-
) : safeContextProperty('imageLoading', false) ? (
|
|
2148
|
-
<Box className="w-full py-2 items-center">
|
|
2149
|
-
<Spinner color={colors.blue[500]} />
|
|
2150
|
-
</Box>
|
|
2151
|
-
) : (
|
|
2152
|
-
<></>
|
|
2153
|
-
)
|
|
2154
|
-
}
|
|
2155
|
-
scrollToBottom
|
|
2156
|
-
loadEarlier={false}
|
|
2157
|
-
isLoadingEarlier={false}
|
|
2158
|
-
user={{
|
|
2159
|
-
_id: auth?.id || '',
|
|
2160
|
-
}}
|
|
2161
|
-
isTyping={safeContextProperty('loading', false)}
|
|
2162
|
-
alwaysShowSend={safeContextProperty('loading', false) ? false : true}
|
|
2163
|
-
infiniteScroll={true}
|
|
2164
|
-
renderSend={renderSend}
|
|
2165
|
-
renderInputToolbar={renderInputToolbar}
|
|
2166
|
-
minInputToolbarHeight={50}
|
|
2167
|
-
renderActions={renderActions}
|
|
2168
|
-
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
2169
|
-
renderMessage={renderMessage}
|
|
2170
|
-
renderMessageText={renderMessageText}
|
|
2171
|
-
maxInputLength={1000}
|
|
2172
|
-
placeholder="Type a message..."
|
|
2173
|
-
showUserAvatar={true}
|
|
2174
|
-
showAvatarForEveryMessage={false}
|
|
2175
|
-
inverted={true}
|
|
2176
|
-
parsePatterns={(linkStyle) => [
|
|
2177
|
-
{
|
|
2178
|
-
type: 'url',
|
|
2179
|
-
style: { ...linkStyle, color: colors.blue[500] },
|
|
2180
|
-
onPress: (url) => Linking.openURL(url),
|
|
2181
|
-
},
|
|
2182
|
-
{
|
|
2183
|
-
type: 'phone',
|
|
2184
|
-
style: { ...linkStyle, color: colors.blue[500] },
|
|
2185
|
-
onPress: (phone) => Linking.openURL(`tel:${phone}`),
|
|
2186
|
-
},
|
|
2187
|
-
{
|
|
2188
|
-
type: 'email',
|
|
2189
|
-
style: { ...linkStyle, color: colors.blue[500] },
|
|
2190
|
-
onPress: (email) => Linking.openURL(`mailto:${email}`),
|
|
2191
|
-
},
|
|
2192
|
-
]}
|
|
2193
|
-
textInputProps={{
|
|
2194
|
-
style: {
|
|
2195
|
-
borderWidth: 1,
|
|
2196
|
-
borderColor: colors.gray[300],
|
|
2197
|
-
backgroundColor: '#f8f8f8',
|
|
2198
|
-
borderRadius: 20,
|
|
2199
|
-
minHeight: 40,
|
|
2200
|
-
maxHeight: 80,
|
|
2201
|
-
color: '#000',
|
|
2202
|
-
padding: 10,
|
|
2203
|
-
paddingHorizontal: 20,
|
|
2204
|
-
fontSize: 16,
|
|
2205
|
-
flex: 1,
|
|
2206
|
-
},
|
|
2207
|
-
multiline: true,
|
|
2208
|
-
returnKeyType: 'default',
|
|
2209
|
-
enablesReturnKeyAutomatically: true,
|
|
2210
|
-
placeholderTextColor: colors.gray[400],
|
|
2211
|
-
}}
|
|
2212
|
-
minComposerHeight={44}
|
|
2213
|
-
isKeyboardInternallyHandled={true}
|
|
2214
|
-
bottomOffset={Platform.OS === 'ios' ? 20 : 0}
|
|
2215
|
-
renderChatFooter={() => (
|
|
2216
|
-
<>
|
|
2217
|
-
<ImageViewerModal
|
|
2218
|
-
isVisible={isShowImageViewer}
|
|
2219
|
-
setVisible={setImageViewer}
|
|
2220
|
-
modalContent={modalContent}
|
|
2221
|
-
/>
|
|
2222
|
-
<SubscriptionHandler
|
|
2223
|
-
channelId={channelId}
|
|
2224
|
-
subscribeToNewMessages={() =>
|
|
2225
|
-
subscribeToMore({
|
|
2226
|
-
document: CHAT_MESSAGE_ADDED,
|
|
2227
|
-
variables: {
|
|
2228
|
-
channelId: channelId?.toString(),
|
|
2229
|
-
postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
|
|
2230
|
-
},
|
|
2231
|
-
updateQuery: (prev, { subscriptionData }: any) => {
|
|
2232
|
-
if (!subscriptionData.data) return prev;
|
|
2233
|
-
const newMessage: any = subscriptionData?.data?.threadChatMessageAdded;
|
|
2234
|
-
const prevReplyCount: any = prev?.getPostThread?.replyCount;
|
|
2235
|
-
const newReplyCount = (prevReplyCount || 0) + 1;
|
|
2236
|
-
const replies = prev?.getPostThread?.replies || [];
|
|
2237
|
-
|
|
2238
|
-
safeSend({
|
|
2239
|
-
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
2240
|
-
data: {
|
|
2241
|
-
messages: uniqBy(
|
|
2242
|
-
[...safeContextProperty('threadMessages', []), newMessage],
|
|
2243
|
-
({ id }) => id,
|
|
2244
|
-
),
|
|
2245
|
-
totalCount: newReplyCount,
|
|
2246
|
-
threadPost: safeContextProperty('threadPost', []),
|
|
2247
|
-
postThread: safeContextProperty('postThread', null),
|
|
2248
|
-
},
|
|
2249
|
-
});
|
|
2250
|
-
|
|
2251
|
-
return Object.assign({}, prev, {
|
|
2252
|
-
getPostThread: {
|
|
2253
|
-
...prev?.getPostThread,
|
|
2254
|
-
lastReplyAt: newMessage.createdAt,
|
|
2255
|
-
replies: [newMessage, ...replies],
|
|
2256
|
-
replyCount: newReplyCount,
|
|
2257
|
-
updatedAt: newMessage.createdAt,
|
|
2258
|
-
},
|
|
2259
|
-
});
|
|
2260
|
-
},
|
|
2261
|
-
})
|
|
2262
|
-
}
|
|
2263
|
-
/>
|
|
2264
|
-
</>
|
|
2265
|
-
)}
|
|
2266
|
-
messagesContainerStyle={messageList?.length == 0 ? { transform: [{ scaleY: -1 }] } : undefined}
|
|
2267
|
-
renderChatEmpty={() => (
|
|
2268
|
-
<>
|
|
2269
|
-
{!threadLoading && messageList && messageList?.length == 0 && (
|
|
2270
|
-
<Box className="p-5">
|
|
2271
|
-
<Center className="mt-6">
|
|
2272
|
-
<Ionicons name="chatbubbles" size={30} />
|
|
2273
|
-
<Text>You don't have any message yet!</Text>
|
|
2274
|
-
</Center>
|
|
2275
|
-
</Box>
|
|
2276
|
-
)}
|
|
2277
|
-
</>
|
|
2278
|
-
)}
|
|
2279
|
-
lightboxProps={{
|
|
2280
|
-
underlayColor: 'transparent',
|
|
2281
|
-
springConfig: { tension: 90000, friction: 90000 },
|
|
2282
|
-
disabled: true,
|
|
2283
|
-
}}
|
|
2284
|
-
/>
|
|
2285
|
-
</SafeAreaView>
|
|
2286
|
-
);
|
|
2287
|
-
};
|
|
2288
|
-
|
|
2289
|
-
const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: IThreadSubscriptionHandlerProps) => {
|
|
2290
|
-
useEffect(() => subscribeToNewMessages(), [channelId]);
|
|
2291
|
-
return <></>;
|
|
2292
|
-
};
|
|
2293
|
-
|
|
2294
|
-
export const ThreadConversationView = React.memo(ThreadConversationViewComponent);
|
|
2295
|
-
// export const ThreadConversationView = ThreadConversationViewComponent;
|