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.
- package/lib/createApp.js +7 -2
- package/package.json +1 -1
- package/template/App.tsx +118 -0
- package/template/ReactotronConfig.js +5 -0
- package/template/android/app/build.gradle +184 -0
- package/template/android/app/debug.keystore +0 -0
- package/template/android/app/google-services.json +29 -0
- package/template/android/app/proguard-rules.pro +14 -0
- package/template/android/app/src/debug/AndroidManifest.xml +7 -0
- package/template/android/app/src/debugOptimized/AndroidManifest.xml +7 -0
- package/template/android/app/src/main/AndroidManifest.xml +28 -0
- package/template/android/app/src/main/assets/fonts/Outfit-Bold.ttf +0 -0
- package/template/android/app/src/main/assets/fonts/Outfit-Light.ttf +0 -0
- package/template/android/app/src/main/assets/fonts/Outfit-Medium.ttf +0 -0
- package/template/android/app/src/main/assets/fonts/Outfit-Regular.ttf +0 -0
- package/template/android/app/src/main/assets/fonts/Outfit-SemiBold.ttf +0 -0
- package/template/android/app/src/main/java/com/club/yakka/MainActivity.kt +61 -0
- package/template/android/app/src/main/java/com/club/yakka/MainApplication.kt +60 -0
- package/template/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
- package/template/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/template/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
- package/template/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
- package/template/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
- package/template/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
- package/template/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
- package/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
- package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
- package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
- package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- package/template/android/app/src/main/res/values/colors.xml +6 -0
- package/template/android/app/src/main/res/values/strings.xml +5 -0
- package/template/android/app/src/main/res/values/styles.xml +14 -0
- package/template/android/app/src/main/res/values-night/colors.xml +1 -0
- package/template/android/build.gradle +33 -0
- package/template/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/template/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/template/android/gradle.properties +65 -0
- package/template/android/gradlew +251 -0
- package/template/android/gradlew.bat +94 -0
- package/template/android/settings.gradle +39 -0
- package/template/app.json +69 -0
- package/template/assets/adaptive-icon.png +0 -0
- package/template/assets/adaptive-icon1.png +0 -0
- package/template/assets/app_icon.png +0 -0
- package/template/assets/favicon.png +0 -0
- package/template/assets/icon.png +0 -0
- package/template/assets/splash-icon.png +0 -0
- package/template/babel-plugin-disable-font-scaling.js +41 -0
- package/template/babel.config.js +28 -0
- package/template/firebase.json +5 -0
- package/template/index.ts +24 -0
- package/template/ios/.xcode.env +11 -0
- package/template/ios/ClubYakka/AppDelegate.swift +74 -0
- package/template/ios/ClubYakka/ClubYakka-Bridging-Header.h +3 -0
- package/template/ios/ClubYakka/ClubYakka.entitlements +8 -0
- package/template/ios/ClubYakka/GoogleService-Info.plist +30 -0
- package/template/ios/ClubYakka/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
- package/template/ios/ClubYakka/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/template/ios/ClubYakka/Images.xcassets/Contents.json +6 -0
- package/template/ios/ClubYakka/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
- package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +23 -0
- package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image.png +0 -0
- package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png +0 -0
- package/template/ios/ClubYakka/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png +0 -0
- package/template/ios/ClubYakka/Info.plist +101 -0
- package/template/ios/ClubYakka/PrivacyInfo.xcprivacy +50 -0
- package/template/ios/ClubYakka/SplashScreen.storyboard +48 -0
- package/template/ios/ClubYakka/Supporting/Expo.plist +12 -0
- package/template/ios/ClubYakka.xcodeproj/project.pbxproj +669 -0
- package/template/ios/ClubYakka.xcodeproj/xcshareddata/xcschemes/ClubYakka.xcscheme +88 -0
- package/template/ios/ClubYakka.xcworkspace/contents.xcworkspacedata +10 -0
- package/template/ios/ClubYakka.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +5 -0
- package/template/ios/Podfile +84 -0
- package/template/ios/Podfile.lock +2698 -0
- package/template/ios/Podfile.properties.json +8 -0
- package/template/metro.config.js +17 -0
- package/template/package.json +70 -0
- package/template/patches/react-native-background-upload+6.6.0.patch +704 -0
- package/template/react-native.config.js +7 -0
- package/template/src/assets/fonts/Outfit-Bold.ttf +0 -0
- package/template/src/assets/fonts/Outfit-Light.ttf +0 -0
- package/template/src/assets/fonts/Outfit-Medium.ttf +0 -0
- package/template/src/assets/fonts/Outfit-Regular.ttf +0 -0
- package/template/src/assets/fonts/Outfit-SemiBold.ttf +0 -0
- package/template/src/assets/icons/BG.svg +33 -0
- package/template/src/assets/icons/Ic_Users.svg +6 -0
- package/template/src/assets/icons/arrow-left-white.svg +4 -0
- package/template/src/assets/icons/arrow-left.svg +4 -0
- package/template/src/assets/icons/bell.svg +4 -0
- package/template/src/assets/icons/bottomSheetIcone.svg +3 -0
- package/template/src/assets/icons/call.svg +4 -0
- package/template/src/assets/icons/camera.svg +0 -0
- package/template/src/assets/icons/chatAppleGreenBG.svg +22 -0
- package/template/src/assets/icons/check.svg +4 -0
- package/template/src/assets/icons/check_Radio.svg +4 -0
- package/template/src/assets/icons/chevron-right.svg +3 -0
- package/template/src/assets/icons/clubDefauldImage.png +0 -0
- package/template/src/assets/icons/curvBackgroundView.svg +3 -0
- package/template/src/assets/icons/defaultClub.png +0 -0
- package/template/src/assets/icons/email.svg +3 -0
- package/template/src/assets/icons/emptyUser.svg +5 -0
- package/template/src/assets/icons/eye-Hide.svg +8 -0
- package/template/src/assets/icons/eye.svg +4 -0
- package/template/src/assets/icons/gallery.svg +4 -0
- package/template/src/assets/icons/home.svg +4 -0
- package/template/src/assets/icons/ic_Calendar.svg +14 -0
- package/template/src/assets/icons/ic_Calendar_blue.svg +12 -0
- package/template/src/assets/icons/ic_Calendar_white.svg +12 -0
- package/template/src/assets/icons/ic_Chat.svg +14 -0
- package/template/src/assets/icons/ic_ChatAppleGreen.svg +21 -0
- package/template/src/assets/icons/ic_ChatAppleWhite.svg +21 -0
- package/template/src/assets/icons/ic_Download.svg +6 -0
- package/template/src/assets/icons/ic_Events.svg +6 -0
- package/template/src/assets/icons/ic_HeadCoachIcon.svg +3 -0
- package/template/src/assets/icons/ic_Membership.svg +10 -0
- package/template/src/assets/icons/ic_Notification.svg +6 -0
- package/template/src/assets/icons/ic_Raffles.svg +14 -0
- package/template/src/assets/icons/ic_Referral_Members.svg +12 -0
- package/template/src/assets/icons/ic_Shop.svg +13 -0
- package/template/src/assets/icons/ic_Teams.svg +8 -0
- package/template/src/assets/icons/ic_Volunteer.svg +9 -0
- package/template/src/assets/icons/ic_add.svg +3 -0
- package/template/src/assets/icons/ic_addCircle.svg +5 -0
- package/template/src/assets/icons/ic_chatSend.svg +4 -0
- package/template/src/assets/icons/ic_chat_blue_bg.svg +22 -0
- package/template/src/assets/icons/ic_clock_blue.svg +4 -0
- package/template/src/assets/icons/ic_delete.svg +8 -0
- package/template/src/assets/icons/ic_more.svg +5 -0
- package/template/src/assets/icons/ic_mute.svg +10 -0
- package/template/src/assets/icons/ic_pdf.svg +3 -0
- package/template/src/assets/icons/ic_pending_AppleGreen.svg +11 -0
- package/template/src/assets/icons/ic_right_appleGreen.svg +11 -0
- package/template/src/assets/icons/ic_unread_chat_blue_bg.svg +23 -0
- package/template/src/assets/icons/ic_volunteer_Member.svg +8 -0
- package/template/src/assets/icons/index.ts +144 -0
- package/template/src/assets/icons/location-blue.svg +4 -0
- package/template/src/assets/icons/location-white.svg +4 -0
- package/template/src/assets/icons/location.svg +4 -0
- package/template/src/assets/icons/lock.svg +5 -0
- package/template/src/assets/icons/log-out.svg +5 -0
- package/template/src/assets/icons/login_logo.svg +9 -0
- package/template/src/assets/icons/mail.svg +4 -0
- package/template/src/assets/icons/or_saprater.svg +5 -0
- package/template/src/assets/icons/password.svg +4 -0
- package/template/src/assets/icons/profile-appleGreen.svg +6 -0
- package/template/src/assets/icons/search.svg +3 -0
- package/template/src/assets/icons/settings.svg +4 -0
- package/template/src/assets/icons/success.svg +4 -0
- package/template/src/assets/icons/unCheck_Radio.svg +3 -0
- package/template/src/assets/icons/uncheck.svg +3 -0
- package/template/src/assets/icons/upload_Image.svg +6 -0
- package/template/src/assets/icons/upload_Image_Member.svg +6 -0
- package/template/src/assets/icons/user.svg +4 -0
- package/template/src/assets/icons/wifi.svg +1 -0
- package/template/src/assets/images/Splash.png +0 -0
- package/template/src/assets/images/SplashLogo.png +0 -0
- package/template/src/assets/images/background.png +0 -0
- package/template/src/assets/images/clubDefauldImage.png +0 -0
- package/template/src/assets/images/index.tsx +9 -0
- package/template/src/assets/index.ts +1 -0
- package/template/src/components/common/AddMemberModal.tsx +543 -0
- package/template/src/components/common/AppLoader.tsx +91 -0
- package/template/src/components/common/Button.tsx +173 -0
- package/template/src/components/common/ClubCard.tsx +248 -0
- package/template/src/components/common/Icon.tsx +93 -0
- package/template/src/components/common/IconAlt.tsx +65 -0
- package/template/src/components/common/IconButton.tsx +92 -0
- package/template/src/components/common/ImagePicker.tsx +451 -0
- package/template/src/components/common/LoadingSpinner.tsx +30 -0
- package/template/src/components/common/OTPInput.tsx +128 -0
- package/template/src/components/common/ReminderCalendar.tsx +129 -0
- package/template/src/components/common/ReminderCardItem.tsx +91 -0
- package/template/src/components/common/SafeAreaWrapper.tsx +27 -0
- package/template/src/components/common/SetReminderModal.tsx +308 -0
- package/template/src/components/common/SlowInternet.js +57 -0
- package/template/src/components/common/TeamCard.tsx +297 -0
- package/template/src/components/common/TextInput.tsx +227 -0
- package/template/src/components/common/ToastConfig.tsx +103 -0
- package/template/src/components/common/ToastManager.ts +86 -0
- package/template/src/components/common/UploadProgressModal.tsx +284 -0
- package/template/src/components/common/WrapperContainer.tsx +39 -0
- package/template/src/components/common/index.ts +19 -0
- package/template/src/constants/Constants.tsx +7 -0
- package/template/src/constants/Fonts.tsx +30 -0
- package/template/src/constants/index.ts +45 -0
- package/template/src/constants/strings.ts +211 -0
- package/template/src/constants/theme.ts +72 -0
- package/template/src/contexts/AuthContext.tsx +268 -0
- package/template/src/contexts/index.ts +2 -0
- package/template/src/hooks/index.ts +3 -0
- package/template/src/hooks/useImageUpload.ts +199 -0
- package/template/src/index.ts +8 -0
- package/template/src/navigation/AuthStack.tsx +67 -0
- package/template/src/navigation/MainStack.tsx +183 -0
- package/template/src/navigation/MiddleStack.tsx +35 -0
- package/template/src/navigation/RootNavigator.tsx +101 -0
- package/template/src/navigation/index.ts +5 -0
- package/template/src/navigation/navigationRef.ts +5 -0
- package/template/src/providers/QueryProvider.tsx +30 -0
- package/template/src/screens/DetailsScreen.tsx +271 -0
- package/template/src/screens/HomeScreen.tsx +736 -0
- package/template/src/screens/ProfileScreen.tsx +202 -0
- package/template/src/screens/SettingsScreen.tsx +253 -0
- package/template/src/screens/SportHubScreen.tsx +280 -0
- package/template/src/screens/auth/AddMamber.tsx +428 -0
- package/template/src/screens/auth/ForgotPasswordScreen.tsx +176 -0
- package/template/src/screens/auth/LoginScreen.tsx +286 -0
- package/template/src/screens/auth/OTPVerifyScreen.tsx +359 -0
- package/template/src/screens/auth/RegisterScreen.tsx +430 -0
- package/template/src/screens/auth/SuccessScreen.tsx +201 -0
- package/template/src/screens/auth/WalcomeScreen.tsx +274 -0
- package/template/src/screens/auth/index.ts +8 -0
- package/template/src/screens/chat/ChatScreen.tsx +1819 -0
- package/template/src/screens/chat/ChatThreadsScreen.tsx +360 -0
- package/template/src/screens/chat/ReportMessageScreen.tsx +238 -0
- package/template/src/screens/clubs/Announcements.tsx +426 -0
- package/template/src/screens/clubs/BuyRaffleTicketsScreen.tsx +568 -0
- package/template/src/screens/clubs/ClubDeteils.tsx +497 -0
- package/template/src/screens/clubs/JoinClub.tsx +841 -0
- package/template/src/screens/events/EventScreen.tsx +460 -0
- package/template/src/screens/index.ts +42 -0
- package/template/src/screens/raffles/MyReferralMembersScreen.tsx +758 -0
- package/template/src/screens/raffles/RaffleDetailsScreen.tsx +762 -0
- package/template/src/screens/raffles/RafflesScreen.tsx +495 -0
- package/template/src/screens/raffles/SetRaffleReminderScreen.tsx +390 -0
- package/template/src/screens/teams/JoinTeamScreen.tsx +464 -0
- package/template/src/screens/teams/MyTeamDetailsScreen.tsx +979 -0
- package/template/src/screens/teams/MyTeamScreen.tsx +568 -0
- package/template/src/screens/teams/PendingRequestsScreen.tsx +426 -0
- package/template/src/screens/volunteerOpportunities/SetReminderScreen.tsx +631 -0
- package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesDetailsScreen.tsx +1049 -0
- package/template/src/screens/volunteerOpportunities/VolunteerOpportunitiesScreen.tsx +608 -0
- package/template/src/services/api.ts +167 -0
- package/template/src/services/authService.ts +422 -0
- package/template/src/services/index.ts +5 -0
- package/template/src/services/mainServices.ts +1963 -0
- package/template/src/stores/authStore.ts +159 -0
- package/template/src/stores/index.ts +2 -0
- package/template/src/types/index.ts +85 -0
- package/template/src/types/navigation.ts +206 -0
- package/template/src/utils/AzureUploaderService.ts +371 -0
- package/template/src/utils/ClubSearchManager.ts +222 -0
- package/template/src/utils/NotificationManager.ts +503 -0
- package/template/src/utils/UploadDebugUtil.ts +107 -0
- package/template/src/utils/imagePicker.ts +321 -0
- package/template/src/utils/index.ts +111 -0
- package/template/src/utils/permissions.ts +277 -0
- package/template/src/utils/scaling.ts +14 -0
- package/template/src/utils/storage.ts +247 -0
- package/template/src/utils/usePermissions.ts +275 -0
- package/template/src/utils/validation.ts +340 -0
- package/template/tsconfig.json +50 -0
- 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
|
+
};
|