create-gufran-expo-app 2.0.0 → 2.0.2

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.
Files changed (266) hide show
  1. package/lib/createApp.js +7 -2
  2. package/package.json +1 -1
  3. package/template/App.tsx +118 -0
  4. package/template/ReactotronConfig.js +5 -0
  5. package/template/android/app/build.gradle +184 -0
  6. package/template/android/app/debug.keystore +0 -0
  7. package/template/android/app/google-services.json +29 -0
  8. package/template/android/app/proguard-rules.pro +14 -0
  9. package/template/android/app/src/debug/AndroidManifest.xml +7 -0
  10. package/template/android/app/src/debugOptimized/AndroidManifest.xml +7 -0
  11. package/template/android/app/src/main/AndroidManifest.xml +28 -0
  12. package/template/android/app/src/main/assets/fonts/Outfit-Bold.ttf +0 -0
  13. package/template/android/app/src/main/assets/fonts/Outfit-Light.ttf +0 -0
  14. package/template/android/app/src/main/assets/fonts/Outfit-Medium.ttf +0 -0
  15. package/template/android/app/src/main/assets/fonts/Outfit-Regular.ttf +0 -0
  16. package/template/android/app/src/main/assets/fonts/Outfit-SemiBold.ttf +0 -0
  17. package/template/android/app/src/main/java/com/club/yakka/MainActivity.kt +61 -0
  18. package/template/android/app/src/main/java/com/club/yakka/MainApplication.kt +60 -0
  19. package/template/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
  20. package/template/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  21. package/template/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
  22. package/template/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
  23. package/template/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
  24. package/template/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
  25. package/template/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
  26. package/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  27. package/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  28. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  29. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
  30. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  31. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  32. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
  33. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  34. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  35. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
  36. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  37. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  38. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
  39. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  40. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  41. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
  42. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  43. package/template/android/app/src/main/res/values/colors.xml +6 -0
  44. package/template/android/app/src/main/res/values/strings.xml +5 -0
  45. package/template/android/app/src/main/res/values/styles.xml +14 -0
  46. package/template/android/app/src/main/res/values-night/colors.xml +1 -0
  47. package/template/android/build.gradle +33 -0
  48. package/template/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  49. package/template/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  50. package/template/android/gradle.properties +65 -0
  51. package/template/android/gradlew +251 -0
  52. package/template/android/gradlew.bat +94 -0
  53. package/template/android/settings.gradle +39 -0
  54. package/template/app.json +69 -0
  55. package/template/assets/adaptive-icon.png +0 -0
  56. package/template/assets/adaptive-icon1.png +0 -0
  57. package/template/assets/app_icon.png +0 -0
  58. package/template/assets/favicon.png +0 -0
  59. package/template/assets/icon.png +0 -0
  60. package/template/assets/splash-icon.png +0 -0
  61. package/template/babel-plugin-disable-font-scaling.js +41 -0
  62. package/template/babel.config.js +28 -0
  63. package/template/firebase.json +5 -0
  64. package/template/index.ts +24 -0
  65. package/template/ios/.xcode.env +11 -0
  66. package/template/ios/ClubYakka/AppDelegate.swift +74 -0
  67. package/template/ios/ClubYakka/ClubYakka-Bridging-Header.h +3 -0
  68. package/template/ios/ClubYakka/ClubYakka.entitlements +8 -0
  69. package/template/ios/ClubYakka/GoogleService-Info.plist +30 -0
  70. package/template/ios/ClubYakka/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
  71. package/template/ios/ClubYakka/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
  72. package/template/ios/ClubYakka/Images.xcassets/Contents.json +6 -0
  73. package/template/ios/ClubYakka/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
  74. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +23 -0
  75. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image.png +0 -0
  76. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png +0 -0
  77. package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png +0 -0
  78. package/template/ios/ClubYakka/Info.plist +101 -0
  79. package/template/ios/ClubYakka/PrivacyInfo.xcprivacy +50 -0
  80. package/template/ios/ClubYakka/SplashScreen.storyboard +48 -0
  81. package/template/ios/ClubYakka/Supporting/Expo.plist +12 -0
  82. package/template/ios/ClubYakka.xcodeproj/project.pbxproj +669 -0
  83. package/template/ios/ClubYakka.xcodeproj/xcshareddata/xcschemes/ClubYakka.xcscheme +88 -0
  84. package/template/ios/ClubYakka.xcworkspace/contents.xcworkspacedata +10 -0
  85. package/template/ios/ClubYakka.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +5 -0
  86. package/template/ios/Podfile +84 -0
  87. package/template/ios/Podfile.lock +2698 -0
  88. package/template/ios/Podfile.properties.json +8 -0
  89. package/template/metro.config.js +17 -0
  90. package/template/package.json +70 -0
  91. package/template/patches/react-native-background-upload+6.6.0.patch +704 -0
  92. package/template/react-native.config.js +7 -0
  93. package/template/src/assets/fonts/Outfit-Bold.ttf +0 -0
  94. package/template/src/assets/fonts/Outfit-Light.ttf +0 -0
  95. package/template/src/assets/fonts/Outfit-Medium.ttf +0 -0
  96. package/template/src/assets/fonts/Outfit-Regular.ttf +0 -0
  97. package/template/src/assets/fonts/Outfit-SemiBold.ttf +0 -0
  98. package/template/src/assets/icons/BG.svg +33 -0
  99. package/template/src/assets/icons/Ic_Users.svg +6 -0
  100. package/template/src/assets/icons/arrow-left-white.svg +4 -0
  101. package/template/src/assets/icons/arrow-left.svg +4 -0
  102. package/template/src/assets/icons/bell.svg +4 -0
  103. package/template/src/assets/icons/bottomSheetIcone.svg +3 -0
  104. package/template/src/assets/icons/call.svg +4 -0
  105. package/template/src/assets/icons/camera.svg +0 -0
  106. package/template/src/assets/icons/chatAppleGreenBG.svg +22 -0
  107. package/template/src/assets/icons/check.svg +4 -0
  108. package/template/src/assets/icons/check_Radio.svg +4 -0
  109. package/template/src/assets/icons/chevron-right.svg +3 -0
  110. package/template/src/assets/icons/clubDefauldImage.png +0 -0
  111. package/template/src/assets/icons/curvBackgroundView.svg +3 -0
  112. package/template/src/assets/icons/defaultClub.png +0 -0
  113. package/template/src/assets/icons/email.svg +3 -0
  114. package/template/src/assets/icons/emptyUser.svg +5 -0
  115. package/template/src/assets/icons/eye-Hide.svg +8 -0
  116. package/template/src/assets/icons/eye.svg +4 -0
  117. package/template/src/assets/icons/gallery.svg +4 -0
  118. package/template/src/assets/icons/home.svg +4 -0
  119. package/template/src/assets/icons/ic_Calendar.svg +14 -0
  120. package/template/src/assets/icons/ic_Calendar_blue.svg +12 -0
  121. package/template/src/assets/icons/ic_Calendar_white.svg +12 -0
  122. package/template/src/assets/icons/ic_Chat.svg +14 -0
  123. package/template/src/assets/icons/ic_ChatAppleGreen.svg +21 -0
  124. package/template/src/assets/icons/ic_ChatAppleWhite.svg +21 -0
  125. package/template/src/assets/icons/ic_Download.svg +6 -0
  126. package/template/src/assets/icons/ic_Events.svg +6 -0
  127. package/template/src/assets/icons/ic_HeadCoachIcon.svg +3 -0
  128. package/template/src/assets/icons/ic_Membership.svg +10 -0
  129. package/template/src/assets/icons/ic_Notification.svg +6 -0
  130. package/template/src/assets/icons/ic_Raffles.svg +14 -0
  131. package/template/src/assets/icons/ic_Referral_Members.svg +12 -0
  132. package/template/src/assets/icons/ic_Shop.svg +13 -0
  133. package/template/src/assets/icons/ic_Teams.svg +8 -0
  134. package/template/src/assets/icons/ic_Volunteer.svg +9 -0
  135. package/template/src/assets/icons/ic_add.svg +3 -0
  136. package/template/src/assets/icons/ic_addCircle.svg +5 -0
  137. package/template/src/assets/icons/ic_chatSend.svg +4 -0
  138. package/template/src/assets/icons/ic_chat_blue_bg.svg +22 -0
  139. package/template/src/assets/icons/ic_clock_blue.svg +4 -0
  140. package/template/src/assets/icons/ic_delete.svg +8 -0
  141. package/template/src/assets/icons/ic_more.svg +5 -0
  142. package/template/src/assets/icons/ic_mute.svg +10 -0
  143. package/template/src/assets/icons/ic_pdf.svg +3 -0
  144. package/template/src/assets/icons/ic_pending_AppleGreen.svg +11 -0
  145. package/template/src/assets/icons/ic_right_appleGreen.svg +11 -0
  146. package/template/src/assets/icons/ic_unread_chat_blue_bg.svg +23 -0
  147. package/template/src/assets/icons/ic_volunteer_Member.svg +8 -0
  148. package/template/src/assets/icons/index.ts +144 -0
  149. package/template/src/assets/icons/location-blue.svg +4 -0
  150. package/template/src/assets/icons/location-white.svg +4 -0
  151. package/template/src/assets/icons/location.svg +4 -0
  152. package/template/src/assets/icons/lock.svg +5 -0
  153. package/template/src/assets/icons/log-out.svg +5 -0
  154. package/template/src/assets/icons/login_logo.svg +9 -0
  155. package/template/src/assets/icons/mail.svg +4 -0
  156. package/template/src/assets/icons/or_saprater.svg +5 -0
  157. package/template/src/assets/icons/password.svg +4 -0
  158. package/template/src/assets/icons/profile-appleGreen.svg +6 -0
  159. package/template/src/assets/icons/search.svg +3 -0
  160. package/template/src/assets/icons/settings.svg +4 -0
  161. package/template/src/assets/icons/success.svg +4 -0
  162. package/template/src/assets/icons/unCheck_Radio.svg +3 -0
  163. package/template/src/assets/icons/uncheck.svg +3 -0
  164. package/template/src/assets/icons/upload_Image.svg +6 -0
  165. package/template/src/assets/icons/upload_Image_Member.svg +6 -0
  166. package/template/src/assets/icons/user.svg +4 -0
  167. package/template/src/assets/icons/wifi.svg +1 -0
  168. package/template/src/assets/images/Splash.png +0 -0
  169. package/template/src/assets/images/SplashLogo.png +0 -0
  170. package/template/src/assets/images/background.png +0 -0
  171. package/template/src/assets/images/clubDefauldImage.png +0 -0
  172. package/template/src/assets/images/index.tsx +9 -0
  173. package/template/src/assets/index.ts +1 -0
  174. package/template/src/components/common/AddMemberModal.tsx +543 -0
  175. package/template/src/components/common/AppLoader.tsx +91 -0
  176. package/template/src/components/common/Button.tsx +173 -0
  177. package/template/src/components/common/ClubCard.tsx +248 -0
  178. package/template/src/components/common/Icon.tsx +93 -0
  179. package/template/src/components/common/IconAlt.tsx +65 -0
  180. package/template/src/components/common/IconButton.tsx +92 -0
  181. package/template/src/components/common/ImagePicker.tsx +451 -0
  182. package/template/src/components/common/LoadingSpinner.tsx +30 -0
  183. package/template/src/components/common/OTPInput.tsx +128 -0
  184. package/template/src/components/common/ReminderCalendar.tsx +129 -0
  185. package/template/src/components/common/ReminderCardItem.tsx +91 -0
  186. package/template/src/components/common/SafeAreaWrapper.tsx +27 -0
  187. package/template/src/components/common/SetReminderModal.tsx +308 -0
  188. package/template/src/components/common/SlowInternet.js +57 -0
  189. package/template/src/components/common/TeamCard.tsx +297 -0
  190. package/template/src/components/common/TextInput.tsx +227 -0
  191. package/template/src/components/common/ToastConfig.tsx +103 -0
  192. package/template/src/components/common/ToastManager.ts +86 -0
  193. package/template/src/components/common/UploadProgressModal.tsx +284 -0
  194. package/template/src/components/common/WrapperContainer.tsx +39 -0
  195. package/template/src/components/common/index.ts +19 -0
  196. package/template/src/constants/Constants.tsx +7 -0
  197. package/template/src/constants/Fonts.tsx +30 -0
  198. package/template/src/constants/index.ts +45 -0
  199. package/template/src/constants/strings.ts +211 -0
  200. package/template/src/constants/theme.ts +72 -0
  201. package/template/src/contexts/AuthContext.tsx +268 -0
  202. package/template/src/contexts/index.ts +2 -0
  203. package/template/src/hooks/index.ts +3 -0
  204. package/template/src/hooks/useImageUpload.ts +199 -0
  205. package/template/src/index.ts +8 -0
  206. package/template/src/navigation/AuthStack.tsx +67 -0
  207. package/template/src/navigation/MainStack.tsx +183 -0
  208. package/template/src/navigation/MiddleStack.tsx +35 -0
  209. package/template/src/navigation/RootNavigator.tsx +101 -0
  210. package/template/src/navigation/index.ts +5 -0
  211. package/template/src/navigation/navigationRef.ts +5 -0
  212. package/template/src/providers/QueryProvider.tsx +30 -0
  213. package/template/src/screens/DetailsScreen.tsx +271 -0
  214. package/template/src/screens/HomeScreen.tsx +736 -0
  215. package/template/src/screens/ProfileScreen.tsx +202 -0
  216. package/template/src/screens/SettingsScreen.tsx +253 -0
  217. package/template/src/screens/SportHubScreen.tsx +280 -0
  218. package/template/src/screens/auth/AddMamber.tsx +428 -0
  219. package/template/src/screens/auth/ForgotPasswordScreen.tsx +176 -0
  220. package/template/src/screens/auth/LoginScreen.tsx +286 -0
  221. package/template/src/screens/auth/OTPVerifyScreen.tsx +359 -0
  222. package/template/src/screens/auth/RegisterScreen.tsx +430 -0
  223. package/template/src/screens/auth/SuccessScreen.tsx +201 -0
  224. package/template/src/screens/auth/WalcomeScreen.tsx +274 -0
  225. package/template/src/screens/auth/index.ts +8 -0
  226. package/template/src/screens/chat/ChatScreen.tsx +1819 -0
  227. package/template/src/screens/chat/ChatThreadsScreen.tsx +360 -0
  228. package/template/src/screens/chat/ReportMessageScreen.tsx +238 -0
  229. package/template/src/screens/clubs/Announcements.tsx +426 -0
  230. package/template/src/screens/clubs/BuyRaffleTicketsScreen.tsx +568 -0
  231. package/template/src/screens/clubs/ClubDeteils.tsx +497 -0
  232. package/template/src/screens/clubs/JoinClub.tsx +841 -0
  233. package/template/src/screens/events/EventScreen.tsx +460 -0
  234. package/template/src/screens/index.ts +42 -0
  235. package/template/src/screens/raffles/MyReferralMembersScreen.tsx +758 -0
  236. package/template/src/screens/raffles/RaffleDetailsScreen.tsx +762 -0
  237. package/template/src/screens/raffles/RafflesScreen.tsx +495 -0
  238. package/template/src/screens/raffles/SetRaffleReminderScreen.tsx +390 -0
  239. package/template/src/screens/teams/JoinTeamScreen.tsx +464 -0
  240. package/template/src/screens/teams/MyTeamDetailsScreen.tsx +979 -0
  241. package/template/src/screens/teams/MyTeamScreen.tsx +568 -0
  242. package/template/src/screens/teams/PendingRequestsScreen.tsx +426 -0
  243. package/template/src/screens/volunteerOpportunities/SetReminderScreen.tsx +631 -0
  244. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesDetailsScreen.tsx +1049 -0
  245. package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesScreen.tsx +608 -0
  246. package/template/src/services/api.ts +167 -0
  247. package/template/src/services/authService.ts +422 -0
  248. package/template/src/services/index.ts +5 -0
  249. package/template/src/services/mainServices.ts +1963 -0
  250. package/template/src/stores/authStore.ts +159 -0
  251. package/template/src/stores/index.ts +2 -0
  252. package/template/src/types/index.ts +85 -0
  253. package/template/src/types/navigation.ts +206 -0
  254. package/template/src/utils/AzureUploaderService.ts +371 -0
  255. package/template/src/utils/ClubSearchManager.ts +222 -0
  256. package/template/src/utils/NotificationManager.ts +503 -0
  257. package/template/src/utils/UploadDebugUtil.ts +107 -0
  258. package/template/src/utils/imagePicker.ts +321 -0
  259. package/template/src/utils/index.ts +111 -0
  260. package/template/src/utils/permissions.ts +277 -0
  261. package/template/src/utils/scaling.ts +14 -0
  262. package/template/src/utils/storage.ts +247 -0
  263. package/template/src/utils/usePermissions.ts +275 -0
  264. package/template/src/utils/validation.ts +340 -0
  265. package/template/tsconfig.json +50 -0
  266. package/template/types/svg.d.ts +6 -0
@@ -0,0 +1,1819 @@
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
+ });