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,321 @@
1
+ import * as ImagePicker from 'expo-image-picker';
2
+ import { launchCamera as RNLaunchCamera, ImagePickerResponse, MediaType, CameraOptions, PhotoQuality } from 'react-native-image-picker';
3
+ import { Alert, Linking, Platform } from 'react-native';
4
+ import { requestPermission } from './permissions';
5
+
6
+ export interface ImagePickerResult {
7
+ uri: string;
8
+ width: number;
9
+ height: number;
10
+ type?: string;
11
+ fileName?: string;
12
+ fileSize?: number;
13
+ }
14
+
15
+ export interface ImagePickerOptions {
16
+ allowsEditing?: boolean;
17
+ aspect?: [number, number];
18
+ quality?: number;
19
+ base64?: boolean;
20
+ exif?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Request camera permission
25
+ */
26
+ export const requestCameraPermission = async (): Promise<boolean> => {
27
+ const result = await requestPermission('camera');
28
+ return result.granted;
29
+ };
30
+
31
+ /**
32
+ * Request camera roll permission
33
+ */
34
+ export const requestCameraRollPermission = async (): Promise<boolean> => {
35
+ const result = await requestPermission('cameraRoll');
36
+ return result.granted;
37
+ };
38
+
39
+ /**
40
+ * Convert quality number to PhotoQuality type
41
+ */
42
+ const convertQualityToPhotoQuality = (quality: number): PhotoQuality => {
43
+ // PhotoQuality accepts: 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1
44
+ const allowedValues: PhotoQuality[] = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
45
+ // Clamp quality between 0 and 1
46
+ const clampedQuality = Math.max(0, Math.min(1, quality));
47
+ // Round to nearest allowed value
48
+ const rounded = Math.round(clampedQuality * 10) / 10;
49
+ // Find closest allowed value
50
+ return allowedValues.reduce((prev, curr) =>
51
+ Math.abs(curr - rounded) < Math.abs(prev - rounded) ? curr : prev
52
+ );
53
+ };
54
+
55
+ /**
56
+ * Launch camera to take a photo
57
+ */
58
+ export const launchCamera = async (options: ImagePickerOptions = {}): Promise<ImagePickerResult | null> => {
59
+ try {
60
+ // Check camera permission
61
+ const hasPermission = await requestCameraPermission();
62
+ if (!hasPermission) {
63
+ Alert.alert(
64
+ 'Permission Required',
65
+ 'Camera permission is required to take photos. Please enable it in settings.',
66
+ [
67
+ { text: 'Cancel', style: 'cancel' },
68
+ { text: 'Settings', onPress: () => {
69
+ // You can add logic to open app settings here
70
+ Linking.openSettings();
71
+ }},
72
+ ]
73
+ );
74
+ return null;
75
+ }
76
+
77
+ // Prepare react-native-image-picker options
78
+ const cameraOptions: CameraOptions = {
79
+ mediaType: 'photo' as MediaType,
80
+ quality: convertQualityToPhotoQuality(options.quality ?? 0.8),
81
+ includeBase64: options.base64 ?? false,
82
+ saveToPhotos: false, // Set to false to avoid requesting photo library permission on iOS
83
+
84
+ };
85
+
86
+ // Handle aspect ratio if allowsEditing is true
87
+ if (options.allowsEditing ?? true) {
88
+ const [aspectX, aspectY] = options.aspect ?? [1, 1];
89
+ // react-native-image-picker doesn't support aspect ratio directly
90
+ // but we can use maxWidth and maxHeight for basic constraints
91
+ // For square images (1:1), we can set equal max dimensions
92
+ if (aspectX === aspectY) {
93
+ cameraOptions.maxWidth = 1080;
94
+ cameraOptions.maxHeight = 1080;
95
+ }
96
+ }
97
+
98
+ // Use Promise wrapper for react-native-image-picker
99
+ const result: ImagePickerResponse = await new Promise((resolve, reject) => {
100
+ RNLaunchCamera(cameraOptions, (response) => {
101
+ resolve(response);
102
+ });
103
+ });
104
+
105
+ // Check if user cancelled
106
+ if (result.didCancel) {
107
+ return null;
108
+ }
109
+
110
+ // Check for errors
111
+ if (result.errorCode) {
112
+ console.error('Camera error:', result.errorMessage);
113
+ Alert.alert('Error', result.errorMessage || 'Failed to open camera. Please try again.');
114
+ return null;
115
+ }
116
+
117
+ // Extract image data
118
+ if (result.assets && result.assets.length > 0) {
119
+ const asset = result.assets[0];
120
+ return {
121
+ uri: asset.uri || '',
122
+ width: asset.width || 0,
123
+ height: asset.height || 0,
124
+ type: asset.type || 'image/jpeg',
125
+ fileName: `image_${Date.now()}.jpg`,
126
+ fileSize: asset.fileSize || 0,
127
+ };
128
+ }
129
+
130
+ return null;
131
+ } catch (error) {
132
+ console.error('Error launching camera:', error);
133
+ Alert.alert('Error', 'Failed to open camera. Please try again.');
134
+ return null;
135
+ }
136
+ };
137
+
138
+ /**
139
+ * Launch image library to select a photo
140
+ */
141
+ export const launchImageLibrary = async (options: ImagePickerOptions = {}): Promise<ImagePickerResult | null> => {
142
+ try {
143
+ // Request gallery permission for iOS only
144
+ if (Platform.OS === 'ios') {
145
+ const hasPermission = await requestCameraRollPermission();
146
+ if (!hasPermission) {
147
+ Alert.alert(
148
+ 'Permission Required',
149
+ 'Photo library permission is required to select images. Please enable it in settings.',
150
+ [
151
+ { text: 'Cancel', style: 'cancel' },
152
+ { text: 'Settings', onPress: () => {
153
+ Linking.openSettings();
154
+ }},
155
+ ]
156
+ );
157
+ return null;
158
+ }
159
+ }
160
+
161
+ const result = await ImagePicker.launchImageLibraryAsync({
162
+ mediaTypes: ["images"],
163
+ quality: options.quality ?? 0.8,
164
+ aspect: options.aspect ?? [1, 1],
165
+ base64: options.base64 ?? false,
166
+ exif: options.exif ?? false,
167
+ });
168
+
169
+ console.log("result11===>>>>", JSON.stringify(result));
170
+
171
+ if (!result.canceled && result.assets && result.assets.length > 0) {
172
+ const asset = result.assets[0];
173
+ return {
174
+ uri: asset.uri,
175
+ width: asset.width,
176
+ height: asset.height,
177
+ type: asset.type,
178
+ fileName: `image_${Date.now()}.jpg`,
179
+ fileSize: asset.fileSize,
180
+ };
181
+ }
182
+
183
+ return null;
184
+ } catch (error) {
185
+ console.error('Error launching image library:', error);
186
+ Alert.alert('Error', 'Failed to open photo library. Please try again.');
187
+ return null;
188
+ }
189
+ };
190
+
191
+ /**
192
+ * Show action sheet with camera and gallery options
193
+ */
194
+ export const showImagePickerOptions = (
195
+ onCameraPress: () => void,
196
+ onGalleryPress: () => void,
197
+ onCancelPress?: () => void
198
+ ): void => {
199
+ const options = [
200
+ { text: 'Camera', onPress: onCameraPress },
201
+ { text: 'Gallery', onPress: onGalleryPress },
202
+ { text: 'Cancel', onPress: onCancelPress, style: 'cancel' as const },
203
+ ];
204
+
205
+ Alert.alert(
206
+ 'Select Image',
207
+ 'Choose how you want to add an image',
208
+ options,
209
+ { cancelable: true }
210
+ );
211
+ };
212
+
213
+ /**
214
+ * Complete image picker flow with action sheet
215
+ */
216
+ export const pickImage = async (options: ImagePickerOptions & { source?: 'camera' | 'gallery' } = {}): Promise<ImagePickerResult | null> => {
217
+ if (options.source === 'camera') {
218
+ console.log("options--->", options);
219
+ return await launchCamera(options);
220
+ } else if (options.source === 'gallery') {
221
+ return await launchImageLibrary(options);
222
+ }
223
+
224
+ return new Promise((resolve) => {
225
+ showImagePickerOptions(
226
+ async () => {
227
+ const result = await launchCamera(options);
228
+ resolve(result);
229
+ },
230
+ async () => {
231
+ const result = await launchImageLibrary(options);
232
+ resolve(result);
233
+ },
234
+ () => {
235
+ resolve(null);
236
+ }
237
+ );
238
+ });
239
+ };
240
+
241
+ /**
242
+ * Get image picker permissions status
243
+ */
244
+ export const getImagePickerPermissions = async () => {
245
+ const cameraStatus = await ImagePicker.getCameraPermissionsAsync();
246
+ const mediaLibraryStatus = await ImagePicker.getMediaLibraryPermissionsAsync();
247
+
248
+ return {
249
+ camera: {
250
+ granted: cameraStatus.granted,
251
+ canAskAgain: cameraStatus.canAskAgain,
252
+ },
253
+ mediaLibrary: {
254
+ granted: mediaLibraryStatus.granted,
255
+ canAskAgain: mediaLibraryStatus.canAskAgain,
256
+ },
257
+ };
258
+ };
259
+
260
+ /**
261
+ * Request all image picker permissions
262
+ */
263
+ export const requestImagePickerPermissions = async () => {
264
+ const cameraStatus = await ImagePicker.requestCameraPermissionsAsync();
265
+ const mediaLibraryStatus = await ImagePicker.requestMediaLibraryPermissionsAsync();
266
+
267
+ return {
268
+ camera: {
269
+ granted: cameraStatus.granted,
270
+ canAskAgain: cameraStatus.canAskAgain,
271
+ },
272
+ mediaLibrary: {
273
+ granted: mediaLibraryStatus.granted,
274
+ canAskAgain: mediaLibraryStatus.canAskAgain,
275
+ },
276
+ };
277
+ };
278
+
279
+ /**
280
+ * Utility to format file size
281
+ */
282
+ export const formatFileSize = (bytes: number): string => {
283
+ if (bytes === 0) return '0 Bytes';
284
+
285
+ const k = 1024;
286
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
287
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
288
+
289
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
290
+ };
291
+
292
+ /**
293
+ * Utility to validate image file
294
+ */
295
+ export const validateImageFile = (result: ImagePickerResult): { isValid: boolean; error?: string } => {
296
+ const maxSize = 10 * 1024 * 1024; // 10MB
297
+ const minSize = 1024; // 1KB
298
+
299
+ if (result.fileSize && result.fileSize > maxSize) {
300
+ return {
301
+ isValid: false,
302
+ error: `Image size is too large. Maximum allowed size is ${formatFileSize(maxSize)}.`,
303
+ };
304
+ }
305
+
306
+ if (result.fileSize && result.fileSize < minSize) {
307
+ return {
308
+ isValid: false,
309
+ error: 'Image size is too small. Please select a valid image.',
310
+ };
311
+ }
312
+
313
+ if (!result.uri) {
314
+ return {
315
+ isValid: false,
316
+ error: 'Invalid image. Please try again.',
317
+ };
318
+ }
319
+
320
+ return { isValid: true };
321
+ };
@@ -0,0 +1,111 @@
1
+ // Validation utilities - Re-export from validation class for backward compatibility
2
+ export * from './validation';
3
+
4
+ // String utilities
5
+ export const capitalize = (str: string): string => {
6
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
7
+ };
8
+
9
+ export const truncateText = (text: string, maxLength: number): string => {
10
+ if (text.length <= maxLength) return text;
11
+ return text.substring(0, maxLength).trim() + '...';
12
+ };
13
+
14
+ export const generateId = (): string => {
15
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
16
+ };
17
+
18
+ // Date utilities
19
+ export const formatDate = (date: Date): string => {
20
+ return new Intl.DateTimeFormat('en-US', {
21
+ year: 'numeric',
22
+ month: 'short',
23
+ day: 'numeric',
24
+ }).format(date);
25
+ };
26
+
27
+ export const formatDateTime = (date: Date): string => {
28
+ return new Intl.DateTimeFormat('en-US', {
29
+ year: 'numeric',
30
+ month: 'short',
31
+ day: 'numeric',
32
+ hour: '2-digit',
33
+ minute: '2-digit',
34
+ }).format(date);
35
+ };
36
+
37
+ export const formatReminderDateTime = (dateString: string): string => {
38
+ try {
39
+ const date = new Date(dateString);
40
+
41
+ // Format: "30 May 2025, 3:00 PM"
42
+ const day = date.getDate();
43
+ const month = date.toLocaleString('en-US', { month: 'short' });
44
+ const year = date.getFullYear();
45
+ const hours = date.getHours();
46
+ const minutes = date.getMinutes();
47
+
48
+ // Convert to 12-hour format
49
+ const period = hours >= 12 ? 'PM' : 'AM';
50
+ const displayHours = hours % 12 || 12;
51
+ const displayMinutes = minutes.toString().padStart(2, '0');
52
+
53
+ return `${day} ${month} ${year}, ${displayHours}:${displayMinutes} ${period}`;
54
+ } catch (error) {
55
+ console.error('Error formatting reminder date:', error);
56
+ return dateString;
57
+ }
58
+ };
59
+
60
+ export const isToday = (date: Date): boolean => {
61
+ const today = new Date();
62
+ return date.toDateString() === today.toDateString();
63
+ };
64
+
65
+ // Platform utilities
66
+ export const isIOS = (): boolean => {
67
+ return require('react-native').Platform.OS === 'ios';
68
+ };
69
+
70
+ export const isAndroid = (): boolean => {
71
+ return require('react-native').Platform.OS === 'android';
72
+ };
73
+
74
+ // API utilities
75
+ export const createQueryString = (params: Record<string, any>): string => {
76
+ const searchParams = new URLSearchParams();
77
+
78
+ Object.entries(params).forEach(([key, value]) => {
79
+ if (value !== null && value !== undefined && value !== '') {
80
+ searchParams.append(key, String(value));
81
+ }
82
+ });
83
+
84
+ return searchParams.toString();
85
+ };
86
+
87
+ export const delay = (ms: number): Promise<void> => {
88
+ return new Promise(resolve => setTimeout(resolve, ms));
89
+ };
90
+
91
+ // Re-export storage utilities
92
+ export * from './storage';
93
+
94
+ // Re-export permission utilities
95
+ export * from './permissions';
96
+
97
+ // Re-export permission hooks
98
+ export * from './usePermissions';
99
+
100
+ // Re-export image picker utilities
101
+ export * from './imagePicker';
102
+
103
+ // Re-export UploadDebugUtil
104
+ export * from './UploadDebugUtil';
105
+
106
+ // Re-export AzureUploaderService
107
+ export * from './AzureUploaderService';
108
+
109
+ // Re-export ClubSearchManager
110
+ export * from './ClubSearchManager';
111
+
@@ -0,0 +1,277 @@
1
+ import { Platform } from 'react-native';
2
+ import {
3
+ PERMISSIONS,
4
+ RESULTS,
5
+ Permission,
6
+ PermissionStatus,
7
+ request,
8
+ check,
9
+ requestMultiple,
10
+ checkMultiple,
11
+ } from 'react-native-permissions';
12
+
13
+ export type PermissionType =
14
+ | 'camera'
15
+ | 'cameraRoll';
16
+
17
+ export interface PermissionResult {
18
+ status: 'granted' | 'denied' | 'undetermined' | 'blocked';
19
+ canAskAgain: boolean;
20
+ expires?: 'never' | number;
21
+ }
22
+
23
+ export interface PermissionRequestResult {
24
+ granted: boolean;
25
+ status: 'granted' | 'denied' | 'undetermined' | 'blocked';
26
+ canAskAgain: boolean;
27
+ expires?: 'never' | number;
28
+ }
29
+
30
+ // Map permission types to react-native-permissions constants
31
+ const getPermissionConstant = (permission: PermissionType): Permission => {
32
+ const permissionMap: Record<PermissionType, Permission> = {
33
+ camera: Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA,
34
+ cameraRoll: Platform.OS === 'ios' ? PERMISSIONS.IOS.PHOTO_LIBRARY : PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
35
+ };
36
+
37
+ return permissionMap[permission];
38
+ };
39
+
40
+ // Convert react-native-permissions status to our format
41
+ const convertPermissionStatus = (status: PermissionStatus): PermissionResult => {
42
+ const statusMap: Record<PermissionStatus, PermissionResult['status']> = {
43
+ [RESULTS.GRANTED]: 'granted',
44
+ [RESULTS.DENIED]: 'denied',
45
+ [RESULTS.BLOCKED]: 'blocked',
46
+ [RESULTS.UNAVAILABLE]: 'denied',
47
+ [RESULTS.LIMITED]: 'granted',
48
+ };
49
+
50
+ return {
51
+ status: statusMap[status] || 'denied',
52
+ canAskAgain: status === RESULTS.DENIED,
53
+ expires: 'never',
54
+ };
55
+ };
56
+
57
+ /**
58
+ * Request a single permission
59
+ */
60
+ export const requestPermission = async (permission: PermissionType): Promise<PermissionRequestResult> => {
61
+ try {
62
+ const permissionConstant = getPermissionConstant(permission);
63
+ const status = await request(permissionConstant);
64
+ const result = convertPermissionStatus(status);
65
+
66
+ return {
67
+ granted: result.status === 'granted',
68
+ status: result.status,
69
+ canAskAgain: result.canAskAgain,
70
+ expires: result.expires,
71
+ };
72
+ } catch (error) {
73
+ console.error(`Error requesting permission ${permission}:`, error);
74
+ return {
75
+ granted: false,
76
+ status: 'denied',
77
+ canAskAgain: false,
78
+ };
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Request multiple permissions at once
84
+ */
85
+ export const requestMultiplePermissions = async (permissions: PermissionType[]): Promise<Record<PermissionType, PermissionRequestResult>> => {
86
+ try {
87
+ const permissionConstants = permissions.map(getPermissionConstant);
88
+ const results = await requestMultiple(permissionConstants);
89
+ const permissionResults: Record<PermissionType, PermissionRequestResult> = {} as any;
90
+
91
+ permissions.forEach((permission, index) => {
92
+ const status = results[permissionConstants[index]];
93
+ const result = convertPermissionStatus(status);
94
+
95
+ permissionResults[permission] = {
96
+ granted: result.status === 'granted',
97
+ status: result.status,
98
+ canAskAgain: result.canAskAgain,
99
+ expires: result.expires,
100
+ };
101
+ });
102
+
103
+ return permissionResults;
104
+ } catch (error) {
105
+ console.error('Error requesting multiple permissions:', error);
106
+ const failedResults: Record<PermissionType, PermissionRequestResult> = {} as any;
107
+
108
+ permissions.forEach(permission => {
109
+ failedResults[permission] = {
110
+ granted: false,
111
+ status: 'denied',
112
+ canAskAgain: false,
113
+ };
114
+ });
115
+
116
+ return failedResults;
117
+ }
118
+ };
119
+
120
+ /**
121
+ * Get the current status of a permission without requesting it
122
+ */
123
+ export const getPermissionStatus = async (permission: PermissionType): Promise<PermissionResult> => {
124
+ try {
125
+ const permissionConstant = getPermissionConstant(permission);
126
+ const status = await check(permissionConstant);
127
+ return convertPermissionStatus(status);
128
+ } catch (error) {
129
+ console.error(`Error getting permission status for ${permission}:`, error);
130
+ return {
131
+ status: 'denied',
132
+ canAskAgain: false,
133
+ };
134
+ }
135
+ };
136
+
137
+ /**
138
+ * Get the current status of multiple permissions
139
+ */
140
+ export const getMultiplePermissionStatuses = async (permissions: PermissionType[]): Promise<Record<PermissionType, PermissionResult>> => {
141
+ try {
142
+ const permissionConstants = permissions.map(getPermissionConstant);
143
+ const results = await checkMultiple(permissionConstants);
144
+ const permissionResults: Record<PermissionType, PermissionResult> = {} as any;
145
+
146
+ permissions.forEach((permission, index) => {
147
+ const status = results[permissionConstants[index]];
148
+ permissionResults[permission] = convertPermissionStatus(status);
149
+ });
150
+
151
+ return permissionResults;
152
+ } catch (error) {
153
+ console.error('Error getting multiple permission statuses:', error);
154
+ const failedResults: Record<PermissionType, PermissionResult> = {} as any;
155
+
156
+ permissions.forEach(permission => {
157
+ failedResults[permission] = {
158
+ status: 'denied',
159
+ canAskAgain: false,
160
+ };
161
+ });
162
+
163
+ return failedResults;
164
+ }
165
+ };
166
+
167
+ /**
168
+ * Check if a permission is granted
169
+ */
170
+ export const isPermissionGranted = async (permission: PermissionType): Promise<boolean> => {
171
+ const status = await getPermissionStatus(permission);
172
+ return status.status === 'granted';
173
+ };
174
+
175
+ /**
176
+ * Check if multiple permissions are granted
177
+ */
178
+ export const arePermissionsGranted = async (permissions: PermissionType[]): Promise<Record<PermissionType, boolean>> => {
179
+ const statuses = await getMultiplePermissionStatuses(permissions);
180
+ const results: Record<PermissionType, boolean> = {} as any;
181
+
182
+ permissions.forEach(permission => {
183
+ results[permission] = statuses[permission].status === 'granted';
184
+ });
185
+
186
+ return results;
187
+ };
188
+
189
+ /**
190
+ * Request permission with user-friendly error handling
191
+ */
192
+ export const requestPermissionWithFallback = async (
193
+ permission: PermissionType,
194
+ onDenied?: () => void,
195
+ onError?: (error: Error) => void
196
+ ): Promise<boolean> => {
197
+ try {
198
+ const result = await requestPermission(permission);
199
+
200
+ if (!result.granted) {
201
+ if (result.canAskAgain) {
202
+ // Permission was denied but can ask again
203
+ console.log(`Permission ${permission} was denied but can ask again`);
204
+ } else {
205
+ // Permission was denied and cannot ask again
206
+ console.log(`Permission ${permission} was denied and cannot ask again`);
207
+ onDenied?.();
208
+ }
209
+ }
210
+
211
+ return result.granted;
212
+ } catch (error) {
213
+ console.error(`Error requesting permission ${permission}:`, error);
214
+ onError?.(error as Error);
215
+ return false;
216
+ }
217
+ };
218
+
219
+ /**
220
+ * Common permission combinations
221
+ */
222
+ export const PermissionGroups = {
223
+ camera: ['camera', 'cameraRoll'] as PermissionType[],
224
+ media: ['camera', 'cameraRoll', 'audioRecording'] as PermissionType[],
225
+ } as const;
226
+
227
+ /**
228
+ * Request a group of related permissions
229
+ */
230
+ export const requestPermissionGroup = async (group: keyof typeof PermissionGroups): Promise<Record<PermissionType, PermissionRequestResult>> => {
231
+ const permissions = PermissionGroups[group];
232
+ return requestMultiplePermissions(permissions);
233
+ };
234
+
235
+ /**
236
+ * Check if all permissions in a group are granted
237
+ */
238
+ export const isPermissionGroupGranted = async (group: keyof typeof PermissionGroups): Promise<boolean> => {
239
+ const permissions = PermissionGroups[group];
240
+ const results = await arePermissionsGranted(permissions);
241
+ return Object.values(results).every(granted => granted);
242
+ };
243
+
244
+ /**
245
+ * Platform-specific permission helpers
246
+ */
247
+ export const PlatformPermissions = {
248
+ /**
249
+ * Check if the platform supports the permission
250
+ */
251
+ isSupported: (permission: PermissionType): boolean => {
252
+ if (Platform.OS === 'ios') {
253
+ // iOS supports all permissions
254
+ return true;
255
+ } else if (Platform.OS === 'android') {
256
+ // Android-specific permission checks
257
+ const androidSupportedPermissions: PermissionType[] = [
258
+ 'camera',
259
+ 'cameraRoll',
260
+ ];
261
+ return androidSupportedPermissions.includes(permission);
262
+ }
263
+ return false;
264
+ },
265
+
266
+ /**
267
+ * Get platform-specific permission name
268
+ */
269
+ getPlatformPermissionName: (permission: PermissionType): string => {
270
+ const permissionNames: Record<PermissionType, string> = {
271
+ camera: Platform.OS === 'ios' ? 'Camera' : 'Camera',
272
+ cameraRoll: Platform.OS === 'ios' ? 'Photo Library' : 'Storage'
273
+ };
274
+
275
+ return permissionNames[permission] || permission;
276
+ },
277
+ };