@messenger-box/platform-mobile 10.0.3-alpha.16 → 10.0.3-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/lib/routes.json +14 -1
- package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/index.js +125 -16
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +32 -21
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/containers/ConversationView.js +1175 -400
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +290 -21
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +858 -351
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js +380 -0
- package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +1 -0
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +235 -0
- package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +1 -0
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +438 -0
- package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +1 -0
- package/package.json +4 -4
- package/src/screens/inbox/components/CachedImage/consts.ts +4 -3
- package/src/screens/inbox/components/CachedImage/index.tsx +137 -17
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +35 -9
- package/src/screens/inbox/containers/ConversationView.tsx +1510 -641
- package/src/screens/inbox/containers/Dialogs.tsx +415 -123
- package/src/screens/inbox/containers/ThreadConversationView.tsx +1053 -288
- package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +20 -0
- package/src/screens/inbox/containers/workflow/conversation-xstate.ts +313 -0
- package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +196 -0
- package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +401 -0
|
@@ -14,14 +14,14 @@ import {
|
|
|
14
14
|
Spinner,
|
|
15
15
|
Text,
|
|
16
16
|
} from '@admin-layout/gluestack-ui-mobile';
|
|
17
|
-
import { Platform } from 'react-native';
|
|
17
|
+
import { Platform, Linking, SafeAreaView, View, TouchableHighlight } from 'react-native';
|
|
18
18
|
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
|
19
19
|
import { useSelector } from 'react-redux';
|
|
20
20
|
import { orderBy, startCase, uniqBy } from 'lodash-es';
|
|
21
21
|
import * as ImagePicker from 'expo-image-picker';
|
|
22
22
|
import { encode as atob } from 'base-64';
|
|
23
23
|
import { Ionicons, MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons';
|
|
24
|
-
import { Actions, GiftedChat, IMessage, MessageText, Send } from 'react-native-gifted-chat';
|
|
24
|
+
import { Actions, GiftedChat, IMessage, MessageText, Send, Composer, InputToolbar } from 'react-native-gifted-chat';
|
|
25
25
|
import { IPost, IPostThread, PreDefinedRole, IExpoNotificationData, IFileInfo } from 'common';
|
|
26
26
|
import {
|
|
27
27
|
OnThreadChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
|
|
@@ -40,6 +40,19 @@ import { config } from '../config';
|
|
|
40
40
|
import { ImageViewerModal, SlackMessage } from '../components/SlackMessageContainer';
|
|
41
41
|
import CachedImage from '../components/CachedImage';
|
|
42
42
|
import colors from 'tailwindcss/colors';
|
|
43
|
+
import {
|
|
44
|
+
threadConversationXstate,
|
|
45
|
+
Actions as ThreadActions,
|
|
46
|
+
BaseState,
|
|
47
|
+
MainState,
|
|
48
|
+
} from './workflow/thread-conversation-xstate';
|
|
49
|
+
|
|
50
|
+
// Define an extended interface for ImagePickerAsset with url property
|
|
51
|
+
interface ExtendedImagePickerAsset extends ImagePicker.ImagePickerAsset {
|
|
52
|
+
url?: string;
|
|
53
|
+
fileName?: string;
|
|
54
|
+
mimeType?: string;
|
|
55
|
+
}
|
|
43
56
|
|
|
44
57
|
const {
|
|
45
58
|
MESSAGES_PER_PAGE,
|
|
@@ -57,6 +70,264 @@ const createdAtText = (value: string) => {
|
|
|
57
70
|
return format(new Date(value), 'MMM dd, yyyy');
|
|
58
71
|
};
|
|
59
72
|
|
|
73
|
+
// Create a safer version of useMachine to handle potential errors
|
|
74
|
+
function useSafeMachine(machine) {
|
|
75
|
+
// Define the state type
|
|
76
|
+
interface SafeStateType {
|
|
77
|
+
context: {
|
|
78
|
+
channelId: any;
|
|
79
|
+
postParentId: any;
|
|
80
|
+
role: any;
|
|
81
|
+
threadMessages: any[];
|
|
82
|
+
totalCount: number;
|
|
83
|
+
skip: number;
|
|
84
|
+
loading: boolean;
|
|
85
|
+
loadingOldMessages: boolean;
|
|
86
|
+
error: any;
|
|
87
|
+
selectedImage: string;
|
|
88
|
+
files: any[];
|
|
89
|
+
images: any[];
|
|
90
|
+
messageText: string;
|
|
91
|
+
imageLoading: boolean;
|
|
92
|
+
postThread: any;
|
|
93
|
+
threadPost: any[];
|
|
94
|
+
isScrollToBottom: boolean;
|
|
95
|
+
};
|
|
96
|
+
value: string;
|
|
97
|
+
matches?: (stateValue: string) => boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Initialize with default state
|
|
101
|
+
const [state, setState] = useState<SafeStateType>({
|
|
102
|
+
context: {
|
|
103
|
+
channelId: null,
|
|
104
|
+
postParentId: null,
|
|
105
|
+
role: null,
|
|
106
|
+
threadMessages: [],
|
|
107
|
+
totalCount: 0,
|
|
108
|
+
skip: 0,
|
|
109
|
+
loading: false,
|
|
110
|
+
loadingOldMessages: false,
|
|
111
|
+
error: null,
|
|
112
|
+
selectedImage: '',
|
|
113
|
+
files: [],
|
|
114
|
+
images: [],
|
|
115
|
+
messageText: '',
|
|
116
|
+
imageLoading: false,
|
|
117
|
+
postThread: null,
|
|
118
|
+
threadPost: [],
|
|
119
|
+
isScrollToBottom: false,
|
|
120
|
+
},
|
|
121
|
+
value: 'idle',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Create a safe send function
|
|
125
|
+
const send = useCallback((event) => {
|
|
126
|
+
try {
|
|
127
|
+
// Log event for debugging
|
|
128
|
+
console.log('Thread Event received:', event.type);
|
|
129
|
+
|
|
130
|
+
// Handle specific events manually
|
|
131
|
+
if (event.type === ThreadActions.INITIAL_CONTEXT) {
|
|
132
|
+
setState((prev) => ({
|
|
133
|
+
...prev,
|
|
134
|
+
context: {
|
|
135
|
+
...prev.context,
|
|
136
|
+
channelId: event.data?.channelId || null,
|
|
137
|
+
postParentId: event.data?.postParentId || null,
|
|
138
|
+
role: event.data?.role || null,
|
|
139
|
+
},
|
|
140
|
+
value: BaseState.FetchThreadMessages,
|
|
141
|
+
}));
|
|
142
|
+
} else if (event.type === ThreadActions.SET_THREAD_MESSAGES) {
|
|
143
|
+
setState((prev) => ({
|
|
144
|
+
...prev,
|
|
145
|
+
context: {
|
|
146
|
+
...prev.context,
|
|
147
|
+
threadMessages: event.data?.messages || [],
|
|
148
|
+
totalCount: event.data?.totalCount || 0,
|
|
149
|
+
loading: false,
|
|
150
|
+
loadingOldMessages: false,
|
|
151
|
+
threadPost: event.data?.threadPost || [],
|
|
152
|
+
postThread: event.data?.postThread || null,
|
|
153
|
+
},
|
|
154
|
+
value: 'active',
|
|
155
|
+
}));
|
|
156
|
+
} else if (event.type === ThreadActions.CLEAR_MESSAGES) {
|
|
157
|
+
setState((prev) => ({
|
|
158
|
+
...prev,
|
|
159
|
+
context: {
|
|
160
|
+
...prev.context,
|
|
161
|
+
threadMessages: [],
|
|
162
|
+
totalCount: 0,
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
} else if (event.type === ThreadActions.SET_MESSAGE_TEXT) {
|
|
166
|
+
setState((prev) => ({
|
|
167
|
+
...prev,
|
|
168
|
+
context: {
|
|
169
|
+
...prev.context,
|
|
170
|
+
messageText: event.data?.messageText || '',
|
|
171
|
+
},
|
|
172
|
+
}));
|
|
173
|
+
} else if (event.type === ThreadActions.FETCH_MORE_MESSAGES) {
|
|
174
|
+
setState((prev) => ({
|
|
175
|
+
...prev,
|
|
176
|
+
context: {
|
|
177
|
+
...prev.context,
|
|
178
|
+
loadingOldMessages: true,
|
|
179
|
+
},
|
|
180
|
+
value: MainState.FetchMoreMessages,
|
|
181
|
+
}));
|
|
182
|
+
} else if (event.type === ThreadActions.SET_IMAGE) {
|
|
183
|
+
setState((prev) => ({
|
|
184
|
+
...prev,
|
|
185
|
+
context: {
|
|
186
|
+
...prev.context,
|
|
187
|
+
selectedImage: event.data?.image || '',
|
|
188
|
+
images: event.data?.images || [],
|
|
189
|
+
files: event.data?.files || [],
|
|
190
|
+
imageLoading: false,
|
|
191
|
+
},
|
|
192
|
+
}));
|
|
193
|
+
} else if (event.type === ThreadActions.CLEAR_IMAGE) {
|
|
194
|
+
setState((prev) => ({
|
|
195
|
+
...prev,
|
|
196
|
+
context: {
|
|
197
|
+
...prev.context,
|
|
198
|
+
selectedImage: '',
|
|
199
|
+
images: [],
|
|
200
|
+
files: [],
|
|
201
|
+
},
|
|
202
|
+
}));
|
|
203
|
+
} else if (event.type === ThreadActions.START_LOADING) {
|
|
204
|
+
setState((prev) => ({
|
|
205
|
+
...prev,
|
|
206
|
+
context: {
|
|
207
|
+
...prev.context,
|
|
208
|
+
loading: true,
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
} else if (event.type === ThreadActions.STOP_LOADING) {
|
|
212
|
+
setState((prev) => ({
|
|
213
|
+
...prev,
|
|
214
|
+
context: {
|
|
215
|
+
...prev.context,
|
|
216
|
+
loading: false,
|
|
217
|
+
},
|
|
218
|
+
}));
|
|
219
|
+
} else if (event.type === ThreadActions.SEND_THREAD_MESSAGE) {
|
|
220
|
+
console.log('Sending message event with text:', event.data?.messageText);
|
|
221
|
+
setState((prev) => ({
|
|
222
|
+
...prev,
|
|
223
|
+
context: {
|
|
224
|
+
...prev.context,
|
|
225
|
+
loading: true,
|
|
226
|
+
// Keep the message text until we're done sending
|
|
227
|
+
messageText: event.data?.messageText || prev.context.messageText,
|
|
228
|
+
},
|
|
229
|
+
value: MainState.SendThreadMessage,
|
|
230
|
+
}));
|
|
231
|
+
} else if (event.type === ThreadActions.SEND_THREAD_MESSAGE_WITH_FILE) {
|
|
232
|
+
console.log('Sending message with file event, text:', event.data?.messageText);
|
|
233
|
+
setState((prev) => ({
|
|
234
|
+
...prev,
|
|
235
|
+
context: {
|
|
236
|
+
...prev.context,
|
|
237
|
+
loading: true,
|
|
238
|
+
// Keep the message text until we're done sending
|
|
239
|
+
messageText: event.data?.messageText || prev.context.messageText,
|
|
240
|
+
},
|
|
241
|
+
value: MainState.SendThreadMessageWithFile,
|
|
242
|
+
}));
|
|
243
|
+
} else if (
|
|
244
|
+
event.type === 'SEND_THREAD_MESSAGE_SUCCESS' ||
|
|
245
|
+
event.type === 'SEND_THREAD_MESSAGE_WITH_FILE_SUCCESS'
|
|
246
|
+
) {
|
|
247
|
+
console.log('Handling send success event:', event.type, 'with message:', event.data?.message?.id);
|
|
248
|
+
setState((prev) => {
|
|
249
|
+
// Make sure we have the message data
|
|
250
|
+
if (!event.data?.message) {
|
|
251
|
+
console.warn('Send success event without message data');
|
|
252
|
+
return {
|
|
253
|
+
...prev,
|
|
254
|
+
context: {
|
|
255
|
+
...prev.context,
|
|
256
|
+
loading: false,
|
|
257
|
+
messageText: '', // Clear input
|
|
258
|
+
images: [],
|
|
259
|
+
selectedImage: '',
|
|
260
|
+
files: [],
|
|
261
|
+
},
|
|
262
|
+
value: 'active',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Add the new message to our threadMessages
|
|
267
|
+
const newMessage = event.data.message;
|
|
268
|
+
const updatedMessages = [newMessage, ...prev.context.threadMessages];
|
|
269
|
+
|
|
270
|
+
console.log('Updated thread messages list after send, now has', updatedMessages.length, 'messages');
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
...prev,
|
|
274
|
+
context: {
|
|
275
|
+
...prev.context,
|
|
276
|
+
loading: false,
|
|
277
|
+
messageText: '', // Always clear input text after sending
|
|
278
|
+
images: [],
|
|
279
|
+
selectedImage: '',
|
|
280
|
+
files: [],
|
|
281
|
+
threadMessages: updatedMessages,
|
|
282
|
+
totalCount: prev.context.totalCount + 1,
|
|
283
|
+
},
|
|
284
|
+
value: 'active',
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
} else if (event.type === 'FETCH_MORE_MESSAGES_SUCCESS') {
|
|
288
|
+
setState((prev) => {
|
|
289
|
+
const newMessages = event.data?.messages || [];
|
|
290
|
+
return {
|
|
291
|
+
...prev,
|
|
292
|
+
context: {
|
|
293
|
+
...prev.context,
|
|
294
|
+
loadingOldMessages: false,
|
|
295
|
+
threadMessages: uniqBy([...prev.context.threadMessages, ...newMessages], ({ id }) => id),
|
|
296
|
+
},
|
|
297
|
+
value: 'active',
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
} else if (event.type === 'ERROR') {
|
|
301
|
+
setState((prev) => ({
|
|
302
|
+
...prev,
|
|
303
|
+
context: {
|
|
304
|
+
...prev.context,
|
|
305
|
+
loading: false,
|
|
306
|
+
loadingOldMessages: false,
|
|
307
|
+
error: event.data?.message || 'Unknown error',
|
|
308
|
+
},
|
|
309
|
+
value: 'error',
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error('Error in thread conversation send function:', error);
|
|
314
|
+
}
|
|
315
|
+
}, []);
|
|
316
|
+
|
|
317
|
+
// Add a custom matches function to the state
|
|
318
|
+
const stateWithMatches = useMemo(() => {
|
|
319
|
+
return {
|
|
320
|
+
...state,
|
|
321
|
+
matches: (checkState) => {
|
|
322
|
+
return state.value === checkState;
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}, [state]);
|
|
326
|
+
|
|
327
|
+
// Return as a tuple to match useMachine API
|
|
328
|
+
return [stateWithMatches, send] as const;
|
|
329
|
+
}
|
|
330
|
+
|
|
60
331
|
interface IMessageProps extends IMessage {
|
|
61
332
|
type: string;
|
|
62
333
|
propsConfiguration?: any;
|
|
@@ -77,34 +348,84 @@ interface IThreadSubscriptionHandlerProps {
|
|
|
77
348
|
channelId: string;
|
|
78
349
|
}
|
|
79
350
|
|
|
80
|
-
const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParentIdThread, role }: any) => {
|
|
351
|
+
const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParentIdThread, role }: any): JSX.Element => {
|
|
81
352
|
const { params } = useRoute<any>();
|
|
82
353
|
const [channelToTop, setChannelToTop] = useState(0);
|
|
83
|
-
|
|
354
|
+
|
|
355
|
+
// Create a ref to track if component is mounted
|
|
356
|
+
const isMountedRef = useRef(true);
|
|
357
|
+
|
|
358
|
+
// Use our safer custom implementation instead of the problematic useMachine
|
|
359
|
+
const [state, send] = useSafeMachine(threadConversationXstate);
|
|
360
|
+
|
|
361
|
+
// Define safe functions first to avoid "used before declaration" errors
|
|
362
|
+
const safeContext = useCallback(() => {
|
|
363
|
+
try {
|
|
364
|
+
return state?.context || {};
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error('Error accessing state.context:', error);
|
|
367
|
+
return {};
|
|
368
|
+
}
|
|
369
|
+
}, [state]);
|
|
370
|
+
|
|
371
|
+
const safeContextProperty = useCallback(
|
|
372
|
+
(property, defaultValue = null) => {
|
|
373
|
+
try {
|
|
374
|
+
return state?.context?.[property] ?? defaultValue;
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error(`Error accessing state.context.${property}:`, error);
|
|
377
|
+
return defaultValue;
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
[state],
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const safeMatches = useCallback(
|
|
384
|
+
(stateValue) => {
|
|
385
|
+
try {
|
|
386
|
+
return state?.matches?.(stateValue) || false;
|
|
387
|
+
} catch (error) {
|
|
388
|
+
console.error(`Error calling state.matches with ${stateValue}:`, error);
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
[state],
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
const safeSend = useCallback(
|
|
396
|
+
(event) => {
|
|
397
|
+
try {
|
|
398
|
+
send(event);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error('Error sending event to state machine:', error, event);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
[send],
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
// Use a ref to track the current machine snapshot for safer access
|
|
407
|
+
const stateRef = useRef(state);
|
|
408
|
+
|
|
409
|
+
// Keep the ref updated with the latest snapshot
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
stateRef.current = state;
|
|
412
|
+
}, [state]);
|
|
413
|
+
|
|
84
414
|
const auth: any = useSelector(userSelector);
|
|
85
|
-
const [totalCount, setTotalCount] = useState<any>(0);
|
|
86
415
|
const [selectedImage, setImage] = useState<string>('');
|
|
87
|
-
const [loadingOldMessages, setLoadingOldMessages] = useState<boolean>(false);
|
|
88
|
-
const [loadEarlierMsg, setLoadEarlierMsg] = useState(false);
|
|
89
416
|
const navigation = useNavigation<any>();
|
|
90
417
|
const [files, setFiles] = useState<File[]>([]);
|
|
91
418
|
const [images, setImages] = useState<ImagePicker.ImagePickerAsset[]>([]);
|
|
92
|
-
const [msg, setMsg] = useState<string>('');
|
|
93
|
-
const [loading, setLoading] = useState(false);
|
|
94
|
-
const [imageLoading, setImageLoading] = useState(false);
|
|
95
|
-
const [expoTokens, setExpoTokens] = useState<any[]>([]);
|
|
96
419
|
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
97
420
|
const [imageObject, setImageObject] = useState<any>({});
|
|
98
421
|
const [parentId, setParentId] = useState<any>(postParentId);
|
|
99
|
-
const [
|
|
100
|
-
const { startUpload } = useUploadFilesNative();
|
|
101
|
-
const [threadPost, setThreadPost] = useState<any[]>([]);
|
|
102
|
-
const [isScrollToBottom, setIsScrollToBottom] = useState(false);
|
|
422
|
+
const [expoTokens, setExpoTokens] = useState<any[]>([]);
|
|
103
423
|
const threadMessageListRef = useRef<any>(null);
|
|
104
424
|
|
|
105
425
|
// const [sendThreadMessage] = useSendThreadMessageMutation();
|
|
106
426
|
const [sendThreadMessage] = useCreatePostThreadMutation();
|
|
107
427
|
const [sendExpoNotificationOnPostMutation] = useSendExpoNotificationOnPostMutation();
|
|
428
|
+
const { startUpload } = useUploadFilesNative();
|
|
108
429
|
|
|
109
430
|
const [
|
|
110
431
|
getThreadMessages,
|
|
@@ -123,27 +444,61 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
123
444
|
</Button>
|
|
124
445
|
),
|
|
125
446
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
447
|
+
|
|
448
|
+
// Set initial context when focused
|
|
449
|
+
if (channelId && postParentId) {
|
|
450
|
+
safeSend({
|
|
451
|
+
type: ThreadActions.INITIAL_CONTEXT,
|
|
452
|
+
data: {
|
|
453
|
+
channelId,
|
|
454
|
+
postParentId,
|
|
455
|
+
role,
|
|
456
|
+
},
|
|
133
457
|
});
|
|
134
458
|
}
|
|
459
|
+
|
|
135
460
|
setParentId(postParentId);
|
|
136
461
|
|
|
137
462
|
return () => {
|
|
138
|
-
|
|
139
|
-
setChannelMessages([]);
|
|
140
|
-
setThreadPost([]);
|
|
463
|
+
safeSend({ type: ThreadActions.CLEAR_MESSAGES });
|
|
141
464
|
};
|
|
142
465
|
}, [postParentId]),
|
|
143
466
|
);
|
|
144
467
|
|
|
468
|
+
// Effect for when in FetchThreadMessages state
|
|
145
469
|
useEffect(() => {
|
|
146
|
-
|
|
470
|
+
if (safeMatches(BaseState.FetchThreadMessages)) {
|
|
471
|
+
fetchThreadMessages();
|
|
472
|
+
}
|
|
473
|
+
}, [state.value]);
|
|
474
|
+
|
|
475
|
+
// Effect for when in FetchMoreMessages state
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
if (safeMatches(MainState.FetchMoreMessages)) {
|
|
478
|
+
onFetchOld();
|
|
479
|
+
}
|
|
480
|
+
}, [state.value]);
|
|
481
|
+
|
|
482
|
+
// Effect for when in SendThreadMessage state
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (safeMatches(MainState.SendThreadMessage)) {
|
|
485
|
+
const messageText = safeContextProperty('messageText', '');
|
|
486
|
+
console.log('Sending message from state transition, text:', messageText);
|
|
487
|
+
sendThreadMessageHandler(messageText);
|
|
488
|
+
}
|
|
489
|
+
}, [state.value]);
|
|
490
|
+
|
|
491
|
+
// Effect for when in SendThreadMessageWithFile state
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
if (safeMatches(MainState.SendThreadMessageWithFile)) {
|
|
494
|
+
const messageText = safeContextProperty('messageText', '');
|
|
495
|
+
const images = safeContextProperty('images', []);
|
|
496
|
+
sendThreadMessageWithFileHandler(messageText, images);
|
|
497
|
+
}
|
|
498
|
+
}, [state.value]);
|
|
499
|
+
|
|
500
|
+
// Fetch thread messages function
|
|
501
|
+
const fetchThreadMessages = useCallback(() => {
|
|
147
502
|
if (channelId && parentId) {
|
|
148
503
|
getThreadMessages({
|
|
149
504
|
variables: {
|
|
@@ -153,9 +508,34 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
153
508
|
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
154
509
|
limit: MESSAGES_PER_PAGE,
|
|
155
510
|
},
|
|
156
|
-
})
|
|
511
|
+
})
|
|
512
|
+
.then(({ data }) => {
|
|
513
|
+
if (data?.getPostThread) {
|
|
514
|
+
const threads: any = data.getPostThread;
|
|
515
|
+
const threadPost = threads?.post ?? [];
|
|
516
|
+
const threadReplies = threads?.replies ?? [];
|
|
517
|
+
const messageTotalCount = threads?.replyCount ?? 0;
|
|
518
|
+
const messages = [...threadReplies];
|
|
519
|
+
|
|
520
|
+
safeSend({
|
|
521
|
+
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
522
|
+
data: {
|
|
523
|
+
messages,
|
|
524
|
+
totalCount: messageTotalCount,
|
|
525
|
+
threadPost: threadPost,
|
|
526
|
+
postThread: threads,
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
.catch((error) => {
|
|
532
|
+
safeSend({
|
|
533
|
+
type: 'ERROR',
|
|
534
|
+
data: { message: error.message },
|
|
535
|
+
});
|
|
536
|
+
});
|
|
157
537
|
}
|
|
158
|
-
}, [parentId]);
|
|
538
|
+
}, [channelId, parentId, role]);
|
|
159
539
|
|
|
160
540
|
React.useEffect(() => {
|
|
161
541
|
if (data?.getPostThread) {
|
|
@@ -163,42 +543,45 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
163
543
|
const threadPost = threads?.post ?? [];
|
|
164
544
|
const threadReplies = threads?.replies ?? [];
|
|
165
545
|
const messeageTotalCount = threads?.replyCount ?? 0;
|
|
166
|
-
const messages = [
|
|
546
|
+
const messages = [...threadReplies];
|
|
167
547
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
548
|
+
safeSend({
|
|
549
|
+
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
550
|
+
data: {
|
|
551
|
+
messages,
|
|
552
|
+
totalCount: messeageTotalCount,
|
|
553
|
+
threadPost: threadPost,
|
|
554
|
+
postThread: threads,
|
|
555
|
+
},
|
|
556
|
+
});
|
|
174
557
|
}
|
|
175
|
-
|
|
176
|
-
// scrollToBottom();
|
|
177
|
-
// if (!isPostParentIdThread) {
|
|
178
|
-
// // setTotalCount((pc: any) => pc + threadTotalCount);
|
|
179
|
-
// setChannelMessages((oldMessages: any) => uniqBy([...threadMessage, ...oldMessages], ({ id }) => id));
|
|
180
|
-
// }
|
|
181
|
-
}, [data, channelMessages, loadingOldMessages, totalCount, isPostParentIdThread, isScrollToBottom]);
|
|
182
|
-
|
|
183
|
-
const setThreadMessages = (messages: any, messagesTotalCount: number) => {
|
|
184
|
-
setChannelMessages((oldMessages: any) => uniqBy([...messages, ...oldMessages], ({ id }) => id));
|
|
185
|
-
setTotalCount(messagesTotalCount);
|
|
186
|
-
};
|
|
558
|
+
}, [data]);
|
|
187
559
|
|
|
188
560
|
React.useEffect(() => {
|
|
189
|
-
if (selectedImage)
|
|
190
|
-
|
|
561
|
+
if (safeContextProperty('selectedImage')) {
|
|
562
|
+
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
563
|
+
}
|
|
564
|
+
}, [safeContextProperty('selectedImage')]);
|
|
191
565
|
|
|
192
566
|
const scrollToBottom = React.useCallback(() => {
|
|
193
567
|
if (threadMessageListRef?.current) {
|
|
194
|
-
|
|
195
|
-
threadMessageListRef.current.scrollTop = threadMessageListRef.current.scrollHeight;
|
|
568
|
+
threadMessageListRef.current.scrollToBottom();
|
|
196
569
|
}
|
|
197
570
|
}, [threadMessageListRef]);
|
|
198
571
|
|
|
199
572
|
const onFetchOld = useCallback(() => {
|
|
200
|
-
|
|
201
|
-
|
|
573
|
+
const totalCount = safeContextProperty('totalCount', 0);
|
|
574
|
+
const threadMessages = safeContextProperty('threadMessages', []);
|
|
575
|
+
|
|
576
|
+
if (totalCount > threadMessages.length && !safeContextProperty('loadingOldMessages', false)) {
|
|
577
|
+
console.log('Loading more messages - current count:', threadMessages.length, 'of', totalCount);
|
|
578
|
+
|
|
579
|
+
// Set the loading state specifically for old messages
|
|
580
|
+
safeSend({
|
|
581
|
+
type: ThreadActions.START_LOADING,
|
|
582
|
+
data: { loadingOldMessages: true },
|
|
583
|
+
});
|
|
584
|
+
|
|
202
585
|
fetchMoreMessages({
|
|
203
586
|
variables: {
|
|
204
587
|
channelId: channelId?.toString(),
|
|
@@ -206,79 +589,261 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
206
589
|
postParentId: parentId?.toString(),
|
|
207
590
|
selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
|
|
208
591
|
limit: MESSAGES_PER_PAGE,
|
|
209
|
-
skip:
|
|
592
|
+
skip: threadMessages.length,
|
|
210
593
|
},
|
|
211
594
|
})
|
|
212
595
|
.then((res: any) => {
|
|
213
596
|
if (res?.data?.getPostThread) {
|
|
214
597
|
const threads: any = res?.data?.getPostThread;
|
|
215
598
|
const threadReplies = threads?.replies ?? [];
|
|
216
|
-
|
|
217
|
-
|
|
599
|
+
|
|
600
|
+
console.log('Successfully loaded more messages:', threadReplies.length);
|
|
601
|
+
|
|
602
|
+
safeSend({
|
|
603
|
+
type: 'FETCH_MORE_MESSAGES_SUCCESS',
|
|
604
|
+
data: {
|
|
605
|
+
messages: threadReplies,
|
|
606
|
+
loadingOldMessages: false,
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
} else {
|
|
610
|
+
console.log('No thread data returned when loading more messages');
|
|
611
|
+
safeSend({
|
|
612
|
+
type: ThreadActions.STOP_LOADING,
|
|
613
|
+
data: { loadingOldMessages: false },
|
|
614
|
+
});
|
|
218
615
|
}
|
|
219
616
|
})
|
|
220
|
-
.finally(() => {
|
|
221
|
-
setLoadEarlierMsg(false);
|
|
222
|
-
setLoadingOldMessages(false);
|
|
223
|
-
})
|
|
224
617
|
.catch((error: any) => {
|
|
225
|
-
|
|
226
|
-
|
|
618
|
+
console.error('Error fetching more messages:', error);
|
|
619
|
+
safeSend({
|
|
620
|
+
type: 'ERROR',
|
|
621
|
+
data: {
|
|
622
|
+
message: error.message,
|
|
623
|
+
loadingOldMessages: false,
|
|
624
|
+
},
|
|
625
|
+
});
|
|
227
626
|
});
|
|
627
|
+
} else {
|
|
628
|
+
console.log('No more messages to load or already loading');
|
|
228
629
|
}
|
|
229
|
-
}, [parentId, channelId,
|
|
630
|
+
}, [parentId, channelId, state.context]);
|
|
230
631
|
|
|
231
|
-
|
|
232
|
-
// return contentOffset.y <= 100; // 100px from top
|
|
233
|
-
// };
|
|
632
|
+
let onScroll = false;
|
|
234
633
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
634
|
+
const handleScrollToTop = ({ nativeEvent }: any) => {
|
|
635
|
+
// Check if we're near the top of the list
|
|
636
|
+
if (isCloseToTop(nativeEvent)) {
|
|
637
|
+
if (
|
|
638
|
+
!safeContextProperty('loadingOldMessages', false) &&
|
|
639
|
+
safeContextProperty('totalCount', 0) > safeContextProperty('threadMessages', []).length
|
|
640
|
+
) {
|
|
641
|
+
console.log('Near top of list - loading older messages');
|
|
642
|
+
safeSend({ type: ThreadActions.FETCH_MORE_MESSAGES });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
238
645
|
};
|
|
239
646
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
return new File([u8arr], filename, { type: mime });
|
|
647
|
+
const handleEndReached = () => {
|
|
648
|
+
// This triggers when scrolled to the bottom
|
|
649
|
+
console.log('Reached end of message list');
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
653
|
+
const paddingToTop = 80;
|
|
654
|
+
return contentOffset.y <= paddingToTop;
|
|
250
655
|
};
|
|
251
656
|
|
|
252
657
|
const onSelectImages = async () => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
658
|
+
try {
|
|
659
|
+
safeSend({ type: ThreadActions.START_LOADING });
|
|
660
|
+
|
|
661
|
+
const imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
662
|
+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
663
|
+
allowsEditing: true,
|
|
664
|
+
aspect: [4, 3],
|
|
665
|
+
quality: 0.8, // Reduced from 1 for better performance
|
|
666
|
+
base64: true,
|
|
667
|
+
allowsMultipleSelection: false, // Set to true if you want to support multiple images
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
if (imageSource.canceled) {
|
|
671
|
+
console.log('Image selection was canceled');
|
|
672
|
+
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (!imageSource.assets || imageSource.assets.length === 0 || !imageSource.assets[0]?.base64) {
|
|
677
|
+
console.error('No valid image data received');
|
|
678
|
+
safeSend({
|
|
679
|
+
type: 'ERROR',
|
|
680
|
+
data: { message: 'No valid image data received' },
|
|
681
|
+
});
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Get the first asset
|
|
686
|
+
const asset = imageSource.assets[0];
|
|
687
|
+
|
|
688
|
+
// Derive file extension from mime type or default to jpg
|
|
689
|
+
const fileExtension = asset.mimeType ? asset.mimeType.split('/').pop() || 'jpg' : 'jpg';
|
|
690
|
+
|
|
691
|
+
// Create a more descriptive filename with timestamp
|
|
692
|
+
const filename = `image_${Date.now()}.${fileExtension}`;
|
|
693
|
+
|
|
694
|
+
// Create data URL with proper mime type
|
|
695
|
+
const mimeType = asset.mimeType || 'image/jpeg';
|
|
696
|
+
const image = `data:${mimeType};base64,${asset.base64}`;
|
|
697
|
+
|
|
698
|
+
// Create file-like object suitable for React Native
|
|
699
|
+
const fileData = {
|
|
700
|
+
uri: asset.uri,
|
|
701
|
+
type: mimeType,
|
|
702
|
+
name: filename,
|
|
703
|
+
base64: asset.base64,
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
console.log(`Selected image: ${filename}, type: ${mimeType}`);
|
|
707
|
+
|
|
708
|
+
safeSend({
|
|
709
|
+
type: ThreadActions.SET_IMAGE,
|
|
710
|
+
data: {
|
|
711
|
+
image,
|
|
712
|
+
files: [fileData],
|
|
713
|
+
images: [asset as ImagePicker.ImagePickerAsset],
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
} catch (error) {
|
|
717
|
+
console.error('Error selecting image:', error);
|
|
718
|
+
safeSend({
|
|
719
|
+
type: 'ERROR',
|
|
720
|
+
data: { message: error.message || 'Failed to select image' },
|
|
721
|
+
});
|
|
267
722
|
}
|
|
268
|
-
if (imageSource.canceled) setLoading(false);
|
|
269
723
|
};
|
|
270
724
|
|
|
271
|
-
|
|
725
|
+
// Define message sending handlers
|
|
726
|
+
const sendThreadMessageHandler = useCallback(
|
|
272
727
|
async (message: string) => {
|
|
273
|
-
|
|
274
|
-
|
|
728
|
+
console.log('Sending message:', message);
|
|
729
|
+
|
|
730
|
+
if (!channelId) {
|
|
731
|
+
console.error('No channelId provided');
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Allow empty messages with spaces or blank content - GiftedChat sometimes sends these
|
|
736
|
+
// But use the actual message if available
|
|
737
|
+
const messageContent = message?.trim() || ' ';
|
|
738
|
+
console.log('Using message content for sending:', messageContent);
|
|
275
739
|
|
|
276
740
|
const postId = objectId();
|
|
741
|
+
console.log('Generated postId:', postId);
|
|
742
|
+
|
|
743
|
+
safeSend({ type: ThreadActions.START_LOADING });
|
|
744
|
+
|
|
745
|
+
try {
|
|
746
|
+
console.log('Sending mutation with variables:', {
|
|
747
|
+
channelId,
|
|
748
|
+
postThreadId: safeContextProperty('postThread')?.id,
|
|
749
|
+
postParentId: !parentId || parentId == 0 ? null : parentId,
|
|
750
|
+
message: messageContent,
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
const result = await sendThreadMessage({
|
|
754
|
+
variables: {
|
|
755
|
+
channelId,
|
|
756
|
+
postThreadId: safeContextProperty('postThread') && safeContextProperty('postThread')?.id,
|
|
757
|
+
postParentId: !parentId || parentId == 0 ? null : parentId,
|
|
758
|
+
threadMessageInput: {
|
|
759
|
+
content: messageContent,
|
|
760
|
+
role,
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
update: (cache, { data, errors }: any) => {
|
|
764
|
+
console.log('Send message update callback - data:', data, 'errors:', errors);
|
|
765
|
+
|
|
766
|
+
if (!data || errors) {
|
|
767
|
+
console.error('Send message failed:', errors);
|
|
768
|
+
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
console.log('Message sent successfully:', data?.createPostThread?.lastMessage);
|
|
773
|
+
|
|
774
|
+
// Add the new message to our local state
|
|
775
|
+
const newMessage = data?.createPostThread?.lastMessage;
|
|
776
|
+
|
|
777
|
+
// Reset the message text and add the new message
|
|
778
|
+
safeSend({
|
|
779
|
+
type: 'SEND_THREAD_MESSAGE_SUCCESS',
|
|
780
|
+
data: {
|
|
781
|
+
message: newMessage,
|
|
782
|
+
messageText: '', // Clear the message text now
|
|
783
|
+
},
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
if (!parentId || parentId == 0) {
|
|
787
|
+
console.log('Setting new parentId:', data?.createPostThread?.lastMessage?.id);
|
|
788
|
+
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
setChannelToTop(channelToTop + 1);
|
|
792
|
+
|
|
793
|
+
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
794
|
+
sendPushNotification(lastMessageId, channelId, parentId, data?.createPostThread?.data?.id);
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
console.log('Send mutation result:', result);
|
|
799
|
+
} catch (error) {
|
|
800
|
+
console.error('Error sending message:', error);
|
|
801
|
+
safeSend({
|
|
802
|
+
type: 'ERROR',
|
|
803
|
+
data: { message: error.message || 'Failed to send message' },
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
[channelId, parentId, state.context, role],
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
const sendThreadMessageWithFileHandler = useCallback(
|
|
811
|
+
async (message: string, images: any[]) => {
|
|
812
|
+
console.log('Sending message with file:', message, 'Images:', images.length);
|
|
813
|
+
|
|
814
|
+
if (!channelId) {
|
|
815
|
+
console.error('No channelId provided');
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (images.length === 0) {
|
|
820
|
+
console.error('No images to send');
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Allow empty message content for file uploads
|
|
825
|
+
// But use the actual message if available
|
|
826
|
+
const messageContent = message?.trim() || ' ';
|
|
827
|
+
console.log('Using message content for file send:', messageContent);
|
|
828
|
+
|
|
829
|
+
const postId = objectId();
|
|
830
|
+
console.log('Generated postId for file upload:', postId);
|
|
831
|
+
|
|
832
|
+
try {
|
|
833
|
+
// Prepare image assets in the format expected by the upload service
|
|
834
|
+
const preparedImages = images.map((img) => ({
|
|
835
|
+
uri: img.uri,
|
|
836
|
+
type: img.mimeType || 'image/jpeg',
|
|
837
|
+
name: img.fileName || `image_${Date.now()}.jpg`,
|
|
838
|
+
base64: img.base64,
|
|
839
|
+
width: img.width || 0,
|
|
840
|
+
height: img.height || 0,
|
|
841
|
+
})) as ImagePicker.ImagePickerAsset[];
|
|
842
|
+
|
|
843
|
+
console.log('Starting file upload with prepared images:', preparedImages.length);
|
|
277
844
|
|
|
278
|
-
if (images && images.length > 0) {
|
|
279
|
-
setLoading(true);
|
|
280
845
|
const uploadResponse = await startUpload({
|
|
281
|
-
file:
|
|
846
|
+
file: preparedImages,
|
|
282
847
|
saveUploadedFile: {
|
|
283
848
|
variables: {
|
|
284
849
|
postId,
|
|
@@ -290,77 +855,78 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
290
855
|
},
|
|
291
856
|
},
|
|
292
857
|
});
|
|
293
|
-
|
|
858
|
+
|
|
859
|
+
if (uploadResponse?.error) {
|
|
860
|
+
console.error('File upload failed:', uploadResponse.error);
|
|
861
|
+
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
294
865
|
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
866
|
+
console.log('Files uploaded successfully:', uploadedFiles?.length);
|
|
867
|
+
|
|
295
868
|
if (uploadResponse.data) {
|
|
296
|
-
setImage('');
|
|
297
|
-
setFiles([]);
|
|
298
|
-
setImages([]);
|
|
299
|
-
//setLoading(false);
|
|
300
869
|
const files = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
301
|
-
|
|
870
|
+
console.log('File IDs for message:', files);
|
|
871
|
+
|
|
872
|
+
console.log('Sending message with attached files');
|
|
873
|
+
const result = await sendThreadMessage({
|
|
302
874
|
variables: {
|
|
303
875
|
postId,
|
|
304
876
|
channelId,
|
|
305
|
-
postThreadId: postThread && postThread?.id,
|
|
877
|
+
postThreadId: safeContextProperty('postThread') && safeContextProperty('postThread')?.id,
|
|
306
878
|
postParentId: !parentId || parentId == 0 ? null : parentId,
|
|
307
879
|
threadMessageInput: {
|
|
308
|
-
content:
|
|
880
|
+
content: messageContent,
|
|
309
881
|
files,
|
|
310
882
|
role,
|
|
311
883
|
},
|
|
312
884
|
},
|
|
313
885
|
update: (cache, { data, errors }: any) => {
|
|
886
|
+
console.log('Send message with file update callback - data:', data, 'errors:', errors);
|
|
887
|
+
|
|
314
888
|
if (!data || errors) {
|
|
315
|
-
|
|
889
|
+
console.error('Send message with file failed:', errors);
|
|
890
|
+
safeSend({ type: ThreadActions.STOP_LOADING });
|
|
316
891
|
return;
|
|
317
892
|
}
|
|
318
|
-
|
|
319
|
-
|
|
893
|
+
|
|
894
|
+
console.log('Message with file sent successfully:', data?.createPostThread?.lastMessage);
|
|
895
|
+
|
|
896
|
+
// Add the new message to our local state
|
|
897
|
+
const newMessage = data?.createPostThread?.lastMessage;
|
|
898
|
+
|
|
899
|
+
safeSend({
|
|
900
|
+
type: 'SEND_THREAD_MESSAGE_WITH_FILE_SUCCESS',
|
|
901
|
+
data: {
|
|
902
|
+
message: newMessage,
|
|
903
|
+
messageText: '', // Clear the message text now
|
|
904
|
+
},
|
|
905
|
+
});
|
|
906
|
+
|
|
320
907
|
if (!parentId || parentId == 0) {
|
|
908
|
+
console.log('Setting new parentId:', data?.createPostThread?.lastMessage?.id);
|
|
321
909
|
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
322
910
|
}
|
|
323
911
|
|
|
324
912
|
setChannelToTop(channelToTop + 1);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const msg = message == '' ? 'Send a file' : message;
|
|
913
|
+
|
|
914
|
+
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
328
915
|
sendPushNotification(lastMessageId, channelId, parentId, data?.createPostThread?.data?.id);
|
|
329
916
|
},
|
|
330
917
|
});
|
|
331
|
-
}
|
|
332
|
-
} else {
|
|
333
|
-
setLoading(true);
|
|
334
|
-
await sendThreadMessage({
|
|
335
|
-
variables: {
|
|
336
|
-
channelId,
|
|
337
|
-
postThreadId: postThread && postThread?.id,
|
|
338
|
-
postParentId: !parentId || parentId == 0 ? null : parentId,
|
|
339
|
-
threadMessageInput: {
|
|
340
|
-
content: message,
|
|
341
|
-
role,
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
update: (cache, { data, errors }: any) => {
|
|
345
|
-
if (!data || errors) {
|
|
346
|
-
setLoading(false);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
setPostThread(data?.createPostThread.data);
|
|
350
|
-
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
351
|
-
if (!parentId || parentId == 0) {
|
|
352
|
-
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
353
|
-
}
|
|
354
|
-
setChannelToTop(channelToTop + 1);
|
|
355
|
-
setLoading(false);
|
|
356
|
-
setMsg('');
|
|
357
918
|
|
|
358
|
-
|
|
359
|
-
|
|
919
|
+
console.log('Send with file mutation result:', result);
|
|
920
|
+
}
|
|
921
|
+
} catch (error) {
|
|
922
|
+
console.error('Error sending message with file:', error);
|
|
923
|
+
safeSend({
|
|
924
|
+
type: 'ERROR',
|
|
925
|
+
data: { message: error.message || 'Failed to send message with file' },
|
|
360
926
|
});
|
|
361
927
|
}
|
|
362
928
|
},
|
|
363
|
-
[
|
|
929
|
+
[channelId, parentId, state.context, role, startUpload],
|
|
364
930
|
);
|
|
365
931
|
|
|
366
932
|
const sendPushNotification = async (messageId: string, channelId: string, parentId: any, threadId: string) => {
|
|
@@ -382,56 +948,86 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
382
948
|
};
|
|
383
949
|
|
|
384
950
|
const messageList = useMemo(() => {
|
|
385
|
-
|
|
951
|
+
const threadMessages = safeContextProperty('threadMessages', []);
|
|
952
|
+
console.log(`Creating message list from ${threadMessages.length} thread messages`);
|
|
953
|
+
|
|
386
954
|
let res: any = [];
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
msg
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
955
|
+
if (threadMessages?.length) {
|
|
956
|
+
// We need to convert the threadMessages into the format expected by GiftedChat
|
|
957
|
+
// Use a Set to track IDs and prevent duplicates
|
|
958
|
+
const messageIds = new Set();
|
|
959
|
+
|
|
960
|
+
res = threadMessages
|
|
961
|
+
.filter((msg) => {
|
|
962
|
+
// Skip duplicate IDs
|
|
963
|
+
if (!msg.id || messageIds.has(msg.id)) {
|
|
964
|
+
console.log('Skipping duplicate message ID:', msg.id);
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
messageIds.add(msg.id);
|
|
968
|
+
return true;
|
|
969
|
+
})
|
|
970
|
+
.map((msg) => {
|
|
971
|
+
// Generate a unique _id if needed by combining id and createdAt
|
|
972
|
+
const uniqueId = msg.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
973
|
+
|
|
974
|
+
let message: IMessageProps = {
|
|
975
|
+
_id: uniqueId,
|
|
976
|
+
text: msg.message || '',
|
|
977
|
+
createdAt: new Date(msg.createdAt),
|
|
978
|
+
user: {
|
|
979
|
+
_id: msg?.author?.id ?? auth?.profile?.id,
|
|
980
|
+
name:
|
|
981
|
+
msg?.author?.givenName ??
|
|
982
|
+
auth?.profile?.given_name + ' ' + msg?.author?.familyName ??
|
|
983
|
+
auth?.profile?.family_name,
|
|
984
|
+
avatar: msg?.author?.picture ?? auth?.profile?.picture,
|
|
985
|
+
},
|
|
986
|
+
type: msg?.type || '',
|
|
987
|
+
image: msg?.files?.data?.[0]?.url,
|
|
988
|
+
sent: msg?.isDelivered || true,
|
|
989
|
+
received: msg?.isRead || false,
|
|
990
|
+
propsConfiguration: msg?.propsConfiguration,
|
|
991
|
+
};
|
|
992
|
+
return message;
|
|
993
|
+
});
|
|
421
994
|
}
|
|
422
|
-
|
|
423
|
-
//
|
|
424
|
-
|
|
995
|
+
|
|
996
|
+
// Sort messages by date (newest first as required by GiftedChat)
|
|
997
|
+
const sortedMessages = res.sort((a, b) => b.createdAt - a.createdAt);
|
|
998
|
+
return sortedMessages;
|
|
999
|
+
}, [safeContextProperty('threadMessages'), auth]);
|
|
425
1000
|
|
|
426
1001
|
const renderSend = (props) => {
|
|
1002
|
+
// Check if there's an image selected
|
|
1003
|
+
const hasImage = safeContextProperty('selectedImage', '') !== '';
|
|
1004
|
+
|
|
1005
|
+
// Enable send button if there's text OR an image
|
|
1006
|
+
const isDisabled = !hasImage && (!props.text || props.text.trim().length === 0);
|
|
1007
|
+
|
|
427
1008
|
return (
|
|
428
|
-
<Send
|
|
429
|
-
|
|
1009
|
+
<Send
|
|
1010
|
+
{...props}
|
|
1011
|
+
containerStyle={{
|
|
1012
|
+
alignItems: 'center',
|
|
1013
|
+
justifyContent: 'center',
|
|
1014
|
+
marginHorizontal: 4,
|
|
1015
|
+
marginBottom: 0,
|
|
1016
|
+
}}
|
|
1017
|
+
disabled={isDisabled}
|
|
1018
|
+
>
|
|
1019
|
+
<Box
|
|
1020
|
+
style={{
|
|
1021
|
+
width: 32,
|
|
1022
|
+
height: 32,
|
|
1023
|
+
alignItems: 'center',
|
|
1024
|
+
justifyContent: 'center',
|
|
1025
|
+
}}
|
|
1026
|
+
>
|
|
430
1027
|
<MaterialCommunityIcons
|
|
431
1028
|
name="send-circle"
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
color="#2e64e5"
|
|
1029
|
+
size={30}
|
|
1030
|
+
color={isDisabled ? colors.gray[400] : colors.blue[500]}
|
|
435
1031
|
/>
|
|
436
1032
|
</Box>
|
|
437
1033
|
</Send>
|
|
@@ -469,11 +1065,6 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
469
1065
|
params = { reservationId: actionId };
|
|
470
1066
|
}
|
|
471
1067
|
|
|
472
|
-
// if (attachment?.callToAction?.link?.includes('my-reservation-details')) {
|
|
473
|
-
// action = CALL_TO_ACTION_PATH;
|
|
474
|
-
// actionId = attachment?.callToAction?.link.split('/').pop();
|
|
475
|
-
// }
|
|
476
|
-
|
|
477
1068
|
return (
|
|
478
1069
|
<>
|
|
479
1070
|
{attachment?.callToAction && action ? (
|
|
@@ -483,7 +1074,6 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
483
1074
|
size={'sm'}
|
|
484
1075
|
className={`border-[${CALL_TO_ACTION_BUTTON_BORDERCOLOR}]`}
|
|
485
1076
|
onPress={() => action && params && navigation.navigate(action, params)}
|
|
486
|
-
//onPress={() => navigation.navigate(action, { reservationId: actionId })}
|
|
487
1077
|
>
|
|
488
1078
|
<ButtonText className={`color-[${CALL_TO_ACTION_TEXT_COLOR}]`}>
|
|
489
1079
|
{attachment.callToAction.title}
|
|
@@ -499,10 +1089,6 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
499
1089
|
) : (
|
|
500
1090
|
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
501
1091
|
)}
|
|
502
|
-
{/* <MessageText
|
|
503
|
-
{...props}
|
|
504
|
-
textStyle={{ left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 } }}
|
|
505
|
-
/> */}
|
|
506
1092
|
</>
|
|
507
1093
|
);
|
|
508
1094
|
} else {
|
|
@@ -514,37 +1100,95 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
514
1100
|
return (
|
|
515
1101
|
<Actions
|
|
516
1102
|
{...props}
|
|
517
|
-
|
|
1103
|
+
options={{
|
|
1104
|
+
['Choose from Library']: onSelectImages,
|
|
1105
|
+
['Cancel']: () => {}, // Add this option to make the sheet dismissible
|
|
1106
|
+
}}
|
|
1107
|
+
optionTintColor="#000000"
|
|
1108
|
+
cancelButtonIndex={1} // Set the Cancel option as the cancel button
|
|
1109
|
+
icon={() => (
|
|
1110
|
+
<Box
|
|
1111
|
+
style={{
|
|
1112
|
+
width: 32,
|
|
1113
|
+
height: 32,
|
|
1114
|
+
alignItems: 'center',
|
|
1115
|
+
justifyContent: 'center',
|
|
1116
|
+
}}
|
|
1117
|
+
>
|
|
1118
|
+
<Ionicons name="image" size={24} color={colors.blue[500]} />
|
|
1119
|
+
</Box>
|
|
1120
|
+
)}
|
|
1121
|
+
containerStyle={{
|
|
1122
|
+
alignItems: 'center',
|
|
1123
|
+
justifyContent: 'center',
|
|
1124
|
+
marginLeft: 8,
|
|
1125
|
+
marginBottom: 0,
|
|
1126
|
+
}}
|
|
518
1127
|
/>
|
|
519
1128
|
);
|
|
520
1129
|
};
|
|
521
1130
|
|
|
522
1131
|
const renderAccessory = (props) => {
|
|
1132
|
+
const selectedImage = safeContextProperty('selectedImage', '');
|
|
1133
|
+
|
|
1134
|
+
if (!selectedImage) {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
523
1138
|
return (
|
|
524
|
-
<
|
|
525
|
-
{
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1139
|
+
<View
|
|
1140
|
+
style={{
|
|
1141
|
+
height: 80,
|
|
1142
|
+
padding: 10,
|
|
1143
|
+
backgroundColor: 'white',
|
|
1144
|
+
borderTopWidth: 1,
|
|
1145
|
+
borderTopColor: '#e0e0e0',
|
|
1146
|
+
flexDirection: 'row',
|
|
1147
|
+
alignItems: 'center',
|
|
1148
|
+
}}
|
|
1149
|
+
>
|
|
1150
|
+
<View
|
|
1151
|
+
style={{
|
|
1152
|
+
flex: 1,
|
|
1153
|
+
flexDirection: 'row',
|
|
1154
|
+
alignItems: 'center',
|
|
1155
|
+
// justifyContent: 'space-between',
|
|
1156
|
+
paddingHorizontal: 20,
|
|
1157
|
+
}}
|
|
1158
|
+
>
|
|
1159
|
+
<Image
|
|
1160
|
+
key={state?.context?.selectedImage}
|
|
1161
|
+
alt={'selected image'}
|
|
1162
|
+
source={{ uri: state?.context?.selectedImage }}
|
|
1163
|
+
size={'xs'}
|
|
1164
|
+
style={{
|
|
1165
|
+
width: 5,
|
|
1166
|
+
height: 5,
|
|
1167
|
+
borderRadius: 5,
|
|
1168
|
+
marginRight: 20,
|
|
1169
|
+
}}
|
|
1170
|
+
/>
|
|
1171
|
+
|
|
1172
|
+
<TouchableHighlight
|
|
1173
|
+
underlayColor="#dddddd"
|
|
1174
|
+
onPress={() => safeSend({ type: ThreadActions.CLEAR_IMAGE })}
|
|
1175
|
+
style={{
|
|
1176
|
+
backgroundColor: '#f44336',
|
|
1177
|
+
paddingVertical: 2,
|
|
1178
|
+
paddingHorizontal: 5,
|
|
1179
|
+
borderRadius: 5,
|
|
1180
|
+
marginLeft: 10,
|
|
1181
|
+
elevation: 3,
|
|
1182
|
+
shadowColor: '#000',
|
|
1183
|
+
shadowOffset: { width: 0, height: 1 },
|
|
1184
|
+
shadowOpacity: 0.3,
|
|
1185
|
+
shadowRadius: 2,
|
|
1186
|
+
}}
|
|
1187
|
+
>
|
|
1188
|
+
<Text style={{ color: 'white', fontWeight: 'bold' }}>X</Text>
|
|
1189
|
+
</TouchableHighlight>
|
|
1190
|
+
</View>
|
|
1191
|
+
</View>
|
|
548
1192
|
);
|
|
549
1193
|
};
|
|
550
1194
|
|
|
@@ -560,11 +1204,9 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
560
1204
|
<CachedImage
|
|
561
1205
|
style={{ width: '100%', height: '100%' }}
|
|
562
1206
|
resizeMode={'cover'}
|
|
563
|
-
// cacheKey={`${_id}-conversation-modal-image-key`}
|
|
564
1207
|
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
565
1208
|
source={{
|
|
566
1209
|
uri: image,
|
|
567
|
-
//headers: `Authorization: Bearer ${token}`,
|
|
568
1210
|
expiresIn: 86400,
|
|
569
1211
|
}}
|
|
570
1212
|
alt={'image'}
|
|
@@ -576,37 +1218,52 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
576
1218
|
return <SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />;
|
|
577
1219
|
};
|
|
578
1220
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (!loadingOldMessages && isCloseToTop(nativeEvent) && totalCount > channelMessages?.length) {
|
|
585
|
-
onFetchOld();
|
|
586
|
-
}
|
|
587
|
-
};
|
|
1221
|
+
// Define a memo that provides the current message text from state
|
|
1222
|
+
const currentMessageText = useMemo(() => {
|
|
1223
|
+
const text = safeContextProperty('messageText', '') || ' ';
|
|
1224
|
+
return text;
|
|
1225
|
+
}, [safeContextProperty('messageText')]);
|
|
588
1226
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
1227
|
+
// Define a custom renderInputToolbar function
|
|
1228
|
+
const renderInputToolbar = (props) => {
|
|
1229
|
+
return (
|
|
1230
|
+
<InputToolbar
|
|
1231
|
+
{...props}
|
|
1232
|
+
containerStyle={{
|
|
1233
|
+
backgroundColor: 'white',
|
|
1234
|
+
borderTopWidth: 1,
|
|
1235
|
+
borderTopColor: colors.gray[200],
|
|
1236
|
+
paddingHorizontal: 4,
|
|
1237
|
+
paddingVertical: 4,
|
|
1238
|
+
}}
|
|
1239
|
+
primaryStyle={{
|
|
1240
|
+
alignItems: 'center',
|
|
1241
|
+
}}
|
|
1242
|
+
/>
|
|
1243
|
+
);
|
|
595
1244
|
};
|
|
596
1245
|
|
|
597
1246
|
return (
|
|
598
|
-
|
|
599
|
-
{(loadingOldMessages
|
|
600
|
-
|
|
1247
|
+
<SafeAreaView style={{ flex: 1 }}>
|
|
1248
|
+
{safeContextProperty('loadingOldMessages', false) && (
|
|
1249
|
+
<Box className="absolute top-10 left-0 right-0 z-10 items-center">
|
|
1250
|
+
<Box className="bg-blue-500/20 rounded-full px-4 py-2 flex-row items-center">
|
|
1251
|
+
<Spinner color={colors.blue[500]} size="small" />
|
|
1252
|
+
<Text className="text-sm font-medium color-blue-600 ml-2">Loading messages...</Text>
|
|
1253
|
+
</Box>
|
|
1254
|
+
</Box>
|
|
1255
|
+
)}
|
|
601
1256
|
{isPostParentIdThread && (
|
|
602
1257
|
<>
|
|
603
|
-
{threadPost?.length > 0 && (
|
|
1258
|
+
{safeContextProperty('threadPost', [])?.length > 0 && (
|
|
604
1259
|
<>
|
|
605
1260
|
<VStack className="px-2 pt-2 pb-0" space={'sm'}>
|
|
606
1261
|
<HStack space={'sm'} className="items-center">
|
|
607
1262
|
<Avatar className="bg-transparent" size={'md'}>
|
|
608
1263
|
<AvatarFallbackText>
|
|
609
|
-
{startCase(
|
|
1264
|
+
{startCase(
|
|
1265
|
+
safeContextProperty('threadPost')[0]?.author?.username?.charAt(0),
|
|
1266
|
+
)}
|
|
610
1267
|
</AvatarFallbackText>
|
|
611
1268
|
<AvatarImage
|
|
612
1269
|
alt="image"
|
|
@@ -616,31 +1273,36 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
616
1273
|
borderColor: '#fff',
|
|
617
1274
|
}}
|
|
618
1275
|
source={{
|
|
619
|
-
uri: threadPost[0]?.author?.picture,
|
|
1276
|
+
uri: safeContextProperty('threadPost')[0]?.author?.picture,
|
|
620
1277
|
}}
|
|
621
1278
|
/>
|
|
622
1279
|
</Avatar>
|
|
623
1280
|
<Box>
|
|
624
1281
|
<Text className="font-bold color-black">
|
|
625
|
-
{threadPost[0]?.author?.givenName ?? ''}{' '}
|
|
626
|
-
{threadPost[0]?.author?.familyName ?? ''}
|
|
1282
|
+
{safeContextProperty('threadPost')[0]?.author?.givenName ?? ''}{' '}
|
|
1283
|
+
{safeContextProperty('threadPost')[0]?.author?.familyName ?? ''}
|
|
627
1284
|
</Text>
|
|
628
1285
|
<Text className="pl-0 color-gray-500">
|
|
629
|
-
{createdAtText(threadPost[0]?.createdAt)} at{' '}
|
|
630
|
-
{format(
|
|
1286
|
+
{createdAtText(safeContextProperty('threadPost')[0]?.createdAt)} at{' '}
|
|
1287
|
+
{format(
|
|
1288
|
+
new Date(safeContextProperty('threadPost')[0]?.createdAt),
|
|
1289
|
+
'hh:ss:a',
|
|
1290
|
+
)}
|
|
631
1291
|
</Text>
|
|
632
1292
|
</Box>
|
|
633
1293
|
</HStack>
|
|
634
1294
|
<HStack space={'sm'} className="px-2 items-center">
|
|
635
|
-
<Text>{threadPost[0]?.message ?? ''}</Text>
|
|
1295
|
+
<Text>{safeContextProperty('threadPost')[0]?.message ?? ''}</Text>
|
|
636
1296
|
</HStack>
|
|
637
1297
|
</VStack>
|
|
638
1298
|
|
|
639
1299
|
<Box className="py-4">
|
|
640
1300
|
<Box className="px-4 py-2 border-t border-b border-gray-200">
|
|
641
1301
|
<Text className="font-bold color-gray-600">
|
|
642
|
-
{threadPost[0]?.replies?.totalCount}{' '}
|
|
643
|
-
{threadPost[0]?.replies?.totalCount > 0
|
|
1302
|
+
{safeContextProperty('threadPost')[0]?.replies?.totalCount}{' '}
|
|
1303
|
+
{safeContextProperty('threadPost')[0]?.replies?.totalCount > 0
|
|
1304
|
+
? 'replies'
|
|
1305
|
+
: 'reply'}
|
|
644
1306
|
</Text>
|
|
645
1307
|
</Box>
|
|
646
1308
|
</Box>
|
|
@@ -654,51 +1316,144 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
654
1316
|
renderLoading={() => <Spinner color={colors.blue[500]} />}
|
|
655
1317
|
messages={messageList}
|
|
656
1318
|
listViewProps={{
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1319
|
+
onScroll: handleScrollToTop,
|
|
1320
|
+
onEndReached: handleEndReached,
|
|
1321
|
+
onEndReachedThreshold: 0.2,
|
|
1322
|
+
contentContainerStyle: {
|
|
1323
|
+
paddingBottom: 10,
|
|
1324
|
+
},
|
|
1325
|
+
maintainVisibleContentPosition: {
|
|
1326
|
+
minIndexForVisible: 0,
|
|
1327
|
+
autoscrollToTopThreshold: 100,
|
|
1328
|
+
},
|
|
1329
|
+
scrollEventThrottle: 100,
|
|
1330
|
+
keyboardDismissMode: 'on-drag',
|
|
1331
|
+
keyboardShouldPersistTaps: 'handled',
|
|
1332
|
+
}}
|
|
1333
|
+
onSend={(messages) => {
|
|
1334
|
+
if (!messages || messages.length === 0) {
|
|
1335
|
+
console.log('No messages to send');
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Use the actual message text from the state, not the one from GiftedChat
|
|
1340
|
+
// GiftedChat sometimes sends blank messages even when there's text in the input
|
|
1341
|
+
const currentInputText = currentMessageText;
|
|
1342
|
+
const messageToSend = currentInputText?.trim() || messages[0]?.text?.trim() || ' ';
|
|
1343
|
+
|
|
1344
|
+
console.log('GiftedChat onSend triggered with text from state:', messageToSend);
|
|
1345
|
+
|
|
1346
|
+
// Make sure we update the message text in state
|
|
1347
|
+
safeSend({
|
|
1348
|
+
type: ThreadActions.SET_MESSAGE_TEXT,
|
|
1349
|
+
data: { messageText: '' },
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
// Then send the message
|
|
1353
|
+
if (safeContextProperty('images', []).length > 0) {
|
|
1354
|
+
console.log(
|
|
1355
|
+
'Sending message with file:',
|
|
1356
|
+
messageToSend,
|
|
1357
|
+
'Images:',
|
|
1358
|
+
safeContextProperty('images', []).length,
|
|
1359
|
+
);
|
|
1360
|
+
safeSend({
|
|
1361
|
+
type: ThreadActions.SEND_THREAD_MESSAGE_WITH_FILE,
|
|
1362
|
+
data: { messageText: messageToSend },
|
|
1363
|
+
});
|
|
1364
|
+
} else {
|
|
1365
|
+
console.log('Sending text message:', messageToSend);
|
|
1366
|
+
safeSend({
|
|
1367
|
+
type: ThreadActions.SEND_THREAD_MESSAGE,
|
|
1368
|
+
data: { messageText: messageToSend },
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
}}
|
|
1372
|
+
text={currentMessageText}
|
|
1373
|
+
onInputTextChanged={(text) => {
|
|
1374
|
+
// Don't log every keystroke to reduce console spam
|
|
1375
|
+
if (text.length % 5 === 0 || text.length < 5) {
|
|
1376
|
+
console.log('Input text changed:', text);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// Set the text in the state
|
|
1380
|
+
safeSend({
|
|
1381
|
+
type: ThreadActions.SET_MESSAGE_TEXT,
|
|
1382
|
+
data: { messageText: text },
|
|
1383
|
+
});
|
|
660
1384
|
}}
|
|
661
|
-
onSend={(messages) => handleSend(messages[0]?.text ?? ' ')}
|
|
662
|
-
text={msg ? msg : ' '}
|
|
663
|
-
onInputTextChanged={(text) => setMsg(text)}
|
|
664
1385
|
renderFooter={() =>
|
|
665
|
-
loading ? (
|
|
666
|
-
<
|
|
667
|
-
|
|
668
|
-
|
|
1386
|
+
safeContextProperty('loading', false) && !safeContextProperty('loadingOldMessages', false) ? (
|
|
1387
|
+
<Box className="w-full py-2 items-center">
|
|
1388
|
+
<Spinner color={colors.blue[500]} />
|
|
1389
|
+
</Box>
|
|
1390
|
+
) : safeContextProperty('imageLoading', false) ? (
|
|
1391
|
+
<Box className="w-full py-2 items-center">
|
|
1392
|
+
<Spinner color={colors.blue[500]} />
|
|
1393
|
+
</Box>
|
|
669
1394
|
) : (
|
|
670
|
-
|
|
1395
|
+
<></>
|
|
671
1396
|
)
|
|
672
1397
|
}
|
|
673
1398
|
scrollToBottom
|
|
1399
|
+
loadEarlier={false}
|
|
1400
|
+
isLoadingEarlier={false}
|
|
674
1401
|
user={{
|
|
675
1402
|
_id: auth?.id || '',
|
|
676
1403
|
}}
|
|
677
1404
|
isTyping={true}
|
|
678
|
-
alwaysShowSend={loading ? false : true}
|
|
679
|
-
// onLoadEarlier={onFetchOld}
|
|
1405
|
+
alwaysShowSend={safeContextProperty('loading', false) ? false : true}
|
|
680
1406
|
infiniteScroll={true}
|
|
681
1407
|
renderSend={renderSend}
|
|
682
|
-
|
|
683
|
-
// isLoadingEarlier={loadEarlierMsg}
|
|
684
|
-
// renderLoadEarlier={() =>
|
|
685
|
-
// !loadEarlierMsg && (
|
|
686
|
-
// <Center py={2}>
|
|
687
|
-
// <Button
|
|
688
|
-
// onPress={() => onFetchOld(channelMessages?.length)}
|
|
689
|
-
// variant={'outline'}
|
|
690
|
-
// _text={{ color: 'black', fontSize: 15, fontWeight: 'bold' }}
|
|
691
|
-
// >
|
|
692
|
-
// Load earlier messages
|
|
693
|
-
// </Button>
|
|
694
|
-
// </Center>
|
|
695
|
-
// )
|
|
696
|
-
// }
|
|
697
|
-
renderMessageText={renderMessageText}
|
|
1408
|
+
renderInputToolbar={renderInputToolbar}
|
|
698
1409
|
minInputToolbarHeight={50}
|
|
699
1410
|
renderActions={renderActions}
|
|
700
|
-
renderAccessory={renderAccessory}
|
|
1411
|
+
renderAccessory={!!state?.context?.selectedImage ? renderAccessory : undefined}
|
|
701
1412
|
renderMessage={renderMessage}
|
|
1413
|
+
maxInputLength={1000}
|
|
1414
|
+
placeholder="Type a message..."
|
|
1415
|
+
showUserAvatar={true}
|
|
1416
|
+
showAvatarForEveryMessage={false}
|
|
1417
|
+
inverted={true}
|
|
1418
|
+
parsePatterns={(linkStyle) => [
|
|
1419
|
+
{
|
|
1420
|
+
type: 'url',
|
|
1421
|
+
style: { ...linkStyle, color: colors.blue[500] },
|
|
1422
|
+
onPress: (url) => Linking.openURL(url),
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
type: 'phone',
|
|
1426
|
+
style: { ...linkStyle, color: colors.blue[500] },
|
|
1427
|
+
onPress: (phone) => Linking.openURL(`tel:${phone}`),
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
type: 'email',
|
|
1431
|
+
style: { ...linkStyle, color: colors.blue[500] },
|
|
1432
|
+
onPress: (email) => Linking.openURL(`mailto:${email}`),
|
|
1433
|
+
},
|
|
1434
|
+
]}
|
|
1435
|
+
textInputProps={{
|
|
1436
|
+
style: {
|
|
1437
|
+
borderWidth: 1,
|
|
1438
|
+
borderColor: colors.gray[300],
|
|
1439
|
+
backgroundColor: '#f8f8f8',
|
|
1440
|
+
borderRadius: 20,
|
|
1441
|
+
minHeight: 40,
|
|
1442
|
+
maxHeight: 80,
|
|
1443
|
+
color: '#000',
|
|
1444
|
+
padding: 10,
|
|
1445
|
+
paddingHorizontal: 20,
|
|
1446
|
+
fontSize: 16,
|
|
1447
|
+
flex: 1,
|
|
1448
|
+
},
|
|
1449
|
+
multiline: true,
|
|
1450
|
+
returnKeyType: 'default',
|
|
1451
|
+
enablesReturnKeyAutomatically: true,
|
|
1452
|
+
placeholderTextColor: colors.gray[400],
|
|
1453
|
+
}}
|
|
1454
|
+
minComposerHeight={44}
|
|
1455
|
+
isKeyboardInternallyHandled={true}
|
|
1456
|
+
bottomOffset={Platform.OS === 'ios' ? 20 : 0}
|
|
702
1457
|
renderChatFooter={() => (
|
|
703
1458
|
<>
|
|
704
1459
|
<ImageViewerModal
|
|
@@ -721,10 +1476,20 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
721
1476
|
const prevReplyCount: any = prev?.getPostThread?.replyCount;
|
|
722
1477
|
const newReplyCount = prevReplyCount || 0 + 1;
|
|
723
1478
|
const replies = prev?.getPostThread?.replies || [];
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1479
|
+
|
|
1480
|
+
safeSend({
|
|
1481
|
+
type: ThreadActions.SET_THREAD_MESSAGES,
|
|
1482
|
+
data: {
|
|
1483
|
+
messages: uniqBy(
|
|
1484
|
+
[...safeContextProperty('threadMessages', []), newMessage],
|
|
1485
|
+
({ id }) => id,
|
|
1486
|
+
),
|
|
1487
|
+
totalCount: newReplyCount,
|
|
1488
|
+
threadPost: safeContextProperty('threadPost', []),
|
|
1489
|
+
postThread: safeContextProperty('postThread', null),
|
|
1490
|
+
},
|
|
1491
|
+
});
|
|
1492
|
+
|
|
728
1493
|
return Object.assign({}, prev, {
|
|
729
1494
|
getPostThread: {
|
|
730
1495
|
...prev?.getPostThread,
|
|
@@ -759,7 +1524,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
759
1524
|
disabled: true,
|
|
760
1525
|
}}
|
|
761
1526
|
/>
|
|
762
|
-
|
|
1527
|
+
</SafeAreaView>
|
|
763
1528
|
);
|
|
764
1529
|
};
|
|
765
1530
|
|
|
@@ -768,5 +1533,5 @@ const SubscriptionHandler = ({ subscribeToNewMessages, channelId }: IThreadSubsc
|
|
|
768
1533
|
return <></>;
|
|
769
1534
|
};
|
|
770
1535
|
|
|
771
|
-
export const ThreadConversationView = ThreadConversationViewComponent;
|
|
772
|
-
// export const ThreadConversationView =
|
|
1536
|
+
export const ThreadConversationView = React.memo(ThreadConversationViewComponent);
|
|
1537
|
+
// export const ThreadConversationView = ThreadConversationViewComponent;
|