@messenger-box/platform-mobile 10.0.3-alpha.34 → 10.0.3-alpha.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/lib/screens/inbox/components/CachedImage/index.js +125 -93
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/DialogsListItem.js +80 -256
- package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +222 -324
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +0 -2
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/containers/ConversationView.js +487 -888
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +243 -547
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +409 -1364
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/package.json +4 -4
- package/src/screens/inbox/components/CachedImage/index.tsx +191 -140
- package/src/screens/inbox/components/DialogsListItem.tsx +112 -345
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +316 -437
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +2 -4
- package/src/screens/inbox/containers/ConversationView.tsx +676 -993
- package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
- package/src/screens/inbox/containers/Dialogs.tsx +345 -636
- package/src/screens/inbox/containers/ThreadConversationView.tsx +661 -1887
- 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
|
@@ -13,16 +13,25 @@ import {
|
|
|
13
13
|
Image,
|
|
14
14
|
Spinner,
|
|
15
15
|
Text,
|
|
16
|
+
Skeleton,
|
|
16
17
|
} from '@admin-layout/gluestack-ui-mobile';
|
|
17
|
-
import { Platform,
|
|
18
|
+
import { Platform, TouchableHighlight, View } from 'react-native';
|
|
18
19
|
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
|
19
20
|
import { useSelector } from 'react-redux';
|
|
20
21
|
import { orderBy, startCase, uniqBy } from 'lodash-es';
|
|
21
22
|
import * as ImagePicker from 'expo-image-picker';
|
|
22
23
|
import { encode as atob } from 'base-64';
|
|
23
24
|
import { Ionicons, MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons';
|
|
24
|
-
import { Actions, GiftedChat, IMessage, MessageText, Send,
|
|
25
|
-
import {
|
|
25
|
+
import { Actions, GiftedChat, IMessage, MessageText, Send, InputToolbar, Composer } from 'react-native-gifted-chat';
|
|
26
|
+
import {
|
|
27
|
+
IPost,
|
|
28
|
+
IPostThread,
|
|
29
|
+
PreDefinedRole,
|
|
30
|
+
IExpoNotificationData,
|
|
31
|
+
IFileInfo,
|
|
32
|
+
FileRefType,
|
|
33
|
+
PostTypeEnum,
|
|
34
|
+
} from 'common';
|
|
26
35
|
import {
|
|
27
36
|
OnThreadChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
|
|
28
37
|
useCreatePostThreadMutation,
|
|
@@ -40,19 +49,6 @@ import { config } from '../config';
|
|
|
40
49
|
import { ImageViewerModal, SlackMessage } from '../components/SlackMessageContainer';
|
|
41
50
|
import CachedImage from '../components/CachedImage';
|
|
42
51
|
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
52
|
|
|
57
53
|
const {
|
|
58
54
|
MESSAGES_PER_PAGE,
|
|
@@ -64,383 +60,12 @@ const {
|
|
|
64
60
|
|
|
65
61
|
const createdAtText = (value: string) => {
|
|
66
62
|
if (!value) return '';
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
}
|
|
63
|
+
let date = new Date(value);
|
|
64
|
+
if (isToday(date)) return 'Today';
|
|
65
|
+
if (isYesterday(date)) return 'Yesterday';
|
|
66
|
+
return format(new Date(value), 'MMM dd, yyyy');
|
|
84
67
|
};
|
|
85
68
|
|
|
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
69
|
interface IMessageProps extends IMessage {
|
|
445
70
|
type: string;
|
|
446
71
|
propsConfiguration?: any;
|
|
@@ -461,148 +86,48 @@ interface IThreadSubscriptionHandlerProps {
|
|
|
461
86
|
channelId: string;
|
|
462
87
|
}
|
|
463
88
|
|
|
464
|
-
|
|
89
|
+
// Define an extended interface for ImagePickerAsset with url property
|
|
90
|
+
interface ExtendedImagePickerAsset extends ImagePicker.ImagePickerAsset {
|
|
91
|
+
url?: string;
|
|
92
|
+
fileName?: string;
|
|
93
|
+
mimeType?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParentIdThread, role }: any) => {
|
|
465
97
|
const { params } = useRoute<any>();
|
|
466
98
|
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
|
-
|
|
99
|
+
const [channelMessages, setChannelMessages] = useState<any>([]);
|
|
527
100
|
const auth: any = useSelector(userSelector);
|
|
101
|
+
const [totalCount, setTotalCount] = useState<any>(0);
|
|
528
102
|
const [selectedImage, setImage] = useState<string>('');
|
|
103
|
+
const [loadingOldMessages, setLoadingOldMessages] = useState<boolean>(false);
|
|
104
|
+
const [loadEarlierMsg, setLoadEarlierMsg] = useState(false);
|
|
529
105
|
const navigation = useNavigation<any>();
|
|
530
106
|
const [files, setFiles] = useState<File[]>([]);
|
|
531
|
-
const [images, setImages] = useState<
|
|
107
|
+
const [images, setImages] = useState<ExtendedImagePickerAsset[]>([]);
|
|
108
|
+
const [msg, setMsg] = useState<string>('');
|
|
109
|
+
const [loading, setLoading] = useState(false);
|
|
110
|
+
const [imageLoading, setImageLoading] = useState(false);
|
|
111
|
+
const [uploadingMessageId, setUploadingMessageId] = useState<string | null>(null);
|
|
112
|
+
const [expoTokens, setExpoTokens] = useState<any[]>([]);
|
|
532
113
|
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
533
114
|
const [imageObject, setImageObject] = useState<any>({});
|
|
534
115
|
const [parentId, setParentId] = useState<any>(postParentId);
|
|
535
|
-
const [
|
|
116
|
+
const [postThread, setPostThread] = useState<IPostThread>();
|
|
117
|
+
const { startUpload } = useUploadFilesNative();
|
|
118
|
+
const [threadPost, setThreadPost] = useState<any[]>([]);
|
|
119
|
+
const [isScrollToBottom, setIsScrollToBottom] = useState(false);
|
|
536
120
|
const threadMessageListRef = useRef<any>(null);
|
|
537
121
|
|
|
538
122
|
// const [sendThreadMessage] = useSendThreadMessageMutation();
|
|
539
123
|
const [sendThreadMessage] = useCreatePostThreadMutation();
|
|
540
124
|
const [sendExpoNotificationOnPostMutation] = useSendExpoNotificationOnPostMutation();
|
|
541
|
-
const { startUpload } = useUploadFilesNative();
|
|
542
125
|
|
|
543
126
|
const [
|
|
544
127
|
getThreadMessages,
|
|
545
128
|
{ data, loading: threadLoading, fetchMore: fetchMoreMessages, refetch: refetchThreadMessages, subscribeToMore },
|
|
546
129
|
] = useGetPostThreadLazyQuery({ fetchPolicy: 'cache-and-network' });
|
|
547
130
|
|
|
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
131
|
useFocusEffect(
|
|
607
132
|
React.useCallback(() => {
|
|
608
133
|
// navigation?.setOptions({ title: params?.title ?? 'Thread' });
|
|
@@ -614,118 +139,40 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
614
139
|
<MaterialIcons size={20} name="arrow-back-ios" color={'black'} />
|
|
615
140
|
</Button>
|
|
616
141
|
),
|
|
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
142
|
});
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
postParentId,
|
|
631
|
-
role,
|
|
632
|
-
},
|
|
143
|
+
if (postParentId) {
|
|
144
|
+
refetchThreadMessages({
|
|
145
|
+
channelId: channelId?.toString(),
|
|
146
|
+
role: role?.toString(),
|
|
147
|
+
postParentId: postParentId?.toString(),
|
|
148
|
+
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
149
|
+
limit: MESSAGES_PER_PAGE,
|
|
633
150
|
});
|
|
634
151
|
}
|
|
635
|
-
|
|
636
152
|
setParentId(postParentId);
|
|
637
153
|
|
|
638
154
|
return () => {
|
|
639
|
-
|
|
155
|
+
setTotalCount(0);
|
|
156
|
+
setChannelMessages([]);
|
|
157
|
+
setThreadPost([]);
|
|
640
158
|
};
|
|
641
|
-
}, [postParentId
|
|
159
|
+
}, [postParentId]),
|
|
642
160
|
);
|
|
643
161
|
|
|
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
162
|
useEffect(() => {
|
|
653
|
-
if (
|
|
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(() => {
|
|
163
|
+
//if (parentId && parentId == 0) {
|
|
678
164
|
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
165
|
getThreadMessages({
|
|
688
166
|
variables: {
|
|
689
167
|
channelId: channelId?.toString(),
|
|
690
168
|
role: role?.toString(),
|
|
691
169
|
postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
|
|
692
170
|
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
693
|
-
limit:
|
|
171
|
+
limit: MESSAGES_PER_PAGE,
|
|
694
172
|
},
|
|
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
|
-
});
|
|
173
|
+
});
|
|
727
174
|
}
|
|
728
|
-
}, [
|
|
175
|
+
}, [parentId]);
|
|
729
176
|
|
|
730
177
|
React.useEffect(() => {
|
|
731
178
|
if (data?.getPostThread) {
|
|
@@ -733,684 +180,353 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
733
180
|
const threadPost = threads?.post ?? [];
|
|
734
181
|
const threadReplies = threads?.replies ?? [];
|
|
735
182
|
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')]);
|
|
183
|
+
const messages = [threadPost, ...threadReplies];
|
|
755
184
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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;
|
|
185
|
+
if (
|
|
186
|
+
(messages && messages.length > 0 && messeageTotalCount > totalCount) ||
|
|
187
|
+
(messages && messages.length > 0 && channelMessages.length === 0)
|
|
188
|
+
) {
|
|
189
|
+
setThreadMessages(messages, messeageTotalCount);
|
|
190
|
+
}
|
|
788
191
|
}
|
|
789
|
-
|
|
192
|
+
if (isScrollToBottom && channelMessages) scrollToBottom();
|
|
193
|
+
// scrollToBottom();
|
|
194
|
+
// if (!isPostParentIdThread) {
|
|
195
|
+
// // setTotalCount((pc: any) => pc + threadTotalCount);
|
|
196
|
+
// setChannelMessages((oldMessages: any) => uniqBy([...threadMessage, ...oldMessages], ({ id }) => id));
|
|
197
|
+
// }
|
|
198
|
+
}, [data, channelMessages, loadingOldMessages, totalCount, isPostParentIdThread, isScrollToBottom]);
|
|
199
|
+
|
|
200
|
+
const setThreadMessages = (messages: any, messagesTotalCount: number) => {
|
|
201
|
+
setChannelMessages((oldMessages: any) => uniqBy([...messages, ...oldMessages], ({ id }) => id));
|
|
202
|
+
setTotalCount(messagesTotalCount);
|
|
203
|
+
};
|
|
790
204
|
|
|
791
|
-
// Watch for failed load attempts and auto-fix the count after 3 consecutive failures
|
|
792
205
|
React.useEffect(() => {
|
|
793
|
-
|
|
794
|
-
|
|
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')]);
|
|
206
|
+
if (selectedImage) setImageLoading(false);
|
|
207
|
+
}, [selectedImage]);
|
|
823
208
|
|
|
824
209
|
const scrollToBottom = React.useCallback(() => {
|
|
825
210
|
if (threadMessageListRef?.current) {
|
|
826
|
-
|
|
211
|
+
setIsScrollToBottom(false);
|
|
212
|
+
threadMessageListRef.current.scrollTop = threadMessageListRef.current.scrollHeight;
|
|
827
213
|
}
|
|
828
214
|
}, [threadMessageListRef]);
|
|
829
215
|
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
216
|
+
const onFetchOld = useCallback(() => {
|
|
217
|
+
if (totalCount > channelMessages.length && !loadingOldMessages) {
|
|
218
|
+
setLoadEarlierMsg(true);
|
|
219
|
+
fetchMoreMessages({
|
|
220
|
+
variables: {
|
|
221
|
+
channelId: channelId?.toString(),
|
|
222
|
+
role: role?.toString(),
|
|
223
|
+
postParentId: parentId?.toString(),
|
|
224
|
+
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
225
|
+
limit: MESSAGES_PER_PAGE,
|
|
226
|
+
skip: channelMessages.length - 1,
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
.then((res: any) => {
|
|
230
|
+
if (res?.data?.getPostThread) {
|
|
231
|
+
const threads: any = res?.data?.getPostThread;
|
|
232
|
+
const threadReplies = threads?.replies ?? [];
|
|
233
|
+
const messeageTotalCount = threads?.replyCount ?? 0;
|
|
234
|
+
setThreadMessages(threadReplies, messeageTotalCount);
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
.finally(() => {
|
|
238
|
+
setLoadEarlierMsg(false);
|
|
239
|
+
setLoadingOldMessages(false);
|
|
240
|
+
})
|
|
241
|
+
.catch((error: any) => {
|
|
242
|
+
setLoadEarlierMsg(false);
|
|
243
|
+
setLoadingOldMessages(false);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}, [parentId, channelId, totalCount, channelMessages]);
|
|
836
247
|
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
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
|
-
);
|
|
248
|
+
// const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
249
|
+
// return contentOffset.y <= 100; // 100px from top
|
|
250
|
+
// };
|
|
859
251
|
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
252
|
+
const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
253
|
+
const paddingToTop = 60;
|
|
254
|
+
return contentSize.height - layoutMeasurement.height - paddingToTop <= contentOffset.y;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const dataURLtoFile = (dataurl: any, filename: any) => {
|
|
258
|
+
var arr = dataurl.split(','),
|
|
259
|
+
mime = arr[0].match(/:(.*?);/)[1],
|
|
260
|
+
bstr = atob(arr[1]),
|
|
261
|
+
n = bstr.length,
|
|
262
|
+
u8arr = new Uint8Array(n);
|
|
263
|
+
while (n--) {
|
|
264
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
265
|
+
}
|
|
266
|
+
return new File([u8arr], filename, { type: mime });
|
|
863
267
|
};
|
|
864
268
|
|
|
865
269
|
const onSelectImages = async () => {
|
|
866
|
-
|
|
867
|
-
safeSend({ type: ThreadActions.START_LOADING });
|
|
270
|
+
setImageLoading(true);
|
|
868
271
|
|
|
869
|
-
|
|
272
|
+
try {
|
|
273
|
+
let imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
870
274
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
871
275
|
allowsEditing: true,
|
|
872
276
|
aspect: [4, 3],
|
|
873
|
-
quality: 0.8,
|
|
277
|
+
quality: 0.8,
|
|
874
278
|
base64: true,
|
|
875
|
-
|
|
279
|
+
exif: false,
|
|
876
280
|
});
|
|
877
281
|
|
|
878
|
-
if (imageSource
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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}`;
|
|
282
|
+
if (!imageSource?.canceled) {
|
|
283
|
+
// Get the asset
|
|
284
|
+
const selectedAsset = imageSource?.assets?.[0];
|
|
285
|
+
if (!selectedAsset) {
|
|
286
|
+
setImageLoading(false);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
901
289
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
290
|
+
// Create a base64 image string for preview
|
|
291
|
+
const base64Data = selectedAsset.base64;
|
|
292
|
+
const previewImage = base64Data ? `data:image/jpeg;base64,${base64Data}` : selectedAsset.uri;
|
|
905
293
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
console.log(`Selected image: ${filename}, type: ${mimeType}`);
|
|
294
|
+
// Format the asset for upload service requirements
|
|
295
|
+
const asset: ExtendedImagePickerAsset = {
|
|
296
|
+
...selectedAsset,
|
|
297
|
+
url: selectedAsset.uri,
|
|
298
|
+
fileName: selectedAsset.fileName || `image_${Date.now()}.jpg`,
|
|
299
|
+
mimeType: 'image/jpeg',
|
|
300
|
+
};
|
|
915
301
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
});
|
|
302
|
+
// Update state with the new image
|
|
303
|
+
setImage(previewImage);
|
|
304
|
+
setImages([asset]);
|
|
305
|
+
setImageLoading(false);
|
|
306
|
+
} else {
|
|
307
|
+
setImageLoading(false);
|
|
308
|
+
}
|
|
924
309
|
} catch (error) {
|
|
925
310
|
console.error('Error selecting image:', error);
|
|
926
|
-
|
|
927
|
-
type: 'ERROR',
|
|
928
|
-
data: { message: error.message || 'Failed to select image' },
|
|
929
|
-
});
|
|
311
|
+
setImageLoading(false);
|
|
930
312
|
}
|
|
931
313
|
};
|
|
932
314
|
|
|
933
|
-
|
|
934
|
-
const sendThreadMessageHandler = useCallback(
|
|
315
|
+
const handleSend = useCallback(
|
|
935
316
|
async (message: string) => {
|
|
936
|
-
|
|
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);
|
|
317
|
+
if (!channelId) return;
|
|
318
|
+
if (!message && message !== ' ' && images.length === 0) return;
|
|
947
319
|
|
|
948
320
|
const postId = objectId();
|
|
949
|
-
|
|
321
|
+
const currentDate = new Date();
|
|
322
|
+
|
|
323
|
+
setUploadingMessageId(postId);
|
|
324
|
+
|
|
325
|
+
// Create a temporary preview of the image if uploading
|
|
326
|
+
const previewImageData =
|
|
327
|
+
images.length > 0
|
|
328
|
+
? {
|
|
329
|
+
id: objectId(),
|
|
330
|
+
url: selectedImage,
|
|
331
|
+
name: images[0]?.fileName || 'image.jpg',
|
|
332
|
+
extension: 'jpg',
|
|
333
|
+
mimeType: 'image/jpeg',
|
|
334
|
+
refType: FileRefType.Post,
|
|
335
|
+
height: images[0]?.height || 0,
|
|
336
|
+
width: images[0]?.width || 0,
|
|
337
|
+
}
|
|
338
|
+
: null;
|
|
339
|
+
|
|
340
|
+
// Create optimistic message with common properties
|
|
341
|
+
const optimisticMessage = {
|
|
342
|
+
id: postId,
|
|
343
|
+
message: message || (images.length > 0 ? ' ' : ''),
|
|
344
|
+
createdAt: currentDate.toISOString(),
|
|
345
|
+
author: {
|
|
346
|
+
id: auth?.profile?.id,
|
|
347
|
+
givenName: auth?.profile?.given_name,
|
|
348
|
+
familyName: auth?.profile?.family_name,
|
|
349
|
+
picture: auth?.profile?.picture,
|
|
350
|
+
},
|
|
351
|
+
isDelivered: false,
|
|
352
|
+
isRead: false,
|
|
353
|
+
files: previewImageData
|
|
354
|
+
? {
|
|
355
|
+
data: [previewImageData],
|
|
356
|
+
totalCount: 1,
|
|
357
|
+
}
|
|
358
|
+
: undefined,
|
|
359
|
+
};
|
|
950
360
|
|
|
951
|
-
|
|
361
|
+
// Add optimistic message to UI immediately
|
|
362
|
+
// setChannelMessages((oldMessages: any) => uniqBy([optimisticMessage, ...oldMessages], ({ id }) => id));
|
|
363
|
+
setMsg('');
|
|
952
364
|
|
|
953
365
|
try {
|
|
954
|
-
|
|
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
|
-
}
|
|
366
|
+
let fileIds: string[] | null = null;
|
|
979
367
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
368
|
+
// Handle file uploads if images exist
|
|
369
|
+
if (images && images.length > 0) {
|
|
370
|
+
setLoading(true);
|
|
371
|
+
// Format images for upload
|
|
372
|
+
const imagesToUpload = images.map((img) => {
|
|
373
|
+
return {
|
|
374
|
+
...img,
|
|
375
|
+
uri: img.uri || img.url, // Use either uri or url
|
|
376
|
+
type: img.mimeType || 'image/jpeg',
|
|
377
|
+
name: img.fileName || `image_${Date.now()}.jpg`,
|
|
378
|
+
};
|
|
379
|
+
});
|
|
984
380
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
messageText: '', // Clear the message text now
|
|
381
|
+
const uploadResponse = await startUpload({
|
|
382
|
+
file: imagesToUpload as unknown as ImagePicker.ImagePickerAsset[],
|
|
383
|
+
saveUploadedFile: {
|
|
384
|
+
variables: {
|
|
385
|
+
postId,
|
|
991
386
|
},
|
|
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
387
|
},
|
|
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,
|
|
388
|
+
createUploadLink: {
|
|
389
|
+
variables: {
|
|
390
|
+
postId,
|
|
1091
391
|
},
|
|
1092
392
|
},
|
|
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
393
|
});
|
|
1126
394
|
|
|
1127
|
-
|
|
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
|
-
);
|
|
395
|
+
if (uploadResponse?.error) throw new Error(uploadResponse.error.toString());
|
|
1318
396
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
397
|
+
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
398
|
+
if (uploadResponse.data) {
|
|
399
|
+
// Clear image related states after successful upload
|
|
400
|
+
setImage('');
|
|
401
|
+
setFiles([]);
|
|
402
|
+
setImages([]);
|
|
1322
403
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
404
|
+
fileIds = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
1326
407
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
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
|
-
/>
|
|
408
|
+
// Send message with Apollo mutation
|
|
409
|
+
await sendThreadMessage({
|
|
410
|
+
variables: {
|
|
411
|
+
postId,
|
|
412
|
+
channelId,
|
|
413
|
+
postThreadId: postThread && postThread?.id,
|
|
414
|
+
postParentId: !parentId || parentId === 0 ? null : parentId,
|
|
415
|
+
threadMessageInput: {
|
|
416
|
+
content: message,
|
|
417
|
+
files: fileIds,
|
|
418
|
+
role,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
update: (cache, { data, errors }: any) => {
|
|
422
|
+
if (!data || errors) return;
|
|
1359
423
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
424
|
+
setPostThread(data?.createPostThread?.data);
|
|
425
|
+
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
426
|
+
if (!parentId || parentId === 0) {
|
|
427
|
+
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
428
|
+
}
|
|
429
|
+
setChannelToTop(channelToTop + 1);
|
|
430
|
+
|
|
431
|
+
// Send push notification with message info
|
|
432
|
+
sendPushNotification(lastMessageId, channelId, parentId, data?.createPostThread?.data?.id);
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Update the optimistic message to show it's delivered
|
|
437
|
+
setChannelMessages((oldMessages: any) =>
|
|
438
|
+
oldMessages.map((msg: any) => (msg.id === postId ? { ...msg, isDelivered: true } : msg)),
|
|
439
|
+
);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error('Error sending message:', error);
|
|
442
|
+
// Remove the optimistic message if there was an error
|
|
443
|
+
setChannelMessages((oldMessages: any) => oldMessages.filter((msg) => msg.id !== postId));
|
|
444
|
+
} finally {
|
|
445
|
+
setLoading(false);
|
|
446
|
+
setUploadingMessageId(null);
|
|
447
|
+
}
|
|
1381
448
|
},
|
|
1382
|
-
[
|
|
449
|
+
[auth, channelId, channelToTop, images, parentId, postThread, selectedImage, setChannelMessages],
|
|
1383
450
|
);
|
|
1384
451
|
|
|
1385
|
-
const
|
|
452
|
+
const sendPushNotification = async (messageId: string, channelId: string, parentId: any, threadId: string) => {
|
|
453
|
+
const notificationData: IExpoNotificationData = {
|
|
454
|
+
url: config.THREAD_MESSEGE_PATH,
|
|
455
|
+
params: { channelId, title: params?.title ?? 'Thread', postParentId: parentId, hideTabBar: true },
|
|
456
|
+
screen: 'DialogThreadMessages',
|
|
457
|
+
thread: { id: threadId },
|
|
458
|
+
other: { sound: Platform.OS === 'android' ? undefined : 'default' },
|
|
459
|
+
};
|
|
460
|
+
if (parentId || parentId == 0) {
|
|
461
|
+
await sendExpoNotificationOnPostMutation({
|
|
462
|
+
variables: {
|
|
463
|
+
postId: messageId?.toString(),
|
|
464
|
+
notificationData,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const messageList = useMemo(() => {
|
|
471
|
+
let currentDate = '';
|
|
472
|
+
let res: any = [];
|
|
473
|
+
const filteredMessages =
|
|
474
|
+
channelMessages && channelMessages?.length > 0 ? uniqBy([...channelMessages], ({ id }: any) => id) : [];
|
|
475
|
+
if (filteredMessages?.length) {
|
|
476
|
+
orderBy(filteredMessages, ['createdAt'], ['desc']).map((msg) => {
|
|
477
|
+
let message: IMessageProps = {
|
|
478
|
+
_id: '',
|
|
479
|
+
text: '',
|
|
480
|
+
createdAt: 0,
|
|
481
|
+
user: {
|
|
482
|
+
_id: '',
|
|
483
|
+
name: '',
|
|
484
|
+
avatar: '',
|
|
485
|
+
},
|
|
486
|
+
type: '',
|
|
487
|
+
};
|
|
488
|
+
const date = new Date(msg.createdAt);
|
|
489
|
+
message._id = msg.id;
|
|
490
|
+
message.text = msg.message;
|
|
491
|
+
message.createdAt = date;
|
|
492
|
+
(message.user = {
|
|
493
|
+
_id: msg?.author?.id ?? auth?.profile?.id,
|
|
494
|
+
name:
|
|
495
|
+
msg?.author?.givenName ??
|
|
496
|
+
auth?.profile?.given_name + ' ' + msg?.author?.familyName ??
|
|
497
|
+
auth?.profile?.family_name,
|
|
498
|
+
avatar: msg?.author?.picture ?? auth?.profile?.picture,
|
|
499
|
+
}),
|
|
500
|
+
(message.image = msg.files?.data[0]?.url),
|
|
501
|
+
(message.sent = msg?.isDelivered),
|
|
502
|
+
(message.received = msg?.isRead);
|
|
503
|
+
message.type = msg?.type;
|
|
504
|
+
message.propsConfiguration = msg?.propsConfiguration;
|
|
505
|
+
res.push(message);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return res?.length > 0 ? uniqBy([...res], ({ _id }: any) => _id) : [];
|
|
509
|
+
//return res;
|
|
510
|
+
}, [channelMessages]);
|
|
511
|
+
|
|
512
|
+
const renderSend = useCallback((props) => {
|
|
1386
513
|
return (
|
|
1387
|
-
<
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
alignItems: 'center',
|
|
1398
|
-
}}
|
|
1399
|
-
/>
|
|
514
|
+
<Send {...props}>
|
|
515
|
+
<Box>
|
|
516
|
+
<MaterialCommunityIcons
|
|
517
|
+
name="send-circle"
|
|
518
|
+
style={{ marginBottom: 5, marginRight: 5 }}
|
|
519
|
+
size={32}
|
|
520
|
+
color="#2e64e5"
|
|
521
|
+
/>
|
|
522
|
+
</Box>
|
|
523
|
+
</Send>
|
|
1400
524
|
);
|
|
1401
525
|
}, []);
|
|
1402
526
|
|
|
1403
|
-
const setImageViewerObject = (obj: any, v: boolean) => {
|
|
1404
|
-
setImageObject(obj);
|
|
1405
|
-
setImageViewer(v);
|
|
1406
|
-
};
|
|
1407
|
-
|
|
1408
|
-
// Add back the renderMessageText function (memoized)
|
|
1409
527
|
const renderMessageText = useCallback(
|
|
1410
528
|
(props: any) => {
|
|
1411
529
|
const { currentMessage } = props;
|
|
1412
|
-
|
|
1413
|
-
// For ALERT type messages with call to action
|
|
1414
530
|
if (currentMessage.type === 'ALERT') {
|
|
1415
531
|
const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
|
|
1416
532
|
let action: string = '';
|
|
@@ -1466,30 +582,212 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
1466
582
|
)}
|
|
1467
583
|
</>
|
|
1468
584
|
);
|
|
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 {
|
|
585
|
+
} else {
|
|
1477
586
|
return <MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />;
|
|
1478
587
|
}
|
|
1479
588
|
},
|
|
1480
|
-
[
|
|
589
|
+
[navigation, role],
|
|
1481
590
|
);
|
|
1482
591
|
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
);
|
|
592
|
+
// const renderActions = useCallback((props) => {
|
|
593
|
+
// return (
|
|
594
|
+
// <Actions
|
|
595
|
+
// {...props}
|
|
596
|
+
// icon={() => <Ionicons name={'image'} size={30} color={'black'} onPress={onSelectImages} />}
|
|
597
|
+
// />
|
|
598
|
+
// );
|
|
599
|
+
// }, [onSelectImages]);
|
|
600
|
+
|
|
601
|
+
// Render action buttons (including image upload)
|
|
602
|
+
const renderActions = (props) => {
|
|
603
|
+
return (
|
|
604
|
+
<Actions
|
|
605
|
+
{...props}
|
|
606
|
+
options={{
|
|
607
|
+
['Choose from Library']: onSelectImages,
|
|
608
|
+
['Cancel']: () => {}, // Add this option to make the sheet dismissible
|
|
609
|
+
}}
|
|
610
|
+
optionTintColor="#000000"
|
|
611
|
+
cancelButtonIndex={1} // Set the Cancel option as the cancel button
|
|
612
|
+
icon={() => (
|
|
613
|
+
<Box className="w-8 h-8 items-center justify-center">
|
|
614
|
+
<Ionicons name="image" size={24} color={colors.blue[500]} />
|
|
615
|
+
</Box>
|
|
616
|
+
)}
|
|
617
|
+
containerStyle={{
|
|
618
|
+
alignItems: 'center',
|
|
619
|
+
justifyContent: 'center',
|
|
620
|
+
marginLeft: 8,
|
|
621
|
+
marginBottom: 0,
|
|
622
|
+
}}
|
|
623
|
+
/>
|
|
624
|
+
);
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// const renderAccessory = useCallback(() => {
|
|
628
|
+
// if (!selectedImage) return null;
|
|
629
|
+
|
|
630
|
+
// return (
|
|
631
|
+
// <Box className="bg-white border-t border-gray-200 px-2 py-2">
|
|
632
|
+
// <HStack className="items-center justify-between">
|
|
633
|
+
// <HStack space="sm" className="items-center">
|
|
634
|
+
// {imageLoading ? (
|
|
635
|
+
// <Box className="w-12 h-12 rounded-md bg-gray-100 overflow-hidden flex items-center justify-center">
|
|
636
|
+
// <Spinner size="small" color={colors.blue[500]} />
|
|
637
|
+
// </Box>
|
|
638
|
+
// ) : (
|
|
639
|
+
// <Box className="relative">
|
|
640
|
+
// <Image
|
|
641
|
+
// className="rounded-md"
|
|
642
|
+
// key={selectedImage}
|
|
643
|
+
// alt={'Selected image'}
|
|
644
|
+
// source={{ uri: selectedImage }}
|
|
645
|
+
// style={{
|
|
646
|
+
// width: 48,
|
|
647
|
+
// height: 48,
|
|
648
|
+
// borderRadius: 4,
|
|
649
|
+
// }}
|
|
650
|
+
// />
|
|
651
|
+
// <Box className="absolute -top-1 -right-1 bg-white rounded-full shadow-sm">
|
|
652
|
+
// <Button
|
|
653
|
+
// variant="link"
|
|
654
|
+
// size="xs"
|
|
655
|
+
// onPress={() => {
|
|
656
|
+
// setFiles([]);
|
|
657
|
+
// setImage('');
|
|
658
|
+
// setImages([]);
|
|
659
|
+
// }}
|
|
660
|
+
// >
|
|
661
|
+
// <MaterialIcons name="cancel" size={16} color={colors.red[500]} />
|
|
662
|
+
// </Button>
|
|
663
|
+
// </Box>
|
|
664
|
+
// </Box>
|
|
665
|
+
// )}
|
|
666
|
+
// <Text className="text-sm text-gray-600 ml-2">
|
|
667
|
+
// {imageLoading ? 'Processing image...' : 'Ready to send'}
|
|
668
|
+
// </Text>
|
|
669
|
+
// </HStack>
|
|
670
|
+
// </HStack>
|
|
671
|
+
// </Box>
|
|
672
|
+
// );
|
|
673
|
+
// }, [selectedImage, imageLoading]);
|
|
674
|
+
|
|
675
|
+
const renderAccessory = useCallback(() => {
|
|
676
|
+
if (!selectedImage) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return (
|
|
681
|
+
<View
|
|
682
|
+
style={{
|
|
683
|
+
height: 70,
|
|
684
|
+
backgroundColor: 'white',
|
|
685
|
+
borderTopWidth: 1,
|
|
686
|
+
borderTopColor: '#e0e0e0',
|
|
687
|
+
flexDirection: 'row',
|
|
688
|
+
alignItems: 'center',
|
|
689
|
+
margin: 0,
|
|
690
|
+
padding: 0,
|
|
691
|
+
paddingVertical: 0,
|
|
692
|
+
position: 'absolute',
|
|
693
|
+
bottom: Platform.OS === 'ios' ? 105 : 95, // Position well above the input area
|
|
694
|
+
left: 0,
|
|
695
|
+
right: 0,
|
|
696
|
+
zIndex: 1,
|
|
697
|
+
elevation: 3,
|
|
698
|
+
shadowColor: '#000',
|
|
699
|
+
shadowOffset: { width: 0, height: -1 },
|
|
700
|
+
shadowOpacity: 0.05,
|
|
701
|
+
shadowRadius: 2,
|
|
702
|
+
}}
|
|
703
|
+
>
|
|
704
|
+
<View
|
|
705
|
+
style={{
|
|
706
|
+
flex: 1,
|
|
707
|
+
flexDirection: 'row',
|
|
708
|
+
alignItems: 'center',
|
|
709
|
+
paddingLeft: 15,
|
|
710
|
+
paddingRight: 5,
|
|
711
|
+
}}
|
|
712
|
+
>
|
|
713
|
+
<View
|
|
714
|
+
style={{
|
|
715
|
+
width: 56,
|
|
716
|
+
height: 56,
|
|
717
|
+
marginRight: 15,
|
|
718
|
+
borderRadius: 4,
|
|
719
|
+
backgroundColor: colors.gray[200],
|
|
720
|
+
overflow: 'hidden',
|
|
721
|
+
borderWidth: 1,
|
|
722
|
+
borderColor: '#e0e0e0',
|
|
723
|
+
}}
|
|
724
|
+
>
|
|
725
|
+
<Image
|
|
726
|
+
key={selectedImage}
|
|
727
|
+
alt={'selected image'}
|
|
728
|
+
source={{ uri: selectedImage }}
|
|
729
|
+
style={{
|
|
730
|
+
width: '100%',
|
|
731
|
+
height: '100%',
|
|
732
|
+
}}
|
|
733
|
+
size={'md'}
|
|
734
|
+
/>
|
|
735
|
+
{loading && (
|
|
736
|
+
<View
|
|
737
|
+
style={{
|
|
738
|
+
position: 'absolute',
|
|
739
|
+
top: 0,
|
|
740
|
+
left: 0,
|
|
741
|
+
right: 0,
|
|
742
|
+
bottom: 0,
|
|
743
|
+
backgroundColor: 'rgba(255, 255, 255, 0.7)',
|
|
744
|
+
justifyContent: 'center',
|
|
745
|
+
alignItems: 'center',
|
|
746
|
+
}}
|
|
747
|
+
>
|
|
748
|
+
<Spinner size="small" color={colors.blue[500]} />
|
|
749
|
+
</View>
|
|
750
|
+
)}
|
|
751
|
+
</View>
|
|
752
|
+
|
|
753
|
+
<View style={{ flex: 1 }}>
|
|
754
|
+
<Text style={{ fontSize: 14, fontWeight: '400', color: colors.gray[800] }}>
|
|
755
|
+
{images[0]?.fileName || 'image_' + new Date().getTime() + '.jpg'}
|
|
756
|
+
</Text>
|
|
757
|
+
<Text style={{ fontSize: 12, color: colors.gray[500], marginTop: 2 }}>
|
|
758
|
+
{loading ? 'Preparing...' : 'Ready to send'}
|
|
759
|
+
</Text>
|
|
760
|
+
</View>
|
|
761
|
+
|
|
762
|
+
<TouchableHighlight
|
|
763
|
+
underlayColor={'rgba(0,0,0,0.1)'}
|
|
764
|
+
onPress={() => {
|
|
765
|
+
setFiles([]);
|
|
766
|
+
setImage('');
|
|
767
|
+
setImages([]);
|
|
768
|
+
}}
|
|
769
|
+
style={{
|
|
770
|
+
backgroundColor: colors.red[500],
|
|
771
|
+
borderRadius: 24,
|
|
772
|
+
width: 36,
|
|
773
|
+
height: 36,
|
|
774
|
+
alignItems: 'center',
|
|
775
|
+
justifyContent: 'center',
|
|
776
|
+
marginRight: 10,
|
|
777
|
+
}}
|
|
778
|
+
>
|
|
779
|
+
<Ionicons name="close" size={20} color="white" />
|
|
780
|
+
</TouchableHighlight>
|
|
781
|
+
</View>
|
|
782
|
+
</View>
|
|
783
|
+
);
|
|
784
|
+
}, [selectedImage, loading, images]);
|
|
785
|
+
const setImageViewerObject = useCallback((obj: any, v: boolean) => {
|
|
786
|
+
setImageObject(obj);
|
|
787
|
+
setImageViewer(v);
|
|
788
|
+
}, []);
|
|
1491
789
|
|
|
1492
|
-
const modalContent =
|
|
790
|
+
const modalContent = useMemo(() => {
|
|
1493
791
|
if (!imageObject) return <></>;
|
|
1494
792
|
const { image, _id } = imageObject;
|
|
1495
793
|
return (
|
|
@@ -1506,515 +804,101 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
1506
804
|
);
|
|
1507
805
|
}, [imageObject]);
|
|
1508
806
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
-
});
|
|
807
|
+
const renderMessage = useCallback(
|
|
808
|
+
(props: any) => {
|
|
809
|
+
const { currentMessage } = props;
|
|
1525
810
|
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
);
|
|
811
|
+
// Check if this message is currently being uploaded
|
|
812
|
+
const isUploading = currentMessage._id === uploadingMessageId && loading;
|
|
1570
813
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
814
|
+
if (isUploading) {
|
|
815
|
+
return (
|
|
816
|
+
<Box className="p-3 mb-2 mr-3 self-end rounded-2xl bg-gray-100 max-w-[80%]">
|
|
817
|
+
{currentMessage.text && currentMessage.text.trim() !== '' && (
|
|
818
|
+
<Box className="h-4 mb-2 rounded bg-gray-200 overflow-hidden">
|
|
819
|
+
<Skeleton variant="rounded" className="flex-1" />
|
|
820
|
+
</Box>
|
|
821
|
+
)}
|
|
822
|
+
{currentMessage.image && (
|
|
823
|
+
<Box className="h-[150px] w-[150px] rounded-lg bg-gray-200 overflow-hidden mt-1">
|
|
824
|
+
<Box className="flex-1 items-center justify-center">
|
|
825
|
+
<Spinner size="small" color={colors.blue[500]} />
|
|
826
|
+
</Box>
|
|
827
|
+
<Skeleton variant="rounded" className="flex-1" />
|
|
828
|
+
</Box>
|
|
829
|
+
)}
|
|
830
|
+
</Box>
|
|
831
|
+
);
|
|
1584
832
|
}
|
|
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
833
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
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;
|
|
834
|
+
return (
|
|
835
|
+
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
836
|
+
);
|
|
837
|
+
},
|
|
838
|
+
[uploadingMessageId, loading, isShowImageViewer, setImageViewerObject],
|
|
839
|
+
);
|
|
1622
840
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
841
|
+
const renderInputToolbar = useCallback((props) => {
|
|
842
|
+
return (
|
|
843
|
+
<InputToolbar
|
|
844
|
+
{...props}
|
|
845
|
+
containerStyle={{
|
|
846
|
+
backgroundColor: 'white',
|
|
847
|
+
borderTopWidth: 1,
|
|
848
|
+
borderTopColor: colors.gray[200],
|
|
849
|
+
paddingHorizontal: 4,
|
|
850
|
+
paddingVertical: 0,
|
|
851
|
+
paddingTop: 2,
|
|
852
|
+
marginBottom: 0,
|
|
853
|
+
marginTop: 0,
|
|
854
|
+
}}
|
|
855
|
+
primaryStyle={{
|
|
856
|
+
alignItems: 'center',
|
|
857
|
+
}}
|
|
858
|
+
/>
|
|
859
|
+
);
|
|
860
|
+
}, []);
|
|
1628
861
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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
|
-
}
|
|
862
|
+
const renderLoadEarlier = useCallback(() => {
|
|
863
|
+
return loadingOldMessages ? (
|
|
864
|
+
<Box
|
|
865
|
+
style={{
|
|
866
|
+
padding: 10,
|
|
867
|
+
backgroundColor: 'rgba(255,255,255,0.8)',
|
|
868
|
+
borderRadius: 10,
|
|
869
|
+
marginTop: 10,
|
|
870
|
+
}}
|
|
871
|
+
>
|
|
872
|
+
<Spinner size="small" color={colors.blue[500]} />
|
|
873
|
+
</Box>
|
|
874
|
+
) : null;
|
|
875
|
+
}, [loadingOldMessages]);
|
|
1658
876
|
|
|
1659
|
-
|
|
1660
|
-
registerLoadAttemptResult(true);
|
|
877
|
+
let onScroll = false;
|
|
1661
878
|
|
|
1662
|
-
|
|
879
|
+
const onMomentumScrollBegin = ({ nativeEvent }: any) => {
|
|
880
|
+
onScroll = true;
|
|
881
|
+
if (!loadingOldMessages && isCloseToTop(nativeEvent) && totalCount > channelMessages?.length) {
|
|
882
|
+
onFetchOld();
|
|
883
|
+
}
|
|
884
|
+
};
|
|
1663
885
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
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]);
|
|
886
|
+
const onEndReached = () => {
|
|
887
|
+
if (!onScroll) return;
|
|
888
|
+
onScroll = false;
|
|
889
|
+
};
|
|
1708
890
|
|
|
1709
891
|
return (
|
|
1710
|
-
|
|
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
|
-
)}
|
|
892
|
+
<>
|
|
2007
893
|
{isPostParentIdThread && (
|
|
2008
894
|
<>
|
|
2009
|
-
{
|
|
895
|
+
{threadPost?.length > 0 && (
|
|
2010
896
|
<>
|
|
2011
897
|
<VStack className="px-2 pt-2 pb-0" space={'sm'}>
|
|
2012
898
|
<HStack space={'sm'} className="items-center">
|
|
2013
899
|
<Avatar className="bg-transparent" size={'md'}>
|
|
2014
900
|
<AvatarFallbackText>
|
|
2015
|
-
{startCase(
|
|
2016
|
-
safeContextProperty('threadPost')[0]?.author?.username?.charAt(0),
|
|
2017
|
-
)}
|
|
901
|
+
{startCase(threadPost[0]?.author?.username?.charAt(0))}
|
|
2018
902
|
</AvatarFallbackText>
|
|
2019
903
|
<AvatarImage
|
|
2020
904
|
alt="image"
|
|
@@ -2024,45 +908,31 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
2024
908
|
borderColor: '#fff',
|
|
2025
909
|
}}
|
|
2026
910
|
source={{
|
|
2027
|
-
uri:
|
|
911
|
+
uri: threadPost[0]?.author?.picture,
|
|
2028
912
|
}}
|
|
2029
913
|
/>
|
|
2030
914
|
</Avatar>
|
|
2031
915
|
<Box>
|
|
2032
916
|
<Text className="font-bold color-black">
|
|
2033
|
-
{
|
|
2034
|
-
{
|
|
917
|
+
{threadPost[0]?.author?.givenName ?? ''}{' '}
|
|
918
|
+
{threadPost[0]?.author?.familyName ?? ''}
|
|
2035
919
|
</Text>
|
|
2036
920
|
<Text className="pl-0 color-gray-500">
|
|
2037
|
-
{createdAtText(
|
|
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
|
-
})()}
|
|
921
|
+
{createdAtText(threadPost[0]?.createdAt)} at{' '}
|
|
922
|
+
{format(new Date(threadPost[0]?.createdAt), 'hh:ss:a')}
|
|
2051
923
|
</Text>
|
|
2052
924
|
</Box>
|
|
2053
925
|
</HStack>
|
|
2054
926
|
<HStack space={'sm'} className="px-2 items-center">
|
|
2055
|
-
<Text>{
|
|
927
|
+
<Text>{threadPost[0]?.message ?? ''}</Text>
|
|
2056
928
|
</HStack>
|
|
2057
929
|
</VStack>
|
|
2058
930
|
|
|
2059
931
|
<Box className="py-4">
|
|
2060
932
|
<Box className="px-4 py-2 border-t border-b border-gray-200">
|
|
2061
933
|
<Text className="font-bold color-gray-600">
|
|
2062
|
-
{
|
|
2063
|
-
{
|
|
2064
|
-
? 'replies'
|
|
2065
|
-
: 'reply'}
|
|
934
|
+
{threadPost[0]?.replies?.totalCount}{' '}
|
|
935
|
+
{threadPost[0]?.replies?.totalCount > 0 ? 'replies' : 'reply'}
|
|
2066
936
|
</Text>
|
|
2067
937
|
</Box>
|
|
2068
938
|
</Box>
|
|
@@ -2072,146 +942,60 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
2072
942
|
)}
|
|
2073
943
|
<GiftedChat
|
|
2074
944
|
ref={threadMessageListRef}
|
|
2075
|
-
wrapInSafeArea={
|
|
2076
|
-
renderLoading={() => <
|
|
945
|
+
wrapInSafeArea={true}
|
|
946
|
+
renderLoading={() => <Skeleton variant="rounded" style={{ flex: 1 }} />}
|
|
2077
947
|
messages={messageList}
|
|
2078
948
|
listViewProps={{
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
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
|
-
});
|
|
949
|
+
onEndReached: onEndReached,
|
|
950
|
+
onEndReachedThreshold: 0.5,
|
|
951
|
+
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
2141
952
|
}}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
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
|
-
}
|
|
953
|
+
onSend={(messages) => handleSend(messages[0]?.text ?? ' ')}
|
|
954
|
+
text={msg ? msg : ' '}
|
|
955
|
+
onInputTextChanged={(text) => setMsg(text)}
|
|
956
|
+
renderFooter={() => null}
|
|
2155
957
|
scrollToBottom
|
|
2156
|
-
loadEarlier={false}
|
|
2157
|
-
isLoadingEarlier={false}
|
|
2158
958
|
user={{
|
|
2159
959
|
_id: auth?.id || '',
|
|
2160
960
|
}}
|
|
2161
|
-
isTyping={
|
|
2162
|
-
alwaysShowSend={
|
|
961
|
+
isTyping={false}
|
|
962
|
+
alwaysShowSend={true}
|
|
2163
963
|
infiniteScroll={true}
|
|
2164
964
|
renderSend={renderSend}
|
|
965
|
+
renderMessageText={renderMessageText}
|
|
2165
966
|
renderInputToolbar={renderInputToolbar}
|
|
2166
967
|
minInputToolbarHeight={50}
|
|
2167
968
|
renderActions={renderActions}
|
|
2168
|
-
renderAccessory={!!
|
|
969
|
+
renderAccessory={!!selectedImage ? renderAccessory : undefined}
|
|
2169
970
|
renderMessage={renderMessage}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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
|
-
]}
|
|
971
|
+
renderLoadEarlier={renderLoadEarlier}
|
|
972
|
+
loadEarlier={totalCount > channelMessages.length}
|
|
973
|
+
isLoadingEarlier={loadingOldMessages}
|
|
974
|
+
bottomOffset={Platform.OS === 'ios' ? 10 : 0}
|
|
2193
975
|
textInputProps={{
|
|
2194
976
|
style: {
|
|
2195
977
|
borderWidth: 1,
|
|
2196
978
|
borderColor: colors.gray[300],
|
|
2197
979
|
backgroundColor: '#f8f8f8',
|
|
2198
980
|
borderRadius: 20,
|
|
2199
|
-
minHeight:
|
|
981
|
+
minHeight: 36,
|
|
2200
982
|
maxHeight: 80,
|
|
2201
983
|
color: '#000',
|
|
2202
|
-
padding:
|
|
2203
|
-
paddingHorizontal:
|
|
984
|
+
padding: 8,
|
|
985
|
+
paddingHorizontal: 15,
|
|
2204
986
|
fontSize: 16,
|
|
2205
987
|
flex: 1,
|
|
988
|
+
marginVertical: 2,
|
|
989
|
+
marginBottom: 0,
|
|
2206
990
|
},
|
|
2207
991
|
multiline: true,
|
|
2208
992
|
returnKeyType: 'default',
|
|
2209
993
|
enablesReturnKeyAutomatically: true,
|
|
2210
994
|
placeholderTextColor: colors.gray[400],
|
|
2211
995
|
}}
|
|
2212
|
-
minComposerHeight={
|
|
2213
|
-
|
|
2214
|
-
|
|
996
|
+
minComposerHeight={36}
|
|
997
|
+
maxComposerHeight={100}
|
|
998
|
+
placeholder="Type a message..."
|
|
2215
999
|
renderChatFooter={() => (
|
|
2216
1000
|
<>
|
|
2217
1001
|
<ImageViewerModal
|
|
@@ -2232,22 +1016,12 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
2232
1016
|
if (!subscriptionData.data) return prev;
|
|
2233
1017
|
const newMessage: any = subscriptionData?.data?.threadChatMessageAdded;
|
|
2234
1018
|
const prevReplyCount: any = prev?.getPostThread?.replyCount;
|
|
2235
|
-
const newReplyCount =
|
|
1019
|
+
const newReplyCount = prevReplyCount || 0 + 1;
|
|
2236
1020
|
const replies = prev?.getPostThread?.replies || [];
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
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
|
-
|
|
1021
|
+
setChannelMessages((oldMessages: any) =>
|
|
1022
|
+
uniqBy([...oldMessages, newMessage], ({ id }) => id),
|
|
1023
|
+
);
|
|
1024
|
+
setTotalCount(newReplyCount);
|
|
2251
1025
|
return Object.assign({}, prev, {
|
|
2252
1026
|
getPostThread: {
|
|
2253
1027
|
...prev?.getPostThread,
|
|
@@ -2263,7 +1037,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
2263
1037
|
/>
|
|
2264
1038
|
</>
|
|
2265
1039
|
)}
|
|
2266
|
-
messagesContainerStyle={messageList?.length == 0
|
|
1040
|
+
messagesContainerStyle={messageList?.length == 0 && { transform: [{ scaleY: -1 }] }}
|
|
2267
1041
|
renderChatEmpty={() => (
|
|
2268
1042
|
<>
|
|
2269
1043
|
{!threadLoading && messageList && messageList?.length == 0 && (
|
|
@@ -2282,7 +1056,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
2282
1056
|
disabled: true,
|
|
2283
1057
|
}}
|
|
2284
1058
|
/>
|
|
2285
|
-
|
|
1059
|
+
</>
|
|
2286
1060
|
);
|
|
2287
1061
|
};
|
|
2288
1062
|
|
|
@@ -2291,5 +1065,5 @@ const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: IThreadSubsc
|
|
|
2291
1065
|
return <></>;
|
|
2292
1066
|
};
|
|
2293
1067
|
|
|
2294
|
-
export const ThreadConversationView =
|
|
2295
|
-
// export const ThreadConversationView = ThreadConversationViewComponent;
|
|
1068
|
+
export const ThreadConversationView = ThreadConversationViewComponent;
|
|
1069
|
+
// export const ThreadConversationView = React.memo(ThreadConversationViewComponent);
|