@messenger-box/platform-mobile 10.0.3-alpha.16 → 10.0.3-alpha.162
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 +248 -0
- package/lib/compute.js +2 -3
- package/lib/compute.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/module.js.map +1 -1
- package/lib/queries/inboxQueries.js +65 -0
- package/lib/queries/inboxQueries.js.map +1 -0
- package/lib/routes.json +13 -1
- package/lib/screens/inbox/DialogMessages.js +5 -3
- package/lib/screens/inbox/DialogMessages.js.map +1 -1
- package/lib/screens/inbox/DialogThreadMessages.js +3 -7
- package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
- package/lib/screens/inbox/DialogThreads.js +57 -18
- package/lib/screens/inbox/DialogThreads.js.map +1 -1
- package/lib/screens/inbox/Inbox.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/index.js +168 -46
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/DialogItem.js +169 -0
- package/lib/screens/inbox/components/DialogItem.js.map +1 -0
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js +313 -0
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
- package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js +197 -0
- package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js.map +1 -0
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +141 -31
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js.map +1 -1
- package/lib/screens/inbox/components/SubscriptionHandler.js +24 -0
- package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
- package/lib/screens/inbox/components/ThreadsViewItem.js +66 -44
- package/lib/screens/inbox/components/ThreadsViewItem.js.map +1 -1
- package/lib/screens/inbox/config/config.js +2 -2
- package/lib/screens/inbox/config/config.js.map +1 -1
- package/lib/screens/inbox/containers/ConversationView.js +1205 -432
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +193 -80
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +724 -207
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadsView.js +83 -50
- package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
- package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
- package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
- package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js +108 -0
- package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -0
- package/lib/screens/inbox/workflow/dialog-threads-xstate.js +151 -0
- package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +2 -0
- package/src/queries/inboxQueries.ts +299 -0
- package/src/queries/index.d.ts +2 -0
- package/src/queries/index.ts +1 -0
- package/src/screens/inbox/DialogMessages.tsx +2 -1
- package/src/screens/inbox/DialogThreadMessages.tsx +3 -11
- package/src/screens/inbox/DialogThreads.tsx +53 -60
- package/src/screens/inbox/components/Actionsheet.tsx +30 -0
- package/src/screens/inbox/components/CachedImage/consts.ts +4 -3
- package/src/screens/inbox/components/CachedImage/index.tsx +232 -61
- package/src/screens/inbox/components/DialogItem.tsx +306 -0
- package/src/screens/inbox/components/DialogsListItem.tsx +230 -170
- package/src/screens/inbox/components/ExpandableInput.tsx +460 -0
- package/src/screens/inbox/components/ExpandableInputActionSheet.tsx +518 -0
- package/src/screens/inbox/components/GiftedChatInboxComponent.tsx +411 -0
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +330 -171
- package/src/screens/inbox/components/SlackInput.tsx +23 -0
- package/src/screens/inbox/components/SlackMessageContainer/PaymentMessage.tsx +199 -0
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +233 -23
- package/src/screens/inbox/components/SmartLoader.tsx +61 -0
- package/src/screens/inbox/components/SubscriptionHandler.tsx +41 -0
- package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +41 -24
- package/src/screens/inbox/components/ThreadsViewItem.tsx +177 -265
- package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
- package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
- package/src/screens/inbox/config/config.ts +2 -2
- package/src/screens/inbox/containers/ConversationView.tsx +2004 -698
- package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
- package/src/screens/inbox/containers/Dialogs.tsx +402 -196
- package/src/screens/inbox/containers/SupportServiceDialogs.tsx +2 -2
- package/src/screens/inbox/containers/ThreadConversationView.tsx +1337 -301
- package/src/screens/inbox/containers/ThreadsView.tsx +105 -186
- 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
- package/src/screens/inbox/hooks/useInboxMessages.ts +34 -0
- package/src/screens/inbox/hooks/useSafeDialogThreadsMachine.ts +136 -0
- package/src/screens/inbox/index.ts +37 -0
- package/src/screens/inbox/machines/threadsMachine.ts +147 -0
- package/src/screens/inbox/workflow/dialog-threads-xstate.ts +163 -0
- package/lib/screens/inbox/components/DialogsListItem.js +0 -175
- package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -165
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
|
@@ -13,25 +13,45 @@ import {
|
|
|
13
13
|
Image,
|
|
14
14
|
Spinner,
|
|
15
15
|
Text,
|
|
16
|
+
Skeleton,
|
|
17
|
+
Toast,
|
|
18
|
+
ToastTitle,
|
|
19
|
+
ToastDescription,
|
|
20
|
+
useToast,
|
|
21
|
+
Divider,
|
|
16
22
|
} from '@admin-layout/gluestack-ui-mobile';
|
|
17
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
Platform,
|
|
25
|
+
TouchableHighlight,
|
|
26
|
+
View,
|
|
27
|
+
TouchableOpacity,
|
|
28
|
+
ScrollView,
|
|
29
|
+
Animated,
|
|
30
|
+
Keyboard,
|
|
31
|
+
RefreshControl,
|
|
32
|
+
} from 'react-native';
|
|
18
33
|
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
|
19
34
|
import { useSelector } from 'react-redux';
|
|
20
35
|
import { orderBy, startCase, uniqBy } from 'lodash-es';
|
|
21
36
|
import * as ImagePicker from 'expo-image-picker';
|
|
22
37
|
import { encode as atob } from 'base-64';
|
|
23
38
|
import { Ionicons, MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons';
|
|
24
|
-
import { Actions, GiftedChat, IMessage, MessageText, Send } from 'react-native-gifted-chat';
|
|
25
|
-
import {
|
|
39
|
+
import { Actions, GiftedChat, IMessage, MessageText, Send, InputToolbar, Composer } from 'react-native-gifted-chat';
|
|
40
|
+
import {
|
|
41
|
+
IPost,
|
|
42
|
+
IPostThread,
|
|
43
|
+
PreDefinedRole,
|
|
44
|
+
IExpoNotificationData,
|
|
45
|
+
IFileInfo,
|
|
46
|
+
FileRefType,
|
|
47
|
+
PostTypeEnum,
|
|
48
|
+
} from 'common';
|
|
26
49
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
useThreadMessagesLazyQuery,
|
|
33
|
-
useGetPostThreadLazyQuery,
|
|
34
|
-
} from 'common/graphql';
|
|
50
|
+
CHAT_MESSAGE_ADDED,
|
|
51
|
+
useCreatePostThread,
|
|
52
|
+
useSendExpoNotification,
|
|
53
|
+
useGetPostThreadLazy,
|
|
54
|
+
} from '../../../queries/inboxQueries';
|
|
35
55
|
import { useUploadFilesNative } from '@messenger-box/platform-client';
|
|
36
56
|
import { objectId } from '@messenger-box/core';
|
|
37
57
|
import { format, isToday, isYesterday } from 'date-fns';
|
|
@@ -40,6 +60,8 @@ import { config } from '../config';
|
|
|
40
60
|
import { ImageViewerModal, SlackMessage } from '../components/SlackMessageContainer';
|
|
41
61
|
import CachedImage from '../components/CachedImage';
|
|
42
62
|
import colors from 'tailwindcss/colors';
|
|
63
|
+
import ExpandableInputActionSheet from '../components/ExpandableInputActionSheet';
|
|
64
|
+
import GiftedChatInboxComponent from '../components/GiftedChatInboxComponent';
|
|
43
65
|
|
|
44
66
|
const {
|
|
45
67
|
MESSAGES_PER_PAGE,
|
|
@@ -60,6 +82,9 @@ const createdAtText = (value: string) => {
|
|
|
60
82
|
interface IMessageProps extends IMessage {
|
|
61
83
|
type: string;
|
|
62
84
|
propsConfiguration?: any;
|
|
85
|
+
images?: string[];
|
|
86
|
+
isOptimistic?: boolean;
|
|
87
|
+
isUploading?: boolean;
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
export interface AlertMessageAttachmentsInterface {
|
|
@@ -77,21 +102,88 @@ interface IThreadSubscriptionHandlerProps {
|
|
|
77
102
|
channelId: string;
|
|
78
103
|
}
|
|
79
104
|
|
|
105
|
+
// Define an extended interface for ImagePickerAsset with url property
|
|
106
|
+
interface ExtendedImagePickerAsset extends ImagePicker.ImagePickerAsset {
|
|
107
|
+
url?: string;
|
|
108
|
+
fileName?: string;
|
|
109
|
+
mimeType?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Custom notification component
|
|
113
|
+
const ErrorNotification = ({ message, onClose }) => {
|
|
114
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
// Fade in
|
|
118
|
+
Animated.timing(opacity, {
|
|
119
|
+
toValue: 1,
|
|
120
|
+
duration: 300,
|
|
121
|
+
useNativeDriver: true,
|
|
122
|
+
}).start();
|
|
123
|
+
|
|
124
|
+
// Auto hide after 4 seconds
|
|
125
|
+
const timer = setTimeout(() => {
|
|
126
|
+
Animated.timing(opacity, {
|
|
127
|
+
toValue: 0,
|
|
128
|
+
duration: 300,
|
|
129
|
+
useNativeDriver: true,
|
|
130
|
+
}).start(() => onClose && onClose());
|
|
131
|
+
}, 4000);
|
|
132
|
+
|
|
133
|
+
return () => clearTimeout(timer);
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Animated.View
|
|
138
|
+
style={{
|
|
139
|
+
position: 'absolute',
|
|
140
|
+
top: 10,
|
|
141
|
+
left: 10,
|
|
142
|
+
right: 10,
|
|
143
|
+
backgroundColor: '#f44336',
|
|
144
|
+
padding: 15,
|
|
145
|
+
borderRadius: 8,
|
|
146
|
+
shadowColor: '#000',
|
|
147
|
+
shadowOffset: { width: 0, height: 2 },
|
|
148
|
+
shadowOpacity: 0.25,
|
|
149
|
+
shadowRadius: 3.84,
|
|
150
|
+
elevation: 5,
|
|
151
|
+
zIndex: 1000,
|
|
152
|
+
opacity,
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<HStack className="items-center justify-between">
|
|
156
|
+
<Text style={{ color: 'white', fontWeight: 'bold' }}>Upload Failed</Text>
|
|
157
|
+
<TouchableOpacity onPress={onClose}>
|
|
158
|
+
<Ionicons name="close" size={20} color="white" />
|
|
159
|
+
</TouchableOpacity>
|
|
160
|
+
</HStack>
|
|
161
|
+
<Text style={{ color: 'white', marginTop: 5 }}>{message}</Text>
|
|
162
|
+
</Animated.View>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Add a utility function for file validation
|
|
167
|
+
const isValidFileUrl = (url: string | undefined): boolean => {
|
|
168
|
+
return !!url && typeof url === 'string' && url.length > 0;
|
|
169
|
+
};
|
|
170
|
+
|
|
80
171
|
const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParentIdThread, role }: any) => {
|
|
81
172
|
const { params } = useRoute<any>();
|
|
82
173
|
const [channelToTop, setChannelToTop] = useState(0);
|
|
83
174
|
const [channelMessages, setChannelMessages] = useState<any>([]);
|
|
84
175
|
const auth: any = useSelector(userSelector);
|
|
85
176
|
const [totalCount, setTotalCount] = useState<any>(0);
|
|
86
|
-
const [selectedImage,
|
|
177
|
+
const [selectedImage, setSelectedImage] = useState<string>('');
|
|
87
178
|
const [loadingOldMessages, setLoadingOldMessages] = useState<boolean>(false);
|
|
88
179
|
const [loadEarlierMsg, setLoadEarlierMsg] = useState(false);
|
|
89
180
|
const navigation = useNavigation<any>();
|
|
90
181
|
const [files, setFiles] = useState<File[]>([]);
|
|
91
|
-
const [images, setImages] = useState<
|
|
182
|
+
const [images, setImages] = useState<ExtendedImagePickerAsset[]>([]);
|
|
92
183
|
const [msg, setMsg] = useState<string>('');
|
|
93
184
|
const [loading, setLoading] = useState(false);
|
|
94
185
|
const [imageLoading, setImageLoading] = useState(false);
|
|
186
|
+
const [uploadingMessageId, setUploadingMessageId] = useState<string | null>(null);
|
|
95
187
|
const [expoTokens, setExpoTokens] = useState<any[]>([]);
|
|
96
188
|
const [isShowImageViewer, setImageViewer] = useState<boolean>(false);
|
|
97
189
|
const [imageObject, setImageObject] = useState<any>({});
|
|
@@ -101,15 +193,30 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
101
193
|
const [threadPost, setThreadPost] = useState<any[]>([]);
|
|
102
194
|
const [isScrollToBottom, setIsScrollToBottom] = useState(false);
|
|
103
195
|
const threadMessageListRef = useRef<any>(null);
|
|
196
|
+
const toast = useToast();
|
|
197
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
198
|
+
|
|
199
|
+
// Add state for expandable input action sheet
|
|
200
|
+
const [isActionSheetVisible, setActionSheetVisible] = useState(false);
|
|
201
|
+
const [textUpdatedInActionSheet, setTextUpdatedInActionSheet] = useState(false);
|
|
202
|
+
const [lastShownMsg, setLastShownMsg] = useState('');
|
|
203
|
+
const textInputRef = useRef(null);
|
|
104
204
|
|
|
105
205
|
// const [sendThreadMessage] = useSendThreadMessageMutation();
|
|
106
|
-
const [sendThreadMessage] =
|
|
107
|
-
const [sendExpoNotificationOnPostMutation] =
|
|
206
|
+
const [sendThreadMessage] = useCreatePostThread();
|
|
207
|
+
const [sendExpoNotificationOnPostMutation] = useSendExpoNotification();
|
|
108
208
|
|
|
109
209
|
const [
|
|
110
210
|
getThreadMessages,
|
|
111
211
|
{ data, loading: threadLoading, fetchMore: fetchMoreMessages, refetch: refetchThreadMessages, subscribeToMore },
|
|
112
|
-
] =
|
|
212
|
+
] = useGetPostThreadLazy({ fetchPolicy: 'cache-and-network' });
|
|
213
|
+
|
|
214
|
+
// Add new state for tracking uploads
|
|
215
|
+
const [pendingUploads, setPendingUploads] = useState<Record<string, any>>({});
|
|
216
|
+
const [uploadErrors, setUploadErrors] = useState<Record<string, string>>({});
|
|
217
|
+
const [isUploadingImage, setIsUploadingImage] = useState(false);
|
|
218
|
+
|
|
219
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
113
220
|
|
|
114
221
|
useFocusEffect(
|
|
115
222
|
React.useCallback(() => {
|
|
@@ -199,6 +306,8 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
199
306
|
const onFetchOld = useCallback(() => {
|
|
200
307
|
if (totalCount > channelMessages.length && !loadingOldMessages) {
|
|
201
308
|
setLoadEarlierMsg(true);
|
|
309
|
+
setLoadingOldMessages(true);
|
|
310
|
+
|
|
202
311
|
fetchMoreMessages({
|
|
203
312
|
variables: {
|
|
204
313
|
channelId: channelId?.toString(),
|
|
@@ -220,14 +329,24 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
220
329
|
.finally(() => {
|
|
221
330
|
setLoadEarlierMsg(false);
|
|
222
331
|
setLoadingOldMessages(false);
|
|
332
|
+
setRefreshing(false);
|
|
223
333
|
})
|
|
224
334
|
.catch((error: any) => {
|
|
225
335
|
setLoadEarlierMsg(false);
|
|
226
336
|
setLoadingOldMessages(false);
|
|
337
|
+
setRefreshing(false);
|
|
227
338
|
});
|
|
339
|
+
} else {
|
|
340
|
+
setRefreshing(false);
|
|
228
341
|
}
|
|
229
342
|
}, [parentId, channelId, totalCount, channelMessages]);
|
|
230
343
|
|
|
344
|
+
// Pull to refresh handler
|
|
345
|
+
const onRefresh = useCallback(() => {
|
|
346
|
+
setRefreshing(true);
|
|
347
|
+
onFetchOld();
|
|
348
|
+
}, [onFetchOld]);
|
|
349
|
+
|
|
231
350
|
// const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
|
|
232
351
|
// return contentOffset.y <= 100; // 100px from top
|
|
233
352
|
// };
|
|
@@ -249,118 +368,438 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
249
368
|
return new File([u8arr], filename, { type: mime });
|
|
250
369
|
};
|
|
251
370
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
base64: true,
|
|
371
|
+
// Add a helper function for removing messages from the UI when uploads fail
|
|
372
|
+
const removeMessageFromUI = useCallback((messageId: string) => {
|
|
373
|
+
// Remove from pending uploads
|
|
374
|
+
setPendingUploads((prev) => {
|
|
375
|
+
const newPending = { ...prev };
|
|
376
|
+
delete newPending[messageId];
|
|
377
|
+
return newPending;
|
|
260
378
|
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
379
|
+
|
|
380
|
+
// Also remove any error state
|
|
381
|
+
setUploadErrors((prev) => {
|
|
382
|
+
const newErrors = { ...prev };
|
|
383
|
+
delete newErrors[messageId];
|
|
384
|
+
return newErrors;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Reset upload state to ensure we don't get stuck with loading indicator
|
|
388
|
+
setIsUploadingImage(false);
|
|
389
|
+
}, []);
|
|
390
|
+
|
|
391
|
+
// const onSelectImages = async () => {
|
|
392
|
+
// setImageLoading(true);
|
|
393
|
+
|
|
394
|
+
// try {
|
|
395
|
+
// let imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
396
|
+
// mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
397
|
+
// allowsEditing: true,
|
|
398
|
+
// aspect: [4, 3],
|
|
399
|
+
// quality: 0.8,
|
|
400
|
+
// base64: true,
|
|
401
|
+
// exif: false,
|
|
402
|
+
// });
|
|
403
|
+
|
|
404
|
+
// if (!imageSource?.canceled) {
|
|
405
|
+
// // Get the asset
|
|
406
|
+
// const selectedAsset = imageSource?.assets?.[0];
|
|
407
|
+
// if (!selectedAsset) {
|
|
408
|
+
// setImageLoading(false);
|
|
409
|
+
// return;
|
|
410
|
+
// }
|
|
411
|
+
|
|
412
|
+
// // Create a base64 image string for preview
|
|
413
|
+
// const base64Data = selectedAsset.base64;
|
|
414
|
+
// const previewImage = base64Data ? `data:image/jpeg;base64,${base64Data}` : selectedAsset.uri;
|
|
415
|
+
|
|
416
|
+
// // Format the asset for upload service requirements
|
|
417
|
+
// const asset: ExtendedImagePickerAsset = {
|
|
418
|
+
// ...selectedAsset,
|
|
419
|
+
// url: selectedAsset.uri,
|
|
420
|
+
// fileName: selectedAsset.fileName || `image_${Date.now()}.jpg`,
|
|
421
|
+
// mimeType: 'image/jpeg',
|
|
422
|
+
// };
|
|
423
|
+
|
|
424
|
+
// // Update state with the new image
|
|
425
|
+
// setSelectedImage(previewImage);
|
|
426
|
+
// setImages([asset]);
|
|
427
|
+
// setImageLoading(false);
|
|
428
|
+
// } else {
|
|
429
|
+
// setImageLoading(false);
|
|
430
|
+
// }
|
|
431
|
+
// } catch (error) {
|
|
432
|
+
// console.error('Error selecting image:', error);
|
|
433
|
+
// setImageLoading(false);
|
|
434
|
+
// }
|
|
435
|
+
// };
|
|
436
|
+
|
|
437
|
+
// Image selection handler
|
|
438
|
+
const onSelectImages = async () => {
|
|
439
|
+
setLoading(true);
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
let imageSource = await ImagePicker.launchImageLibraryAsync({
|
|
443
|
+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
444
|
+
allowsEditing: false,
|
|
445
|
+
aspect: [4, 3],
|
|
446
|
+
quality: 0.8,
|
|
447
|
+
base64: true,
|
|
448
|
+
exif: false,
|
|
449
|
+
allowsMultipleSelection: true, // Enable multiple selection
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
if (!imageSource?.canceled) {
|
|
453
|
+
// Get all selected assets
|
|
454
|
+
const selectedAssets = imageSource?.assets || [];
|
|
455
|
+
if (selectedAssets.length === 0) {
|
|
456
|
+
setLoading(false);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Process all selected images
|
|
461
|
+
const newImages = selectedAssets.map((selectedAsset) => {
|
|
462
|
+
// Create a base64 image string for preview
|
|
463
|
+
const base64Data = selectedAsset.base64;
|
|
464
|
+
const previewImage = base64Data ? `data:image/jpeg;base64,${base64Data}` : selectedAsset.uri;
|
|
465
|
+
|
|
466
|
+
// Format the asset for upload service requirements
|
|
467
|
+
const asset: ExtendedImagePickerAsset = {
|
|
468
|
+
...selectedAsset,
|
|
469
|
+
url: selectedAsset.uri,
|
|
470
|
+
fileName: selectedAsset.fileName || `image_${Date.now()}.jpg`,
|
|
471
|
+
mimeType: 'image/jpeg',
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
return asset;
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Set preview for the first image (for backward compatibility)
|
|
478
|
+
if (newImages.length > 0) {
|
|
479
|
+
const base64Data = newImages[0].base64;
|
|
480
|
+
const previewImage = base64Data ? `data:image/jpeg;base64,${base64Data}` : newImages[0].uri;
|
|
481
|
+
setSelectedImage(previewImage);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Add new images to existing ones
|
|
485
|
+
setImages((currentImages) => [...currentImages, ...newImages]);
|
|
486
|
+
setLoading(false); // Set loading to false after successful selection
|
|
487
|
+
} else {
|
|
488
|
+
setLoading(false);
|
|
489
|
+
}
|
|
490
|
+
} catch (error) {
|
|
491
|
+
setLoading(false);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// Action sheet handlers
|
|
496
|
+
const showActionSheet = useCallback(() => {
|
|
497
|
+
setLastShownMsg(msg); // Store current message text when opening sheet
|
|
498
|
+
setActionSheetVisible(true);
|
|
499
|
+
}, [msg]);
|
|
500
|
+
|
|
501
|
+
const handleActionSheetClose = useCallback(() => {
|
|
502
|
+
// Close the sheet
|
|
503
|
+
setActionSheetVisible(false);
|
|
504
|
+
|
|
505
|
+
// If user didn't make any changes, restore the original message
|
|
506
|
+
if (!textUpdatedInActionSheet) {
|
|
507
|
+
setMsg(lastShownMsg);
|
|
267
508
|
}
|
|
268
|
-
|
|
509
|
+
|
|
510
|
+
// Reset the flag
|
|
511
|
+
setTextUpdatedInActionSheet(false);
|
|
512
|
+
}, [textUpdatedInActionSheet, lastShownMsg]);
|
|
513
|
+
|
|
514
|
+
const handleActionSheetSend = () => {
|
|
515
|
+
// Send the message using the existing handleSend function
|
|
516
|
+
handleSend(msg);
|
|
517
|
+
|
|
518
|
+
// Close the action sheet
|
|
519
|
+
setActionSheetVisible(false);
|
|
269
520
|
};
|
|
270
521
|
|
|
522
|
+
// Add focus/blur handlers for the input field
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
// When showing the action sheet, we should hide the keyboard for the main input
|
|
525
|
+
if (isActionSheetVisible && Platform.OS === 'ios') {
|
|
526
|
+
Keyboard.dismiss();
|
|
527
|
+
}
|
|
528
|
+
}, [isActionSheetVisible]);
|
|
529
|
+
|
|
530
|
+
// Keep track of whether the input is in focus to help sync UI states
|
|
531
|
+
const [isGiftedInputFocused, setIsGiftedInputFocused] = useState(false);
|
|
532
|
+
|
|
271
533
|
const handleSend = useCallback(
|
|
272
534
|
async (message: string) => {
|
|
535
|
+
const newMessageText = message && message.length > 0 ? message || ' ' : ' ';
|
|
536
|
+
// Check if we can send a message (channel and thread exists)
|
|
537
|
+
|
|
273
538
|
if (!channelId) return;
|
|
274
|
-
|
|
539
|
+
|
|
540
|
+
// Allow sending if we have text OR images (image-only messages are valid)
|
|
541
|
+
const hasText = !!newMessageText && newMessageText !== ' ';
|
|
542
|
+
const hasImages = images && images.length > 0;
|
|
543
|
+
|
|
544
|
+
if (!hasText && !hasImages) return;
|
|
545
|
+
|
|
546
|
+
// Start loading state
|
|
547
|
+
setLoading(true);
|
|
275
548
|
|
|
276
549
|
const postId = objectId();
|
|
550
|
+
const currentDate = new Date();
|
|
277
551
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
552
|
+
// Store current values before clearing
|
|
553
|
+
const currentMessageText = message || '';
|
|
554
|
+
const currentImages = [...images];
|
|
555
|
+
|
|
556
|
+
// Clear UI immediately for next message
|
|
557
|
+
setMsg('');
|
|
558
|
+
setSelectedImage('');
|
|
559
|
+
setImages([]);
|
|
560
|
+
setFiles([]);
|
|
561
|
+
|
|
562
|
+
// Close the action sheet if it's open
|
|
563
|
+
if (isActionSheetVisible) {
|
|
564
|
+
setActionSheetVisible(false);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Create optimistic message with all images for immediate display
|
|
568
|
+
const optimisticMessage = {
|
|
569
|
+
id: postId,
|
|
570
|
+
message: currentMessageText || (currentImages.length > 0 ? ' ' : ''),
|
|
571
|
+
createdAt: currentDate.toISOString(),
|
|
572
|
+
author: {
|
|
573
|
+
id: auth?.profile?.id,
|
|
574
|
+
givenName: auth?.profile?.given_name,
|
|
575
|
+
familyName: auth?.profile?.family_name,
|
|
576
|
+
picture: auth?.profile?.picture,
|
|
577
|
+
},
|
|
578
|
+
isDelivered: false, // Mark as not delivered yet during upload
|
|
579
|
+
isRead: false,
|
|
580
|
+
isOptimistic: true, // Flag to identify optimistic messages
|
|
581
|
+
files:
|
|
582
|
+
currentImages.length > 0
|
|
583
|
+
? {
|
|
584
|
+
data: currentImages
|
|
585
|
+
.filter((img) => img && (img.uri || img.url)) // Filter out invalid images
|
|
586
|
+
.map((img) => ({
|
|
587
|
+
id: objectId(),
|
|
588
|
+
url: img.uri || img.url,
|
|
589
|
+
localUri: img.uri || img.url, // Keep local URI for preview
|
|
590
|
+
name: img.fileName || `image_${Date.now()}.jpg`,
|
|
591
|
+
extension: 'jpg',
|
|
592
|
+
mimeType: 'image/jpeg',
|
|
593
|
+
refType: FileRefType.Post,
|
|
594
|
+
height: img.height || 0,
|
|
595
|
+
width: img.width || 0,
|
|
596
|
+
isUploading: true, // Flag to indicate this file is uploading
|
|
597
|
+
})),
|
|
598
|
+
totalCount: currentImages.filter((img) => img && (img.uri || img.url)).length, // Only count valid images
|
|
599
|
+
}
|
|
600
|
+
: undefined,
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// Only add optimistic message if there's content to send
|
|
604
|
+
if (
|
|
605
|
+
(optimisticMessage.message && optimisticMessage.message.trim() !== '') ||
|
|
606
|
+
(optimisticMessage.files && optimisticMessage.files.data && optimisticMessage.files.data.length > 0)
|
|
607
|
+
) {
|
|
608
|
+
setChannelMessages((oldMessages: any) => uniqBy([optimisticMessage, ...oldMessages], ({ id }) => id));
|
|
609
|
+
setIsScrollToBottom(true);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
try {
|
|
613
|
+
let fileIds: string[] | null = null;
|
|
614
|
+
|
|
615
|
+
// Handle file uploads if images exist
|
|
616
|
+
if (currentImages && currentImages.length > 0) {
|
|
617
|
+
// Filter out any invalid images before upload
|
|
618
|
+
const validImages = currentImages.filter((img) => img && (img.uri || img.url));
|
|
619
|
+
|
|
620
|
+
if (validImages.length === 0) {
|
|
621
|
+
// No valid images to upload, just send the text message
|
|
622
|
+
setIsUploadingImage(false);
|
|
623
|
+
} else {
|
|
624
|
+
setIsUploadingImage(true);
|
|
625
|
+
|
|
626
|
+
// Format images for upload
|
|
627
|
+
const imagesToUpload = validImages.map((img) => {
|
|
628
|
+
return {
|
|
629
|
+
...img,
|
|
630
|
+
uri: img.uri || img.url, // Use either uri or url
|
|
631
|
+
type: img.mimeType || 'image/jpeg',
|
|
632
|
+
name: img.fileName || `image_${Date.now()}.jpg`,
|
|
633
|
+
};
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// Track upload progress with messageId
|
|
637
|
+
setUploadingMessageId(postId);
|
|
638
|
+
setPendingUploads((prev) => ({
|
|
639
|
+
...prev,
|
|
640
|
+
[postId]: {
|
|
641
|
+
images: validImages,
|
|
642
|
+
message: currentMessageText,
|
|
311
643
|
},
|
|
312
|
-
}
|
|
313
|
-
update: (cache, { data, errors }: any) => {
|
|
314
|
-
if (!data || errors) {
|
|
315
|
-
setLoading(false);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
setPostThread(data?.createPostThread?.data);
|
|
319
|
-
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
320
|
-
if (!parentId || parentId == 0) {
|
|
321
|
-
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
322
|
-
}
|
|
644
|
+
}));
|
|
323
645
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
646
|
+
const uploadResponse = await startUpload({
|
|
647
|
+
file: imagesToUpload as unknown as ImagePicker.ImagePickerAsset[],
|
|
648
|
+
saveUploadedFile: {
|
|
649
|
+
variables: {
|
|
650
|
+
postId,
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
createUploadLink: {
|
|
654
|
+
variables: {
|
|
655
|
+
postId,
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
if (uploadResponse?.error) {
|
|
661
|
+
throw new Error(uploadResponse.error.toString());
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
|
|
665
|
+
if (uploadResponse.data) {
|
|
666
|
+
fileIds = uploadedFiles?.map((f: any) => f.id) ?? null;
|
|
667
|
+
|
|
668
|
+
// Update message with uploaded file information
|
|
669
|
+
setChannelMessages((oldMessages: any) => {
|
|
670
|
+
return oldMessages.map((msg) => {
|
|
671
|
+
if (msg.id === postId && msg.isOptimistic) {
|
|
672
|
+
// Update the uploaded files with real URLs from server
|
|
673
|
+
return {
|
|
674
|
+
...msg,
|
|
675
|
+
isOptimistic: false,
|
|
676
|
+
files: {
|
|
677
|
+
...msg.files,
|
|
678
|
+
data: uploadedFiles
|
|
679
|
+
.filter((file) => isValidFileUrl(file.url))
|
|
680
|
+
.map((file, index) => ({
|
|
681
|
+
...msg.files.data[index],
|
|
682
|
+
url: file.url,
|
|
683
|
+
isUploading: false,
|
|
684
|
+
})),
|
|
685
|
+
},
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
return msg;
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Clear pending upload now that it's complete
|
|
694
|
+
setPendingUploads((prev) => {
|
|
695
|
+
const newPending = { ...prev };
|
|
696
|
+
delete newPending[postId];
|
|
697
|
+
return newPending;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
331
700
|
}
|
|
332
|
-
|
|
333
|
-
|
|
701
|
+
|
|
702
|
+
// Send message with Apollo mutation
|
|
334
703
|
await sendThreadMessage({
|
|
335
704
|
variables: {
|
|
705
|
+
postId,
|
|
336
706
|
channelId,
|
|
337
707
|
postThreadId: postThread && postThread?.id,
|
|
338
|
-
postParentId: !parentId || parentId
|
|
708
|
+
postParentId: !parentId || parentId === 0 ? null : parentId,
|
|
339
709
|
threadMessageInput: {
|
|
340
|
-
content:
|
|
710
|
+
content: currentMessageText,
|
|
711
|
+
files: fileIds,
|
|
341
712
|
role,
|
|
342
713
|
},
|
|
343
714
|
},
|
|
715
|
+
optimisticResponse: {
|
|
716
|
+
createPostThread: {
|
|
717
|
+
__typename: 'ThreadMessageSent',
|
|
718
|
+
lastMessage: {
|
|
719
|
+
__typename: 'Post',
|
|
720
|
+
id: postId,
|
|
721
|
+
message: currentMessageText || ' ',
|
|
722
|
+
createdAt: new Date().toISOString(),
|
|
723
|
+
author: {
|
|
724
|
+
__typename: 'UserAccount',
|
|
725
|
+
id: auth?.profile?.id,
|
|
726
|
+
givenName: auth?.profile?.given_name,
|
|
727
|
+
familyName: auth?.profile?.family_name,
|
|
728
|
+
picture: auth?.profile?.picture,
|
|
729
|
+
},
|
|
730
|
+
channel: { __typename: 'Channel', id: channelId.toString() },
|
|
731
|
+
} as any, // Type assertion to avoid TypeScript errors
|
|
732
|
+
data: {
|
|
733
|
+
__typename: 'PostThread',
|
|
734
|
+
id: postThread?.id || objectId(),
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
},
|
|
344
738
|
update: (cache, { data, errors }: any) => {
|
|
345
|
-
if (!data || errors)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
setPostThread(data?.createPostThread.data);
|
|
739
|
+
if (!data || errors) return;
|
|
740
|
+
|
|
741
|
+
setPostThread(data?.createPostThread?.data);
|
|
350
742
|
const lastMessageId = data?.createPostThread?.lastMessage?.id;
|
|
351
|
-
if (!parentId || parentId
|
|
743
|
+
if (!parentId || parentId === 0) {
|
|
352
744
|
setParentId(data?.createPostThread?.lastMessage?.id);
|
|
353
745
|
}
|
|
354
746
|
setChannelToTop(channelToTop + 1);
|
|
355
|
-
setLoading(false);
|
|
356
|
-
setMsg('');
|
|
357
747
|
|
|
748
|
+
// Send push notification with message info
|
|
358
749
|
sendPushNotification(lastMessageId, channelId, parentId, data?.createPostThread?.data?.id);
|
|
359
750
|
},
|
|
360
751
|
});
|
|
752
|
+
} catch (error) {
|
|
753
|
+
console.error('Error sending message:', error);
|
|
754
|
+
|
|
755
|
+
// Format error message based on environment
|
|
756
|
+
let formattedErrorMessage = 'Failed to upload image. Please try again.';
|
|
757
|
+
|
|
758
|
+
if (__DEV__) {
|
|
759
|
+
// Show detailed error in development
|
|
760
|
+
if (error.name === 'ApolloError') {
|
|
761
|
+
formattedErrorMessage = error.message
|
|
762
|
+
.replace('[ApolloError: ', '')
|
|
763
|
+
.replace(']', '')
|
|
764
|
+
.split(';')[0]; // Take first part of error for clarity
|
|
765
|
+
} else {
|
|
766
|
+
formattedErrorMessage = error.message || 'Unknown error occurred';
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
// Show friendly message in production
|
|
770
|
+
if (error.message?.includes('network') || error.message?.includes('timeout')) {
|
|
771
|
+
formattedErrorMessage = 'Network error. Please check your connection.';
|
|
772
|
+
} else if (error.message?.includes('permission')) {
|
|
773
|
+
formattedErrorMessage = 'Permission denied for this operation.';
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Show custom error notification
|
|
778
|
+
setErrorMessage(formattedErrorMessage);
|
|
779
|
+
|
|
780
|
+
// Remove the optimistic message if there was an error
|
|
781
|
+
setChannelMessages((oldMessages: any) => oldMessages.filter((msg) => msg.id !== postId));
|
|
782
|
+
|
|
783
|
+
// Clean up any pending upload states
|
|
784
|
+
removeMessageFromUI(postId);
|
|
785
|
+
} finally {
|
|
786
|
+
setLoading(false);
|
|
787
|
+
setIsUploadingImage(false);
|
|
788
|
+
setUploadingMessageId(null);
|
|
361
789
|
}
|
|
362
790
|
},
|
|
363
|
-
[
|
|
791
|
+
[
|
|
792
|
+
auth,
|
|
793
|
+
channelId,
|
|
794
|
+
channelToTop,
|
|
795
|
+
images,
|
|
796
|
+
parentId,
|
|
797
|
+
postThread,
|
|
798
|
+
selectedImage,
|
|
799
|
+
setChannelMessages,
|
|
800
|
+
removeMessageFromUI,
|
|
801
|
+
role,
|
|
802
|
+
],
|
|
364
803
|
);
|
|
365
804
|
|
|
366
805
|
const sendPushNotification = async (messageId: string, channelId: string, parentId: any, threadId: string) => {
|
|
@@ -381,6 +820,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
381
820
|
}
|
|
382
821
|
};
|
|
383
822
|
|
|
823
|
+
// Improved messageList function to better handle images
|
|
384
824
|
const messageList = useMemo(() => {
|
|
385
825
|
let currentDate = '';
|
|
386
826
|
let res: any = [];
|
|
@@ -403,17 +843,56 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
403
843
|
message._id = msg.id;
|
|
404
844
|
message.text = msg.message;
|
|
405
845
|
message.createdAt = date;
|
|
406
|
-
|
|
846
|
+
message.user = {
|
|
407
847
|
_id: msg?.author?.id ?? auth?.profile?.id,
|
|
408
848
|
name:
|
|
409
849
|
msg?.author?.givenName ??
|
|
410
850
|
auth?.profile?.given_name + ' ' + msg?.author?.familyName ??
|
|
411
851
|
auth?.profile?.family_name,
|
|
412
852
|
avatar: msg?.author?.picture ?? auth?.profile?.picture,
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// Handle image uploads with proper UI feedback
|
|
856
|
+
message.isOptimistic = msg?.isOptimistic || false;
|
|
857
|
+
message.isUploading = msg.files?.data?.some((file) => file.isUploading) || false;
|
|
858
|
+
|
|
859
|
+
// Improved image handling - ensure we have valid image URLs
|
|
860
|
+
if (msg.files?.data?.length > 0) {
|
|
861
|
+
// Filter out any invalid files first
|
|
862
|
+
const validFiles = msg.files.data.filter(
|
|
863
|
+
(file) =>
|
|
864
|
+
file && (file.url || file.localUri) && typeof (file.url || file.localUri) === 'string',
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
if (validFiles.length > 0) {
|
|
868
|
+
// For the first image (used in single image display)
|
|
869
|
+
const firstFile = validFiles[0];
|
|
870
|
+
const imageUrl = firstFile.isUploading
|
|
871
|
+
? firstFile.localUri || firstFile.url
|
|
872
|
+
: firstFile.url || firstFile.localUri;
|
|
873
|
+
|
|
874
|
+
if (imageUrl) {
|
|
875
|
+
message.image = imageUrl;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// For multiple images
|
|
879
|
+
if (validFiles.length > 1) {
|
|
880
|
+
message.images = validFiles
|
|
881
|
+
.map((file) =>
|
|
882
|
+
file.isUploading ? file.localUri || file.url : file.url || file.localUri,
|
|
883
|
+
)
|
|
884
|
+
.filter((url) => !!url); // Filter out any undefined or empty URLs
|
|
885
|
+
|
|
886
|
+
// If no valid URLs after filtering, don't set the images property
|
|
887
|
+
if (message.images.length === 0) {
|
|
888
|
+
delete message.images;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
message.sent = msg?.isDelivered;
|
|
895
|
+
message.received = msg?.isRead;
|
|
417
896
|
message.type = msg?.type;
|
|
418
897
|
message.propsConfiguration = msg?.propsConfiguration;
|
|
419
898
|
res.push(message);
|
|
@@ -423,148 +902,494 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
423
902
|
//return res;
|
|
424
903
|
}, [channelMessages]);
|
|
425
904
|
|
|
426
|
-
const renderSend = (
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
style={{ marginBottom: 5, marginRight: 5 }}
|
|
433
|
-
size={32}
|
|
434
|
-
color="#2e64e5"
|
|
435
|
-
/>
|
|
436
|
-
</Box>
|
|
437
|
-
</Send>
|
|
438
|
-
);
|
|
439
|
-
};
|
|
905
|
+
const renderSend = useCallback(
|
|
906
|
+
(props) => {
|
|
907
|
+
// If action sheet is visible, don't show the default send button
|
|
908
|
+
if (isActionSheetVisible) {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
440
911
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
912
|
+
// Enable the send button if there's text OR we have images
|
|
913
|
+
const hasContent = !!props.text || images?.length > 0;
|
|
914
|
+
const canSend = channelId && hasContent;
|
|
915
|
+
const isDisabled = !canSend || isUploadingImage || loading;
|
|
916
|
+
|
|
917
|
+
return (
|
|
918
|
+
<Send
|
|
919
|
+
{...props}
|
|
920
|
+
disabled={isDisabled}
|
|
921
|
+
containerStyle={{
|
|
922
|
+
justifyContent: 'center',
|
|
923
|
+
alignItems: 'center',
|
|
924
|
+
height: 40,
|
|
925
|
+
width: 44,
|
|
926
|
+
marginRight: 4,
|
|
927
|
+
marginBottom: 0,
|
|
928
|
+
marginLeft: 4,
|
|
929
|
+
}}
|
|
930
|
+
// onSend={(messages, shouldReset) => {
|
|
931
|
+
// // Always use our custom handler to ensure images are handled properly
|
|
932
|
+
// console.log('messages', messages);
|
|
933
|
+
// // handleSend(props.text || ' ');
|
|
934
|
+
// }}
|
|
935
|
+
>
|
|
936
|
+
<View style={{ padding: 4 }}>
|
|
937
|
+
{isUploadingImage || loading ? (
|
|
938
|
+
<Spinner size="small" color={colors.blue[500]} />
|
|
939
|
+
) : (
|
|
940
|
+
<MaterialCommunityIcons
|
|
941
|
+
name="send-circle"
|
|
942
|
+
size={32}
|
|
943
|
+
color={isDisabled ? colors.gray[400] : colors.blue[500]}
|
|
944
|
+
/>
|
|
945
|
+
)}
|
|
946
|
+
</View>
|
|
947
|
+
</Send>
|
|
948
|
+
);
|
|
949
|
+
},
|
|
950
|
+
[channelId, images, isUploadingImage, loading, isActionSheetVisible],
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
const renderMessageText1 = useCallback(
|
|
954
|
+
(props: any) => {
|
|
955
|
+
const { currentMessage } = props;
|
|
956
|
+
if (currentMessage.type === 'ALERT') {
|
|
957
|
+
const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
|
|
958
|
+
let action: string = '';
|
|
959
|
+
let actionId: any = '';
|
|
960
|
+
let params: any = {};
|
|
961
|
+
if (attachment?.callToAction?.extraParams) {
|
|
962
|
+
const extraParams: any = attachment?.callToAction?.extraParams;
|
|
963
|
+
const route: any = extraParams?.route ?? null;
|
|
964
|
+
let path: any = null;
|
|
965
|
+
let param: any = null;
|
|
966
|
+
if (role && role == PreDefinedRole.Guest) {
|
|
967
|
+
path = route?.guest?.name ? route?.guest?.name ?? null : null;
|
|
968
|
+
param = route?.guest?.params ? route?.guest?.params ?? null : null;
|
|
969
|
+
} else if (role && role == PreDefinedRole.Owner) {
|
|
970
|
+
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
971
|
+
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
972
|
+
} else {
|
|
973
|
+
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
974
|
+
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
action = path;
|
|
978
|
+
params = { ...param };
|
|
979
|
+
} else if (attachment?.callToAction?.link) {
|
|
980
|
+
action = CALL_TO_ACTION_PATH;
|
|
981
|
+
actionId = attachment?.callToAction?.link.split('/').pop();
|
|
982
|
+
params = { reservationId: actionId };
|
|
462
983
|
}
|
|
463
984
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
985
|
+
return (
|
|
986
|
+
<>
|
|
987
|
+
{attachment?.callToAction && action ? (
|
|
988
|
+
<Box className={`bg-[${CALL_TO_ACTION_BOX_BGCOLOR}] rounded-[15] pb-2`}>
|
|
989
|
+
<Button
|
|
990
|
+
variant={'outline'}
|
|
991
|
+
size={'sm'}
|
|
992
|
+
className={`border-[${CALL_TO_ACTION_BUTTON_BORDERCOLOR}]`}
|
|
993
|
+
onPress={() => action && params && navigation.navigate(action, params)}
|
|
994
|
+
>
|
|
995
|
+
<ButtonText className={`color-[${CALL_TO_ACTION_TEXT_COLOR}]`}>
|
|
996
|
+
{attachment.callToAction.title}
|
|
997
|
+
</ButtonText>
|
|
998
|
+
</Button>
|
|
999
|
+
<MessageText
|
|
1000
|
+
{...props}
|
|
1001
|
+
textStyle={{
|
|
1002
|
+
left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 },
|
|
1003
|
+
}}
|
|
1004
|
+
/>
|
|
1005
|
+
</Box>
|
|
1006
|
+
) : (
|
|
1007
|
+
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
1008
|
+
)}
|
|
1009
|
+
</>
|
|
1010
|
+
);
|
|
1011
|
+
} else {
|
|
1012
|
+
return <MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />;
|
|
470
1013
|
}
|
|
1014
|
+
},
|
|
1015
|
+
[navigation, role],
|
|
1016
|
+
);
|
|
471
1017
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1018
|
+
const renderMessageText = useCallback(
|
|
1019
|
+
(props: any) => {
|
|
1020
|
+
const { currentMessage } = props;
|
|
1021
|
+
const lastReply: any =
|
|
1022
|
+
currentMessage?.replies?.data?.length > 0 ? currentMessage?.replies?.data?.[0] : null;
|
|
476
1023
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
1024
|
+
// Do not render anything if the message text is empty or only whitespace
|
|
1025
|
+
if (!currentMessage?.text || currentMessage.text.trim() === '') {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (currentMessage.type === 'ALERT') {
|
|
1030
|
+
const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
|
|
1031
|
+
let action: string = '';
|
|
1032
|
+
let actionId: any = '';
|
|
1033
|
+
let params: any = {};
|
|
1034
|
+
|
|
1035
|
+
if (attachment?.callToAction?.extraParams) {
|
|
1036
|
+
const extraParams: any = attachment?.callToAction?.extraParams;
|
|
1037
|
+
const route: any = extraParams?.route ?? null;
|
|
1038
|
+
let path: any = null;
|
|
1039
|
+
let param: any = null;
|
|
1040
|
+
if (role && role == PreDefinedRole.Guest) {
|
|
1041
|
+
path = route?.guest?.name ? route?.guest?.name ?? null : null;
|
|
1042
|
+
param = route?.guest?.params ? route?.guest?.params ?? null : null;
|
|
1043
|
+
} else if (role && role == PreDefinedRole.Owner) {
|
|
1044
|
+
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
1045
|
+
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
1046
|
+
} else {
|
|
1047
|
+
path = route?.host?.name ? route?.host?.name ?? null : null;
|
|
1048
|
+
param = route?.host?.params ? route?.host?.params ?? null : null;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
action = path;
|
|
1052
|
+
params = { ...param };
|
|
1053
|
+
} else if (attachment?.callToAction?.link) {
|
|
1054
|
+
action = CALL_TO_ACTION_PATH;
|
|
1055
|
+
actionId = attachment?.callToAction?.link.split('/').pop();
|
|
1056
|
+
params = { reservationId: actionId };
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return (
|
|
1060
|
+
<>
|
|
1061
|
+
{attachment?.callToAction && action ? (
|
|
1062
|
+
<Box className={`bg-[${CALL_TO_ACTION_BOX_BGCOLOR}] rounded-[15] pb-2`}>
|
|
1063
|
+
<Button
|
|
1064
|
+
variant={'outline'}
|
|
1065
|
+
size={'sm'}
|
|
1066
|
+
className={`border-[${CALL_TO_ACTION_BUTTON_BORDERCOLOR}]`}
|
|
1067
|
+
onPress={() => action && params && navigation.navigate(action, params)}
|
|
1068
|
+
>
|
|
1069
|
+
<ButtonText className={`color-[${CALL_TO_ACTION_TEXT_COLOR}]`}>
|
|
1070
|
+
{attachment.callToAction.title}
|
|
1071
|
+
</ButtonText>
|
|
1072
|
+
</Button>
|
|
1073
|
+
<MessageText
|
|
1074
|
+
{...props}
|
|
1075
|
+
textStyle={{
|
|
1076
|
+
left: { marginLeft: 5, color: CALL_TO_ACTION_TEXT_COLOR, paddingHorizontal: 2 },
|
|
1077
|
+
}}
|
|
1078
|
+
/>
|
|
1079
|
+
</Box>
|
|
1080
|
+
) : (
|
|
1081
|
+
<TouchableHighlight
|
|
1082
|
+
underlayColor={'#c0c0c0'}
|
|
1083
|
+
style={{ width: '100%' }}
|
|
1084
|
+
onPress={() => {
|
|
1085
|
+
if (currentMessage?.isShowThreadMessage)
|
|
1086
|
+
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1087
|
+
channelId: channelId,
|
|
1088
|
+
title: 'Message',
|
|
1089
|
+
postParentId: currentMessage?._id,
|
|
1090
|
+
isPostParentIdThread: true,
|
|
1091
|
+
});
|
|
496
1092
|
}}
|
|
497
|
-
|
|
1093
|
+
>
|
|
1094
|
+
<>
|
|
1095
|
+
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
1096
|
+
{currentMessage?.replies?.data?.length > 0 && (
|
|
1097
|
+
<HStack space={'sm'} className="px-1 items-center">
|
|
1098
|
+
<HStack>
|
|
1099
|
+
{currentMessage?.replies?.data
|
|
1100
|
+
?.filter(
|
|
1101
|
+
(v: any, i: any, a: any) =>
|
|
1102
|
+
a.findIndex((t: any) => t?.author?.id === v?.author?.id) ===
|
|
1103
|
+
i,
|
|
1104
|
+
)
|
|
1105
|
+
?.slice(0, 2)
|
|
1106
|
+
?.reverse()
|
|
1107
|
+
?.map((p: any, i: Number) => (
|
|
1108
|
+
<Avatar
|
|
1109
|
+
key={'conversations-view-key-' + i}
|
|
1110
|
+
size={'sm'}
|
|
1111
|
+
className="bg-transparent"
|
|
1112
|
+
>
|
|
1113
|
+
<AvatarFallbackText>
|
|
1114
|
+
{startCase(p?.author?.username?.charAt(0))}
|
|
1115
|
+
</AvatarFallbackText>
|
|
1116
|
+
<AvatarImage
|
|
1117
|
+
alt="user image"
|
|
1118
|
+
style={{
|
|
1119
|
+
borderRadius: 6,
|
|
1120
|
+
borderWidth: 2,
|
|
1121
|
+
borderColor: '#fff',
|
|
1122
|
+
}}
|
|
1123
|
+
source={{
|
|
1124
|
+
uri: p?.author?.picture,
|
|
1125
|
+
}}
|
|
1126
|
+
/>
|
|
1127
|
+
</Avatar>
|
|
1128
|
+
))}
|
|
1129
|
+
</HStack>
|
|
1130
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
|
|
1131
|
+
{currentMessage?.replies?.totalCount}{' '}
|
|
1132
|
+
{currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
|
|
1133
|
+
</Text>
|
|
1134
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
1135
|
+
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
1136
|
+
</Text>
|
|
1137
|
+
</HStack>
|
|
1138
|
+
)}
|
|
1139
|
+
</>
|
|
1140
|
+
</TouchableHighlight>
|
|
1141
|
+
)}
|
|
1142
|
+
</>
|
|
1143
|
+
);
|
|
1144
|
+
} else {
|
|
1145
|
+
return (
|
|
1146
|
+
<TouchableHighlight
|
|
1147
|
+
underlayColor={'#c0c0c0'}
|
|
1148
|
+
style={{ width: '100%' }}
|
|
1149
|
+
onPress={() => {
|
|
1150
|
+
if (currentMessage?.isShowThreadMessage)
|
|
1151
|
+
navigation.navigate(config.THREAD_MESSEGE_PATH, {
|
|
1152
|
+
channelId: channelId,
|
|
1153
|
+
title: 'Message',
|
|
1154
|
+
postParentId: currentMessage?._id,
|
|
1155
|
+
isPostParentIdThread: true,
|
|
1156
|
+
});
|
|
1157
|
+
}}
|
|
1158
|
+
>
|
|
1159
|
+
<>
|
|
1160
|
+
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
1161
|
+
{currentMessage?.replies?.data?.length > 0 && (
|
|
1162
|
+
<HStack space={'sm'} className="px-1 items-center">
|
|
1163
|
+
<HStack>
|
|
1164
|
+
{currentMessage?.replies?.data
|
|
1165
|
+
?.filter(
|
|
1166
|
+
(v: any, i: any, a: any) =>
|
|
1167
|
+
a.findIndex((t: any) => t?.author?.id === v?.author?.id) === i,
|
|
1168
|
+
)
|
|
1169
|
+
?.slice(0, 2)
|
|
1170
|
+
?.reverse()
|
|
1171
|
+
?.map((p: any, i: Number) => (
|
|
1172
|
+
<Avatar
|
|
1173
|
+
key={'conversation-replies-key-' + i}
|
|
1174
|
+
className="bg-transparent"
|
|
1175
|
+
size={'sm'}
|
|
1176
|
+
>
|
|
1177
|
+
<AvatarFallbackText>
|
|
1178
|
+
{startCase(p?.author?.username?.charAt(0))}
|
|
1179
|
+
</AvatarFallbackText>
|
|
1180
|
+
<AvatarImage
|
|
1181
|
+
alt="user image"
|
|
1182
|
+
style={{ borderRadius: 6, borderWidth: 2, borderColor: '#fff' }}
|
|
1183
|
+
source={{
|
|
1184
|
+
uri: p?.author?.picture,
|
|
1185
|
+
}}
|
|
1186
|
+
/>
|
|
1187
|
+
</Avatar>
|
|
1188
|
+
))}
|
|
1189
|
+
</HStack>
|
|
1190
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-blue-800">
|
|
1191
|
+
{currentMessage?.replies?.totalCount}{' '}
|
|
1192
|
+
{currentMessage?.replies?.totalCount == 1 ? 'reply' : 'replies'}
|
|
1193
|
+
</Text>
|
|
1194
|
+
<Text style={{ fontSize: 12 }} className="font-bold color-gray-500">
|
|
1195
|
+
{lastReply ? createdAtText(lastReply?.createdAt) : ''}
|
|
1196
|
+
</Text>
|
|
1197
|
+
</HStack>
|
|
1198
|
+
)}
|
|
1199
|
+
</>
|
|
1200
|
+
</TouchableHighlight>
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
[navigation, channelId, role],
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
// const renderActions = useCallback((props) => {
|
|
1208
|
+
// return (
|
|
1209
|
+
// <Actions
|
|
1210
|
+
// {...props}
|
|
1211
|
+
// icon={() => <Ionicons name={'image'} size={30} color={'black'} onPress={onSelectImages} />}
|
|
1212
|
+
// />
|
|
1213
|
+
// );
|
|
1214
|
+
// }, [onSelectImages]);
|
|
1215
|
+
|
|
1216
|
+
// Render action buttons (including image upload)
|
|
1217
|
+
const renderActions = useCallback(
|
|
1218
|
+
(props) => {
|
|
1219
|
+
return (
|
|
1220
|
+
<Actions
|
|
1221
|
+
{...props}
|
|
1222
|
+
onPressActionButton={onSelectImages} // Direct action when pressing the button
|
|
1223
|
+
options={{
|
|
1224
|
+
['Choose from Library']: () => {
|
|
1225
|
+
// Call the same image selection function used in the ActionSheet
|
|
1226
|
+
onSelectImages();
|
|
1227
|
+
},
|
|
1228
|
+
['Cancel']: () => {}, // Add this option to make the sheet dismissible
|
|
1229
|
+
}}
|
|
1230
|
+
optionTintColor="#000000"
|
|
1231
|
+
cancelButtonIndex={1} // Set the Cancel option as the cancel button
|
|
1232
|
+
icon={() => (
|
|
1233
|
+
<Box className="w-8 h-8 items-center justify-center">
|
|
1234
|
+
<Ionicons name="image" size={24} color={colors.blue[500]} />
|
|
498
1235
|
</Box>
|
|
499
|
-
) : (
|
|
500
|
-
<MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />
|
|
501
1236
|
)}
|
|
502
|
-
{
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
1237
|
+
containerStyle={{
|
|
1238
|
+
alignItems: 'center',
|
|
1239
|
+
justifyContent: 'center',
|
|
1240
|
+
marginLeft: 8,
|
|
1241
|
+
marginBottom: 0,
|
|
1242
|
+
}}
|
|
1243
|
+
/>
|
|
507
1244
|
);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
const renderActions = (props) => {
|
|
514
|
-
return (
|
|
515
|
-
<Actions
|
|
516
|
-
{...props}
|
|
517
|
-
icon={() => <Ionicons name={'image'} size={30} color={'black'} onPress={onSelectImages} />}
|
|
518
|
-
/>
|
|
519
|
-
);
|
|
520
|
-
};
|
|
1245
|
+
},
|
|
1246
|
+
[onSelectImages],
|
|
1247
|
+
);
|
|
521
1248
|
|
|
522
|
-
|
|
1249
|
+
// Create a more visible and reliable image preview with cancel button
|
|
1250
|
+
const renderAccessory = useCallback(() => {
|
|
1251
|
+
if (!images.length) return null;
|
|
523
1252
|
return (
|
|
524
|
-
<Box>
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1253
|
+
<Box style={{ position: 'relative', height: 70, backgroundColor: 'transparent', justifyContent: 'center' }}>
|
|
1254
|
+
<ScrollView
|
|
1255
|
+
horizontal
|
|
1256
|
+
showsHorizontalScrollIndicator={false}
|
|
1257
|
+
style={{
|
|
1258
|
+
flexDirection: 'row',
|
|
1259
|
+
paddingLeft: 15,
|
|
1260
|
+
paddingRight: 5,
|
|
1261
|
+
}}
|
|
1262
|
+
contentContainerStyle={{
|
|
1263
|
+
alignItems: 'center',
|
|
1264
|
+
height: '100%',
|
|
1265
|
+
}}
|
|
1266
|
+
>
|
|
1267
|
+
{images.map((img, index) => (
|
|
1268
|
+
<View
|
|
1269
|
+
key={`image-preview-${index}`}
|
|
1270
|
+
style={{
|
|
1271
|
+
width: 40,
|
|
1272
|
+
height: 40,
|
|
1273
|
+
marginRight: 15,
|
|
1274
|
+
borderRadius: 4,
|
|
1275
|
+
backgroundColor: colors.gray[200],
|
|
1276
|
+
overflow: 'hidden',
|
|
1277
|
+
borderWidth: 1,
|
|
1278
|
+
borderColor: '#e0e0e0',
|
|
1279
|
+
position: 'relative',
|
|
1280
|
+
zIndex: 10,
|
|
541
1281
|
}}
|
|
542
1282
|
>
|
|
543
|
-
<
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1283
|
+
<Image
|
|
1284
|
+
alt={`selected image ${index + 1}`}
|
|
1285
|
+
source={{ uri: img.uri || img.url }}
|
|
1286
|
+
style={{ width: '100%', height: '100%' }}
|
|
1287
|
+
size={'md'}
|
|
1288
|
+
/>
|
|
1289
|
+
{/* Cross button at top right */}
|
|
1290
|
+
<TouchableOpacity
|
|
1291
|
+
onPress={() => {
|
|
1292
|
+
const newImages = [...images];
|
|
1293
|
+
newImages.splice(index, 1);
|
|
1294
|
+
setImages(newImages);
|
|
1295
|
+
if (newImages.length === 0) {
|
|
1296
|
+
setSelectedImage('');
|
|
1297
|
+
}
|
|
1298
|
+
}}
|
|
1299
|
+
style={{
|
|
1300
|
+
position: 'absolute',
|
|
1301
|
+
top: -1,
|
|
1302
|
+
right: -1,
|
|
1303
|
+
backgroundColor: 'rgba(0,0,0,0.6)',
|
|
1304
|
+
borderRadius: 12,
|
|
1305
|
+
width: 20,
|
|
1306
|
+
height: 20,
|
|
1307
|
+
alignItems: 'center',
|
|
1308
|
+
justifyContent: 'center',
|
|
1309
|
+
zIndex: 9999,
|
|
1310
|
+
}}
|
|
1311
|
+
>
|
|
1312
|
+
<Ionicons name="close" size={16} color="white" />
|
|
1313
|
+
</TouchableOpacity>
|
|
1314
|
+
</View>
|
|
1315
|
+
))}
|
|
1316
|
+
</ScrollView>
|
|
547
1317
|
</Box>
|
|
548
1318
|
);
|
|
549
|
-
};
|
|
1319
|
+
}, [images]);
|
|
550
1320
|
|
|
551
|
-
|
|
1321
|
+
// Add a custom Composer for Slack-like input
|
|
1322
|
+
const renderComposer = useCallback(
|
|
1323
|
+
(props) => (
|
|
1324
|
+
<TouchableOpacity
|
|
1325
|
+
style={{
|
|
1326
|
+
flexDirection: 'row',
|
|
1327
|
+
alignItems: 'center',
|
|
1328
|
+
backgroundColor: '#fff',
|
|
1329
|
+
minHeight: 48,
|
|
1330
|
+
maxHeight: 48,
|
|
1331
|
+
paddingHorizontal: 12,
|
|
1332
|
+
paddingVertical: 0,
|
|
1333
|
+
marginHorizontal: 8,
|
|
1334
|
+
flex: 1,
|
|
1335
|
+
}}
|
|
1336
|
+
activeOpacity={0.7}
|
|
1337
|
+
onPress={() => {
|
|
1338
|
+
// Use the callback we defined to show the action sheet
|
|
1339
|
+
setActionSheetVisible(true);
|
|
1340
|
+
}}
|
|
1341
|
+
>
|
|
1342
|
+
{/* Left + button */}
|
|
1343
|
+
<TouchableOpacity
|
|
1344
|
+
onPress={(e) => {
|
|
1345
|
+
e.stopPropagation(); // Prevent parent TouchableOpacity from triggering
|
|
1346
|
+
onSelectImages();
|
|
1347
|
+
}}
|
|
1348
|
+
style={{
|
|
1349
|
+
width: 25,
|
|
1350
|
+
height: 25,
|
|
1351
|
+
borderRadius: 20,
|
|
1352
|
+
backgroundColor: '#f5f5f5',
|
|
1353
|
+
alignItems: 'center',
|
|
1354
|
+
justifyContent: 'center',
|
|
1355
|
+
marginRight: 8,
|
|
1356
|
+
}}
|
|
1357
|
+
>
|
|
1358
|
+
<MaterialIcons name="add" size={20} color="#888" />
|
|
1359
|
+
</TouchableOpacity>
|
|
1360
|
+
|
|
1361
|
+
{/* Text placeholder - no need for actual input component */}
|
|
1362
|
+
<Text
|
|
1363
|
+
style={{
|
|
1364
|
+
fontSize: 16,
|
|
1365
|
+
color: msg ? colors.gray[800] : colors.gray[400],
|
|
1366
|
+
flex: 1,
|
|
1367
|
+
}}
|
|
1368
|
+
numberOfLines={1}
|
|
1369
|
+
ellipsizeMode="tail"
|
|
1370
|
+
>
|
|
1371
|
+
{msg ? msg : 'Jot something down'}
|
|
1372
|
+
</Text>
|
|
1373
|
+
</TouchableOpacity>
|
|
1374
|
+
),
|
|
1375
|
+
[msg, onSelectImages],
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
const setImageViewerObject = useCallback((obj: any, v: boolean) => {
|
|
552
1379
|
setImageObject(obj);
|
|
553
1380
|
setImageViewer(v);
|
|
554
|
-
};
|
|
1381
|
+
}, []);
|
|
555
1382
|
|
|
556
|
-
const modalContent =
|
|
1383
|
+
const modalContent = useMemo(() => {
|
|
557
1384
|
if (!imageObject) return <></>;
|
|
558
1385
|
const { image, _id } = imageObject;
|
|
559
1386
|
return (
|
|
560
1387
|
<CachedImage
|
|
561
1388
|
style={{ width: '100%', height: '100%' }}
|
|
562
1389
|
resizeMode={'cover'}
|
|
563
|
-
// cacheKey={`${_id}-conversation-modal-image-key`}
|
|
564
1390
|
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
565
1391
|
source={{
|
|
566
1392
|
uri: image,
|
|
567
|
-
//headers: `Authorization: Bearer ${token}`,
|
|
568
1393
|
expiresIn: 86400,
|
|
569
1394
|
}}
|
|
570
1395
|
alt={'image'}
|
|
@@ -572,32 +1397,163 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
572
1397
|
);
|
|
573
1398
|
}, [imageObject]);
|
|
574
1399
|
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
1400
|
+
const renderMessage1 = useCallback(
|
|
1401
|
+
(props: any) => {
|
|
1402
|
+
const { currentMessage } = props;
|
|
1403
|
+
|
|
1404
|
+
// Add special props for handling image upload states
|
|
1405
|
+
const customProps = {
|
|
1406
|
+
...props,
|
|
1407
|
+
isShowImageViewer: isShowImageViewer,
|
|
1408
|
+
setImageViewer: setImageViewerObject,
|
|
1409
|
+
// Pass custom flags to indicate uploading/optimistic states
|
|
1410
|
+
isUploading: currentMessage.isUploading || false,
|
|
1411
|
+
isOptimistic: currentMessage.isOptimistic || false,
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
// Use SlackMessage component with our enhanced props
|
|
1415
|
+
return <SlackMessage {...customProps} />;
|
|
1416
|
+
},
|
|
1417
|
+
[isShowImageViewer, setImageViewerObject],
|
|
1418
|
+
);
|
|
1419
|
+
const renderMessage = useCallback(
|
|
1420
|
+
(props: any) => {
|
|
1421
|
+
// For all messages, use the SlackMessage component directly
|
|
1422
|
+
return (
|
|
1423
|
+
<SlackMessage {...props} isShowImageViewer={isShowImageViewer} setImageViewer={setImageViewerObject} />
|
|
1424
|
+
);
|
|
1425
|
+
},
|
|
1426
|
+
[isShowImageViewer],
|
|
1427
|
+
);
|
|
1428
|
+
|
|
1429
|
+
const renderInputToolbar = useCallback(
|
|
1430
|
+
(props) => {
|
|
1431
|
+
if (isActionSheetVisible) return null;
|
|
1432
|
+
|
|
1433
|
+
return (
|
|
1434
|
+
<InputToolbar
|
|
1435
|
+
{...props}
|
|
1436
|
+
containerStyle={{
|
|
1437
|
+
backgroundColor: 'transparent',
|
|
1438
|
+
padding: 0,
|
|
1439
|
+
margin: 0,
|
|
1440
|
+
borderTopWidth: 1,
|
|
1441
|
+
borderTopColor: colors.gray[200],
|
|
1442
|
+
}}
|
|
1443
|
+
primaryStyle={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}
|
|
1444
|
+
// Add action slot for direct image selection button
|
|
1445
|
+
// renderActions={() => (
|
|
1446
|
+
// <TouchableOpacity
|
|
1447
|
+
// onPress={onSelectImages}
|
|
1448
|
+
// style={{
|
|
1449
|
+
// padding: 8,
|
|
1450
|
+
// marginLeft: 4,
|
|
1451
|
+
// marginBottom: Platform.OS === 'ios' ? 0 : 3,
|
|
1452
|
+
// }}
|
|
1453
|
+
// >
|
|
1454
|
+
// <Ionicons name="image" size={24} color={colors.blue[500]} />
|
|
1455
|
+
// </TouchableOpacity>
|
|
1456
|
+
// )}
|
|
1457
|
+
/>
|
|
1458
|
+
);
|
|
1459
|
+
},
|
|
1460
|
+
[isActionSheetVisible, onSelectImages],
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1463
|
+
const renderLoadEarlier = useCallback(() => {
|
|
1464
|
+
return loadingOldMessages && !refreshing ? (
|
|
1465
|
+
<Box
|
|
1466
|
+
style={{
|
|
1467
|
+
padding: 10,
|
|
1468
|
+
backgroundColor: 'rgba(255,255,255,0.8)',
|
|
1469
|
+
borderRadius: 10,
|
|
1470
|
+
marginTop: 10,
|
|
1471
|
+
alignItems: 'center',
|
|
1472
|
+
}}
|
|
1473
|
+
>
|
|
1474
|
+
<Spinner size="small" color={colors.blue[500]} />
|
|
1475
|
+
<Text style={{ fontSize: 12, color: colors.gray[600], marginTop: 4 }}>Loading earlier messages...</Text>
|
|
1476
|
+
</Box>
|
|
1477
|
+
) : null;
|
|
1478
|
+
}, [loadingOldMessages, refreshing]);
|
|
578
1479
|
|
|
579
1480
|
let onScroll = false;
|
|
580
1481
|
|
|
581
1482
|
const onMomentumScrollBegin = ({ nativeEvent }: any) => {
|
|
582
1483
|
onScroll = true;
|
|
583
|
-
console.log('scroll top');
|
|
584
1484
|
if (!loadingOldMessages && isCloseToTop(nativeEvent) && totalCount > channelMessages?.length) {
|
|
585
1485
|
onFetchOld();
|
|
586
1486
|
}
|
|
587
1487
|
};
|
|
588
1488
|
|
|
589
1489
|
const onEndReached = () => {
|
|
590
|
-
console.log('on end reached');
|
|
591
1490
|
if (!onScroll) return;
|
|
592
|
-
// load messages, show ActivityIndicator
|
|
593
1491
|
onScroll = false;
|
|
594
|
-
// setLoadingOldMessages(true);
|
|
595
1492
|
};
|
|
596
1493
|
|
|
1494
|
+
const renderChatFooter = useCallback(() => {
|
|
1495
|
+
return (
|
|
1496
|
+
<>
|
|
1497
|
+
<ImageViewerModal
|
|
1498
|
+
isVisible={isShowImageViewer}
|
|
1499
|
+
setVisible={setImageViewer}
|
|
1500
|
+
modalContent={modalContent}
|
|
1501
|
+
/>
|
|
1502
|
+
<SubscriptionHandler
|
|
1503
|
+
channelId={channelId}
|
|
1504
|
+
subscribeToNewMessages={() =>
|
|
1505
|
+
subscribeToMore({
|
|
1506
|
+
document: CHAT_MESSAGE_ADDED,
|
|
1507
|
+
variables: {
|
|
1508
|
+
channelId: channelId?.toString(),
|
|
1509
|
+
postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
|
|
1510
|
+
},
|
|
1511
|
+
updateQuery: (prev, { subscriptionData }: any) => {
|
|
1512
|
+
if (!subscriptionData.data) return prev;
|
|
1513
|
+
const newMessage: any = subscriptionData?.data?.threadChatMessageAdded;
|
|
1514
|
+
const prevReplyCount: any = prev?.getPostThread?.replyCount;
|
|
1515
|
+
const newReplyCount = prevReplyCount || 0 + 1;
|
|
1516
|
+
const replies = prev?.getPostThread?.replies || [];
|
|
1517
|
+
setChannelMessages((oldMessages: any) =>
|
|
1518
|
+
uniqBy([...oldMessages, newMessage], ({ id }) => id),
|
|
1519
|
+
);
|
|
1520
|
+
setTotalCount(newReplyCount);
|
|
1521
|
+
return Object.assign({}, prev, {
|
|
1522
|
+
getPostThread: {
|
|
1523
|
+
...prev?.getPostThread,
|
|
1524
|
+
lastReplyAt: newMessage.createdAt,
|
|
1525
|
+
replies: [newMessage, ...replies],
|
|
1526
|
+
replyCount: newReplyCount,
|
|
1527
|
+
updatedAt: newMessage.createdAt,
|
|
1528
|
+
},
|
|
1529
|
+
});
|
|
1530
|
+
},
|
|
1531
|
+
})
|
|
1532
|
+
}
|
|
1533
|
+
/>
|
|
1534
|
+
</>
|
|
1535
|
+
);
|
|
1536
|
+
}, [channelId, isShowImageViewer, modalContent, parentId, setImageViewer, subscribeToMore]);
|
|
1537
|
+
|
|
1538
|
+
const handleRemoveImage = useCallback(
|
|
1539
|
+
(index: number) => {
|
|
1540
|
+
const newImages = [...images];
|
|
1541
|
+
newImages.splice(index, 1);
|
|
1542
|
+
setImages(newImages);
|
|
1543
|
+
if (newImages.length === 0) {
|
|
1544
|
+
setSelectedImage('');
|
|
1545
|
+
if (textInputRef.current && typeof textInputRef.current.focus === 'function') {
|
|
1546
|
+
textInputRef.current.focus();
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
[images],
|
|
1551
|
+
);
|
|
1552
|
+
|
|
597
1553
|
return (
|
|
598
|
-
|
|
599
|
-
{
|
|
600
|
-
{
|
|
1554
|
+
<View style={{ flex: 1, marginTop: -40 }}>
|
|
1555
|
+
{errorMessage ? <ErrorNotification message={errorMessage} onClose={() => setErrorMessage('')} /> : null}
|
|
1556
|
+
{loading && <Spinner color={'#3b82f6'} />}
|
|
601
1557
|
{isPostParentIdThread && (
|
|
602
1558
|
<>
|
|
603
1559
|
{threadPost?.length > 0 && (
|
|
@@ -648,99 +1604,151 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
648
1604
|
)}
|
|
649
1605
|
</>
|
|
650
1606
|
)}
|
|
651
|
-
<
|
|
1607
|
+
<GiftedChatInboxComponent
|
|
652
1608
|
ref={threadMessageListRef}
|
|
653
|
-
|
|
654
|
-
|
|
1609
|
+
onRemoveImage={handleRemoveImage}
|
|
1610
|
+
images={images}
|
|
1611
|
+
onSelectImages={onSelectImages}
|
|
1612
|
+
selectedImage={selectedImage}
|
|
1613
|
+
setSelectedImage={setSelectedImage}
|
|
1614
|
+
isUploadingImage={isUploadingImage}
|
|
1615
|
+
loading={loading}
|
|
1616
|
+
wrapInSafeArea={true}
|
|
1617
|
+
inverted={true}
|
|
1618
|
+
renderLoading={() => <Skeleton variant="rounded" style={{ flex: 1 }} />}
|
|
655
1619
|
messages={messageList}
|
|
656
1620
|
listViewProps={{
|
|
657
1621
|
onEndReached: onEndReached,
|
|
658
1622
|
onEndReachedThreshold: 0.5,
|
|
659
1623
|
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
1624
|
+
style: { marginTop: 0 },
|
|
1625
|
+
contentContainerStyle: {
|
|
1626
|
+
paddingTop: 0,
|
|
1627
|
+
paddingBottom: selectedImage ? 90 : 0,
|
|
1628
|
+
},
|
|
1629
|
+
refreshControl: (
|
|
1630
|
+
<RefreshControl
|
|
1631
|
+
refreshing={refreshing}
|
|
1632
|
+
onRefresh={onRefresh}
|
|
1633
|
+
colors={[colors.blue[500]]}
|
|
1634
|
+
tintColor={colors.blue[500]}
|
|
1635
|
+
title="Loading earlier messages..."
|
|
1636
|
+
titleColor={colors.gray[600]}
|
|
1637
|
+
/>
|
|
1638
|
+
),
|
|
660
1639
|
}}
|
|
661
1640
|
onSend={(messages) => handleSend(messages[0]?.text ?? ' ')}
|
|
662
|
-
text={msg
|
|
1641
|
+
text={msg || ' '}
|
|
663
1642
|
onInputTextChanged={(text) => setMsg(text)}
|
|
664
|
-
renderFooter={() =>
|
|
665
|
-
loading ? (
|
|
666
|
-
<Spinner color={colors.blue[500]} />
|
|
667
|
-
) : imageLoading ? (
|
|
668
|
-
<Spinner color={colors.blue[500]} />
|
|
669
|
-
) : (
|
|
670
|
-
''
|
|
671
|
-
)
|
|
672
|
-
}
|
|
1643
|
+
renderFooter={() => null}
|
|
673
1644
|
scrollToBottom
|
|
674
1645
|
user={{
|
|
675
1646
|
_id: auth?.id || '',
|
|
676
1647
|
}}
|
|
677
|
-
|
|
678
|
-
alwaysShowSend={loading ? false : true}
|
|
679
|
-
// onLoadEarlier={onFetchOld}
|
|
1648
|
+
placeholder="Jot something down"
|
|
680
1649
|
infiniteScroll={true}
|
|
681
1650
|
renderSend={renderSend}
|
|
682
|
-
// loadEarlier={data?.messages?.totalCount > channelMessages.length}
|
|
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
1651
|
renderMessageText={renderMessageText}
|
|
698
|
-
minInputToolbarHeight={50}
|
|
699
|
-
renderActions={renderActions}
|
|
700
|
-
renderAccessory={renderAccessory}
|
|
701
1652
|
renderMessage={renderMessage}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1653
|
+
renderLoadEarlier={renderLoadEarlier}
|
|
1654
|
+
loadEarlier={totalCount > channelMessages.length}
|
|
1655
|
+
isLoadingEarlier={loadingOldMessages}
|
|
1656
|
+
textInputProps={{
|
|
1657
|
+
multiline: true,
|
|
1658
|
+
returnKeyType: 'default',
|
|
1659
|
+
enablesReturnKeyAutomatically: true,
|
|
1660
|
+
placeholderTextColor: colors.gray[400],
|
|
1661
|
+
// Allow direct editing in the main input too for better UX
|
|
1662
|
+
editable: true,
|
|
1663
|
+
onFocus: () => setIsGiftedInputFocused(true),
|
|
1664
|
+
onBlur: () => setIsGiftedInputFocused(false),
|
|
1665
|
+
}}
|
|
1666
|
+
renderChatFooter={renderChatFooter}
|
|
1667
|
+
messagesContainerStyle={{
|
|
1668
|
+
...(messageList?.length == 0 ? { transform: [{ scaleY: -1 }] } : {}),
|
|
1669
|
+
// marginTop: -40, // Negative margin to remove the top padding
|
|
1670
|
+
paddingTop: 0,
|
|
1671
|
+
}}
|
|
1672
|
+
// renderChatEmpty={() => (
|
|
1673
|
+
// <>
|
|
1674
|
+
// {!threadLoading && messageList && messageList?.length == 0 && (
|
|
1675
|
+
// <Box className="p-5">
|
|
1676
|
+
// <Center className="mt-6">
|
|
1677
|
+
// <Ionicons name="chatbubbles" size={30} />
|
|
1678
|
+
// <Text>You don't have any message yet!</Text>
|
|
1679
|
+
// </Center>
|
|
1680
|
+
// </Box>
|
|
1681
|
+
// )}
|
|
1682
|
+
// </>
|
|
1683
|
+
// )}
|
|
1684
|
+
/>
|
|
1685
|
+
{/* <GiftedChat
|
|
1686
|
+
ref={threadMessageListRef}
|
|
1687
|
+
wrapInSafeArea={true}
|
|
1688
|
+
inverted={true}
|
|
1689
|
+
renderLoading={() => <Skeleton variant="rounded" style={{ flex: 1 }} />}
|
|
1690
|
+
messages={messageList}
|
|
1691
|
+
listViewProps={{
|
|
1692
|
+
onEndReached: onEndReached,
|
|
1693
|
+
onEndReachedThreshold: 0.5,
|
|
1694
|
+
onMomentumScrollBegin: onMomentumScrollBegin,
|
|
1695
|
+
style: { marginTop: 0 },
|
|
1696
|
+
contentContainerStyle: {
|
|
1697
|
+
paddingTop: 0,
|
|
1698
|
+
paddingBottom: selectedImage ? 90 : 0,
|
|
1699
|
+
},
|
|
1700
|
+
refreshControl: (
|
|
1701
|
+
<RefreshControl
|
|
1702
|
+
refreshing={refreshing}
|
|
1703
|
+
onRefresh={onRefresh}
|
|
1704
|
+
colors={[colors.blue[500]]}
|
|
1705
|
+
tintColor={colors.blue[500]}
|
|
1706
|
+
title="Loading earlier messages..."
|
|
1707
|
+
titleColor={colors.gray[600]}
|
|
740
1708
|
/>
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1709
|
+
),
|
|
1710
|
+
}}
|
|
1711
|
+
onSend={(messages) => handleSend(messages[0]?.text ?? ' ')}
|
|
1712
|
+
text={msg || ' '}
|
|
1713
|
+
onInputTextChanged={(text) => setMsg(text)}
|
|
1714
|
+
renderFooter={() => null}
|
|
1715
|
+
scrollToBottom
|
|
1716
|
+
user={{
|
|
1717
|
+
_id: auth?.id || '',
|
|
1718
|
+
}}
|
|
1719
|
+
isTyping={false}
|
|
1720
|
+
alwaysShowSend={true}
|
|
1721
|
+
infiniteScroll={true}
|
|
1722
|
+
renderSend={renderSend}
|
|
1723
|
+
renderMessageText={renderMessageText}
|
|
1724
|
+
renderInputToolbar={renderInputToolbar}
|
|
1725
|
+
renderComposer={renderComposer}
|
|
1726
|
+
minInputToolbarHeight={isActionSheetVisible ? 0 : 56}
|
|
1727
|
+
renderAccessory={images.length > 0 ? renderAccessory : null}
|
|
1728
|
+
renderMessage={renderMessage}
|
|
1729
|
+
renderLoadEarlier={renderLoadEarlier}
|
|
1730
|
+
loadEarlier={totalCount > channelMessages.length}
|
|
1731
|
+
isLoadingEarlier={loadingOldMessages}
|
|
1732
|
+
bottomOffset={Platform.OS === 'ios' ? (selectedImage ? 90 : 10) : 0}
|
|
1733
|
+
textInputProps={{
|
|
1734
|
+
multiline: true,
|
|
1735
|
+
returnKeyType: 'default',
|
|
1736
|
+
enablesReturnKeyAutomatically: true,
|
|
1737
|
+
placeholderTextColor: colors.gray[400],
|
|
1738
|
+
// Allow direct editing in the main input too for better UX
|
|
1739
|
+
editable: true,
|
|
1740
|
+
onFocus: () => setIsGiftedInputFocused(true),
|
|
1741
|
+
onBlur: () => setIsGiftedInputFocused(false),
|
|
1742
|
+
}}
|
|
1743
|
+
minComposerHeight={36}
|
|
1744
|
+
maxComposerHeight={100}
|
|
1745
|
+
isKeyboardInternallyHandled={false}
|
|
1746
|
+
renderChatFooter={renderChatFooter}
|
|
1747
|
+
messagesContainerStyle={{
|
|
1748
|
+
...(messageList?.length == 0 ? { transform: [{ scaleY: -1 }] } : {}),
|
|
1749
|
+
// marginTop: -40, // Negative margin to remove the top padding
|
|
1750
|
+
paddingTop: 0,
|
|
1751
|
+
}}
|
|
744
1752
|
renderChatEmpty={() => (
|
|
745
1753
|
<>
|
|
746
1754
|
{!threadLoading && messageList && messageList?.length == 0 && (
|
|
@@ -758,8 +1766,36 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
|
|
|
758
1766
|
springConfig: { tension: 90000, friction: 90000 },
|
|
759
1767
|
disabled: true,
|
|
760
1768
|
}}
|
|
761
|
-
/>
|
|
762
|
-
|
|
1769
|
+
/> */}
|
|
1770
|
+
|
|
1771
|
+
{/* Add the expandable action sheet */}
|
|
1772
|
+
{/* {isActionSheetVisible && <Divider />}
|
|
1773
|
+
{isActionSheetVisible && (
|
|
1774
|
+
<ExpandableInputActionSheet
|
|
1775
|
+
isVisible={isActionSheetVisible}
|
|
1776
|
+
onClose={handleActionSheetClose}
|
|
1777
|
+
onSend={handleActionSheetSend}
|
|
1778
|
+
onSelectImages={onSelectImages}
|
|
1779
|
+
text={msg}
|
|
1780
|
+
onChangeText={(text) => {
|
|
1781
|
+
setMsg(text);
|
|
1782
|
+
setTextUpdatedInActionSheet(true);
|
|
1783
|
+
}}
|
|
1784
|
+
textInputRef={textInputRef}
|
|
1785
|
+
isSendDisabled={(!msg.trim() && images.length === 0) || isUploadingImage}
|
|
1786
|
+
selectedImages={images}
|
|
1787
|
+
loading={loading || isUploadingImage}
|
|
1788
|
+
onRemoveImage={(index) => {
|
|
1789
|
+
const newImages = [...images];
|
|
1790
|
+
newImages.splice(index, 1);
|
|
1791
|
+
setImages(newImages);
|
|
1792
|
+
if (newImages.length === 0) {
|
|
1793
|
+
setSelectedImage('');
|
|
1794
|
+
}
|
|
1795
|
+
}}
|
|
1796
|
+
/>
|
|
1797
|
+
)} */}
|
|
1798
|
+
</View>
|
|
763
1799
|
);
|
|
764
1800
|
};
|
|
765
1801
|
|