create-gufran-expo-app 2.0.0 → 2.0.1

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,451 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ Image,
8
+ Modal,
9
+ ActivityIndicator,
10
+ Animated,
11
+ } from 'react-native';
12
+ import { Svg, Circle } from 'react-native-svg';
13
+ import {
14
+ pickImage,
15
+ ImagePickerResult,
16
+ ImagePickerOptions,
17
+ validateImageFile
18
+ } from '../../utils/imagePicker';
19
+ import SVG from '../../assets/icons';
20
+ import { moderateScale } from '../../utils/scaling';
21
+ import { IconAlt } from './IconAlt';
22
+ import { theme } from '../../constants';
23
+ import { SvgProps } from 'react-native-svg';
24
+
25
+
26
+ interface ImagePickerComponentProps {
27
+ onImageSelected?: (result: ImagePickerResult) => void;
28
+ loading?: boolean;
29
+ onError?: (error: string) => void;
30
+ imageUri?: string;
31
+ options?: ImagePickerOptions;
32
+ disabled?: boolean;
33
+ style?: any;
34
+ defaultIcon?: React.FC<SvgProps> | string;
35
+ progress?: number;
36
+ }
37
+
38
+ export const ImagePickerComponent: React.FC<ImagePickerComponentProps> = ({
39
+ onImageSelected,
40
+ loading,
41
+ onError,
42
+ imageUri,
43
+ options = {},
44
+ disabled = false,
45
+ style,
46
+ defaultIcon,
47
+ progress = 0,
48
+ }) => {
49
+ const [isLoading, setIsLoading] = useState(loading);
50
+ const overlayOpacity = useRef(new Animated.Value(0)).current;
51
+ const animatedProgress = useRef(new Animated.Value(progress || 0)).current;
52
+
53
+ const size = moderateScale(150);
54
+ const strokeWidth = moderateScale(6);
55
+ const radius = (size - strokeWidth) / 2;
56
+ const circumference = 2 * Math.PI * radius;
57
+
58
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
59
+
60
+ useEffect(() => {
61
+ Animated.timing(animatedProgress, {
62
+ toValue: Math.max(0, Math.min(100, progress || 0)),
63
+ duration: 220,
64
+ useNativeDriver: true,
65
+ }).start();
66
+ }, [progress, animatedProgress]);
67
+
68
+ useEffect(() => {
69
+ if (loading) {
70
+ Animated.timing(overlayOpacity, {
71
+ toValue: 1,
72
+ duration: 220,
73
+ useNativeDriver: true,
74
+ }).start();
75
+ } else {
76
+ overlayOpacity.setValue(0);
77
+ }
78
+ }, [loading, overlayOpacity]);
79
+
80
+ const handleImagePress = async () => {
81
+ if (disabled || loading) return;
82
+
83
+ try {
84
+ setIsLoading(true);
85
+ const result = await pickImage(options);
86
+
87
+ if (result) {
88
+ // Validate the image
89
+ const validation = validateImageFile(result);
90
+
91
+ if (!validation.isValid) {
92
+ onError?.(validation.error || 'Invalid image');
93
+ return;
94
+ }
95
+
96
+ onImageSelected?.(result);
97
+ }
98
+ } catch (error) {
99
+ console.error('Image picker error:', error);
100
+ onError?.('Failed to select image. Please try again.');
101
+ } finally {
102
+ setIsLoading(false);
103
+ }
104
+ };
105
+
106
+ return (
107
+ <TouchableOpacity
108
+ style={[styles.container, style]}
109
+ onPress={handleImagePress}
110
+ disabled={disabled || loading}
111
+ activeOpacity={0.7}
112
+ >
113
+ {imageUri ? (
114
+ <TouchableOpacity onPress={handleImagePress} style={styles.imageContainer}>
115
+ <Image resizeMode='cover' source={{ uri: imageUri }} style={styles.image} />
116
+ {loading && (
117
+ <View pointerEvents="none" style={styles.progressRingContainer}>
118
+ <Svg width={size} height={size}>
119
+ <Circle
120
+ cx={size / 2}
121
+ cy={size / 2}
122
+ r={radius}
123
+ stroke={theme.colors.white}
124
+ strokeWidth={strokeWidth}
125
+ opacity={0.4}
126
+ fill="transparent"
127
+ />
128
+ <AnimatedCircle
129
+ cx={size / 2}
130
+ cy={size / 2}
131
+ r={radius}
132
+ stroke={theme.colors.blue}
133
+ strokeWidth={strokeWidth}
134
+ strokeLinecap="round"
135
+ fill="transparent"
136
+ strokeDasharray={`${circumference} ${circumference}`}
137
+ strokeDashoffset={animatedProgress.interpolate({
138
+ inputRange: [0, 100],
139
+ outputRange: [circumference, 0],
140
+ })}
141
+ transform={`rotate(-90 ${size / 2} ${size / 2})`}
142
+ />
143
+ </Svg>
144
+ </View>
145
+ )}
146
+ {loading && (
147
+ <Animated.View style={[styles.loadingOverlay, { opacity: overlayOpacity }]}>
148
+ <ActivityIndicator size="large" color={theme.colors.white} />
149
+ <Text style={styles.loadingText}>Uploading {Math.floor(progress || 0)}%</Text>
150
+ </Animated.View>
151
+ )}
152
+ </TouchableOpacity>
153
+ ) : (
154
+ <View style={styles.placeholderContainer}>
155
+ {defaultIcon && typeof defaultIcon === 'function' ? (
156
+ React.createElement(defaultIcon, {
157
+ width: moderateScale(150),
158
+ height: moderateScale(150),
159
+ color: theme.colors.textSecondary,
160
+ })
161
+ ) : (
162
+ <IconAlt
163
+ name={defaultIcon as any}
164
+ color={theme.colors.textSecondary}
165
+ size={moderateScale(150)}
166
+ />
167
+ )}
168
+ {loading && (
169
+ <View pointerEvents="none" style={styles.progressRingContainer}>
170
+ <Svg width={size} height={size}>
171
+ <Circle
172
+ cx={size / 2}
173
+ cy={size / 2}
174
+ r={radius}
175
+ stroke={theme.colors.white}
176
+ strokeWidth={strokeWidth}
177
+ opacity={0.4}
178
+ fill="transparent"
179
+ />
180
+ <AnimatedCircle
181
+ cx={size / 2}
182
+ cy={size / 2}
183
+ r={radius}
184
+ stroke={theme.colors.white}
185
+ strokeWidth={strokeWidth}
186
+ strokeLinecap="round"
187
+ fill="transparent"
188
+ strokeDasharray={`${circumference} ${circumference}`}
189
+ strokeDashoffset={animatedProgress.interpolate({
190
+ inputRange: [0, 100],
191
+ outputRange: [circumference, 0],
192
+ })}
193
+ transform={`rotate(-90 ${size / 2} ${size / 2})`}
194
+ />
195
+ </Svg>
196
+ </View>
197
+ )}
198
+ {loading && (
199
+ <Animated.View style={[styles.loadingOverlay, { opacity: overlayOpacity }]}>
200
+ <ActivityIndicator size="large" color={theme.colors.primary} />
201
+ <Text style={styles.loadingText}>Uploading {Math.floor(progress || 0)}%</Text>
202
+ </Animated.View>
203
+ )}
204
+ </View>
205
+ )}
206
+ </TouchableOpacity>
207
+ );
208
+ };
209
+
210
+ interface ImagePickerModalProps {
211
+ visible: boolean;
212
+ onClose: () => void;
213
+ onImageSelected: (result: ImagePickerResult) => void;
214
+ onError?: (error: string) => void;
215
+ options?: ImagePickerOptions;
216
+ }
217
+
218
+ export const ImagePickerModal: React.FC<ImagePickerModalProps> = ({
219
+ visible,
220
+ onClose,
221
+ onImageSelected,
222
+ onError,
223
+ options = {},
224
+ }) => {
225
+ const [isLoading, setIsLoading] = useState(false);
226
+
227
+ const handleCameraPress = async () => {
228
+ try {
229
+ setIsLoading(true);
230
+ const result = await pickImage({ ...options, source: 'camera' });
231
+
232
+ if (result) {
233
+ const validation = validateImageFile(result);
234
+ if (!validation.isValid) {
235
+ onError?.(validation.error || 'Invalid image');
236
+ return;
237
+ }
238
+ onImageSelected(result);
239
+ onClose();
240
+ }
241
+ } catch (error) {
242
+ console.error('Camera error:', error);
243
+ onError?.('Failed to take photo. Please try again.');
244
+ } finally {
245
+ setIsLoading(false);
246
+ }
247
+ };
248
+
249
+ const handleGalleryPress = async () => {
250
+ try {
251
+ setIsLoading(true);
252
+ const result = await pickImage({ ...options, source: 'gallery' });
253
+
254
+ if (result) {
255
+ const validation = validateImageFile(result);
256
+ if (!validation.isValid) {
257
+ onError?.(validation.error || 'Invalid image');
258
+ return;
259
+ }
260
+ onImageSelected(result);
261
+ onClose();
262
+ }
263
+ } catch (error) {
264
+ console.error('Gallery error:', error);
265
+ onError?.('Failed to select image. Please try again.');
266
+ } finally {
267
+ setIsLoading(false);
268
+ }
269
+ };
270
+
271
+ return (
272
+ <Modal
273
+ visible={visible}
274
+ transparent
275
+ animationType="fade"
276
+ onRequestClose={onClose}
277
+ >
278
+ <View style={styles.modalOverlay}>
279
+ <View style={styles.modalContent}>
280
+ <Text style={styles.modalTitle}>Select Image</Text>
281
+ <Text style={styles.modalSubtitle}>Choose how you want to add an image</Text>
282
+
283
+ <View style={styles.optionsContainer}>
284
+ <TouchableOpacity
285
+ style={[styles.optionButton, isLoading && styles.disabledButton]}
286
+ onPress={handleCameraPress}
287
+ disabled={isLoading}
288
+ >
289
+ <Text style={styles.optionText}>Camera</Text>
290
+ </TouchableOpacity>
291
+
292
+ <TouchableOpacity
293
+ style={[styles.optionButton, isLoading && styles.disabledButton]}
294
+ onPress={handleGalleryPress}
295
+ disabled={isLoading}
296
+ >
297
+ <SVG.gallery height={30} width={30} style={styles.optionIcon} />
298
+ <Text style={styles.optionText}>Gallery</Text>
299
+ </TouchableOpacity>
300
+ </View>
301
+
302
+ <TouchableOpacity
303
+ style={styles.cancelButton}
304
+ onPress={onClose}
305
+ disabled={isLoading}
306
+ >
307
+ <Text style={styles.cancelButtonText}>Cancel</Text>
308
+ </TouchableOpacity>
309
+ </View>
310
+ </View>
311
+ </Modal>
312
+ );
313
+ };
314
+
315
+ const styles = StyleSheet.create({
316
+ container: {
317
+ },
318
+ imageContainer: {
319
+ position: 'relative',
320
+ },
321
+ image: {
322
+ width: moderateScale(150),
323
+ height: moderateScale(150),
324
+ borderRadius: moderateScale(75),
325
+ },
326
+ loadingOverlay: {
327
+ position: 'absolute',
328
+ top: 0,
329
+ left: 0,
330
+ right: 0,
331
+ bottom: 0,
332
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
333
+ justifyContent: 'center',
334
+ alignItems: 'center',
335
+ borderRadius: moderateScale(75),
336
+ },
337
+ progressRingContainer: {
338
+ position: 'absolute',
339
+ top: 0,
340
+ left: 0,
341
+ right: 0,
342
+ bottom: 0,
343
+ justifyContent: 'center',
344
+ alignItems: 'center',
345
+ zIndex:1
346
+ },
347
+ loadingText: {
348
+ color: theme.colors.white,
349
+ fontSize: moderateScale(12),
350
+ fontWeight: '600',
351
+ marginTop: moderateScale(8),
352
+ textAlign: 'center',
353
+ },
354
+ overlay: {
355
+ position: 'absolute',
356
+ top: 0,
357
+ left: 0,
358
+ right: 0,
359
+ bottom: 0,
360
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
361
+ justifyContent: 'center',
362
+ alignItems: 'center',
363
+ opacity: 0,
364
+ },
365
+ uploadIcon: {
366
+ tintColor: 'white',
367
+ marginBottom: 8,
368
+ },
369
+ overlayText: {
370
+ color: 'white',
371
+ fontSize: 14,
372
+ fontWeight: '500',
373
+ },
374
+ placeholderContainer: {
375
+ justifyContent: 'center',
376
+ alignItems: 'center',
377
+ padding: 20,
378
+ },
379
+ placeholderIcon: {
380
+ tintColor: '#6c757d',
381
+ marginBottom: 12,
382
+ },
383
+ placeholderText: {
384
+ fontSize: 16,
385
+ color: '#6c757d',
386
+ textAlign: 'center',
387
+ fontWeight: '500',
388
+ },
389
+ modalOverlay: {
390
+ flex: 1,
391
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
392
+ justifyContent: 'center',
393
+ alignItems: 'center',
394
+ padding: 20,
395
+ },
396
+ modalContent: {
397
+ backgroundColor: 'white',
398
+ borderRadius: 16,
399
+ padding: 24,
400
+ width: '100%',
401
+ maxWidth: 320,
402
+ },
403
+ modalTitle: {
404
+ fontSize: 20,
405
+ fontWeight: 'bold',
406
+ textAlign: 'center',
407
+ marginBottom: 8,
408
+ color: '#333',
409
+ },
410
+ modalSubtitle: {
411
+ fontSize: 14,
412
+ color: '#666',
413
+ textAlign: 'center',
414
+ marginBottom: 24,
415
+ },
416
+ optionsContainer: {
417
+ flexDirection: 'row',
418
+ justifyContent: 'space-around',
419
+ marginBottom: 24,
420
+ },
421
+ optionButton: {
422
+ alignItems: 'center',
423
+ padding: 16,
424
+ borderRadius: 12,
425
+ backgroundColor: '#f8f9fa',
426
+ minWidth: 100,
427
+ },
428
+ disabledButton: {
429
+ opacity: 0.5,
430
+ },
431
+ optionIcon: {
432
+ tintColor: '#007AFF',
433
+ marginBottom: 8,
434
+ },
435
+ optionText: {
436
+ fontSize: 14,
437
+ fontWeight: '500',
438
+ color: '#333',
439
+ },
440
+ cancelButton: {
441
+ backgroundColor: '#6c757d',
442
+ padding: 12,
443
+ borderRadius: 8,
444
+ alignItems: 'center',
445
+ },
446
+ cancelButtonText: {
447
+ color: 'white',
448
+ fontSize: 16,
449
+ fontWeight: '500',
450
+ },
451
+ });
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { View, ActivityIndicator, StyleSheet, ViewStyle } from 'react-native';
3
+ import { theme } from '../../constants';
4
+
5
+ interface LoadingSpinnerProps {
6
+ size?: 'small' | 'large';
7
+ color?: string;
8
+ style?: ViewStyle;
9
+ }
10
+
11
+ export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
12
+ size = 'large',
13
+ color = theme.colors.primary,
14
+ style,
15
+ }) => {
16
+ return (
17
+ <View style={[styles.container, style]}>
18
+ <ActivityIndicator size={size} color={color} />
19
+ </View>
20
+ );
21
+ };
22
+
23
+ const styles = StyleSheet.create({
24
+ container: {
25
+ flex: 1,
26
+ justifyContent: 'center',
27
+ alignItems: 'center',
28
+ backgroundColor: theme.colors.background,
29
+ },
30
+ });
@@ -0,0 +1,128 @@
1
+ import React, { forwardRef, useImperativeHandle, useRef } from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import { OtpInput } from 'react-native-otp-entry';
4
+ import { theme } from '../../constants';
5
+ import { moderateScale } from '../../utils/scaling';
6
+
7
+ interface OTPInputProps {
8
+ onTextChange: (text: string) => void;
9
+ onFilled?: (text: string) => void;
10
+ numberOfDigits?: number;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ export interface OTPInputRef {
15
+ clear: () => void;
16
+ focus: () => void;
17
+ }
18
+
19
+ export const OTPInputComponent = forwardRef<OTPInputRef, OTPInputProps>(({
20
+ onTextChange,
21
+ onFilled,
22
+ numberOfDigits = 4,
23
+ disabled = false,
24
+ }, ref) => {
25
+ const otpInputRef = useRef<any>(null);
26
+
27
+ useImperativeHandle(ref, () => ({
28
+ clear: () => {
29
+ if (otpInputRef.current) {
30
+ otpInputRef.current.clear();
31
+ }
32
+ },
33
+ focus: () => {
34
+ if (otpInputRef.current) {
35
+ otpInputRef.current.focus();
36
+ }
37
+ },
38
+ }));
39
+
40
+ return (
41
+ <View style={styles.container}>
42
+ <OtpInput
43
+ ref={otpInputRef}
44
+ numberOfDigits={numberOfDigits}
45
+ onTextChange={onTextChange}
46
+ onFilled={onFilled}
47
+ disabled={disabled}
48
+ focusColor={theme.colors.primary}
49
+ focusStickBlinkingDuration={500}
50
+ secureTextEntry={false}
51
+ autoFocus={true}
52
+ hideStick={false}
53
+ blurOnFilled={false}
54
+ type="numeric"
55
+ textInputProps={{
56
+ accessibilityLabel: 'One-Time Password',
57
+ accessibilityHint: 'Enter the verification code sent to your email',
58
+ }}
59
+ theme={{
60
+ containerStyle: styles.otpContainer,
61
+ pinCodeContainerStyle: styles.pinCodeContainer,
62
+ pinCodeTextStyle: styles.pinCodeText,
63
+ focusStickStyle: styles.focusStick,
64
+ focusedPinCodeContainerStyle: styles.focusedPinCodeContainer,
65
+ }}
66
+ />
67
+ </View>
68
+ );
69
+ });
70
+
71
+ OTPInputComponent.displayName = 'OTPInputComponent';
72
+
73
+ const styles = StyleSheet.create({
74
+ container: {
75
+ alignItems: 'center',
76
+ marginVertical: theme.spacing.md,
77
+ },
78
+ otpContainer: {
79
+ flexDirection: 'row',
80
+ justifyContent: 'center',
81
+ alignItems: 'center',
82
+ },
83
+ pinCodeContainer: {
84
+ width: moderateScale(70),
85
+ height: moderateScale(50),
86
+ borderRadius: moderateScale(25),
87
+ borderWidth: 1.5,
88
+ borderColor: theme.colors.border,
89
+ backgroundColor: theme.colors.background,
90
+ justifyContent: 'center',
91
+ alignItems: 'center',
92
+ marginHorizontal: moderateScale(8),
93
+ shadowColor: theme.colors.black,
94
+ shadowOffset: {
95
+ width: 0,
96
+ height: 2,
97
+ },
98
+ shadowOpacity: 0.1,
99
+ shadowRadius: 3.84,
100
+ elevation: 5,
101
+ },
102
+ pinCodeText: {
103
+ fontSize: moderateScale(20),
104
+ fontWeight: '600',
105
+ color: theme.colors.text,
106
+ textAlign: 'center',
107
+ },
108
+ focusStick: {
109
+ width: moderateScale(2),
110
+ height: moderateScale(30),
111
+ backgroundColor: theme.colors.primary,
112
+ borderRadius: moderateScale(1),
113
+ },
114
+ focusedPinCodeContainer: {
115
+ borderColor: theme.colors.primary,
116
+ borderWidth: 2,
117
+ shadowColor: theme.colors.primary,
118
+ shadowOffset: {
119
+ width: 0,
120
+ height: 0,
121
+ },
122
+ shadowOpacity: 0.3,
123
+ shadowRadius: 8,
124
+ elevation: 8,
125
+ },
126
+ });
127
+
128
+ export default OTPInputComponent;