@gaozh1024/photo-picker 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/screens/PhotoAlbumScreen.tsx","../src/internal/photoPickerCallbackRegistry.ts","../src/native.ts","../src/utils/photoPickerFlow.ts","../src/screens/PhotoCropScreen.tsx"],"sourcesContent":["export { MEDIA_PICKER_ROUTES, mediaPickerColors } from './constants';\nexport { PhotoAlbumScreen } from './screens/PhotoAlbumScreen';\nexport { PhotoCropScreen } from './screens/PhotoCropScreen';\nexport { clearPickerCache, isPhotoPickerNativeError, pickMedia, releaseMedia } from './native';\nexport {\n clearPhotoAlbumCompleteCallback,\n getPhotoAlbumCompleteCallback,\n registerPhotoAlbumCompleteCallback,\n} from './internal/photoPickerCallbackRegistry';\nexport {\n createCroppedPhotoAlbumItem,\n formatPhotoPickerText,\n normalizeOpenOptions,\n normalizePickerMediaType,\n resolvePhotoPickerUiConfig,\n} from './utils/photoPickerFlow';\nexport type {\n MediaPickerRouteNames,\n PhotoAlbumCropOptions,\n PhotoAlbumItem,\n PhotoAlbumOpenMediaType,\n PhotoAlbumOpenOptions,\n PhotoAlbumScreenProps,\n PhotoAlbumUiConfig,\n PhotoAlbumUiTexts,\n PhotoAlbumUiTheme,\n PhotoCropScreenProps,\n PhotoPickerOptions,\n PhotoPickerNativeError,\n PhotoPickerNativeErrorCode,\n PhotoPickerResult,\n PickerBackend,\n PickerMetadata,\n PickerSource,\n} from './types';\n","export const MEDIA_PICKER_ROUTES = {\n PHOTO_ALBUM: 'PhotoAlbum',\n PHOTO_CROP: 'PhotoCrop',\n} as const;\n\nexport const mediaPickerColors = {\n primary: { 500: '#3b82f6' },\n slate: {\n 50: '#f8fafc',\n 100: '#f1f5f9',\n 200: '#e2e8f0',\n 700: '#334155',\n 800: '#1e293b',\n 900: '#0f172a',\n 950: '#020617',\n },\n} as const;\n","import React from 'react';\nimport { ActivityIndicator, Alert, Pressable, StyleSheet, Text, View } from 'react-native';\nimport { MEDIA_PICKER_ROUTES } from '../constants';\nimport {\n clearPhotoAlbumCompleteCallback,\n getPhotoAlbumCompleteCallback,\n} from '../internal/photoPickerCallbackRegistry';\nimport { pickMedia } from '../native';\nimport type { MediaPickerRouteNames, PhotoAlbumItem, PhotoAlbumScreenProps } from '../types';\nimport {\n formatPhotoPickerText,\n normalizeOpenOptions,\n resolvePhotoPickerUiConfig,\n} from '../utils/photoPickerFlow';\n\n/**\n * Compatibility route for applications that previously navigated to PhotoAlbum.\n * It immediately opens the system picker; the app never enumerates the device library.\n */\nexport function PhotoAlbumScreen({ route, navigation }: PhotoAlbumScreenProps) {\n const startedRef = React.useRef(false);\n const [errorMessage, setErrorMessage] = React.useState<string>();\n const callbackId = route?.params?.callbackId;\n const options = React.useMemo(\n () =>\n normalizeOpenOptions({\n ...route?.params?.options,\n maxSelection: route?.params?.options?.maxSelection ?? route?.params?.maxSelection,\n allowsMultipleSelection:\n route?.params?.options?.allowsMultipleSelection ?? route?.params?.allowsMultipleSelection,\n mediaType:\n route?.params?.options?.mediaType ??\n (route?.params?.mediaTypes?.length === 1 ? route.params.mediaTypes[0] : 'all'),\n }),\n [\n route?.params?.allowsMultipleSelection,\n route?.params?.maxSelection,\n route?.params?.mediaTypes,\n route?.params?.options,\n ]\n );\n const uiConfig = React.useMemo(\n () =>\n resolvePhotoPickerUiConfig({\n ...route?.params?.uiConfig,\n ...options.uiConfig,\n texts: {\n ...route?.params?.uiConfig?.texts,\n ...options.uiConfig?.texts,\n },\n }),\n [options.uiConfig, route?.params?.uiConfig]\n );\n const routeNames: MediaPickerRouteNames = React.useMemo(\n () => ({\n photoAlbum: route?.params?.routeNames?.photoAlbum ?? MEDIA_PICKER_ROUTES.PHOTO_ALBUM,\n photoCrop: route?.params?.routeNames?.photoCrop ?? MEDIA_PICKER_ROUTES.PHOTO_CROP,\n }),\n [route?.params?.routeNames?.photoAlbum, route?.params?.routeNames?.photoCrop]\n );\n\n const close = React.useCallback(() => {\n clearPhotoAlbumCompleteCallback(callbackId);\n navigation?.goBack();\n }, [callbackId, navigation]);\n\n const complete = React.useCallback(\n (assets: PhotoAlbumItem[]) => {\n const overLimitVideo =\n options.maxVideoDuration == null\n ? undefined\n : assets.find(\n asset =>\n asset.mediaType === 'video' && (asset.duration ?? 0) > options.maxVideoDuration!\n );\n\n if (overLimitVideo) {\n Alert.alert(\n uiConfig.texts.durationLimitAlertTitle,\n formatPhotoPickerText(uiConfig.texts.durationLimitAlertMessage, {\n maxDuration: options.maxVideoDuration!,\n })\n );\n return;\n }\n\n if (options.crop) {\n const photo = assets[0];\n if (!photo) return;\n navigation?.navigate?.(routeNames.photoCrop, {\n photo,\n crop: options.crop,\n quality: options.quality,\n callbackId,\n routeNames,\n uiConfig: options.uiConfig,\n });\n return;\n }\n\n getPhotoAlbumCompleteCallback(callbackId)?.(assets);\n clearPhotoAlbumCompleteCallback(callbackId);\n navigation?.goBack();\n },\n [\n callbackId,\n navigation,\n options,\n routeNames,\n uiConfig.texts.durationLimitAlertMessage,\n uiConfig.texts.durationLimitAlertTitle,\n ]\n );\n\n const openPicker = React.useCallback(async () => {\n setErrorMessage(undefined);\n try {\n const result = await pickMedia({\n mediaType: options.mediaType,\n maxSelection: options.maxSelection,\n allowsMultipleSelection: options.allowsMultipleSelection,\n cacheMode: 'copy',\n });\n if (result.cancelled) {\n close();\n return;\n }\n complete(result.assets);\n } catch (error) {\n setErrorMessage(error instanceof Error ? error.message : uiConfig.texts.openAlbumError);\n }\n }, [\n close,\n complete,\n options.allowsMultipleSelection,\n options.maxSelection,\n options.mediaType,\n uiConfig.texts.openAlbumError,\n ]);\n\n React.useEffect(() => {\n if (startedRef.current) return;\n startedRef.current = true;\n void openPicker();\n }, [openPicker]);\n\n React.useEffect(\n () => () => {\n if (!options.crop) {\n clearPhotoAlbumCompleteCallback(callbackId);\n }\n },\n [callbackId, options.crop]\n );\n\n return (\n <View style={styles.container}>\n <ActivityIndicator size=\"large\" color=\"#3b82f6\" />\n <Text style={styles.title}>\n {errorMessage ? uiConfig.texts.openAlbumError : uiConfig.texts.albumTitle}\n </Text>\n {errorMessage ? <Text style={styles.message}>{errorMessage}</Text> : null}\n {errorMessage ? (\n <View style={styles.actions}>\n <Pressable onPress={() => void openPicker()} style={styles.primaryButton}>\n <Text style={styles.primaryButtonText}>重试</Text>\n </Pressable>\n <Pressable onPress={close} style={styles.secondaryButton}>\n <Text style={styles.secondaryButtonText}>取消</Text>\n </Pressable>\n </View>\n ) : null}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n padding: 24,\n backgroundColor: '#f8fafc',\n },\n title: { marginTop: 16, color: '#0f172a', fontSize: 16, fontWeight: '600' },\n message: { marginTop: 8, color: '#64748b', fontSize: 13, lineHeight: 20, textAlign: 'center' },\n actions: { flexDirection: 'row', gap: 12, marginTop: 20 },\n primaryButton: {\n borderRadius: 8,\n backgroundColor: '#2563eb',\n paddingHorizontal: 18,\n paddingVertical: 10,\n },\n primaryButtonText: { color: '#ffffff', fontSize: 14, fontWeight: '600' },\n secondaryButton: {\n borderRadius: 8,\n borderWidth: 1,\n borderColor: '#cbd5e1',\n paddingHorizontal: 18,\n paddingVertical: 10,\n },\n secondaryButtonText: { color: '#334155', fontSize: 14, fontWeight: '600' },\n});\n","import type { PhotoAlbumItem } from '../types';\n\ntype PhotoPickerCompleteCallback = (photos: PhotoAlbumItem[]) => void;\n\nconst callbackRegistry = new Map<string, PhotoPickerCompleteCallback>();\n\nexport function registerPhotoAlbumCompleteCallback(callback: PhotoPickerCompleteCallback): string {\n const callbackId = `photo-picker-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n callbackRegistry.set(callbackId, callback);\n return callbackId;\n}\n\nexport function getPhotoAlbumCompleteCallback(callbackId?: string | null) {\n return callbackId ? callbackRegistry.get(callbackId) : undefined;\n}\n\nexport function clearPhotoAlbumCompleteCallback(callbackId?: string | null) {\n if (callbackId) callbackRegistry.delete(callbackId);\n}\n","import { Platform } from 'react-native';\nimport { requireNativeModule } from 'expo-modules-core';\nimport type {\n PhotoPickerNativeError,\n PhotoPickerNativeErrorCode,\n PhotoPickerOptions,\n PhotoPickerResult,\n} from './types';\n\nexport type { PhotoPickerNativeError, PhotoPickerNativeErrorCode } from './types';\n\ntype PhotoPickerNativeModule = {\n pickMedia(options: PhotoPickerOptions): Promise<PhotoPickerResult>;\n releaseMedia(uris: string[]): Promise<void>;\n clearPickerCache(): Promise<void>;\n};\n\nfunction getNativeModule(): PhotoPickerNativeModule {\n if (Platform.OS !== 'android') {\n throw new Error(\n '@gaozh1024/photo-picker currently provides the native system picker on Android only.'\n );\n }\n\n try {\n return requireNativeModule<PhotoPickerNativeModule>('PhotoPickerModule');\n } catch {\n throw new Error(\n 'PhotoPickerModule is unavailable. Rebuild the Android app after installing @gaozh1024/photo-picker.'\n );\n }\n}\n\nexport function pickMedia(options: PhotoPickerOptions = {}): Promise<PhotoPickerResult> {\n return getNativeModule().pickMedia(options);\n}\n\nexport function isPhotoPickerNativeError(\n error: unknown,\n code?: PhotoPickerNativeErrorCode\n): error is PhotoPickerNativeError {\n if (!error || typeof error !== 'object' || !('code' in error)) return false;\n const errorCode = (error as { code?: unknown }).code;\n return (\n (errorCode === 'PICKER_BUSY' ||\n errorCode === 'PICKER_LAUNCH_FAILED' ||\n errorCode === 'PICKER_SELECTION_LIMIT_UNSUPPORTED') &&\n (code === undefined || errorCode === code)\n );\n}\n\nexport function releaseMedia(uris: string[]) {\n return getNativeModule().releaseMedia(uris);\n}\n\nexport function clearPickerCache() {\n return getNativeModule().clearPickerCache();\n}\n","import type {\n PhotoAlbumCropOptions,\n PhotoAlbumItem,\n PhotoAlbumOpenOptions,\n PhotoAlbumOpenMediaType,\n PhotoAlbumUiConfig,\n PhotoAlbumUiTexts,\n} from '../types';\n\nconst DEFAULT_TEXTS: Required<\n Pick<\n PhotoAlbumUiTexts,\n | 'albumTitle'\n | 'cropTitle'\n | 'cropConfirmButton'\n | 'cropSavingButton'\n | 'cropCircleHint'\n | 'cropRectHint'\n | 'cropMissingPhoto'\n | 'durationLimitAlertTitle'\n | 'durationLimitAlertMessage'\n | 'openAlbumError'\n >\n> = {\n albumTitle: '选择媒体',\n cropTitle: '裁剪图片',\n cropConfirmButton: '完成',\n cropSavingButton: '保存中',\n cropCircleHint: '拖动和缩放图片,使主体位于圆形区域内',\n cropRectHint: '拖动和缩放图片,调整裁剪区域',\n cropMissingPhoto: '未找到可裁剪图片',\n durationLimitAlertTitle: '提示',\n durationLimitAlertMessage: '视频时长不能超过 {maxDuration} 秒',\n openAlbumError: '打开系统选择器失败',\n};\n\nexport function normalizeOpenOptions(\n options?: PhotoAlbumOpenOptions\n): Required<Pick<PhotoAlbumOpenOptions, 'maxSelection' | 'mediaType' | 'allowsMultipleSelection'>> &\n PhotoAlbumOpenOptions {\n const requestedMaxSelection = options?.maxSelection ?? 9;\n const maxSelection = Number.isFinite(requestedMaxSelection)\n ? Math.max(1, Math.floor(requestedMaxSelection))\n : 1;\n const crop = options?.crop;\n const allowsMultipleSelection = crop\n ? false\n : (options?.allowsMultipleSelection ?? maxSelection > 1);\n\n return {\n ...options,\n crop,\n maxSelection: crop ? 1 : allowsMultipleSelection ? maxSelection : 1,\n mediaType: crop ? 'photo' : (options?.mediaType ?? 'all'),\n allowsMultipleSelection,\n };\n}\n\nexport function resolvePhotoPickerUiConfig(uiConfig?: PhotoAlbumUiConfig) {\n return {\n texts: {\n ...DEFAULT_TEXTS,\n ...uiConfig?.texts,\n },\n };\n}\n\nexport function formatPhotoPickerText(\n template: string,\n variables: Record<string, string | number>\n) {\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => String(variables[key] ?? ''));\n}\n\nexport function createCroppedPhotoAlbumItem(\n photo: PhotoAlbumItem,\n manipulated: { uri: string; width: number; height: number },\n crop?: PhotoAlbumCropOptions\n): PhotoAlbumItem {\n const baseName = photo.filename ?? photo.fileName ?? 'photo';\n const croppedName = `${baseName.replace(/\\.[^.]+$/, '')}-cropped.jpg`;\n return {\n ...photo,\n id: `${photo.id}-crop-${Date.now()}`,\n uri: manipulated.uri,\n localUri: manipulated.uri,\n filename: croppedName,\n fileName: croppedName,\n mimeType: 'image/jpeg',\n width: manipulated.width,\n height: manipulated.height,\n edited: true,\n crop: {\n ...crop,\n width: manipulated.width,\n height: manipulated.height,\n },\n };\n}\n\nexport function normalizePickerMediaType(\n mediaType?: PhotoAlbumOpenMediaType\n): PhotoAlbumOpenMediaType {\n return mediaType === 'photo' || mediaType === 'video' || mediaType === 'all' ? mediaType : 'all';\n}\n","import React from 'react';\nimport { Dimensions, Pressable, StyleSheet, Text, View } from 'react-native';\nimport { useIsFocused } from '@react-navigation/native';\nimport { CropZoom, type CropZoomRefType } from 'react-native-zoom-toolkit';\nimport { Image } from 'expo-image';\nimport { SaveFormat, manipulateAsync } from 'expo-image-manipulator';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport {\n clearPhotoAlbumCompleteCallback,\n getPhotoAlbumCompleteCallback,\n} from '../internal/photoPickerCallbackRegistry';\nimport type { PhotoCropScreenProps } from '../types';\nimport { createCroppedPhotoAlbumItem, resolvePhotoPickerUiConfig } from '../utils/photoPickerFlow';\n\nconst { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');\nconst HEADER_ROW_HEIGHT = 48;\nconst FOOTER_MIN_HEIGHT = 52;\nconst CONTENT_HORIZONTAL_PADDING = 16;\nconst CONTENT_VERTICAL_PADDING = 12;\n\nfunction getCropBoxSize(aspect: [number, number], maxWidth: number, maxHeight: number) {\n const ratio = aspect[0] / aspect[1];\n let width = maxWidth;\n let height = width / ratio;\n if (height > maxHeight) {\n height = maxHeight;\n width = height * ratio;\n }\n return { width: Math.floor(width), height: Math.floor(height) };\n}\n\nfunction getImageDisplaySize(\n imageWidth: number,\n imageHeight: number,\n cropWidth: number,\n cropHeight: number\n) {\n const imageRatio = imageWidth / imageHeight;\n const cropRatio = cropWidth / cropHeight;\n return imageRatio > cropRatio\n ? { width: cropHeight * imageRatio, height: cropHeight }\n : { width: cropWidth, height: cropWidth / imageRatio };\n}\n\nexport function PhotoCropScreen({ route, navigation }: PhotoCropScreenProps) {\n const insets = useSafeAreaInsets();\n const isFocused = useIsFocused();\n const cropRef = React.useRef<CropZoomRefType>(null);\n const [saving, setSaving] = React.useState(false);\n const [closing, setClosing] = React.useState(false);\n const [contentLayout, setContentLayout] = React.useState({ width: 0, height: 0 });\n const photo = route?.params?.photo;\n const cropOptions = route?.params?.crop;\n const callbackId = route?.params?.callbackId;\n const uiConfig = React.useMemo(\n () => resolvePhotoPickerUiConfig(route?.params?.uiConfig),\n [route?.params?.uiConfig]\n );\n const quality = route?.params?.quality ?? cropOptions?.quality ?? 1;\n const aspect = cropOptions?.aspect ?? ([1, 1] as [number, number]);\n const isCircleCrop = cropOptions?.shape === 'circle';\n const headerHeight = insets.top + HEADER_ROW_HEIGHT;\n const footerHeight = Math.max(insets.bottom, 16) + FOOTER_MIN_HEIGHT;\n const cropBox = React.useMemo(\n () =>\n getCropBoxSize(\n aspect,\n SCREEN_WIDTH - CONTENT_HORIZONTAL_PADDING * 2,\n SCREEN_HEIGHT - headerHeight - footerHeight - CONTENT_VERTICAL_PADDING * 2\n ),\n [aspect, footerHeight, headerHeight]\n );\n const cropFramePosition = React.useMemo(\n () => ({\n left: Math.max(0, (contentLayout.width - cropBox.width) / 2),\n top: Math.max(0, (contentLayout.height - cropBox.height) / 2),\n }),\n [contentLayout.height, contentLayout.width, cropBox.height, cropBox.width]\n );\n const imageSize = React.useMemo(\n () =>\n !photo\n ? { width: cropBox.width, height: cropBox.height }\n : getImageDisplaySize(\n Math.max(photo.width, 1),\n Math.max(photo.height, 1),\n cropBox.width,\n cropBox.height\n ),\n [cropBox.height, cropBox.width, photo]\n );\n\n const closeFlow = React.useCallback(\n (count = 1) => {\n if (closing) return;\n setClosing(true);\n requestAnimationFrame(() => {\n if (count > 1 && navigation?.pop) navigation.pop(count);\n else navigation?.goBack();\n });\n },\n [closing, navigation]\n );\n\n const handleCancel = React.useCallback(() => {\n clearPhotoAlbumCompleteCallback(callbackId);\n closeFlow();\n }, [callbackId, closeFlow]);\n\n const handleConfirm = React.useCallback(async () => {\n if (!photo || !cropRef.current || saving) return;\n setSaving(true);\n try {\n const result = cropRef.current.crop();\n const manipulated = await manipulateAsync(photo.uri, [{ crop: result.crop }], {\n compress: quality,\n format: SaveFormat.JPEG,\n });\n const croppedPhoto = createCroppedPhotoAlbumItem(photo, manipulated, cropOptions);\n getPhotoAlbumCompleteCallback(callbackId)?.([croppedPhoto]);\n clearPhotoAlbumCompleteCallback(callbackId);\n closeFlow(2);\n } catch (error) {\n console.error('[photo-picker] crop failed', error);\n } finally {\n setSaving(false);\n }\n }, [callbackId, closeFlow, cropOptions, photo, quality, saving]);\n\n if (!photo) {\n return (\n <View style={styles.empty}>\n <Text style={styles.emptyText}>{uiConfig.texts.cropMissingPhoto}</Text>\n </View>\n );\n }\n\n const footerHint = isCircleCrop ? uiConfig.texts.cropCircleHint : uiConfig.texts.cropRectHint;\n const showContent = isFocused && !closing;\n\n return (\n <View style={styles.screen}>\n <View style={[styles.header, { height: headerHeight, paddingTop: insets.top }]}>\n <Pressable onPress={handleCancel} style={styles.headerButton} accessibilityRole=\"button\">\n <Text style={styles.headerIcon}>×</Text>\n </Pressable>\n <Text style={styles.headerTitle}>{uiConfig.texts.cropTitle}</Text>\n <Pressable\n onPress={handleConfirm}\n style={styles.headerButton}\n disabled={saving}\n accessibilityRole=\"button\"\n >\n <Text style={styles.headerAction}>\n {saving ? uiConfig.texts.cropSavingButton : uiConfig.texts.cropConfirmButton}\n </Text>\n </Pressable>\n </View>\n <View\n style={styles.content}\n onLayout={({ nativeEvent }) => setContentLayout(nativeEvent.layout)}\n >\n {showContent ? (\n <CropZoom\n ref={cropRef}\n cropSize={cropBox}\n resolution={{ width: Math.max(photo.width, 1), height: Math.max(photo.height, 1) }}\n >\n <Image\n source={{ uri: photo.uri }}\n style={imageSize}\n contentFit=\"cover\"\n transition={0}\n cachePolicy=\"memory-disk\"\n />\n </CropZoom>\n ) : null}\n {showContent && contentLayout.width > 0 ? (\n <View pointerEvents=\"none\" style={styles.overlay}>\n <View\n style={[styles.mask, { left: 0, top: 0, right: 0, height: cropFramePosition.top }]}\n />\n <View\n style={[\n styles.mask,\n {\n left: 0,\n top: cropFramePosition.top,\n width: cropFramePosition.left,\n height: cropBox.height,\n },\n ]}\n />\n <View\n style={[\n styles.mask,\n {\n left: cropFramePosition.left + cropBox.width,\n top: cropFramePosition.top,\n right: 0,\n height: cropBox.height,\n },\n ]}\n />\n <View\n style={[\n styles.mask,\n { left: 0, right: 0, top: cropFramePosition.top + cropBox.height, bottom: 0 },\n ]}\n />\n <View\n style={[\n styles.cropFrame,\n {\n width: cropBox.width,\n height: cropBox.height,\n left: cropFramePosition.left,\n top: cropFramePosition.top,\n borderRadius: isCircleCrop ? cropBox.width / 2 : 16,\n },\n ]}\n />\n </View>\n ) : null}\n </View>\n <View\n style={[\n styles.footer,\n { minHeight: footerHeight, paddingBottom: Math.max(insets.bottom, 16) },\n ]}\n >\n <Text style={styles.hint}>{footerHint}</Text>\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n screen: { flex: 1, backgroundColor: '#000000' },\n empty: { flex: 1, justifyContent: 'center', alignItems: 'center' },\n emptyText: { color: '#334155', fontSize: 16 },\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n paddingHorizontal: 12,\n backgroundColor: '#000000',\n },\n headerButton: { minWidth: 52, height: 40, justifyContent: 'center', alignItems: 'center' },\n headerTitle: { color: '#ffffff', fontSize: 18, fontWeight: '600' },\n headerIcon: { color: '#ffffff', fontSize: 30, lineHeight: 32 },\n headerAction: { color: '#ffffff', fontSize: 15, fontWeight: '600' },\n content: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n overflow: 'hidden',\n paddingHorizontal: CONTENT_HORIZONTAL_PADDING,\n paddingVertical: CONTENT_VERTICAL_PADDING,\n backgroundColor: '#000000',\n },\n overlay: { ...StyleSheet.absoluteFillObject },\n mask: { position: 'absolute', backgroundColor: 'rgba(0,0,0,0.58)' },\n cropFrame: { position: 'absolute', borderWidth: 2, borderColor: '#ffffff' },\n footer: {\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: 16,\n paddingTop: 12,\n backgroundColor: '#000000',\n },\n hint: { color: 'rgba(255,255,255,0.8)' },\n});\n"],"mappings":"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","names":["import_react_native","React","import_react","import_react_native","import_native","import_jsx_runtime","React","styles"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,568 @@
1
+ // src/constants.ts
2
+ var MEDIA_PICKER_ROUTES = {
3
+ PHOTO_ALBUM: "PhotoAlbum",
4
+ PHOTO_CROP: "PhotoCrop"
5
+ };
6
+ var mediaPickerColors = {
7
+ primary: { 500: "#3b82f6" },
8
+ slate: {
9
+ 50: "#f8fafc",
10
+ 100: "#f1f5f9",
11
+ 200: "#e2e8f0",
12
+ 700: "#334155",
13
+ 800: "#1e293b",
14
+ 900: "#0f172a",
15
+ 950: "#020617"
16
+ }
17
+ };
18
+
19
+ // src/screens/PhotoAlbumScreen.tsx
20
+ import React from "react";
21
+ import { ActivityIndicator, Alert, Pressable, StyleSheet, Text, View } from "react-native";
22
+
23
+ // src/internal/photoPickerCallbackRegistry.ts
24
+ var callbackRegistry = /* @__PURE__ */ new Map();
25
+ function registerPhotoAlbumCompleteCallback(callback) {
26
+ const callbackId = `photo-picker-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
27
+ callbackRegistry.set(callbackId, callback);
28
+ return callbackId;
29
+ }
30
+ function getPhotoAlbumCompleteCallback(callbackId) {
31
+ return callbackId ? callbackRegistry.get(callbackId) : void 0;
32
+ }
33
+ function clearPhotoAlbumCompleteCallback(callbackId) {
34
+ if (callbackId) callbackRegistry.delete(callbackId);
35
+ }
36
+
37
+ // src/native.ts
38
+ import { Platform } from "react-native";
39
+ import { requireNativeModule } from "expo-modules-core";
40
+ function getNativeModule() {
41
+ if (Platform.OS !== "android") {
42
+ throw new Error(
43
+ "@gaozh1024/photo-picker currently provides the native system picker on Android only."
44
+ );
45
+ }
46
+ try {
47
+ return requireNativeModule("PhotoPickerModule");
48
+ } catch {
49
+ throw new Error(
50
+ "PhotoPickerModule is unavailable. Rebuild the Android app after installing @gaozh1024/photo-picker."
51
+ );
52
+ }
53
+ }
54
+ function pickMedia(options = {}) {
55
+ return getNativeModule().pickMedia(options);
56
+ }
57
+ function isPhotoPickerNativeError(error, code) {
58
+ if (!error || typeof error !== "object" || !("code" in error)) return false;
59
+ const errorCode = error.code;
60
+ return (errorCode === "PICKER_BUSY" || errorCode === "PICKER_LAUNCH_FAILED" || errorCode === "PICKER_SELECTION_LIMIT_UNSUPPORTED") && (code === void 0 || errorCode === code);
61
+ }
62
+ function releaseMedia(uris) {
63
+ return getNativeModule().releaseMedia(uris);
64
+ }
65
+ function clearPickerCache() {
66
+ return getNativeModule().clearPickerCache();
67
+ }
68
+
69
+ // src/utils/photoPickerFlow.ts
70
+ var DEFAULT_TEXTS = {
71
+ albumTitle: "\u9009\u62E9\u5A92\u4F53",
72
+ cropTitle: "\u88C1\u526A\u56FE\u7247",
73
+ cropConfirmButton: "\u5B8C\u6210",
74
+ cropSavingButton: "\u4FDD\u5B58\u4E2D",
75
+ cropCircleHint: "\u62D6\u52A8\u548C\u7F29\u653E\u56FE\u7247\uFF0C\u4F7F\u4E3B\u4F53\u4F4D\u4E8E\u5706\u5F62\u533A\u57DF\u5185",
76
+ cropRectHint: "\u62D6\u52A8\u548C\u7F29\u653E\u56FE\u7247\uFF0C\u8C03\u6574\u88C1\u526A\u533A\u57DF",
77
+ cropMissingPhoto: "\u672A\u627E\u5230\u53EF\u88C1\u526A\u56FE\u7247",
78
+ durationLimitAlertTitle: "\u63D0\u793A",
79
+ durationLimitAlertMessage: "\u89C6\u9891\u65F6\u957F\u4E0D\u80FD\u8D85\u8FC7 {maxDuration} \u79D2",
80
+ openAlbumError: "\u6253\u5F00\u7CFB\u7EDF\u9009\u62E9\u5668\u5931\u8D25"
81
+ };
82
+ function normalizeOpenOptions(options) {
83
+ const requestedMaxSelection = options?.maxSelection ?? 9;
84
+ const maxSelection = Number.isFinite(requestedMaxSelection) ? Math.max(1, Math.floor(requestedMaxSelection)) : 1;
85
+ const crop = options?.crop;
86
+ const allowsMultipleSelection = crop ? false : options?.allowsMultipleSelection ?? maxSelection > 1;
87
+ return {
88
+ ...options,
89
+ crop,
90
+ maxSelection: crop ? 1 : allowsMultipleSelection ? maxSelection : 1,
91
+ mediaType: crop ? "photo" : options?.mediaType ?? "all",
92
+ allowsMultipleSelection
93
+ };
94
+ }
95
+ function resolvePhotoPickerUiConfig(uiConfig) {
96
+ return {
97
+ texts: {
98
+ ...DEFAULT_TEXTS,
99
+ ...uiConfig?.texts
100
+ }
101
+ };
102
+ }
103
+ function formatPhotoPickerText(template, variables) {
104
+ return template.replace(/\{(\w+)\}/g, (_, key) => String(variables[key] ?? ""));
105
+ }
106
+ function createCroppedPhotoAlbumItem(photo, manipulated, crop) {
107
+ const baseName = photo.filename ?? photo.fileName ?? "photo";
108
+ const croppedName = `${baseName.replace(/\.[^.]+$/, "")}-cropped.jpg`;
109
+ return {
110
+ ...photo,
111
+ id: `${photo.id}-crop-${Date.now()}`,
112
+ uri: manipulated.uri,
113
+ localUri: manipulated.uri,
114
+ filename: croppedName,
115
+ fileName: croppedName,
116
+ mimeType: "image/jpeg",
117
+ width: manipulated.width,
118
+ height: manipulated.height,
119
+ edited: true,
120
+ crop: {
121
+ ...crop,
122
+ width: manipulated.width,
123
+ height: manipulated.height
124
+ }
125
+ };
126
+ }
127
+ function normalizePickerMediaType(mediaType) {
128
+ return mediaType === "photo" || mediaType === "video" || mediaType === "all" ? mediaType : "all";
129
+ }
130
+
131
+ // src/screens/PhotoAlbumScreen.tsx
132
+ import { jsx, jsxs } from "react/jsx-runtime";
133
+ function PhotoAlbumScreen({ route, navigation }) {
134
+ const startedRef = React.useRef(false);
135
+ const [errorMessage, setErrorMessage] = React.useState();
136
+ const callbackId = route?.params?.callbackId;
137
+ const options = React.useMemo(
138
+ () => normalizeOpenOptions({
139
+ ...route?.params?.options,
140
+ maxSelection: route?.params?.options?.maxSelection ?? route?.params?.maxSelection,
141
+ allowsMultipleSelection: route?.params?.options?.allowsMultipleSelection ?? route?.params?.allowsMultipleSelection,
142
+ mediaType: route?.params?.options?.mediaType ?? (route?.params?.mediaTypes?.length === 1 ? route.params.mediaTypes[0] : "all")
143
+ }),
144
+ [
145
+ route?.params?.allowsMultipleSelection,
146
+ route?.params?.maxSelection,
147
+ route?.params?.mediaTypes,
148
+ route?.params?.options
149
+ ]
150
+ );
151
+ const uiConfig = React.useMemo(
152
+ () => resolvePhotoPickerUiConfig({
153
+ ...route?.params?.uiConfig,
154
+ ...options.uiConfig,
155
+ texts: {
156
+ ...route?.params?.uiConfig?.texts,
157
+ ...options.uiConfig?.texts
158
+ }
159
+ }),
160
+ [options.uiConfig, route?.params?.uiConfig]
161
+ );
162
+ const routeNames = React.useMemo(
163
+ () => ({
164
+ photoAlbum: route?.params?.routeNames?.photoAlbum ?? MEDIA_PICKER_ROUTES.PHOTO_ALBUM,
165
+ photoCrop: route?.params?.routeNames?.photoCrop ?? MEDIA_PICKER_ROUTES.PHOTO_CROP
166
+ }),
167
+ [route?.params?.routeNames?.photoAlbum, route?.params?.routeNames?.photoCrop]
168
+ );
169
+ const close = React.useCallback(() => {
170
+ clearPhotoAlbumCompleteCallback(callbackId);
171
+ navigation?.goBack();
172
+ }, [callbackId, navigation]);
173
+ const complete = React.useCallback(
174
+ (assets) => {
175
+ const overLimitVideo = options.maxVideoDuration == null ? void 0 : assets.find(
176
+ (asset) => asset.mediaType === "video" && (asset.duration ?? 0) > options.maxVideoDuration
177
+ );
178
+ if (overLimitVideo) {
179
+ Alert.alert(
180
+ uiConfig.texts.durationLimitAlertTitle,
181
+ formatPhotoPickerText(uiConfig.texts.durationLimitAlertMessage, {
182
+ maxDuration: options.maxVideoDuration
183
+ })
184
+ );
185
+ return;
186
+ }
187
+ if (options.crop) {
188
+ const photo = assets[0];
189
+ if (!photo) return;
190
+ navigation?.navigate?.(routeNames.photoCrop, {
191
+ photo,
192
+ crop: options.crop,
193
+ quality: options.quality,
194
+ callbackId,
195
+ routeNames,
196
+ uiConfig: options.uiConfig
197
+ });
198
+ return;
199
+ }
200
+ getPhotoAlbumCompleteCallback(callbackId)?.(assets);
201
+ clearPhotoAlbumCompleteCallback(callbackId);
202
+ navigation?.goBack();
203
+ },
204
+ [
205
+ callbackId,
206
+ navigation,
207
+ options,
208
+ routeNames,
209
+ uiConfig.texts.durationLimitAlertMessage,
210
+ uiConfig.texts.durationLimitAlertTitle
211
+ ]
212
+ );
213
+ const openPicker = React.useCallback(async () => {
214
+ setErrorMessage(void 0);
215
+ try {
216
+ const result = await pickMedia({
217
+ mediaType: options.mediaType,
218
+ maxSelection: options.maxSelection,
219
+ allowsMultipleSelection: options.allowsMultipleSelection,
220
+ cacheMode: "copy"
221
+ });
222
+ if (result.cancelled) {
223
+ close();
224
+ return;
225
+ }
226
+ complete(result.assets);
227
+ } catch (error) {
228
+ setErrorMessage(error instanceof Error ? error.message : uiConfig.texts.openAlbumError);
229
+ }
230
+ }, [
231
+ close,
232
+ complete,
233
+ options.allowsMultipleSelection,
234
+ options.maxSelection,
235
+ options.mediaType,
236
+ uiConfig.texts.openAlbumError
237
+ ]);
238
+ React.useEffect(() => {
239
+ if (startedRef.current) return;
240
+ startedRef.current = true;
241
+ void openPicker();
242
+ }, [openPicker]);
243
+ React.useEffect(
244
+ () => () => {
245
+ if (!options.crop) {
246
+ clearPhotoAlbumCompleteCallback(callbackId);
247
+ }
248
+ },
249
+ [callbackId, options.crop]
250
+ );
251
+ return /* @__PURE__ */ jsxs(View, { style: styles.container, children: [
252
+ /* @__PURE__ */ jsx(ActivityIndicator, { size: "large", color: "#3b82f6" }),
253
+ /* @__PURE__ */ jsx(Text, { style: styles.title, children: errorMessage ? uiConfig.texts.openAlbumError : uiConfig.texts.albumTitle }),
254
+ errorMessage ? /* @__PURE__ */ jsx(Text, { style: styles.message, children: errorMessage }) : null,
255
+ errorMessage ? /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
256
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => void openPicker(), style: styles.primaryButton, children: /* @__PURE__ */ jsx(Text, { style: styles.primaryButtonText, children: "\u91CD\u8BD5" }) }),
257
+ /* @__PURE__ */ jsx(Pressable, { onPress: close, style: styles.secondaryButton, children: /* @__PURE__ */ jsx(Text, { style: styles.secondaryButtonText, children: "\u53D6\u6D88" }) })
258
+ ] }) : null
259
+ ] });
260
+ }
261
+ var styles = StyleSheet.create({
262
+ container: {
263
+ flex: 1,
264
+ alignItems: "center",
265
+ justifyContent: "center",
266
+ padding: 24,
267
+ backgroundColor: "#f8fafc"
268
+ },
269
+ title: { marginTop: 16, color: "#0f172a", fontSize: 16, fontWeight: "600" },
270
+ message: { marginTop: 8, color: "#64748b", fontSize: 13, lineHeight: 20, textAlign: "center" },
271
+ actions: { flexDirection: "row", gap: 12, marginTop: 20 },
272
+ primaryButton: {
273
+ borderRadius: 8,
274
+ backgroundColor: "#2563eb",
275
+ paddingHorizontal: 18,
276
+ paddingVertical: 10
277
+ },
278
+ primaryButtonText: { color: "#ffffff", fontSize: 14, fontWeight: "600" },
279
+ secondaryButton: {
280
+ borderRadius: 8,
281
+ borderWidth: 1,
282
+ borderColor: "#cbd5e1",
283
+ paddingHorizontal: 18,
284
+ paddingVertical: 10
285
+ },
286
+ secondaryButtonText: { color: "#334155", fontSize: 14, fontWeight: "600" }
287
+ });
288
+
289
+ // src/screens/PhotoCropScreen.tsx
290
+ import React2 from "react";
291
+ import { Dimensions, Pressable as Pressable2, StyleSheet as StyleSheet2, Text as Text2, View as View2 } from "react-native";
292
+ import { useIsFocused } from "@react-navigation/native";
293
+ import { CropZoom } from "react-native-zoom-toolkit";
294
+ import { Image } from "expo-image";
295
+ import { SaveFormat, manipulateAsync } from "expo-image-manipulator";
296
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
297
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
298
+ var { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
299
+ var HEADER_ROW_HEIGHT = 48;
300
+ var FOOTER_MIN_HEIGHT = 52;
301
+ var CONTENT_HORIZONTAL_PADDING = 16;
302
+ var CONTENT_VERTICAL_PADDING = 12;
303
+ function getCropBoxSize(aspect, maxWidth, maxHeight) {
304
+ const ratio = aspect[0] / aspect[1];
305
+ let width = maxWidth;
306
+ let height = width / ratio;
307
+ if (height > maxHeight) {
308
+ height = maxHeight;
309
+ width = height * ratio;
310
+ }
311
+ return { width: Math.floor(width), height: Math.floor(height) };
312
+ }
313
+ function getImageDisplaySize(imageWidth, imageHeight, cropWidth, cropHeight) {
314
+ const imageRatio = imageWidth / imageHeight;
315
+ const cropRatio = cropWidth / cropHeight;
316
+ return imageRatio > cropRatio ? { width: cropHeight * imageRatio, height: cropHeight } : { width: cropWidth, height: cropWidth / imageRatio };
317
+ }
318
+ function PhotoCropScreen({ route, navigation }) {
319
+ const insets = useSafeAreaInsets();
320
+ const isFocused = useIsFocused();
321
+ const cropRef = React2.useRef(null);
322
+ const [saving, setSaving] = React2.useState(false);
323
+ const [closing, setClosing] = React2.useState(false);
324
+ const [contentLayout, setContentLayout] = React2.useState({ width: 0, height: 0 });
325
+ const photo = route?.params?.photo;
326
+ const cropOptions = route?.params?.crop;
327
+ const callbackId = route?.params?.callbackId;
328
+ const uiConfig = React2.useMemo(
329
+ () => resolvePhotoPickerUiConfig(route?.params?.uiConfig),
330
+ [route?.params?.uiConfig]
331
+ );
332
+ const quality = route?.params?.quality ?? cropOptions?.quality ?? 1;
333
+ const aspect = cropOptions?.aspect ?? [1, 1];
334
+ const isCircleCrop = cropOptions?.shape === "circle";
335
+ const headerHeight = insets.top + HEADER_ROW_HEIGHT;
336
+ const footerHeight = Math.max(insets.bottom, 16) + FOOTER_MIN_HEIGHT;
337
+ const cropBox = React2.useMemo(
338
+ () => getCropBoxSize(
339
+ aspect,
340
+ SCREEN_WIDTH - CONTENT_HORIZONTAL_PADDING * 2,
341
+ SCREEN_HEIGHT - headerHeight - footerHeight - CONTENT_VERTICAL_PADDING * 2
342
+ ),
343
+ [aspect, footerHeight, headerHeight]
344
+ );
345
+ const cropFramePosition = React2.useMemo(
346
+ () => ({
347
+ left: Math.max(0, (contentLayout.width - cropBox.width) / 2),
348
+ top: Math.max(0, (contentLayout.height - cropBox.height) / 2)
349
+ }),
350
+ [contentLayout.height, contentLayout.width, cropBox.height, cropBox.width]
351
+ );
352
+ const imageSize = React2.useMemo(
353
+ () => !photo ? { width: cropBox.width, height: cropBox.height } : getImageDisplaySize(
354
+ Math.max(photo.width, 1),
355
+ Math.max(photo.height, 1),
356
+ cropBox.width,
357
+ cropBox.height
358
+ ),
359
+ [cropBox.height, cropBox.width, photo]
360
+ );
361
+ const closeFlow = React2.useCallback(
362
+ (count = 1) => {
363
+ if (closing) return;
364
+ setClosing(true);
365
+ requestAnimationFrame(() => {
366
+ if (count > 1 && navigation?.pop) navigation.pop(count);
367
+ else navigation?.goBack();
368
+ });
369
+ },
370
+ [closing, navigation]
371
+ );
372
+ const handleCancel = React2.useCallback(() => {
373
+ clearPhotoAlbumCompleteCallback(callbackId);
374
+ closeFlow();
375
+ }, [callbackId, closeFlow]);
376
+ const handleConfirm = React2.useCallback(async () => {
377
+ if (!photo || !cropRef.current || saving) return;
378
+ setSaving(true);
379
+ try {
380
+ const result = cropRef.current.crop();
381
+ const manipulated = await manipulateAsync(photo.uri, [{ crop: result.crop }], {
382
+ compress: quality,
383
+ format: SaveFormat.JPEG
384
+ });
385
+ const croppedPhoto = createCroppedPhotoAlbumItem(photo, manipulated, cropOptions);
386
+ getPhotoAlbumCompleteCallback(callbackId)?.([croppedPhoto]);
387
+ clearPhotoAlbumCompleteCallback(callbackId);
388
+ closeFlow(2);
389
+ } catch (error) {
390
+ console.error("[photo-picker] crop failed", error);
391
+ } finally {
392
+ setSaving(false);
393
+ }
394
+ }, [callbackId, closeFlow, cropOptions, photo, quality, saving]);
395
+ if (!photo) {
396
+ return /* @__PURE__ */ jsx2(View2, { style: styles2.empty, children: /* @__PURE__ */ jsx2(Text2, { style: styles2.emptyText, children: uiConfig.texts.cropMissingPhoto }) });
397
+ }
398
+ const footerHint = isCircleCrop ? uiConfig.texts.cropCircleHint : uiConfig.texts.cropRectHint;
399
+ const showContent = isFocused && !closing;
400
+ return /* @__PURE__ */ jsxs2(View2, { style: styles2.screen, children: [
401
+ /* @__PURE__ */ jsxs2(View2, { style: [styles2.header, { height: headerHeight, paddingTop: insets.top }], children: [
402
+ /* @__PURE__ */ jsx2(Pressable2, { onPress: handleCancel, style: styles2.headerButton, accessibilityRole: "button", children: /* @__PURE__ */ jsx2(Text2, { style: styles2.headerIcon, children: "\xD7" }) }),
403
+ /* @__PURE__ */ jsx2(Text2, { style: styles2.headerTitle, children: uiConfig.texts.cropTitle }),
404
+ /* @__PURE__ */ jsx2(
405
+ Pressable2,
406
+ {
407
+ onPress: handleConfirm,
408
+ style: styles2.headerButton,
409
+ disabled: saving,
410
+ accessibilityRole: "button",
411
+ children: /* @__PURE__ */ jsx2(Text2, { style: styles2.headerAction, children: saving ? uiConfig.texts.cropSavingButton : uiConfig.texts.cropConfirmButton })
412
+ }
413
+ )
414
+ ] }),
415
+ /* @__PURE__ */ jsxs2(
416
+ View2,
417
+ {
418
+ style: styles2.content,
419
+ onLayout: ({ nativeEvent }) => setContentLayout(nativeEvent.layout),
420
+ children: [
421
+ showContent ? /* @__PURE__ */ jsx2(
422
+ CropZoom,
423
+ {
424
+ ref: cropRef,
425
+ cropSize: cropBox,
426
+ resolution: { width: Math.max(photo.width, 1), height: Math.max(photo.height, 1) },
427
+ children: /* @__PURE__ */ jsx2(
428
+ Image,
429
+ {
430
+ source: { uri: photo.uri },
431
+ style: imageSize,
432
+ contentFit: "cover",
433
+ transition: 0,
434
+ cachePolicy: "memory-disk"
435
+ }
436
+ )
437
+ }
438
+ ) : null,
439
+ showContent && contentLayout.width > 0 ? /* @__PURE__ */ jsxs2(View2, { pointerEvents: "none", style: styles2.overlay, children: [
440
+ /* @__PURE__ */ jsx2(
441
+ View2,
442
+ {
443
+ style: [styles2.mask, { left: 0, top: 0, right: 0, height: cropFramePosition.top }]
444
+ }
445
+ ),
446
+ /* @__PURE__ */ jsx2(
447
+ View2,
448
+ {
449
+ style: [
450
+ styles2.mask,
451
+ {
452
+ left: 0,
453
+ top: cropFramePosition.top,
454
+ width: cropFramePosition.left,
455
+ height: cropBox.height
456
+ }
457
+ ]
458
+ }
459
+ ),
460
+ /* @__PURE__ */ jsx2(
461
+ View2,
462
+ {
463
+ style: [
464
+ styles2.mask,
465
+ {
466
+ left: cropFramePosition.left + cropBox.width,
467
+ top: cropFramePosition.top,
468
+ right: 0,
469
+ height: cropBox.height
470
+ }
471
+ ]
472
+ }
473
+ ),
474
+ /* @__PURE__ */ jsx2(
475
+ View2,
476
+ {
477
+ style: [
478
+ styles2.mask,
479
+ { left: 0, right: 0, top: cropFramePosition.top + cropBox.height, bottom: 0 }
480
+ ]
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsx2(
484
+ View2,
485
+ {
486
+ style: [
487
+ styles2.cropFrame,
488
+ {
489
+ width: cropBox.width,
490
+ height: cropBox.height,
491
+ left: cropFramePosition.left,
492
+ top: cropFramePosition.top,
493
+ borderRadius: isCircleCrop ? cropBox.width / 2 : 16
494
+ }
495
+ ]
496
+ }
497
+ )
498
+ ] }) : null
499
+ ]
500
+ }
501
+ ),
502
+ /* @__PURE__ */ jsx2(
503
+ View2,
504
+ {
505
+ style: [
506
+ styles2.footer,
507
+ { minHeight: footerHeight, paddingBottom: Math.max(insets.bottom, 16) }
508
+ ],
509
+ children: /* @__PURE__ */ jsx2(Text2, { style: styles2.hint, children: footerHint })
510
+ }
511
+ )
512
+ ] });
513
+ }
514
+ var styles2 = StyleSheet2.create({
515
+ screen: { flex: 1, backgroundColor: "#000000" },
516
+ empty: { flex: 1, justifyContent: "center", alignItems: "center" },
517
+ emptyText: { color: "#334155", fontSize: 16 },
518
+ header: {
519
+ flexDirection: "row",
520
+ alignItems: "center",
521
+ justifyContent: "space-between",
522
+ paddingHorizontal: 12,
523
+ backgroundColor: "#000000"
524
+ },
525
+ headerButton: { minWidth: 52, height: 40, justifyContent: "center", alignItems: "center" },
526
+ headerTitle: { color: "#ffffff", fontSize: 18, fontWeight: "600" },
527
+ headerIcon: { color: "#ffffff", fontSize: 30, lineHeight: 32 },
528
+ headerAction: { color: "#ffffff", fontSize: 15, fontWeight: "600" },
529
+ content: {
530
+ flex: 1,
531
+ justifyContent: "center",
532
+ alignItems: "center",
533
+ overflow: "hidden",
534
+ paddingHorizontal: CONTENT_HORIZONTAL_PADDING,
535
+ paddingVertical: CONTENT_VERTICAL_PADDING,
536
+ backgroundColor: "#000000"
537
+ },
538
+ overlay: { ...StyleSheet2.absoluteFillObject },
539
+ mask: { position: "absolute", backgroundColor: "rgba(0,0,0,0.58)" },
540
+ cropFrame: { position: "absolute", borderWidth: 2, borderColor: "#ffffff" },
541
+ footer: {
542
+ alignItems: "center",
543
+ justifyContent: "center",
544
+ paddingHorizontal: 16,
545
+ paddingTop: 12,
546
+ backgroundColor: "#000000"
547
+ },
548
+ hint: { color: "rgba(255,255,255,0.8)" }
549
+ });
550
+ export {
551
+ MEDIA_PICKER_ROUTES,
552
+ PhotoAlbumScreen,
553
+ PhotoCropScreen,
554
+ clearPhotoAlbumCompleteCallback,
555
+ clearPickerCache,
556
+ createCroppedPhotoAlbumItem,
557
+ formatPhotoPickerText,
558
+ getPhotoAlbumCompleteCallback,
559
+ isPhotoPickerNativeError,
560
+ mediaPickerColors,
561
+ normalizeOpenOptions,
562
+ normalizePickerMediaType,
563
+ pickMedia,
564
+ registerPhotoAlbumCompleteCallback,
565
+ releaseMedia,
566
+ resolvePhotoPickerUiConfig
567
+ };
568
+ //# sourceMappingURL=index.mjs.map