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,371 @@
1
+ import Upload, { UploadOptions } from "react-native-background-upload";
2
+ import { Platform } from "react-native";
3
+ import { UploadDebugUtil } from "./UploadDebugUtil";
4
+
5
+ // These types help ensure the component using this service provides the correct functions.
6
+ type ProgressCallback = (progress: number) => void;
7
+ type CompletionCallback = (uploadedUrl: string) => void;
8
+ type ErrorCallback = (error: Error) => void;
9
+ type CancelCallback = () => void;
10
+
11
+ export interface UploadResult {
12
+ success: boolean;
13
+ url?: string;
14
+ error?: string;
15
+ }
16
+
17
+ export interface UploadOptions {
18
+ maxRetries?: number;
19
+ retryDelay?: number;
20
+ timeout?: number;
21
+ }
22
+
23
+ export class AzureUploadService {
24
+ /**
25
+ * Convert file URI to format compatible with Android upload service
26
+ * Android only supports absolute paths (/) and content:// URIs
27
+ */
28
+ private static normalizeFileUriForAndroid(fileUri: string): string {
29
+ if (Platform.OS !== "android") {
30
+ return fileUri;
31
+ }
32
+
33
+ console.log(`Normalizing file URI for Android: ${fileUri}`);
34
+
35
+ if (fileUri.startsWith("file://")) {
36
+ // Remove file:// prefix to get absolute path
37
+ const absolutePath = fileUri.replace("file://", "");
38
+ console.log(`Converted file:// to absolute path: ${absolutePath}`);
39
+ return absolutePath;
40
+ } else if (fileUri.startsWith("content://")) {
41
+ // Keep content:// URIs as is
42
+ console.log(`Using content URI: ${fileUri}`);
43
+ return fileUri;
44
+ } else if (fileUri.startsWith("/")) {
45
+ // Already an absolute path, keep as is
46
+ console.log(`Using absolute path: ${fileUri}`);
47
+ return fileUri;
48
+ } else {
49
+ // If it's a relative path, make it absolute (fallback)
50
+ const absolutePath = `/${fileUri}`;
51
+ console.log(`Converted to absolute path: ${absolutePath}`);
52
+ return absolutePath;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Alternative file existence check using React Native's built-in methods
58
+ */
59
+ private static async checkFileExistsAlternative(fileUri: string): Promise<boolean> {
60
+ try {
61
+ // For Android, try to access the file directly using the legacy API
62
+ if (Platform.OS === "android") {
63
+ const normalizedPath = this.normalizeFileUriForAndroid(fileUri);
64
+
65
+ // Try using the legacy expo-file-system API as fallback
66
+ const { getInfoAsync } = require('expo-file-system/legacy');
67
+ const fileInfo = await getInfoAsync(normalizedPath);
68
+ console.log(`Alternative file check - Path: ${normalizedPath}, Exists: ${fileInfo.exists}`);
69
+ return fileInfo.exists;
70
+ }
71
+
72
+ // For iOS, use the original method
73
+ return await UploadDebugUtil.checkFileExists(fileUri);
74
+ } catch (error) {
75
+ console.error(`Alternative file check failed for ${fileUri}:`, error);
76
+ return false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Upload file with retry mechanism and comprehensive error handling
82
+ */
83
+ public static async uploadFileWithRetry({
84
+ fileUri,
85
+ sasUrl,
86
+ blobUrl,
87
+ options = {},
88
+ onProgress,
89
+ onComplete,
90
+ onError,
91
+ onCancelled,
92
+ }: {
93
+ fileUri: string;
94
+ sasUrl: string;
95
+ blobUrl: string;
96
+ options?: UploadOptions;
97
+ onProgress?: ProgressCallback;
98
+ onComplete?: CompletionCallback;
99
+ onError?: ErrorCallback;
100
+ onCancelled?: CancelCallback;
101
+ }): Promise<UploadResult> {
102
+ const { maxRetries = 3, retryDelay = 1000 } = options;
103
+ let lastError: Error | null = null;
104
+
105
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
106
+ try {
107
+ console.log(`Upload attempt ${attempt}/${maxRetries}`);
108
+
109
+ const result = await this.uploadFile({
110
+ fileUri,
111
+ sasUrl,
112
+ blobUrl,
113
+ onProgress,
114
+ onComplete,
115
+ onError,
116
+ onCancelled,
117
+ });
118
+
119
+ return {
120
+ success: true,
121
+ url: result,
122
+ };
123
+ } catch (error) {
124
+ lastError = error as Error;
125
+ console.error(`Upload attempt ${attempt} failed:`, error);
126
+
127
+ if (attempt < maxRetries) {
128
+ console.log(`Retrying in ${retryDelay}ms...`);
129
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
130
+ }
131
+ }
132
+ }
133
+
134
+ return {
135
+ success: false,
136
+ error: lastError?.message || 'Upload failed after all retry attempts',
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Validate file before upload
142
+ */
143
+ private static validateFile(fileUri: string): { isValid: boolean; error?: string } {
144
+ if (!fileUri) {
145
+ return { isValid: false, error: 'File URI is required' };
146
+ }
147
+
148
+ // Check file extension
149
+ const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
150
+ const hasValidExtension = allowedExtensions.some(ext =>
151
+ fileUri.toLowerCase().includes(ext)
152
+ );
153
+
154
+ if (!hasValidExtension) {
155
+ return {
156
+ isValid: false,
157
+ error: `Invalid file type. Allowed types: ${allowedExtensions.join(', ')}`
158
+ };
159
+ }
160
+
161
+ return { isValid: true };
162
+ }
163
+
164
+ public static async uploadFile({
165
+ fileUri,
166
+ sasUrl,
167
+ blobUrl,
168
+ onProgress,
169
+ onComplete,
170
+ onError,
171
+ onCancelled,
172
+ }: {
173
+ fileUri: string;
174
+ sasUrl: string;
175
+ blobUrl: string;
176
+ onProgress?: ProgressCallback;
177
+ onComplete?: CompletionCallback;
178
+ onError?: ErrorCallback;
179
+ onCancelled?: CancelCallback;
180
+ }): Promise<string> {
181
+ try {
182
+ console.log(`Starting upload for file: ${fileUri}`);
183
+ console.log(`Platform: ${Platform.OS}`);
184
+ console.log(`SAS URL: ${sasUrl}`);
185
+
186
+ // Validate file before upload
187
+ const validation = this.validateFile(fileUri);
188
+ if (!validation.isValid) {
189
+ const error = new Error(validation.error || 'File validation failed');
190
+ if (onError) {
191
+ onError(error);
192
+ }
193
+ throw error;
194
+ }
195
+
196
+ // Debug upload service on Android
197
+ if (Platform.OS === "android") {
198
+ UploadDebugUtil.debugUploadService();
199
+ UploadDebugUtil.logUploadServiceStatus();
200
+ }
201
+
202
+ // Normalize file URI for Android
203
+ const normalizedFileUri = this.normalizeFileUriForAndroid(fileUri);
204
+
205
+ // Check if file exists (helpful for debugging)
206
+ let fileExists = false;
207
+ try {
208
+ fileExists = await UploadDebugUtil.checkFileExists(fileUri);
209
+ } catch (error) {
210
+ console.warn(`Primary file check failed, trying alternative method: ${error}`);
211
+ try {
212
+ fileExists = await this.checkFileExistsAlternative(fileUri);
213
+ } catch (altError) {
214
+ console.warn(`Alternative file check also failed: ${altError}`);
215
+ // For Android, sometimes the file exists but can't be checked due to permissions
216
+ // Continue with upload anyway - let the upload service handle it
217
+ if (Platform.OS === "android") {
218
+ console.log("Skipping file existence check for Android - proceeding with upload");
219
+ fileExists = true;
220
+ } else {
221
+ fileExists = false;
222
+ }
223
+ }
224
+ }
225
+
226
+ if (!fileExists) {
227
+ const errorMessage = `File does not exist at path: ${fileUri}`;
228
+ console.error(errorMessage);
229
+ if (onError) {
230
+ onError(new Error(errorMessage));
231
+ }
232
+ throw new Error(errorMessage);
233
+ }
234
+
235
+ // Validate the normalized URI
236
+ if (
237
+ Platform.OS === "android" &&
238
+ !UploadDebugUtil.validateFileUri(normalizedFileUri)
239
+ ) {
240
+ const errorMessage = `Invalid file URI for Android: ${normalizedFileUri}. Android upload service only supports absolute paths (/) and content:// URIs.`;
241
+ console.error(errorMessage);
242
+ if (onError) {
243
+ onError(new Error(errorMessage));
244
+ }
245
+ return;
246
+ }
247
+
248
+ // Configure the upload options for the background uploader
249
+ const uploadOptions: UploadOptions = {
250
+ url: sasUrl,
251
+ path: normalizedFileUri,
252
+ method: "PUT",
253
+ type: "raw",
254
+ headers: {
255
+ "x-ms-blob-type": "BlockBlob",
256
+ "Content-Type": "application/octet-stream",
257
+ },
258
+ notification: {
259
+ enabled: true, // Android requires this to be true
260
+ autoClear: true, // Automatically clear the notification after completion
261
+ },
262
+ };
263
+
264
+ // Add Android-specific configurations
265
+ if (Platform.OS === "android") {
266
+ uploadOptions.customUploadId = `upload_${Date.now()}`;
267
+ }
268
+
269
+ console.log(`Upload options:`, JSON.stringify(uploadOptions, null, 2));
270
+
271
+ let uploadId: string;
272
+ try {
273
+ uploadId = await Upload.startUpload(uploadOptions);
274
+ console.log(`Upload started successfully with ID: ${uploadId}`);
275
+ } catch (startUploadError) {
276
+ const errorMessage = `Failed to start upload: ${startUploadError}`;
277
+ console.error(errorMessage);
278
+ if (onError) {
279
+ onError(new Error(errorMessage));
280
+ }
281
+ return;
282
+ }
283
+
284
+ // Set up timeout for Android
285
+ let uploadTimeout: NodeJS.Timeout | null = null;
286
+ if (Platform.OS === "android") {
287
+ uploadTimeout = setTimeout(() => {
288
+ console.log(`Upload timeout for ID: ${uploadId}`);
289
+ Upload.cancelUpload(uploadId);
290
+ if (onError) {
291
+ onError(
292
+ new Error(
293
+ "Upload timeout - Please check your internet connection"
294
+ )
295
+ );
296
+ }
297
+ }, 1800000); // 30 minutes timeout for slow networks
298
+ }
299
+
300
+ const clearUploadTimeout = () => {
301
+ if (uploadTimeout) {
302
+ clearTimeout(uploadTimeout);
303
+ uploadTimeout = null;
304
+ }
305
+ };
306
+
307
+ // Return a Promise that resolves when upload completes
308
+ return new Promise<string>((resolve, reject) => {
309
+ // Add listeners to track the upload lifecycle
310
+ Upload.addListener("progress", uploadId, (data: any) => {
311
+ if (onProgress) {
312
+ onProgress(Math.floor(data.progress));
313
+ }
314
+ });
315
+
316
+ Upload.addListener("completed", uploadId, (data: any) => {
317
+ clearUploadTimeout();
318
+
319
+ // Azure returns 201 on successful PUT. The final URL was already given to us.
320
+ if (data.responseCode === 201) {
321
+ console.log(`Upload completed successfully for ID: ${uploadId}`);
322
+ if (onComplete) {
323
+ onComplete(blobUrl);
324
+ }
325
+ resolve(blobUrl);
326
+ } else {
327
+ const errorMessage = `Upload failed with response code: ${data.responseCode}`;
328
+ console.error(errorMessage);
329
+ const error = new Error(
330
+ `Server responded with status code: ${data.responseCode}\nBody: ${data.responseBody}`
331
+ );
332
+ if (onError) {
333
+ onError(error);
334
+ }
335
+ reject(error);
336
+ }
337
+ });
338
+
339
+ Upload.addListener("cancelled", uploadId, () => {
340
+ clearUploadTimeout();
341
+ console.log(`Upload cancelled for ID: ${uploadId}`);
342
+ const error = new Error("Upload was cancelled");
343
+ if (onCancelled) {
344
+ onCancelled();
345
+ }
346
+ reject(error);
347
+ });
348
+
349
+ Upload.addListener("error", uploadId, (data: any) => {
350
+ clearUploadTimeout();
351
+ console.error(`Upload error for ID: ${uploadId}`, data.error);
352
+ const error = new Error(data.error || "Unknown upload error");
353
+ if (onError) {
354
+ onError(error);
355
+ }
356
+ reject(error);
357
+ });
358
+ });
359
+ } catch (error) {
360
+ console.error("An error occurred during the file upload process:", error);
361
+ if (onError) {
362
+ onError(error as Error);
363
+ }
364
+ throw error; // Re-throw to reject the Promise
365
+ }
366
+ }
367
+ }
368
+
369
+
370
+
371
+
@@ -0,0 +1,222 @@
1
+ import { ClubSearchRequest, ClubSearchResponse } from '../services/authService';
2
+
3
+ export interface ClubData {
4
+ id?: string | number;
5
+ name?: string;
6
+ clubName?: string;
7
+ image?: string;
8
+ profileImage?: string;
9
+ logo?: string;
10
+ members?: number;
11
+ memberCount?: number;
12
+ address?: string;
13
+ location?: string;
14
+ clubCode?: string;
15
+ color?: string;
16
+ description?: string;
17
+ }
18
+
19
+ export interface PaginationState {
20
+ clubs: ClubData[];
21
+ currentPage: number;
22
+ totalPages: number;
23
+ totalCount: number;
24
+ isLoading: boolean;
25
+ isLoadingMore: boolean;
26
+ isRefreshing: boolean;
27
+ hasMoreData: boolean;
28
+ error: string | null;
29
+ }
30
+
31
+ export class ClubSearchManager {
32
+ private state: PaginationState;
33
+ private onStateChange: (state: PaginationState) => void;
34
+ private searchClubs: (params: ClubSearchRequest) => Promise<any>;
35
+ private pageSize: number;
36
+
37
+ constructor(
38
+ onStateChange: (state: PaginationState) => void,
39
+ searchClubs: (params: ClubSearchRequest) => Promise<any>,
40
+ pageSize: number = 5
41
+ ) {
42
+ this.onStateChange = onStateChange;
43
+ this.searchClubs = searchClubs;
44
+ this.pageSize = pageSize;
45
+
46
+ this.state = {
47
+ clubs: [],
48
+ currentPage: 0,
49
+ totalPages: 0,
50
+ totalCount: 0,
51
+ isLoading: false,
52
+ isLoadingMore: false,
53
+ isRefreshing: false,
54
+ hasMoreData: true,
55
+ error: null,
56
+ };
57
+ }
58
+
59
+ // Initial load
60
+ async loadInitialData(): Promise<void> {
61
+ this.setState({
62
+ ...this.state,
63
+ isLoading: true,
64
+ error: null,
65
+ });
66
+
67
+ try {
68
+ const response = await this.searchClubs({
69
+ pageNumber: 1,
70
+ pageSize: this.pageSize,
71
+ });
72
+
73
+ console.log('response1', response);
74
+ const data = response?.data?.data || {};
75
+ const clubs = data || [];
76
+
77
+ this.setState({
78
+ ...this.state,
79
+ clubs,
80
+ currentPage: 1,
81
+ totalPages: data?.totalPages || 0,
82
+ totalCount: data?.totalCount || 0,
83
+ isLoading: false,
84
+ hasMoreData: clubs.length < (data?.totalCount || 0),
85
+ error: null,
86
+ });
87
+ } catch (error: any) {
88
+ this.setState({
89
+ ...this.state,
90
+ isLoading: false,
91
+ error: error?.message || 'Failed to load clubs',
92
+ });
93
+ }
94
+ }
95
+
96
+ // Load more data (pagination)
97
+ async loadMoreData(): Promise<void> {
98
+ if (this.state.isLoadingMore || !this.state.hasMoreData) {
99
+ return;
100
+ }
101
+
102
+ const nextPage = this.state.currentPage + 1;
103
+
104
+ this.setState({
105
+ ...this.state,
106
+ isLoadingMore: true,
107
+ error: null,
108
+ });
109
+
110
+ try {
111
+ const response = await this.searchClubs({
112
+ pageNumber: nextPage,
113
+ pageSize: this.pageSize,
114
+ });
115
+
116
+ console.log('response2', response);
117
+
118
+ const data = response?.data?.data || {};
119
+ const newClubs = data || [];
120
+
121
+ this.setState({
122
+ ...this.state,
123
+ clubs: [...this.state.clubs, ...newClubs],
124
+ currentPage: nextPage,
125
+ totalPages: data?.totalPages || 0,
126
+ totalCount: data?.totalCount || 0,
127
+ isLoadingMore: false,
128
+ hasMoreData: this.state.clubs.length + newClubs.length < (data?.totalCount || 0),
129
+ error: null,
130
+ });
131
+ } catch (error: any) {
132
+ this.setState({
133
+ ...this.state,
134
+ isLoadingMore: false,
135
+ error: error?.message || 'Failed to load more clubs',
136
+ });
137
+ }
138
+ }
139
+
140
+ // Pull to refresh
141
+ async refreshData(): Promise<void> {
142
+ this.setState({
143
+ ...this.state,
144
+ isRefreshing: true,
145
+ error: null,
146
+ });
147
+
148
+ try {
149
+ const response = await this.searchClubs({
150
+ pageNumber: 1,
151
+ pageSize: this.pageSize,
152
+ });
153
+
154
+
155
+ console.log('response3', response);
156
+
157
+ const data = response?.data?.data || {};
158
+ const clubs = data || [];
159
+
160
+ this.setState({
161
+ ...this.state,
162
+ clubs,
163
+ currentPage: 0,
164
+ totalPages: data?.totalPages || 0,
165
+ totalCount: data?.totalCount || 0,
166
+ isRefreshing: false,
167
+ hasMoreData: clubs.length < (data?.totalCount || 0),
168
+ error: null,
169
+ });
170
+ } catch (error: any) {
171
+ this.setState({
172
+ ...this.state,
173
+ isRefreshing: false,
174
+ error: error?.message || 'Failed to refresh clubs',
175
+ });
176
+ }
177
+ }
178
+
179
+ // Reset data
180
+ resetData(): void {
181
+ this.setState({
182
+ clubs: [],
183
+ currentPage: 0,
184
+ totalPages: 0,
185
+ totalCount: 0,
186
+ isLoading: false,
187
+ isLoadingMore: false,
188
+ isRefreshing: false,
189
+ hasMoreData: true,
190
+ error: null,
191
+ });
192
+ }
193
+
194
+ // Get current state
195
+ getState(): PaginationState {
196
+ return { ...this.state };
197
+ }
198
+
199
+ // Set state helper
200
+ private setState(newState: PaginationState): void {
201
+ this.state = newState;
202
+ this.onStateChange(newState);
203
+ }
204
+
205
+ // Check if we can load more
206
+ canLoadMore(): boolean {
207
+ return this.state.hasMoreData && !this.state.isLoadingMore && !this.state.isLoading;
208
+ }
209
+
210
+ // Get loading footer text
211
+ getLoadingFooterText(): string {
212
+ if (this.state.isLoadingMore) {
213
+ return 'Loading more clubs...';
214
+ }
215
+ if (!this.state.hasMoreData && this.state.clubs.length > 0) {
216
+ return 'No more clubs to load';
217
+ }
218
+ return '';
219
+ }
220
+ }
221
+
222
+ export default ClubSearchManager;