create-gufran-expo-app 2.0.4 → 2.0.5
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/package.json +3 -3
- package/template/src/navigation/AuthStack.tsx +6 -25
- package/template/src/navigation/MainStack.tsx +0 -148
- package/template/src/navigation/RootNavigator.tsx +4 -26
- package/template/src/navigation/index.ts +0 -1
- package/template/src/navigation/navigationRef.ts +1 -1
- package/template/src/screens/HomeScreen.tsx +3 -215
- package/template/src/screens/auth/LoginScreen.tsx +13 -13
- package/template/src/screens/auth/index.ts +1 -6
- package/template/src/screens/index.ts +0 -35
- package/template/src/services/api.ts +5 -5
- package/template/src/services/authService.ts +3 -299
- package/template/src/services/mainServices.ts +19 -1914
- package/template/src/types/navigation.ts +5 -155
- package/template/src/utils/index.ts +5 -8
- package/template/src/navigation/MiddleStack.tsx +0 -35
- package/template/src/screens/auth/AddMamber.tsx +0 -428
- package/template/src/screens/auth/ForgotPasswordScreen.tsx +0 -176
- package/template/src/screens/auth/OTPVerifyScreen.tsx +0 -359
- package/template/src/screens/auth/RegisterScreen.tsx +0 -430
- package/template/src/screens/auth/SuccessScreen.tsx +0 -201
- package/template/src/screens/chat/ChatScreen.tsx +0 -1819
- package/template/src/screens/chat/ChatThreadsScreen.tsx +0 -360
- package/template/src/screens/chat/ReportMessageScreen.tsx +0 -238
- package/template/src/screens/clubs/Announcements.tsx +0 -426
- package/template/src/screens/clubs/BuyRaffleTicketsScreen.tsx +0 -568
- package/template/src/screens/clubs/ClubDeteils.tsx +0 -497
- package/template/src/screens/clubs/JoinClub.tsx +0 -841
- package/template/src/screens/events/EventScreen.tsx +0 -460
- package/template/src/screens/raffles/MyReferralMembersScreen.tsx +0 -758
- package/template/src/screens/raffles/RaffleDetailsScreen.tsx +0 -762
- package/template/src/screens/raffles/RafflesScreen.tsx +0 -495
- package/template/src/screens/raffles/SetRaffleReminderScreen.tsx +0 -390
- package/template/src/screens/teams/JoinTeamScreen.tsx +0 -464
- package/template/src/screens/teams/MyTeamDetailsScreen.tsx +0 -979
- package/template/src/screens/teams/MyTeamScreen.tsx +0 -568
- package/template/src/screens/teams/PendingRequestsScreen.tsx +0 -426
- package/template/src/screens/volunteerOpportunities/SetReminderScreen.tsx +0 -631
- package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesDetailsScreen.tsx +0 -1049
- package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesScreen.tsx +0 -608
- package/template/src/utils/ClubSearchManager.ts +0 -222
|
@@ -1,1819 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
2
|
-
import { View, Text, StyleSheet, StatusBar, Platform, TouchableOpacity, Image, Keyboard, ActivityIndicator, Alert, Modal, TextInput, ScrollView } from "react-native";
|
|
3
|
-
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
|
-
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
5
|
-
import { GiftedChat, Bubble, InputToolbar, Composer } from 'react-native-gifted-chat';
|
|
6
|
-
import { SafeAreaView } from "react-native-safe-area-context";
|
|
7
|
-
import { useIsFocused } from "@react-navigation/native";
|
|
8
|
-
import { ImageZoom } from '@likashefqet/react-native-image-zoom';
|
|
9
|
-
import { ChatScreenProps } from "@navigation";
|
|
10
|
-
import { theme } from "@constants";
|
|
11
|
-
import { moderateScale } from "@utils/scaling";
|
|
12
|
-
import { Fonts } from "@constants/Fonts";
|
|
13
|
-
import SVG from "@assets/icons";
|
|
14
|
-
import * as SignalR from "@microsoft/signalr";
|
|
15
|
-
import { useInfiniteChatMessages, ChatMessage, mainService } from "@services/mainServices";
|
|
16
|
-
import { pickImage } from "@utils";
|
|
17
|
-
import ToastManager from "@components/common/ToastManager";
|
|
18
|
-
import { useImageUpload } from "../../hooks";
|
|
19
|
-
import { CHAT_HUB_URL } from "@services";
|
|
20
|
-
|
|
21
|
-
export const ChatScreen: React.FC<ChatScreenProps> = ({
|
|
22
|
-
navigation,
|
|
23
|
-
route,
|
|
24
|
-
}) => {
|
|
25
|
-
const { itemDetails, memberId, teamId, team, userType } = route?.params as any ?? {};
|
|
26
|
-
const [messages, setMessages] = useState([]);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// Use refs for connection management to avoid unnecessary re-renders
|
|
30
|
-
const connectionRef = useRef<SignalR.HubConnection | null>(null);
|
|
31
|
-
const isConnectedRef = useRef(false);
|
|
32
|
-
const isConnectingRef = useRef(false);
|
|
33
|
-
const reconnectAttemptRef = useRef(0);
|
|
34
|
-
|
|
35
|
-
// UI states
|
|
36
|
-
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
|
|
37
|
-
const [showReportModal, setShowReportModal] = useState(false);
|
|
38
|
-
const [selectedMessage, setSelectedMessage] = useState<any>(null);
|
|
39
|
-
const [reportReason, setReportReason] = useState('');
|
|
40
|
-
const [reportError, setReportError] = useState('');
|
|
41
|
-
const [showMoreModal, setShowMoreModal] = useState(false);
|
|
42
|
-
const [isMuted, setIsMuted] = useState(false);
|
|
43
|
-
// Image selection state
|
|
44
|
-
const [selectedImage, setSelectedImage] = useState<any>(null);
|
|
45
|
-
const [isUploadingImage, setIsUploadingImage] = useState(false);
|
|
46
|
-
// Fullscreen image viewer state
|
|
47
|
-
const [isImageViewerVisible, setIsImageViewerVisible] = useState(false);
|
|
48
|
-
const [imageViewerUri, setImageViewerUri] = useState<string | null>(null);
|
|
49
|
-
|
|
50
|
-
let keyboardDidShowListenerRef = useRef<any>(null);
|
|
51
|
-
let keyboardDidHideListenerRef = useRef<any>(null);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Track if this screen is focused
|
|
55
|
-
const isFocused = useIsFocused();
|
|
56
|
-
|
|
57
|
-
// Add API call for fetching messages with pagination
|
|
58
|
-
const { data: chatMessagesData, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading: isLoadingMessages, error: messagesError, refetch: refetchMessages } = useInfiniteChatMessages(
|
|
59
|
-
teamId || 46,
|
|
60
|
-
memberId || 123,
|
|
61
|
-
itemDetails?.threadId || 27,
|
|
62
|
-
userType); // Pass userType to the hook
|
|
63
|
-
|
|
64
|
-
const isMute = chatMessagesData?.pages?.[0]?.data?.data?.isMute;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.log("chatMessagesData", JSON.stringify(chatMessagesData?.pages?.[0]?.data?.data?.isMute));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Transform API messages to GiftedChat format
|
|
71
|
-
const transformedMessages = useMemo(() => {
|
|
72
|
-
if (!chatMessagesData?.pages || chatMessagesData.pages.length === 0) {
|
|
73
|
-
console.log("No chat messages data available");
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const allMessages: any[] = [];
|
|
78
|
-
|
|
79
|
-
chatMessagesData.pages.forEach((page, pageIndex) => {
|
|
80
|
-
// Check if page has the expected structure
|
|
81
|
-
if (!page?.data?.data?.lstMessages) {
|
|
82
|
-
console.log(`Page ${pageIndex} has invalid structure:`, page);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { lstMessages } = page.data.data;
|
|
87
|
-
|
|
88
|
-
lstMessages.forEach((message: ChatMessage, messageIndex) => {
|
|
89
|
-
// Handle sender image - use full URL if it starts with http, otherwise use default
|
|
90
|
-
let senderAvatar = null;
|
|
91
|
-
if (message.senderImage) {
|
|
92
|
-
if (message.senderImage.startsWith('http')) {
|
|
93
|
-
senderAvatar = message.senderImage;
|
|
94
|
-
} else if (message.senderImage !== '/assets/images/noimg.png') {
|
|
95
|
-
// If it's a relative path but not the default no-image path
|
|
96
|
-
senderAvatar = `https://clubyakkastorage.blob.core.windows.net${message.senderImage}`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// Parse sent date with fallback
|
|
104
|
-
let messageDate = new Date();
|
|
105
|
-
if (message.sentAt) {
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
// First try parsing the date as-is (works for most browsers)
|
|
109
|
-
let parsedDate = new Date(message.sentAt);
|
|
110
|
-
|
|
111
|
-
// If that doesn't work, try manual parsing
|
|
112
|
-
if (isNaN(parsedDate.getTime())) {
|
|
113
|
-
// Split date and time parts
|
|
114
|
-
const [datePart, timePart, period] = message.sentAt.split(' ');
|
|
115
|
-
const [month, day, year] = datePart.split('/');
|
|
116
|
-
const [hours, minutes, seconds] = timePart.split(':');
|
|
117
|
-
|
|
118
|
-
// Convert to 24-hour format
|
|
119
|
-
let hour24 = parseInt(hours, 10);
|
|
120
|
-
if (period === 'PM' && hour24 !== 12) {
|
|
121
|
-
hour24 += 12;
|
|
122
|
-
} else if (period === 'AM' && hour24 === 12) {
|
|
123
|
-
hour24 = 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Create date object
|
|
127
|
-
parsedDate = new Date(
|
|
128
|
-
parseInt(year, 10),
|
|
129
|
-
parseInt(month, 10) - 1,
|
|
130
|
-
parseInt(day, 10),
|
|
131
|
-
hour24,
|
|
132
|
-
parseInt(minutes, 10),
|
|
133
|
-
parseInt(seconds, 10)
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!isNaN(parsedDate.getTime())) {
|
|
138
|
-
messageDate = parsedDate;
|
|
139
|
-
} else {
|
|
140
|
-
console.log(`Failed to parse date for message ${message.messageId}:`, message.sentAt);
|
|
141
|
-
}
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.log(`Date parsing error for message ${message.messageId}:`, error, message.sentAt);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Transform to GiftedChat format
|
|
151
|
-
const transformedMessage = {
|
|
152
|
-
_id: message.messageId.toString(),
|
|
153
|
-
text: message.message.trim(),
|
|
154
|
-
createdAt: new Date(message?.sentAt + "Z"),
|
|
155
|
-
image: message?.image,
|
|
156
|
-
user: {
|
|
157
|
-
_id: message.senderId,
|
|
158
|
-
name: message.senderName || 'Unknown User',
|
|
159
|
-
avatar: senderAvatar,
|
|
160
|
-
},
|
|
161
|
-
metadata: {
|
|
162
|
-
threadId: message.threadId,
|
|
163
|
-
userTypeId: message.userTypeId,
|
|
164
|
-
userType: message.userType,
|
|
165
|
-
originalSentAt: new Date(message?.sentAt + "Z"),
|
|
166
|
-
messageId: message.messageId, // Keep original messageId for sorting
|
|
167
|
-
hasImage: !!message?.image,
|
|
168
|
-
image: message.image || null,
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
allMessages.push(transformedMessage);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
console.log(`Transformed ${allMessages.length} messages`);
|
|
177
|
-
|
|
178
|
-
// Sort messages by messageId (index) first (largest first), then by timestamp (newest first)
|
|
179
|
-
// This ensures messages with the largest index appear at the top
|
|
180
|
-
const sortedMessages = allMessages.sort((a, b) => {
|
|
181
|
-
const messageIdA = a.metadata.messageId;
|
|
182
|
-
const messageIdB = b.metadata.messageId;
|
|
183
|
-
|
|
184
|
-
// First sort by messageId (largest first - most recent index)
|
|
185
|
-
if (messageIdA !== messageIdB) {
|
|
186
|
-
return messageIdB - messageIdA;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// If messageIds are the same (unlikely), sort by timestamp (newest first)
|
|
190
|
-
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
console.log('Sorted messages by largest index first:', {
|
|
194
|
-
totalMessages: sortedMessages.length,
|
|
195
|
-
firstMessageId: sortedMessages[0]?.metadata?.messageId,
|
|
196
|
-
lastMessageId: sortedMessages[sortedMessages.length - 1]?.metadata?.messageId
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// Apply additional conditions if needed
|
|
200
|
-
const filteredMessages = sortedMessages.filter((message) => {
|
|
201
|
-
// Filter by current thread ID
|
|
202
|
-
if (itemDetails?.threadId && message.metadata.threadId !== itemDetails.threadId) {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Don't filter out messages that have images, even if text is empty
|
|
207
|
-
if (message.image || message.metadata.hasImage) {
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Filter out empty messages (only if they don't have images)
|
|
212
|
-
if (!message.text || message.text.trim().length === 0) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Add any other conditions here
|
|
217
|
-
return true;
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
console.log(`Filtered to ${filteredMessages.length} messages for current thread`);
|
|
221
|
-
console.log('Final message order (first 5):', filteredMessages.slice(0, 5).map(m => ({
|
|
222
|
-
id: m.metadata.messageId,
|
|
223
|
-
text: m.text.substring(0, 30) + '...',
|
|
224
|
-
createdAt: m.createdAt
|
|
225
|
-
})));
|
|
226
|
-
|
|
227
|
-
return filteredMessages;
|
|
228
|
-
}, [chatMessagesData, itemDetails?.threadId]);
|
|
229
|
-
|
|
230
|
-
// Update local messages when API data changes
|
|
231
|
-
useEffect(() => {
|
|
232
|
-
setIsMuted(isMute);
|
|
233
|
-
if (transformedMessages.length > 0) {
|
|
234
|
-
setMessages(transformedMessages);
|
|
235
|
-
}
|
|
236
|
-
}, [transformedMessages]);
|
|
237
|
-
|
|
238
|
-
// Keyboard event listeners - simple state management
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
if (!isFocused) {
|
|
241
|
-
keyboardDidShowListenerRef.current?.remove();
|
|
242
|
-
keyboardDidHideListenerRef.current?.remove();
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
keyboardDidShowListenerRef.current = Keyboard.addListener(
|
|
247
|
-
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
|
|
248
|
-
() => setIsKeyboardOpen(true)
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
keyboardDidHideListenerRef.current = Keyboard.addListener(
|
|
252
|
-
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
|
|
253
|
-
() => setIsKeyboardOpen(false)
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
return () => {
|
|
257
|
-
keyboardDidShowListenerRef.current?.remove();
|
|
258
|
-
keyboardDidHideListenerRef.current?.remove();
|
|
259
|
-
};
|
|
260
|
-
}, [isFocused]);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
// Handle loading more messages (pagination)
|
|
265
|
-
const handleLoadEarlier = useCallback(() => {
|
|
266
|
-
console.log("🔄 handleLoadEarlier called");
|
|
267
|
-
console.log("📊 Pagination state:", {
|
|
268
|
-
hasNextPage,
|
|
269
|
-
isFetchingNextPage,
|
|
270
|
-
pagesLoaded: chatMessagesData?.pages?.length || 0
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
if (hasNextPage && !isFetchingNextPage) {
|
|
274
|
-
console.log("✅ Fetching next page...");
|
|
275
|
-
fetchNextPage()
|
|
276
|
-
.then(() => {
|
|
277
|
-
console.log("✅ Successfully fetched next page");
|
|
278
|
-
})
|
|
279
|
-
.catch((error) => {
|
|
280
|
-
console.error("❌ Error fetching next page:", error);
|
|
281
|
-
Alert.alert("Error", "Failed to load earlier messages. Please try again.");
|
|
282
|
-
});
|
|
283
|
-
} else {
|
|
284
|
-
console.log("⚠️ Cannot fetch:", {
|
|
285
|
-
hasNextPage,
|
|
286
|
-
isFetchingNextPage,
|
|
287
|
-
reason: !hasNextPage ? "No more pages" : "Already fetching"
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
if (!hasNextPage) {
|
|
291
|
-
Alert.alert("Info", "No more messages to load");
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
295
|
-
|
|
296
|
-
useEffect(() => {
|
|
297
|
-
// Configure automatic reconnect with exponential backoff delays
|
|
298
|
-
// First reconnect attempts immediately, then progressively increases
|
|
299
|
-
const reconnectDelays = [0, 2000, 5000, 10000, 15000, 30000]; // 0s, 2s, 5s, 10s, 15s, 30s
|
|
300
|
-
|
|
301
|
-
const newConnection = new SignalR.HubConnectionBuilder()
|
|
302
|
-
.withUrl(CHAT_HUB_URL)
|
|
303
|
-
.withAutomaticReconnect(reconnectDelays)
|
|
304
|
-
.configureLogging(SignalR.LogLevel.Information)
|
|
305
|
-
.build();
|
|
306
|
-
|
|
307
|
-
// Store in ref
|
|
308
|
-
connectionRef.current = newConnection;
|
|
309
|
-
|
|
310
|
-
// Set up connection event handlers
|
|
311
|
-
newConnection.onclose((error) => {
|
|
312
|
-
console.log("Connection closed:", error);
|
|
313
|
-
isConnectedRef.current = false;
|
|
314
|
-
isConnectingRef.current = false;
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
newConnection.onreconnecting((error) => {
|
|
318
|
-
console.log("Connection reconnecting:", error);
|
|
319
|
-
isConnectedRef.current = false;
|
|
320
|
-
isConnectingRef.current = true;
|
|
321
|
-
reconnectAttemptRef.current += 1;
|
|
322
|
-
console.log(`Reconnection attempt #${reconnectAttemptRef.current}`);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
newConnection.onreconnected((connectionId) => {
|
|
326
|
-
console.log("Connection reconnected:", connectionId);
|
|
327
|
-
isConnectedRef.current = true;
|
|
328
|
-
isConnectingRef.current = false;
|
|
329
|
-
reconnectAttemptRef.current = 0; // Reset counter on successful reconnect
|
|
330
|
-
joinThread(connectionId);
|
|
331
|
-
});
|
|
332
|
-
// Cleanup function
|
|
333
|
-
return () => {
|
|
334
|
-
if (connectionRef.current?.state === SignalR.HubConnectionState.Connected) {
|
|
335
|
-
connectionRef.current.stop();
|
|
336
|
-
}
|
|
337
|
-
connectionRef.current = null;
|
|
338
|
-
};
|
|
339
|
-
}, []);
|
|
340
|
-
|
|
341
|
-
// Start connection when component mounts
|
|
342
|
-
useEffect(() => {
|
|
343
|
-
if (connectionRef.current && !isConnectedRef.current && !isConnectingRef.current) {
|
|
344
|
-
startConnection();
|
|
345
|
-
}
|
|
346
|
-
}, []); // Empty dependency array - only run once on mount
|
|
347
|
-
|
|
348
|
-
const startConnection = async () => {
|
|
349
|
-
if (!connectionRef.current || isConnectingRef.current) return;
|
|
350
|
-
|
|
351
|
-
isConnectingRef.current = true;
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
await connectionRef.current.start();
|
|
355
|
-
console.log("✅ Connected:", connectionRef.current?.connectionId);
|
|
356
|
-
isConnectedRef.current = true;
|
|
357
|
-
isConnectingRef.current = false;
|
|
358
|
-
setupListeners();
|
|
359
|
-
|
|
360
|
-
await joinThread(connectionRef.current?.connectionId);
|
|
361
|
-
} catch (err) {
|
|
362
|
-
console.error("❌ Connection failed:", err);
|
|
363
|
-
isConnectedRef.current = false;
|
|
364
|
-
isConnectingRef.current = false;
|
|
365
|
-
// Retry after 5 seconds with exponential backoff
|
|
366
|
-
setTimeout(startConnection, 5000);
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const setupListeners = () => {
|
|
371
|
-
// if (!connectionRef.current) return;
|
|
372
|
-
connectionRef.current.on("ReceiveMessage", (message) => {
|
|
373
|
-
|
|
374
|
-
console.log("ReceiveMessage received:", message);
|
|
375
|
-
// Handle sender image - use full URL if it starts with http, otherwise use default
|
|
376
|
-
let senderAvatar = undefined;
|
|
377
|
-
if (message?.senderImage) {
|
|
378
|
-
if (message.senderImage.startsWith('http')) {
|
|
379
|
-
senderAvatar = message.senderImage;
|
|
380
|
-
} else if (message.senderImage !== '/assets/images/noimg.png') {
|
|
381
|
-
// If it's a relative path but not the default no-image path
|
|
382
|
-
senderAvatar = `https://clubyakkastorage.blob.core.windows.net${message.senderImage}`;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const newMessage = {
|
|
387
|
-
_id: message.messageId?.toString() || Math.random().toString(),
|
|
388
|
-
text: message?.message || '',
|
|
389
|
-
createdAt: new Date(message?.sentAt + "Z"),
|
|
390
|
-
user: {
|
|
391
|
-
_id: message?.senderId || 0,
|
|
392
|
-
name: message?.senderName || 'Unknown User',
|
|
393
|
-
avatar: senderAvatar,
|
|
394
|
-
},
|
|
395
|
-
image: message?.image || undefined,
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
setMessages(previousMessages => {
|
|
399
|
-
// If there's a matching pending message (same sender, text, and image presence),
|
|
400
|
-
// replace it with the confirmed one to avoid brief duplication.
|
|
401
|
-
const matchIndex = previousMessages.findIndex(m =>
|
|
402
|
-
m?.pending === true &&
|
|
403
|
-
(m?.user?._id === memberId) &&
|
|
404
|
-
((m?.text || '') === (newMessage.text || '')) &&
|
|
405
|
-
(Boolean(m?.image) === Boolean(newMessage.image))
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
if (matchIndex !== -1) {
|
|
409
|
-
const updated = [...previousMessages];
|
|
410
|
-
updated[matchIndex] = newMessage;
|
|
411
|
-
return updated;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Also guard against duplicates by id
|
|
415
|
-
if (previousMessages.some(m => String(m?._id) === String(newMessage._id))) {
|
|
416
|
-
return previousMessages;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return GiftedChat.append(previousMessages, [newMessage]);
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
connectionRef.current.on("MessageDelete", (threadId, messageId) => {
|
|
427
|
-
console.log("MessageDelete received:", { threadId, messageId });
|
|
428
|
-
|
|
429
|
-
// Remove the deleted message from local state
|
|
430
|
-
setMessages(previousMessages => {
|
|
431
|
-
const filteredMessages = previousMessages.filter(msg => {
|
|
432
|
-
// Convert both IDs to the same type for comparison
|
|
433
|
-
return Number(msg._id) !== Number(messageId);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
console.log(`Deleted message ${messageId} from local state. Remaining: ${filteredMessages.length} messages`);
|
|
437
|
-
return filteredMessages;
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
connectionRef.current.on("ReportMessage", (perThreadId, messageId, isReported) => {
|
|
443
|
-
console.log("ReportMessage received:", { perThreadId, messageId, isReported });
|
|
444
|
-
|
|
445
|
-
console.log("ReportMessage received:", { perThreadId, messageId, isReported });
|
|
446
|
-
if (perThreadId == itemDetails?.threadId) {
|
|
447
|
-
if (isReported === true) {
|
|
448
|
-
ToastManager.success("Message has been reported successfully");
|
|
449
|
-
} else {
|
|
450
|
-
ToastManager.error("Unable to report this message. Please try again.");
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
const joinThread = async (connectionId: string | undefined) => {
|
|
457
|
-
if (!connectionRef.current || !connectionId || !itemDetails?.threadId || !memberId) {
|
|
458
|
-
console.log("Cannot join thread - missing required data");
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const data = {
|
|
463
|
-
threadId: itemDetails.threadId,
|
|
464
|
-
memberId: memberId,
|
|
465
|
-
type: 7,
|
|
466
|
-
connectionId: connectionId
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
console.log("Joining thread with data:", data);
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
await connectionRef.current.invoke("JoinThread", itemDetails.threadId, memberId, 7, connectionId);
|
|
473
|
-
console.log(`Successfully joined thread ${itemDetails.threadId}`);
|
|
474
|
-
} catch (err) {
|
|
475
|
-
console.error("JoinThread error:", err);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
// Image upload hook
|
|
480
|
-
const {
|
|
481
|
-
uploadState,
|
|
482
|
-
uploadImage,
|
|
483
|
-
} = useImageUpload({
|
|
484
|
-
containerName: 'chat',
|
|
485
|
-
maxRetries: 3,
|
|
486
|
-
retryDelay: 1000,
|
|
487
|
-
onUploadStart: () => {
|
|
488
|
-
console.log('Upload started');
|
|
489
|
-
},
|
|
490
|
-
onUploadProgress: (progress) => {
|
|
491
|
-
console.log(`Upload progress: ${progress}%`);
|
|
492
|
-
},
|
|
493
|
-
onUploadSuccess: (url) => {
|
|
494
|
-
console.log('Upload successful:', url);
|
|
495
|
-
// ToastManager.success('Success', 'Image uploaded successfully');
|
|
496
|
-
},
|
|
497
|
-
onUploadError: (error) => {
|
|
498
|
-
console.error('Upload error:', error);
|
|
499
|
-
ToastManager.error('Upload Failed', error);
|
|
500
|
-
},
|
|
501
|
-
onUploadComplete: () => {
|
|
502
|
-
console.log('Upload completed');
|
|
503
|
-
},
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const deleteMessage = async (messageId: any) => {
|
|
508
|
-
console.log("Deleting message with ID:", typeof itemDetails?.threadId, typeof messageId, typeof memberId, typeof 7);
|
|
509
|
-
// return
|
|
510
|
-
Alert.alert("Delete Chat", "Do you really want to delete this chat?", [
|
|
511
|
-
{ text: "Cancel", style: "cancel" },
|
|
512
|
-
{
|
|
513
|
-
text: "Yes",
|
|
514
|
-
onPress: () => {
|
|
515
|
-
connectionRef.current?.invoke("DeleteMessage", itemDetails?.threadId, Number(messageId), Number(memberId), 7)
|
|
516
|
-
.then((res) => {
|
|
517
|
-
console.log("Message deleted successfully:", res);
|
|
518
|
-
})
|
|
519
|
-
.catch((err) => {
|
|
520
|
-
console.error("DeleteMessage error:", err);
|
|
521
|
-
});
|
|
522
|
-
},
|
|
523
|
-
},
|
|
524
|
-
]);
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
const onSend = useCallback(async (messages = [], shouldSendImage = false) => {
|
|
528
|
-
// Check if there's either text or an image to send
|
|
529
|
-
const messageText = messages[0]?.text?.trim() || '';
|
|
530
|
-
const hasText = messageText.length > 0;
|
|
531
|
-
const hasImage = selectedImage !== null;
|
|
532
|
-
|
|
533
|
-
if (!hasText && !hasImage) {
|
|
534
|
-
Alert.alert("Error", "Please enter a message or select an image");
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Check if connection is ready
|
|
539
|
-
if (!connectionRef.current || !isConnectedRef.current || connectionRef.current.state !== SignalR.HubConnectionState.Connected) {
|
|
540
|
-
Alert.alert("Connection Error", "Chat connection is not ready. Please wait and try again.");
|
|
541
|
-
console.log("Connection state:", connectionRef.current?.state, "IsConnected:", isConnectedRef.current);
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Check required data
|
|
546
|
-
if (!itemDetails?.threadId || !memberId) {
|
|
547
|
-
Alert.alert("Error", "Missing chat information");
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Create pending message ID
|
|
552
|
-
const pendingMessageId = `pending-${Date.now()}-${Math.random()}`;
|
|
553
|
-
|
|
554
|
-
// Save selected image reference before clearing
|
|
555
|
-
const imageToUpload = selectedImage;
|
|
556
|
-
|
|
557
|
-
try {
|
|
558
|
-
let imageFileName = null;
|
|
559
|
-
|
|
560
|
-
// Add message to state immediately with pending flag
|
|
561
|
-
const pendingMessage = {
|
|
562
|
-
_id: pendingMessageId,
|
|
563
|
-
text: messageText,
|
|
564
|
-
createdAt: new Date(),
|
|
565
|
-
pending: true,
|
|
566
|
-
image: hasImage ? imageToUpload?.uri : null,
|
|
567
|
-
user: {
|
|
568
|
-
_id: memberId,
|
|
569
|
-
},
|
|
570
|
-
metadata: {
|
|
571
|
-
threadId: itemDetails?.threadId,
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
// Add pending message to UI
|
|
576
|
-
setMessages(previousMessages => [pendingMessage, ...previousMessages]);
|
|
577
|
-
|
|
578
|
-
// Clear the input and selected image immediately
|
|
579
|
-
handleRemoveImage();
|
|
580
|
-
|
|
581
|
-
// If there's an image to send, upload it first
|
|
582
|
-
if (hasImage && imageToUpload) {
|
|
583
|
-
setIsUploadingImage(true);
|
|
584
|
-
try {
|
|
585
|
-
const uploadResult = await uploadImage(imageToUpload);
|
|
586
|
-
if (uploadResult.success) {
|
|
587
|
-
imageFileName = imageToUpload?.fileName;
|
|
588
|
-
console.log('Image uploaded successfully:', imageFileName);
|
|
589
|
-
} else {
|
|
590
|
-
console.error('Image upload failed:', uploadResult.error);
|
|
591
|
-
Alert.alert("Upload Error", "Failed to upload image. Please try again.");
|
|
592
|
-
setIsUploadingImage(false);
|
|
593
|
-
|
|
594
|
-
// Remove pending message on failure
|
|
595
|
-
setMessages(previousMessages =>
|
|
596
|
-
previousMessages.filter(msg => msg._id !== pendingMessageId)
|
|
597
|
-
);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
} catch (error) {
|
|
601
|
-
console.error('Image upload error:', error);
|
|
602
|
-
Alert.alert("Upload Error", "Failed to upload image. Please try again.");
|
|
603
|
-
setIsUploadingImage(false);
|
|
604
|
-
|
|
605
|
-
// Remove pending message on failure
|
|
606
|
-
setMessages(previousMessages =>
|
|
607
|
-
previousMessages.filter(msg => msg._id !== pendingMessageId)
|
|
608
|
-
);
|
|
609
|
-
return;
|
|
610
|
-
} finally {
|
|
611
|
-
setIsUploadingImage(false);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Send the message with text and/or image
|
|
616
|
-
await connectionRef.current.invoke("SendMessage", itemDetails?.threadId, memberId, messageText, imageFileName, 7)
|
|
617
|
-
.then((res) => {
|
|
618
|
-
console.log("Message sent successfully:", res);
|
|
619
|
-
})
|
|
620
|
-
.catch((err) => {
|
|
621
|
-
console.error("SendMessage error:", err);
|
|
622
|
-
Alert.alert("Send Error", "Failed to send message. Please try again.");
|
|
623
|
-
|
|
624
|
-
// Remove pending message on failure
|
|
625
|
-
setMessages(previousMessages =>
|
|
626
|
-
previousMessages.filter(msg => msg._id !== pendingMessageId)
|
|
627
|
-
);
|
|
628
|
-
});
|
|
629
|
-
} catch (err) {
|
|
630
|
-
console.error("SendMessage error:", err);
|
|
631
|
-
Alert.alert("Send Error", "Failed to send message. Please try again.");
|
|
632
|
-
|
|
633
|
-
// Remove pending message on failure
|
|
634
|
-
setMessages(previousMessages =>
|
|
635
|
-
previousMessages.filter(msg => msg._id !== pendingMessageId)
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
}, [itemDetails?.threadId, memberId, selectedImage, uploadImage]);
|
|
639
|
-
|
|
640
|
-
const handleGalleryPress = async () => {
|
|
641
|
-
Alert.alert(
|
|
642
|
-
"Select Image",
|
|
643
|
-
"Choose an option to add an image",
|
|
644
|
-
[
|
|
645
|
-
{
|
|
646
|
-
text: "Camera",
|
|
647
|
-
onPress: () => handleImageSelection('camera'),
|
|
648
|
-
},
|
|
649
|
-
{
|
|
650
|
-
text: "Gallery",
|
|
651
|
-
onPress: () => handleImageSelection('gallery'),
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
text: "Cancel",
|
|
655
|
-
style: "cancel",
|
|
656
|
-
},
|
|
657
|
-
],
|
|
658
|
-
{ cancelable: true }
|
|
659
|
-
);
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
const handleImageSelection = async (source: 'camera' | 'gallery') => {
|
|
663
|
-
const options = {};
|
|
664
|
-
try {
|
|
665
|
-
const result = await pickImage({ ...options, source });
|
|
666
|
-
|
|
667
|
-
if (result) {
|
|
668
|
-
// Store the image in state instead of immediately sending
|
|
669
|
-
setSelectedImage(result);
|
|
670
|
-
}
|
|
671
|
-
} catch (error) {
|
|
672
|
-
console.error(`${source} error:`, error);
|
|
673
|
-
Alert.alert('Error', `Failed to select image from ${source}. Please try again.`);
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
const openImageViewer = useCallback((uri: string) => {
|
|
678
|
-
setImageViewerUri(uri);
|
|
679
|
-
setIsImageViewerVisible(true);
|
|
680
|
-
}, []);
|
|
681
|
-
|
|
682
|
-
const closeImageViewer = useCallback(() => {
|
|
683
|
-
setIsImageViewerVisible(false);
|
|
684
|
-
setImageViewerUri(null);
|
|
685
|
-
}, []);
|
|
686
|
-
|
|
687
|
-
const handleRemoveImage = useCallback(() => {
|
|
688
|
-
setSelectedImage(null);
|
|
689
|
-
}, []);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
// Report modal functions
|
|
693
|
-
const openReportModal = useCallback((message: any) => {
|
|
694
|
-
Keyboard.dismiss();
|
|
695
|
-
setSelectedMessage(message);
|
|
696
|
-
setShowReportModal(true);
|
|
697
|
-
}, []);
|
|
698
|
-
|
|
699
|
-
const closeReportModal = useCallback(() => {
|
|
700
|
-
setShowReportModal(false);
|
|
701
|
-
setReportReason('');
|
|
702
|
-
setSelectedMessage(null);
|
|
703
|
-
setReportError('');
|
|
704
|
-
}, []);
|
|
705
|
-
|
|
706
|
-
const handleReportSubmit = async () => {
|
|
707
|
-
console.log("handleReportSubmit", itemDetails?.threadId, memberId, selectedMessage?._id, selectedMessage?.user?._id, selectedMessage?.user?.userTypeId, 'Report');
|
|
708
|
-
|
|
709
|
-
// Clear any previous error
|
|
710
|
-
setReportError('');
|
|
711
|
-
|
|
712
|
-
if (!reportReason.trim()) {
|
|
713
|
-
setReportError("Please provide a reason for reporting this message.");
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
await connectionRef.current.invoke("MessageReport", Number(itemDetails?.threadId), Number(selectedMessage?._id), Number(memberId), 7, String(reportReason))
|
|
719
|
-
.then((res) => {
|
|
720
|
-
console.log("Message reported successfully:", res);
|
|
721
|
-
closeReportModal();
|
|
722
|
-
ToastManager.success("Message has been reported successfully");
|
|
723
|
-
})
|
|
724
|
-
.catch((err) => {
|
|
725
|
-
console.error("MessageReport error:", err);
|
|
726
|
-
ToastManager.error("Unable to report this message. Please try again.");
|
|
727
|
-
});
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
// Memoized bubble renderer for better performance
|
|
731
|
-
const renderBubble = useCallback((props: any) => {
|
|
732
|
-
const isCurrentUser = props.currentMessage.user._id === memberId;
|
|
733
|
-
|
|
734
|
-
return (
|
|
735
|
-
|
|
736
|
-
<View style={styles.messageContainer}>
|
|
737
|
-
{/* Show avatar and name for other users (left side) */}
|
|
738
|
-
{!isCurrentUser && (
|
|
739
|
-
<View style={styles.leftMessageWrapper}>
|
|
740
|
-
<View style={styles.avatarContainer}>
|
|
741
|
-
{props.currentMessage.user.avatar ? (
|
|
742
|
-
<Image
|
|
743
|
-
source={{ uri: props?.currentMessage?.user?.avatar }}
|
|
744
|
-
style={styles.avatar}
|
|
745
|
-
onError={(error) => {
|
|
746
|
-
console.log("Avatar loading error:", error.nativeEvent.error);
|
|
747
|
-
}}
|
|
748
|
-
/>
|
|
749
|
-
) : (
|
|
750
|
-
<View style={styles.defaultAvatar}>
|
|
751
|
-
<Text style={styles.avatarInitial}>
|
|
752
|
-
{props.currentMessage.user.name?.charAt(0) || 'U'}
|
|
753
|
-
</Text>
|
|
754
|
-
</View>
|
|
755
|
-
)}
|
|
756
|
-
</View>
|
|
757
|
-
<View style={styles.messageContentLeft}>
|
|
758
|
-
{props.currentMessage.user.name && (
|
|
759
|
-
<Text style={styles.userName}>
|
|
760
|
-
{props.currentMessage.user.name}
|
|
761
|
-
</Text>
|
|
762
|
-
)}
|
|
763
|
-
<Bubble
|
|
764
|
-
{...props}
|
|
765
|
-
wrapperStyle={{
|
|
766
|
-
left: {
|
|
767
|
-
backgroundColor: theme.colors.blue,
|
|
768
|
-
borderRadius: 16,
|
|
769
|
-
padding: 8,
|
|
770
|
-
marginBottom: 2,
|
|
771
|
-
marginLeft: 0,
|
|
772
|
-
maxWidth: "80%",
|
|
773
|
-
borderWidth: 1,
|
|
774
|
-
borderColor: "#E5E5E5",
|
|
775
|
-
},
|
|
776
|
-
}}
|
|
777
|
-
textStyle={{
|
|
778
|
-
left: { color: theme.colors.white, fontSize: 15 },
|
|
779
|
-
}}
|
|
780
|
-
timeTextStyle={{
|
|
781
|
-
left: { color: theme.colors.white, fontSize: 12, alignSelf: "flex-start" },
|
|
782
|
-
}}
|
|
783
|
-
onLongPress={() => {
|
|
784
|
-
Keyboard.dismiss();
|
|
785
|
-
openReportModal(props.currentMessage);
|
|
786
|
-
}}
|
|
787
|
-
renderMessageImage={(imageProps) => {
|
|
788
|
-
|
|
789
|
-
if (imageProps.currentMessage.pending) {
|
|
790
|
-
return (
|
|
791
|
-
<View style={styles.pendingImageContainer}>
|
|
792
|
-
<ActivityIndicator size="small" color={theme.colors.blue} />
|
|
793
|
-
<Text style={styles.pendingText}>
|
|
794
|
-
{imageProps.currentMessage.text}
|
|
795
|
-
</Text>
|
|
796
|
-
</View>
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
return (
|
|
801
|
-
<TouchableOpacity
|
|
802
|
-
onLongPress={() => {
|
|
803
|
-
Keyboard.dismiss();
|
|
804
|
-
openReportModal(imageProps.currentMessage);
|
|
805
|
-
}}
|
|
806
|
-
onPress={() => {
|
|
807
|
-
if (imageProps.currentMessage?.image) {
|
|
808
|
-
openImageViewer(imageProps.currentMessage.image);
|
|
809
|
-
}
|
|
810
|
-
}}>
|
|
811
|
-
<View style={styles.messageImageContainer}>
|
|
812
|
-
<Image
|
|
813
|
-
source={{ uri: imageProps.currentMessage.image }}
|
|
814
|
-
style={styles.messageImage}
|
|
815
|
-
resizeMode="cover"
|
|
816
|
-
onError={(error) =>
|
|
817
|
-
console.log("Image loading error:", error.nativeEvent.error)
|
|
818
|
-
}
|
|
819
|
-
/>
|
|
820
|
-
</View>
|
|
821
|
-
</TouchableOpacity>
|
|
822
|
-
);
|
|
823
|
-
}}
|
|
824
|
-
/>
|
|
825
|
-
</View>
|
|
826
|
-
</View>
|
|
827
|
-
)}
|
|
828
|
-
|
|
829
|
-
{/* Show messages for current user (right side) - NO AVATAR */}
|
|
830
|
-
{isCurrentUser && (
|
|
831
|
-
<View style={styles.rightMessageWrapper}>
|
|
832
|
-
<Bubble
|
|
833
|
-
{...props}
|
|
834
|
-
wrapperStyle={{
|
|
835
|
-
right: {
|
|
836
|
-
backgroundColor: theme.colors.lightLavenderGray,
|
|
837
|
-
borderRadius: 16,
|
|
838
|
-
padding: 8,
|
|
839
|
-
marginBottom: 2,
|
|
840
|
-
marginRight: moderateScale(15),
|
|
841
|
-
maxWidth: "70%",
|
|
842
|
-
},
|
|
843
|
-
}}
|
|
844
|
-
textStyle={{
|
|
845
|
-
right: {
|
|
846
|
-
color: theme.colors.black,
|
|
847
|
-
fontSize: 15,
|
|
848
|
-
},
|
|
849
|
-
}}
|
|
850
|
-
timeTextStyle={{
|
|
851
|
-
right: { color: theme.colors.black, fontSize: 12, alignSelf: "flex-end" },
|
|
852
|
-
}}
|
|
853
|
-
onLongPress={() => {
|
|
854
|
-
Keyboard.dismiss();
|
|
855
|
-
// alert('Long press detected');
|
|
856
|
-
deleteMessage(props.currentMessage._id);
|
|
857
|
-
}}
|
|
858
|
-
renderMessageImage={(imageProps) => {
|
|
859
|
-
if (imageProps.currentMessage.pending) {
|
|
860
|
-
return (
|
|
861
|
-
<View style={styles.pendingImageContainer}>
|
|
862
|
-
<ActivityIndicator size="small" color={theme.colors.blue} />
|
|
863
|
-
<Text style={styles.pendingText}>Uploading...</Text>
|
|
864
|
-
</View>
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
return (
|
|
869
|
-
<TouchableOpacity
|
|
870
|
-
onLongPress={() => {
|
|
871
|
-
Keyboard.dismiss();
|
|
872
|
-
// alert('Long press detected on image');
|
|
873
|
-
deleteMessage(imageProps.currentMessage._id);
|
|
874
|
-
}}
|
|
875
|
-
onPress={() => {
|
|
876
|
-
if (imageProps.currentMessage?.image) {
|
|
877
|
-
openImageViewer(imageProps.currentMessage.image);
|
|
878
|
-
}
|
|
879
|
-
}}>
|
|
880
|
-
<View style={styles.messageImageContainerRight}>
|
|
881
|
-
<Image
|
|
882
|
-
source={{ uri: imageProps.currentMessage.image }}
|
|
883
|
-
style={styles.messageImage}
|
|
884
|
-
resizeMode="cover"
|
|
885
|
-
onError={(error) =>
|
|
886
|
-
console.log("Image loading error:", error.nativeEvent.error)
|
|
887
|
-
}
|
|
888
|
-
/>
|
|
889
|
-
</View>
|
|
890
|
-
</TouchableOpacity>
|
|
891
|
-
);
|
|
892
|
-
}}
|
|
893
|
-
/>
|
|
894
|
-
</View>
|
|
895
|
-
)}
|
|
896
|
-
</View>
|
|
897
|
-
);
|
|
898
|
-
}, [memberId, openImageViewer, openReportModal, deleteMessage]);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
// Memoized input toolbar with optimized conditional rendering
|
|
902
|
-
const renderInputToolbar = useCallback((props: any) => {
|
|
903
|
-
// Compute margin bottom based on keyboard state
|
|
904
|
-
const inputMarginBottom = Platform.OS === 'android' ? isKeyboardOpen ? moderateScale(-5) : moderateScale(10) : isKeyboardOpen ? moderateScale(-20) : moderateScale(10);
|
|
905
|
-
|
|
906
|
-
return (
|
|
907
|
-
<View style={[styles.newInputToolbarWrapper, { marginBottom: inputMarginBottom }]}>
|
|
908
|
-
{/* Green Plus Icon */}
|
|
909
|
-
<TouchableOpacity
|
|
910
|
-
style={styles.addCommentButton}
|
|
911
|
-
onPress={handleGalleryPress}
|
|
912
|
-
activeOpacity={0.7}
|
|
913
|
-
>
|
|
914
|
-
<SVG.addCircle width={moderateScale(30)} height={moderateScale(30)} />
|
|
915
|
-
</TouchableOpacity>
|
|
916
|
-
|
|
917
|
-
{/* Input Field */}
|
|
918
|
-
<View style={styles.inputFieldContainer}>
|
|
919
|
-
{/* Image Preview - conditional rendering */}
|
|
920
|
-
{selectedImage ? (
|
|
921
|
-
<View style={styles.imagePreviewContainer}>
|
|
922
|
-
<Image
|
|
923
|
-
source={{ uri: selectedImage.uri }}
|
|
924
|
-
style={styles.imagePreview}
|
|
925
|
-
resizeMode="cover"
|
|
926
|
-
/>
|
|
927
|
-
<TouchableOpacity
|
|
928
|
-
style={styles.removeImageButton}
|
|
929
|
-
onPress={handleRemoveImage}
|
|
930
|
-
activeOpacity={0.7}
|
|
931
|
-
>
|
|
932
|
-
<Text style={styles.removeImageText}>✕</Text>
|
|
933
|
-
</TouchableOpacity>
|
|
934
|
-
</View>
|
|
935
|
-
) : null}
|
|
936
|
-
|
|
937
|
-
<InputToolbar
|
|
938
|
-
{...props}
|
|
939
|
-
containerStyle={styles.newInputToolbar}
|
|
940
|
-
renderComposer={(composerProps) => (
|
|
941
|
-
<Composer
|
|
942
|
-
{...composerProps}
|
|
943
|
-
textInputStyle={styles.newTextInput}
|
|
944
|
-
placeholder="Add Comment"
|
|
945
|
-
placeholderTextColor="#999"
|
|
946
|
-
/>
|
|
947
|
-
)}
|
|
948
|
-
renderActions={() => null}
|
|
949
|
-
/>
|
|
950
|
-
</View>
|
|
951
|
-
|
|
952
|
-
{/* Send Button */}
|
|
953
|
-
<TouchableOpacity
|
|
954
|
-
style={styles.newSendButton}
|
|
955
|
-
onPress={() => {
|
|
956
|
-
const messageText = props.text?.trim() || '';
|
|
957
|
-
const hasText = messageText.length > 0;
|
|
958
|
-
const hasImage = selectedImage !== null;
|
|
959
|
-
|
|
960
|
-
if (hasText || hasImage) {
|
|
961
|
-
const messageToSend = [{
|
|
962
|
-
_id: Math.random().toString(),
|
|
963
|
-
text: messageText,
|
|
964
|
-
createdAt: new Date(),
|
|
965
|
-
user: { _id: memberId }
|
|
966
|
-
}];
|
|
967
|
-
props.onSend(messageToSend, true);
|
|
968
|
-
}
|
|
969
|
-
}}
|
|
970
|
-
activeOpacity={0.7}
|
|
971
|
-
disabled={isUploadingImage}
|
|
972
|
-
>
|
|
973
|
-
{isUploadingImage ? (
|
|
974
|
-
<ActivityIndicator size="small" color={theme.colors.blue} />
|
|
975
|
-
) : (
|
|
976
|
-
<SVG.chatSend
|
|
977
|
-
width={moderateScale(30)}
|
|
978
|
-
height={moderateScale(30)}
|
|
979
|
-
/>
|
|
980
|
-
)}
|
|
981
|
-
</TouchableOpacity>
|
|
982
|
-
</View>
|
|
983
|
-
);
|
|
984
|
-
}, [isKeyboardOpen, selectedImage, isUploadingImage, memberId, handleGalleryPress, handleRemoveImage]);
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
return (
|
|
988
|
-
console.log("teamteam", team),
|
|
989
|
-
|
|
990
|
-
<SafeAreaView style={styles.container}>
|
|
991
|
-
<View style={styles.container}>
|
|
992
|
-
<StatusBar backgroundColor={theme.colors.blue} barStyle="light-content" />
|
|
993
|
-
{Platform.OS === "android" && <View style={styles.statusBarBackground} />}
|
|
994
|
-
|
|
995
|
-
<View style={styles.header}>
|
|
996
|
-
<View style={styles.headerTopRow}>
|
|
997
|
-
<TouchableOpacity onPress={() => {
|
|
998
|
-
refetchMessages();
|
|
999
|
-
navigation.goBack();
|
|
1000
|
-
}}>
|
|
1001
|
-
<SVG.arrowLeft_white
|
|
1002
|
-
width={moderateScale(25)}
|
|
1003
|
-
height={moderateScale(25)}
|
|
1004
|
-
/>
|
|
1005
|
-
</TouchableOpacity>
|
|
1006
|
-
<View style={{ flexDirection: "row", flex: 1 }}>
|
|
1007
|
-
<View style={styles.userConSty}>
|
|
1008
|
-
{!!team?.teamProfileImage || team?.clubImage ? (
|
|
1009
|
-
<Image
|
|
1010
|
-
source={{ uri: team?.teamProfileImage || team?.clubImage }}
|
|
1011
|
-
style={styles.userDetailsSty}
|
|
1012
|
-
/>
|
|
1013
|
-
) : (
|
|
1014
|
-
<View style={styles.placeholderLogoHeader}>
|
|
1015
|
-
<SVG.UsersIcon
|
|
1016
|
-
width={moderateScale(20)}
|
|
1017
|
-
height={moderateScale(20)}
|
|
1018
|
-
/>
|
|
1019
|
-
</View>
|
|
1020
|
-
)}
|
|
1021
|
-
</View>
|
|
1022
|
-
<View style={styles.clubInfoContainer}>
|
|
1023
|
-
<Text style={styles.userNameSty}>
|
|
1024
|
-
{team?.teamName || team?.clubName || "Unknown Member"}
|
|
1025
|
-
</Text>
|
|
1026
|
-
<Text style={styles.totalMembersSty}>
|
|
1027
|
-
{team?.totalMembers || 0}{" "}
|
|
1028
|
-
{(team?.totalMembers || 0) === 1 ? "member" : "members"}
|
|
1029
|
-
</Text>
|
|
1030
|
-
</View>
|
|
1031
|
-
</View>
|
|
1032
|
-
<TouchableOpacity
|
|
1033
|
-
style={{ alignSelf: 'center' }}
|
|
1034
|
-
onPress={() => setShowMoreModal(true)}
|
|
1035
|
-
activeOpacity={0.7}
|
|
1036
|
-
>
|
|
1037
|
-
<SVG.more width={moderateScale(18)} height={moderateScale(18)} />
|
|
1038
|
-
</TouchableOpacity>
|
|
1039
|
-
</View>
|
|
1040
|
-
|
|
1041
|
-
</View>
|
|
1042
|
-
<View style={styles.contentList}>
|
|
1043
|
-
<Text style={styles.trainingDayText} numberOfLines={1} ellipsizeMode="tail">
|
|
1044
|
-
{itemDetails?.threadName ? itemDetails?.threadName : `${itemDetails?.slotStartTime}-${itemDetails?.slotEndTime}`}
|
|
1045
|
-
</Text>
|
|
1046
|
-
<View style={styles.contentListWhite}>
|
|
1047
|
-
<GiftedChat
|
|
1048
|
-
messages={messages}
|
|
1049
|
-
onSend={messages => onSend(messages)}
|
|
1050
|
-
user={{
|
|
1051
|
-
_id: memberId,
|
|
1052
|
-
}}
|
|
1053
|
-
renderBubble={renderBubble}
|
|
1054
|
-
alwaysShowSend={true}
|
|
1055
|
-
renderInputToolbar={renderInputToolbar}
|
|
1056
|
-
renderSend={() => <></>}
|
|
1057
|
-
showAvatarForEveryMessage={true}
|
|
1058
|
-
inverted={true}
|
|
1059
|
-
renderUsernameOnMessage={false}
|
|
1060
|
-
maxInputLength={400}
|
|
1061
|
-
minComposerHeight={50}
|
|
1062
|
-
renderAvatar={() => <></>}
|
|
1063
|
-
loadEarlier={hasNextPage}
|
|
1064
|
-
onLoadEarlier={handleLoadEarlier}
|
|
1065
|
-
isLoadingEarlier={isFetchingNextPage}
|
|
1066
|
-
infiniteScroll={true}
|
|
1067
|
-
keyboardShouldPersistTaps="handled"
|
|
1068
|
-
/>
|
|
1069
|
-
</View>
|
|
1070
|
-
</View>
|
|
1071
|
-
|
|
1072
|
-
{/* Report Modal */}
|
|
1073
|
-
<Modal
|
|
1074
|
-
visible={showReportModal}
|
|
1075
|
-
transparent={true}
|
|
1076
|
-
animationType="fade"
|
|
1077
|
-
onRequestClose={closeReportModal}
|
|
1078
|
-
>
|
|
1079
|
-
<KeyboardAwareScrollView
|
|
1080
|
-
enableOnAndroid={true}
|
|
1081
|
-
enableAutomaticScroll={true}
|
|
1082
|
-
extraScrollHeight={Platform.OS === 'ios' ? 120 : 0}
|
|
1083
|
-
contentContainerStyle={styles.modalContainer}
|
|
1084
|
-
keyboardShouldPersistTaps="handled"
|
|
1085
|
-
>
|
|
1086
|
-
<TouchableOpacity
|
|
1087
|
-
style={styles.modalOverlay}
|
|
1088
|
-
activeOpacity={1}
|
|
1089
|
-
onPress={closeReportModal}
|
|
1090
|
-
>
|
|
1091
|
-
<View
|
|
1092
|
-
style={styles.modalContent}
|
|
1093
|
-
onStartShouldSetResponder={() => true}
|
|
1094
|
-
>
|
|
1095
|
-
<View style={styles.modalHeader}>
|
|
1096
|
-
<Text style={styles.modalTitle}>Report Message</Text>
|
|
1097
|
-
<TouchableOpacity onPress={closeReportModal}>
|
|
1098
|
-
<Text style={styles.closeButton}>✕</Text>
|
|
1099
|
-
</TouchableOpacity>
|
|
1100
|
-
</View>
|
|
1101
|
-
|
|
1102
|
-
<ScrollView
|
|
1103
|
-
style={styles.modalBody}
|
|
1104
|
-
showsVerticalScrollIndicator={false}
|
|
1105
|
-
keyboardShouldPersistTaps="handled"
|
|
1106
|
-
contentContainerStyle={styles.modalBodyContent}
|
|
1107
|
-
nestedScrollEnabled={true}
|
|
1108
|
-
>
|
|
1109
|
-
<Text style={styles.modalSubtitle}>
|
|
1110
|
-
Why are you reporting this message?
|
|
1111
|
-
</Text>
|
|
1112
|
-
|
|
1113
|
-
<TextInput
|
|
1114
|
-
style={[
|
|
1115
|
-
styles.reportInput,
|
|
1116
|
-
reportError ? styles.reportInputError : null
|
|
1117
|
-
]}
|
|
1118
|
-
placeholder="Please describe the issue..."
|
|
1119
|
-
placeholderTextColor="#999"
|
|
1120
|
-
multiline
|
|
1121
|
-
numberOfLines={6}
|
|
1122
|
-
maxLength={400}
|
|
1123
|
-
value={reportReason}
|
|
1124
|
-
onChangeText={(text) => {
|
|
1125
|
-
setReportReason(text);
|
|
1126
|
-
if (reportError) setReportError('');
|
|
1127
|
-
}}
|
|
1128
|
-
textAlignVertical="top"
|
|
1129
|
-
/>
|
|
1130
|
-
<View style={styles.inputBottomContainer}>
|
|
1131
|
-
{reportError ? (
|
|
1132
|
-
<Text style={styles.errorText}>{reportError}</Text>
|
|
1133
|
-
) : null}
|
|
1134
|
-
<Text style={[
|
|
1135
|
-
styles.characterCount,
|
|
1136
|
-
reportReason.length >= 400 ? styles.characterCountWarning : null
|
|
1137
|
-
]}>
|
|
1138
|
-
{reportReason.length}/400
|
|
1139
|
-
</Text>
|
|
1140
|
-
</View>
|
|
1141
|
-
</ScrollView>
|
|
1142
|
-
|
|
1143
|
-
<View style={styles.modalFooter}>
|
|
1144
|
-
<TouchableOpacity
|
|
1145
|
-
style={styles.cancelButton}
|
|
1146
|
-
onPress={closeReportModal}
|
|
1147
|
-
>
|
|
1148
|
-
<Text style={styles.cancelButtonText}>Cancel</Text>
|
|
1149
|
-
</TouchableOpacity>
|
|
1150
|
-
<TouchableOpacity
|
|
1151
|
-
style={styles.submitButton}
|
|
1152
|
-
onPress={handleReportSubmit}
|
|
1153
|
-
>
|
|
1154
|
-
<Text style={styles.submitButtonText}>Submit Report</Text>
|
|
1155
|
-
</TouchableOpacity>
|
|
1156
|
-
</View>
|
|
1157
|
-
</View>
|
|
1158
|
-
</TouchableOpacity>
|
|
1159
|
-
</KeyboardAwareScrollView>
|
|
1160
|
-
</Modal>
|
|
1161
|
-
|
|
1162
|
-
{/* Image Viewer Modal */}
|
|
1163
|
-
<Modal
|
|
1164
|
-
visible={isImageViewerVisible}
|
|
1165
|
-
transparent={true}
|
|
1166
|
-
animationType="fade"
|
|
1167
|
-
onRequestClose={closeImageViewer}
|
|
1168
|
-
>
|
|
1169
|
-
<GestureHandlerRootView style={styles.viewerContainer}>
|
|
1170
|
-
<TouchableOpacity style={styles.viewerBackdrop} activeOpacity={1} onPress={closeImageViewer} />
|
|
1171
|
-
<View style={styles.viewerContent}>
|
|
1172
|
-
{imageViewerUri ? (
|
|
1173
|
-
<ImageZoom
|
|
1174
|
-
uri={imageViewerUri}
|
|
1175
|
-
minScale={1}
|
|
1176
|
-
maxScale={5}
|
|
1177
|
-
doubleTapScale={3}
|
|
1178
|
-
isSingleTapEnabled={true}
|
|
1179
|
-
isDoubleTapEnabled={true}
|
|
1180
|
-
onSingleTap={closeImageViewer}
|
|
1181
|
-
style={styles.viewerImage}
|
|
1182
|
-
/>
|
|
1183
|
-
) : null}
|
|
1184
|
-
</View>
|
|
1185
|
-
<TouchableOpacity onPress={closeImageViewer} style={styles.viewerClose}>
|
|
1186
|
-
<Text style={styles.viewerCloseText}>✕</Text>
|
|
1187
|
-
</TouchableOpacity>
|
|
1188
|
-
</GestureHandlerRootView>
|
|
1189
|
-
</Modal>
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
<Modal
|
|
1193
|
-
visible={showMoreModal}
|
|
1194
|
-
transparent={true}
|
|
1195
|
-
animationType="fade"
|
|
1196
|
-
onRequestClose={() => setShowMoreModal(false)}
|
|
1197
|
-
>
|
|
1198
|
-
<View style={styles.bottomSheetOverlay}>
|
|
1199
|
-
<TouchableOpacity style={styles.bottomSheetOverlayTouchable} activeOpacity={1} onPress={() => setShowMoreModal(false)} />
|
|
1200
|
-
<View style={styles.bottomSheetContent}>
|
|
1201
|
-
<View style={styles.bottomSheetHandle} />
|
|
1202
|
-
<TouchableOpacity
|
|
1203
|
-
style={styles.bottomSheetOption}
|
|
1204
|
-
onPress={async () => {
|
|
1205
|
-
try {
|
|
1206
|
-
const response = await mainService.updateChatMuteStatus({
|
|
1207
|
-
chatTypeId: userType == "volunteer" ? 2 : 1,
|
|
1208
|
-
teamId: teamId,
|
|
1209
|
-
memberId: memberId,
|
|
1210
|
-
threadId: itemDetails?.threadId,
|
|
1211
|
-
isMute: !isMuted
|
|
1212
|
-
});
|
|
1213
|
-
setIsMuted(prev => !prev);
|
|
1214
|
-
ToastManager.success(
|
|
1215
|
-
!isMuted ? 'Muted successfully' : 'Unmuted successfully'
|
|
1216
|
-
);
|
|
1217
|
-
} catch (err) {
|
|
1218
|
-
console.error('Mute/Unmute error:', err);
|
|
1219
|
-
ToastManager.error('Failed to update mute status');
|
|
1220
|
-
} finally {
|
|
1221
|
-
setShowMoreModal(false);
|
|
1222
|
-
}
|
|
1223
|
-
}}
|
|
1224
|
-
>
|
|
1225
|
-
<Text style={styles.bottomSheetOptionText}>{isMuted ? 'Unmute' : 'Mute'}</Text>
|
|
1226
|
-
</TouchableOpacity>
|
|
1227
|
-
<TouchableOpacity
|
|
1228
|
-
style={[styles.bottomSheetOption, styles.bottomSheetCancel]}
|
|
1229
|
-
onPress={() => setShowMoreModal(false)}
|
|
1230
|
-
>
|
|
1231
|
-
<Text style={styles.bottomSheetCancelText}>Cancel</Text>
|
|
1232
|
-
</TouchableOpacity>
|
|
1233
|
-
</View>
|
|
1234
|
-
</View>
|
|
1235
|
-
</Modal>
|
|
1236
|
-
|
|
1237
|
-
</View>
|
|
1238
|
-
</SafeAreaView>
|
|
1239
|
-
);
|
|
1240
|
-
};
|
|
1241
|
-
|
|
1242
|
-
const styles = StyleSheet.create({
|
|
1243
|
-
container: {
|
|
1244
|
-
flex: 1,
|
|
1245
|
-
backgroundColor: theme.colors.blue,
|
|
1246
|
-
},
|
|
1247
|
-
modalContainer: {
|
|
1248
|
-
flex: 1,
|
|
1249
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
1250
|
-
},
|
|
1251
|
-
statusBarBackground: {
|
|
1252
|
-
position: "absolute",
|
|
1253
|
-
top: 0,
|
|
1254
|
-
left: 0,
|
|
1255
|
-
right: 0,
|
|
1256
|
-
height: Platform.OS === "ios" ? 44 : 0,
|
|
1257
|
-
backgroundColor: theme.colors.blue,
|
|
1258
|
-
zIndex: 1000,
|
|
1259
|
-
},
|
|
1260
|
-
header: {
|
|
1261
|
-
backgroundColor: theme.colors.blue,
|
|
1262
|
-
paddingTop: Platform.OS === "android" ? theme.spacing.lg : theme.spacing.xs,
|
|
1263
|
-
paddingBottom: Platform.OS === "android" ? -theme.spacing.md : moderateScale(0),
|
|
1264
|
-
},
|
|
1265
|
-
headerTopRow: {
|
|
1266
|
-
flexDirection: "row",
|
|
1267
|
-
paddingHorizontal: theme.spacing.lg,
|
|
1268
|
-
paddingBottom: theme.spacing.sm,
|
|
1269
|
-
},
|
|
1270
|
-
clubInfoContainer: {
|
|
1271
|
-
marginLeft: moderateScale(4),
|
|
1272
|
-
justifyContent: 'center',
|
|
1273
|
-
},
|
|
1274
|
-
userNameSty: {
|
|
1275
|
-
color: theme.colors.white,
|
|
1276
|
-
fontFamily: Fonts.outfitRegular,
|
|
1277
|
-
fontSize: moderateScale(16),
|
|
1278
|
-
},
|
|
1279
|
-
totalMembersSty: {
|
|
1280
|
-
fontFamily: Fonts.outfitRegular,
|
|
1281
|
-
fontSize: moderateScale(11),
|
|
1282
|
-
color: theme.colors.appleGreen,
|
|
1283
|
-
},
|
|
1284
|
-
userDetailsSty: {
|
|
1285
|
-
width: moderateScale(36),
|
|
1286
|
-
height: moderateScale(36),
|
|
1287
|
-
borderRadius: moderateScale(18),
|
|
1288
|
-
},
|
|
1289
|
-
userConSty: {
|
|
1290
|
-
marginLeft: moderateScale(12),
|
|
1291
|
-
marginRight: theme.spacing.xs,
|
|
1292
|
-
width: moderateScale(36),
|
|
1293
|
-
height: moderateScale(36),
|
|
1294
|
-
borderRadius: moderateScale(20),
|
|
1295
|
-
borderWidth: 1.5,
|
|
1296
|
-
borderColor: theme.colors.imageBorder,
|
|
1297
|
-
backgroundColor: theme.colors.imageBorder,
|
|
1298
|
-
alignItems: "center",
|
|
1299
|
-
justifyContent: "center",
|
|
1300
|
-
},
|
|
1301
|
-
placeholderLogoHeader: {
|
|
1302
|
-
width: moderateScale(20),
|
|
1303
|
-
height: moderateScale(20),
|
|
1304
|
-
borderRadius: moderateScale(10),
|
|
1305
|
-
alignItems: "center",
|
|
1306
|
-
justifyContent: "center",
|
|
1307
|
-
},
|
|
1308
|
-
trainingDayContainer: {
|
|
1309
|
-
alignItems: 'center',
|
|
1310
|
-
paddingHorizontal: moderateScale(16),
|
|
1311
|
-
},
|
|
1312
|
-
trainingDayText: {
|
|
1313
|
-
fontFamily: Fonts.outfitMedium,
|
|
1314
|
-
fontSize: moderateScale(16),
|
|
1315
|
-
color: theme.colors.blue,
|
|
1316
|
-
textAlign: 'center',
|
|
1317
|
-
marginTop: moderateScale(8),
|
|
1318
|
-
paddingHorizontal: moderateScale(16),
|
|
1319
|
-
},
|
|
1320
|
-
contentList: {
|
|
1321
|
-
flex: 1,
|
|
1322
|
-
backgroundColor: theme.colors.lightLavenderGray,
|
|
1323
|
-
borderTopLeftRadius: moderateScale(30),
|
|
1324
|
-
borderTopRightRadius: moderateScale(30),
|
|
1325
|
-
},
|
|
1326
|
-
contentListWhite: {
|
|
1327
|
-
flex: 1,
|
|
1328
|
-
backgroundColor: theme.colors.white,
|
|
1329
|
-
borderTopLeftRadius: moderateScale(30),
|
|
1330
|
-
borderTopRightRadius: moderateScale(30),
|
|
1331
|
-
marginTop: theme.spacing.sm,
|
|
1332
|
-
},
|
|
1333
|
-
inputToolbar: {
|
|
1334
|
-
borderTopWidth: 0,
|
|
1335
|
-
backgroundColor: 'transparent',
|
|
1336
|
-
paddingHorizontal: moderateScale(16),
|
|
1337
|
-
paddingVertical: moderateScale(8),
|
|
1338
|
-
},
|
|
1339
|
-
|
|
1340
|
-
textInput: {
|
|
1341
|
-
fontFamily: Fonts.outfitRegular,
|
|
1342
|
-
fontSize: moderateScale(14),
|
|
1343
|
-
color: theme.colors.black,
|
|
1344
|
-
flex: 1,
|
|
1345
|
-
paddingHorizontal: moderateScale(12),
|
|
1346
|
-
paddingVertical: moderateScale(8),
|
|
1347
|
-
minHeight: moderateScale(40),
|
|
1348
|
-
},
|
|
1349
|
-
sendButton: {
|
|
1350
|
-
width: moderateScale(36),
|
|
1351
|
-
height: moderateScale(36),
|
|
1352
|
-
borderRadius: moderateScale(18),
|
|
1353
|
-
backgroundColor: theme.colors.blue,
|
|
1354
|
-
alignItems: 'center',
|
|
1355
|
-
justifyContent: 'center',
|
|
1356
|
-
marginLeft: moderateScale(8),
|
|
1357
|
-
},
|
|
1358
|
-
scrollToBottomStyle: {
|
|
1359
|
-
alignItems: 'center',
|
|
1360
|
-
justifyContent: 'center',
|
|
1361
|
-
height: moderateScale(30),
|
|
1362
|
-
width: moderateScale(30),
|
|
1363
|
-
borderRadius: moderateScale(15),
|
|
1364
|
-
backgroundColor: theme.colors.blue,
|
|
1365
|
-
margin: moderateScale(10),
|
|
1366
|
-
},
|
|
1367
|
-
threadsContainer: {
|
|
1368
|
-
backgroundColor: theme.colors.lightLavenderGray,
|
|
1369
|
-
top: moderateScale(-25),
|
|
1370
|
-
paddingVertical: moderateScale(4),
|
|
1371
|
-
borderTopLeftRadius: moderateScale(30),
|
|
1372
|
-
borderTopRightRadius: moderateScale(30),
|
|
1373
|
-
paddingHorizontal: moderateScale(16),
|
|
1374
|
-
marginBottom: moderateScale(8)
|
|
1375
|
-
},
|
|
1376
|
-
messageContainer: {
|
|
1377
|
-
flexDirection: 'row',
|
|
1378
|
-
marginVertical: moderateScale(4),
|
|
1379
|
-
},
|
|
1380
|
-
leftMessageWrapper: {
|
|
1381
|
-
flexDirection: 'row',
|
|
1382
|
-
alignItems: 'flex-start',
|
|
1383
|
-
},
|
|
1384
|
-
rightMessageWrapper: {
|
|
1385
|
-
flexDirection: 'row',
|
|
1386
|
-
alignItems: 'flex-end',
|
|
1387
|
-
justifyContent: 'flex-end',
|
|
1388
|
-
flex: 1,
|
|
1389
|
-
},
|
|
1390
|
-
avatarContainer: {
|
|
1391
|
-
marginRight: moderateScale(8),
|
|
1392
|
-
},
|
|
1393
|
-
avatar: {
|
|
1394
|
-
width: moderateScale(36),
|
|
1395
|
-
height: moderateScale(36),
|
|
1396
|
-
borderRadius: moderateScale(18),
|
|
1397
|
-
},
|
|
1398
|
-
defaultAvatar: {
|
|
1399
|
-
width: moderateScale(36),
|
|
1400
|
-
height: moderateScale(36),
|
|
1401
|
-
borderRadius: moderateScale(18),
|
|
1402
|
-
backgroundColor: '#E0E0E0',
|
|
1403
|
-
alignItems: 'center',
|
|
1404
|
-
justifyContent: 'center',
|
|
1405
|
-
},
|
|
1406
|
-
avatarInitial: {
|
|
1407
|
-
fontSize: moderateScale(16),
|
|
1408
|
-
color: theme.colors.black,
|
|
1409
|
-
},
|
|
1410
|
-
messageContentLeft: {
|
|
1411
|
-
maxWidth: '90%',
|
|
1412
|
-
},
|
|
1413
|
-
userName: {
|
|
1414
|
-
fontFamily: Fonts.outfitMedium,
|
|
1415
|
-
fontSize: moderateScale(12),
|
|
1416
|
-
color: theme.colors.black,
|
|
1417
|
-
marginBottom: moderateScale(4),
|
|
1418
|
-
},
|
|
1419
|
-
pendingImageContainer: {
|
|
1420
|
-
width: 200,
|
|
1421
|
-
height: 200,
|
|
1422
|
-
borderRadius: 8,
|
|
1423
|
-
backgroundColor: "#f0f0f0",
|
|
1424
|
-
justifyContent: "center",
|
|
1425
|
-
alignItems: "center",
|
|
1426
|
-
},
|
|
1427
|
-
pendingText: {
|
|
1428
|
-
marginTop: 8,
|
|
1429
|
-
color: "#666",
|
|
1430
|
-
fontSize: 14,
|
|
1431
|
-
},
|
|
1432
|
-
messageImageContainer: {
|
|
1433
|
-
maxWidth: moderateScale(220),
|
|
1434
|
-
maxHeight: moderateScale(220),
|
|
1435
|
-
borderRadius: 8,
|
|
1436
|
-
overflow: 'hidden',
|
|
1437
|
-
},
|
|
1438
|
-
messageImageContainerRight: {
|
|
1439
|
-
alignSelf: 'flex-end',
|
|
1440
|
-
maxWidth: moderateScale(220),
|
|
1441
|
-
maxHeight: moderateScale(220),
|
|
1442
|
-
borderRadius: 8,
|
|
1443
|
-
overflow: 'hidden',
|
|
1444
|
-
},
|
|
1445
|
-
messageImage: {
|
|
1446
|
-
width: '100%',
|
|
1447
|
-
aspectRatio: 1,
|
|
1448
|
-
},
|
|
1449
|
-
|
|
1450
|
-
inputToolbarContainer: {
|
|
1451
|
-
backgroundColor: theme.colors.white,
|
|
1452
|
-
},
|
|
1453
|
-
inputToolbarWrapper: {
|
|
1454
|
-
backgroundColor: theme.colors.white,
|
|
1455
|
-
marginHorizontal: moderateScale(16),
|
|
1456
|
-
borderRadius: moderateScale(25),
|
|
1457
|
-
marginVertical: moderateScale(8),
|
|
1458
|
-
borderWidth: 1,
|
|
1459
|
-
borderColor: theme.colors.border,
|
|
1460
|
-
marginBottom: Platform.OS === "ios" ? moderateScale(20) : moderateScale(2),
|
|
1461
|
-
},
|
|
1462
|
-
attachButton: {
|
|
1463
|
-
marginLeft: moderateScale(1),
|
|
1464
|
-
},
|
|
1465
|
-
newInputToolbarWrapper: {
|
|
1466
|
-
flexDirection: 'row',
|
|
1467
|
-
alignItems: 'center',
|
|
1468
|
-
backgroundColor: theme.colors.white,
|
|
1469
|
-
marginHorizontal: moderateScale(16),
|
|
1470
|
-
borderRadius: moderateScale(30),
|
|
1471
|
-
marginVertical: moderateScale(8),
|
|
1472
|
-
borderWidth: 1,
|
|
1473
|
-
borderColor: theme.colors.border,
|
|
1474
|
-
// marginBottom will be set dynamically based on platform and navigation bar state
|
|
1475
|
-
},
|
|
1476
|
-
addCommentButton: {
|
|
1477
|
-
marginLeft: moderateScale(8),
|
|
1478
|
-
},
|
|
1479
|
-
plusIcon: {
|
|
1480
|
-
width: moderateScale(24),
|
|
1481
|
-
height: moderateScale(24),
|
|
1482
|
-
borderRadius: moderateScale(12),
|
|
1483
|
-
backgroundColor: theme.colors.green,
|
|
1484
|
-
alignItems: 'center',
|
|
1485
|
-
justifyContent: 'center',
|
|
1486
|
-
},
|
|
1487
|
-
plusText: {
|
|
1488
|
-
color: theme.colors.white,
|
|
1489
|
-
fontSize: moderateScale(16),
|
|
1490
|
-
fontWeight: 'bold',
|
|
1491
|
-
},
|
|
1492
|
-
inputFieldContainer: {
|
|
1493
|
-
flex: 1,
|
|
1494
|
-
},
|
|
1495
|
-
imagePreviewContainer: {
|
|
1496
|
-
marginHorizontal: moderateScale(12),
|
|
1497
|
-
marginTop: moderateScale(8),
|
|
1498
|
-
marginBottom: moderateScale(4),
|
|
1499
|
-
position: 'relative',
|
|
1500
|
-
},
|
|
1501
|
-
imagePreview: {
|
|
1502
|
-
width: '100%',
|
|
1503
|
-
height: moderateScale(150),
|
|
1504
|
-
borderRadius: moderateScale(8),
|
|
1505
|
-
},
|
|
1506
|
-
removeImageButton: {
|
|
1507
|
-
position: 'absolute',
|
|
1508
|
-
top: moderateScale(8),
|
|
1509
|
-
right: moderateScale(8),
|
|
1510
|
-
width: moderateScale(28),
|
|
1511
|
-
height: moderateScale(28),
|
|
1512
|
-
borderRadius: moderateScale(14),
|
|
1513
|
-
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
1514
|
-
alignItems: 'center',
|
|
1515
|
-
justifyContent: 'center',
|
|
1516
|
-
},
|
|
1517
|
-
removeImageText: {
|
|
1518
|
-
color: theme.colors.white,
|
|
1519
|
-
fontSize: moderateScale(18),
|
|
1520
|
-
fontWeight: 'bold',
|
|
1521
|
-
},
|
|
1522
|
-
newInputToolbar: {
|
|
1523
|
-
borderTopWidth: 0,
|
|
1524
|
-
backgroundColor: 'transparent',
|
|
1525
|
-
},
|
|
1526
|
-
newTextInput: {
|
|
1527
|
-
fontFamily: Fonts.outfitRegular,
|
|
1528
|
-
fontSize: moderateScale(14),
|
|
1529
|
-
color: theme.colors.black,
|
|
1530
|
-
flex: 1,
|
|
1531
|
-
paddingVertical: moderateScale(10),
|
|
1532
|
-
minHeight: moderateScale(40)
|
|
1533
|
-
},
|
|
1534
|
-
newSendButton: {
|
|
1535
|
-
width: moderateScale(46),
|
|
1536
|
-
height: moderateScale(46),
|
|
1537
|
-
borderRadius: moderateScale(23),
|
|
1538
|
-
backgroundColor: theme.colors.lightLavenderGray,
|
|
1539
|
-
alignItems: 'center',
|
|
1540
|
-
justifyContent: 'center',
|
|
1541
|
-
marginRight: moderateScale(8),
|
|
1542
|
-
},
|
|
1543
|
-
// Empty state styles
|
|
1544
|
-
emptyStateContainer: {
|
|
1545
|
-
flex: 1,
|
|
1546
|
-
justifyContent: 'center',
|
|
1547
|
-
alignItems: 'center',
|
|
1548
|
-
paddingHorizontal: moderateScale(32),
|
|
1549
|
-
paddingBottom: moderateScale(100), // Leave space for input toolbar
|
|
1550
|
-
},
|
|
1551
|
-
emptyStateContent: {
|
|
1552
|
-
alignItems: 'center',
|
|
1553
|
-
maxWidth: moderateScale(280),
|
|
1554
|
-
},
|
|
1555
|
-
emptyStateTitle: {
|
|
1556
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1557
|
-
fontSize: moderateScale(18),
|
|
1558
|
-
color: theme.colors.black,
|
|
1559
|
-
marginTop: moderateScale(16),
|
|
1560
|
-
textAlign: 'center',
|
|
1561
|
-
},
|
|
1562
|
-
emptyStateSubtitle: {
|
|
1563
|
-
fontFamily: Fonts.outfitRegular,
|
|
1564
|
-
fontSize: moderateScale(14),
|
|
1565
|
-
color: theme.colors.textSecondary,
|
|
1566
|
-
marginTop: moderateScale(8),
|
|
1567
|
-
textAlign: 'center',
|
|
1568
|
-
lineHeight: moderateScale(20),
|
|
1569
|
-
},
|
|
1570
|
-
errorIconContainer: {
|
|
1571
|
-
marginBottom: moderateScale(8),
|
|
1572
|
-
},
|
|
1573
|
-
emptyIconContainer: {
|
|
1574
|
-
marginBottom: moderateScale(8),
|
|
1575
|
-
},
|
|
1576
|
-
retryButton: {
|
|
1577
|
-
marginTop: moderateScale(20),
|
|
1578
|
-
paddingHorizontal: moderateScale(24),
|
|
1579
|
-
paddingVertical: moderateScale(12),
|
|
1580
|
-
backgroundColor: theme.colors.blue,
|
|
1581
|
-
borderRadius: moderateScale(8),
|
|
1582
|
-
},
|
|
1583
|
-
retryButtonText: {
|
|
1584
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1585
|
-
fontSize: moderateScale(16),
|
|
1586
|
-
color: theme.colors.white,
|
|
1587
|
-
},
|
|
1588
|
-
emptyStateInputContainer: {
|
|
1589
|
-
position: 'absolute',
|
|
1590
|
-
bottom: 0,
|
|
1591
|
-
left: 0,
|
|
1592
|
-
right: 0,
|
|
1593
|
-
backgroundColor: theme.colors.white,
|
|
1594
|
-
},
|
|
1595
|
-
|
|
1596
|
-
modalOverlay: {
|
|
1597
|
-
flex: 1,
|
|
1598
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
1599
|
-
justifyContent: 'center',
|
|
1600
|
-
alignItems: 'center',
|
|
1601
|
-
paddingHorizontal: moderateScale(20),
|
|
1602
|
-
paddingVertical: moderateScale(20),
|
|
1603
|
-
},
|
|
1604
|
-
modalContent: {
|
|
1605
|
-
backgroundColor: theme.colors.white,
|
|
1606
|
-
borderRadius: moderateScale(16),
|
|
1607
|
-
width: '100%',
|
|
1608
|
-
// maxHeight: Platform.OS === 'ios' ? '85%' : '80%',
|
|
1609
|
-
overflow: 'hidden',
|
|
1610
|
-
borderWidth: 1,
|
|
1611
|
-
},
|
|
1612
|
-
modalHeader: {
|
|
1613
|
-
flexDirection: 'row',
|
|
1614
|
-
justifyContent: 'space-between',
|
|
1615
|
-
alignItems: 'center',
|
|
1616
|
-
padding: moderateScale(16),
|
|
1617
|
-
borderBottomWidth: 1,
|
|
1618
|
-
borderBottomColor: theme.colors.border,
|
|
1619
|
-
},
|
|
1620
|
-
modalTitle: {
|
|
1621
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1622
|
-
fontSize: moderateScale(18),
|
|
1623
|
-
color: theme.colors.black,
|
|
1624
|
-
},
|
|
1625
|
-
closeButton: {
|
|
1626
|
-
fontSize: moderateScale(24),
|
|
1627
|
-
color: theme.colors.textSecondary,
|
|
1628
|
-
fontWeight: '300',
|
|
1629
|
-
},
|
|
1630
|
-
modalBody: {
|
|
1631
|
-
flexGrow: 1,
|
|
1632
|
-
maxHeight: moderateScale(250),
|
|
1633
|
-
},
|
|
1634
|
-
modalBodyContent: {
|
|
1635
|
-
padding: moderateScale(16),
|
|
1636
|
-
},
|
|
1637
|
-
modalSubtitle: {
|
|
1638
|
-
fontFamily: Fonts.outfitRegular,
|
|
1639
|
-
fontSize: moderateScale(14),
|
|
1640
|
-
color: theme.colors.textSecondary,
|
|
1641
|
-
marginBottom: moderateScale(12),
|
|
1642
|
-
},
|
|
1643
|
-
reportInput: {
|
|
1644
|
-
fontFamily: Fonts.outfitRegular,
|
|
1645
|
-
fontSize: moderateScale(14),
|
|
1646
|
-
color: theme.colors.black,
|
|
1647
|
-
backgroundColor: theme.colors.lightLavenderGray,
|
|
1648
|
-
borderRadius: moderateScale(8),
|
|
1649
|
-
padding: moderateScale(12),
|
|
1650
|
-
minHeight: moderateScale(120),
|
|
1651
|
-
borderWidth: 1,
|
|
1652
|
-
borderColor: theme.colors.border,
|
|
1653
|
-
},
|
|
1654
|
-
reportInputError: {
|
|
1655
|
-
borderColor: '#FF4444',
|
|
1656
|
-
borderWidth: 1,
|
|
1657
|
-
},
|
|
1658
|
-
errorText: {
|
|
1659
|
-
fontFamily: Fonts.outfitRegular,
|
|
1660
|
-
fontSize: moderateScale(12),
|
|
1661
|
-
color: '#FF4444',
|
|
1662
|
-
marginTop: moderateScale(8),
|
|
1663
|
-
marginLeft: moderateScale(4),
|
|
1664
|
-
},
|
|
1665
|
-
inputBottomContainer: {
|
|
1666
|
-
justifyContent: 'space-between',
|
|
1667
|
-
alignItems: 'flex-start',
|
|
1668
|
-
},
|
|
1669
|
-
characterCount: {
|
|
1670
|
-
fontFamily: Fonts.outfitRegular,
|
|
1671
|
-
fontSize: moderateScale(12),
|
|
1672
|
-
color: theme.colors.textSecondary,
|
|
1673
|
-
alignSelf: 'flex-end',
|
|
1674
|
-
marginTop: moderateScale(8),
|
|
1675
|
-
},
|
|
1676
|
-
characterCountWarning: {
|
|
1677
|
-
color: '#FF4444',
|
|
1678
|
-
},
|
|
1679
|
-
modalFooter: {
|
|
1680
|
-
flexDirection: 'row',
|
|
1681
|
-
borderTopWidth: 1,
|
|
1682
|
-
borderTopColor: theme.colors.border,
|
|
1683
|
-
padding: moderateScale(16),
|
|
1684
|
-
},
|
|
1685
|
-
cancelButton: {
|
|
1686
|
-
flex: 1,
|
|
1687
|
-
paddingVertical: moderateScale(12),
|
|
1688
|
-
alignItems: 'center',
|
|
1689
|
-
justifyContent: 'center',
|
|
1690
|
-
marginRight: moderateScale(8),
|
|
1691
|
-
borderRadius: moderateScale(8),
|
|
1692
|
-
borderWidth: 1,
|
|
1693
|
-
borderColor: theme.colors.border,
|
|
1694
|
-
},
|
|
1695
|
-
cancelButtonText: {
|
|
1696
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1697
|
-
fontSize: moderateScale(16),
|
|
1698
|
-
color: theme.colors.black,
|
|
1699
|
-
},
|
|
1700
|
-
submitButton: {
|
|
1701
|
-
flex: 1,
|
|
1702
|
-
paddingVertical: moderateScale(12),
|
|
1703
|
-
alignItems: 'center',
|
|
1704
|
-
justifyContent: 'center',
|
|
1705
|
-
backgroundColor: theme.colors.blue,
|
|
1706
|
-
borderRadius: moderateScale(8),
|
|
1707
|
-
marginLeft: moderateScale(8),
|
|
1708
|
-
},
|
|
1709
|
-
submitButtonText: {
|
|
1710
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1711
|
-
fontSize: moderateScale(16),
|
|
1712
|
-
color: theme.colors.white,
|
|
1713
|
-
},
|
|
1714
|
-
// Image viewer styles
|
|
1715
|
-
viewerContainer: {
|
|
1716
|
-
flex: 1,
|
|
1717
|
-
backgroundColor: 'rgba(0,0,0,0.9)'
|
|
1718
|
-
},
|
|
1719
|
-
viewerBackdrop: {
|
|
1720
|
-
...StyleSheet.absoluteFillObject as any,
|
|
1721
|
-
},
|
|
1722
|
-
viewerContent: {
|
|
1723
|
-
flex: 1,
|
|
1724
|
-
justifyContent: 'center',
|
|
1725
|
-
alignItems: 'center',
|
|
1726
|
-
},
|
|
1727
|
-
viewerImage: {
|
|
1728
|
-
flex: 1,
|
|
1729
|
-
width: '100%',
|
|
1730
|
-
height: '100%',
|
|
1731
|
-
},
|
|
1732
|
-
viewerControls: {
|
|
1733
|
-
position: 'absolute',
|
|
1734
|
-
bottom: moderateScale(40),
|
|
1735
|
-
flexDirection: 'row',
|
|
1736
|
-
gap: moderateScale(12),
|
|
1737
|
-
backgroundColor: 'rgba(0,0,0,0.3)',
|
|
1738
|
-
paddingHorizontal: moderateScale(12),
|
|
1739
|
-
paddingVertical: moderateScale(8),
|
|
1740
|
-
borderRadius: moderateScale(20),
|
|
1741
|
-
},
|
|
1742
|
-
viewerButton: {
|
|
1743
|
-
paddingHorizontal: moderateScale(12),
|
|
1744
|
-
paddingVertical: moderateScale(6),
|
|
1745
|
-
backgroundColor: 'rgba(255,255,255,0.15)',
|
|
1746
|
-
borderRadius: moderateScale(16),
|
|
1747
|
-
alignItems: 'center',
|
|
1748
|
-
justifyContent: 'center',
|
|
1749
|
-
minWidth: moderateScale(48),
|
|
1750
|
-
},
|
|
1751
|
-
viewerButtonText: {
|
|
1752
|
-
color: '#fff',
|
|
1753
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1754
|
-
fontSize: moderateScale(14),
|
|
1755
|
-
},
|
|
1756
|
-
viewerClose: {
|
|
1757
|
-
position: 'absolute',
|
|
1758
|
-
top: moderateScale(50),
|
|
1759
|
-
right: moderateScale(20),
|
|
1760
|
-
backgroundColor: 'rgba(255,255,255,0.15)',
|
|
1761
|
-
width: moderateScale(36),
|
|
1762
|
-
height: moderateScale(36),
|
|
1763
|
-
borderRadius: moderateScale(18),
|
|
1764
|
-
alignItems: 'center',
|
|
1765
|
-
justifyContent: 'center',
|
|
1766
|
-
},
|
|
1767
|
-
viewerCloseText: {
|
|
1768
|
-
color: '#fff',
|
|
1769
|
-
fontSize: moderateScale(18),
|
|
1770
|
-
},
|
|
1771
|
-
bottomSheetOverlay: {
|
|
1772
|
-
flex: 1,
|
|
1773
|
-
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
1774
|
-
justifyContent: 'flex-end',
|
|
1775
|
-
},
|
|
1776
|
-
bottomSheetOverlayTouchable: {
|
|
1777
|
-
flex: 1,
|
|
1778
|
-
},
|
|
1779
|
-
bottomSheetContent: {
|
|
1780
|
-
backgroundColor: theme.colors.white,
|
|
1781
|
-
borderTopLeftRadius: moderateScale(16),
|
|
1782
|
-
borderTopRightRadius: moderateScale(16),
|
|
1783
|
-
paddingBottom: Platform.OS === 'ios' ? moderateScale(24) : moderateScale(16),
|
|
1784
|
-
paddingTop: moderateScale(8),
|
|
1785
|
-
paddingHorizontal: moderateScale(16),
|
|
1786
|
-
borderTopWidth: 1,
|
|
1787
|
-
borderColor: theme.colors.border,
|
|
1788
|
-
},
|
|
1789
|
-
bottomSheetHandle: {
|
|
1790
|
-
alignSelf: 'center',
|
|
1791
|
-
width: moderateScale(40),
|
|
1792
|
-
height: moderateScale(4),
|
|
1793
|
-
borderRadius: moderateScale(2),
|
|
1794
|
-
backgroundColor: '#D9D9D9',
|
|
1795
|
-
marginBottom: moderateScale(8),
|
|
1796
|
-
},
|
|
1797
|
-
bottomSheetOption: {
|
|
1798
|
-
paddingVertical: moderateScale(14),
|
|
1799
|
-
alignItems: 'center',
|
|
1800
|
-
justifyContent: 'center',
|
|
1801
|
-
borderBottomWidth: 1,
|
|
1802
|
-
borderBottomColor: theme.colors.border,
|
|
1803
|
-
},
|
|
1804
|
-
bottomSheetOptionText: {
|
|
1805
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1806
|
-
fontSize: moderateScale(16),
|
|
1807
|
-
color: theme.colors.black,
|
|
1808
|
-
},
|
|
1809
|
-
bottomSheetCancel: {
|
|
1810
|
-
borderBottomWidth: 0,
|
|
1811
|
-
marginTop: moderateScale(4),
|
|
1812
|
-
marginBottom: Platform.OS === 'android' ? moderateScale(15) : moderateScale(0),
|
|
1813
|
-
},
|
|
1814
|
-
bottomSheetCancelText: {
|
|
1815
|
-
fontFamily: Fonts.outfitSemiBold,
|
|
1816
|
-
fontSize: moderateScale(16),
|
|
1817
|
-
color: theme.colors.textSecondary,
|
|
1818
|
-
},
|
|
1819
|
-
});
|