react-native-ensys-camera-x 1.0.7
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/CustomCamera.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +833 -0
- package/android/build.gradle +135 -0
- package/android/consumer-rules.pro +47 -0
- package/android/src/main/AndroidManifest.xml +72 -0
- package/android/src/main/java/android/print/PdfPrint.java +51 -0
- package/android/src/main/java/com/customcamera/AnimatedImageViewManager.kt +199 -0
- package/android/src/main/java/com/customcamera/AudioPlayerView.kt +322 -0
- package/android/src/main/java/com/customcamera/AudioRecorder.kt +506 -0
- package/android/src/main/java/com/customcamera/BarcodeScannerActivity.kt +361 -0
- package/android/src/main/java/com/customcamera/BlurBackgroundView.kt +199 -0
- package/android/src/main/java/com/customcamera/CacheCleanupManager.kt +386 -0
- package/android/src/main/java/com/customcamera/CameraAspectRatioMode.kt +77 -0
- package/android/src/main/java/com/customcamera/CameraAsset.kt +211 -0
- package/android/src/main/java/com/customcamera/CameraTheme.kt +479 -0
- package/android/src/main/java/com/customcamera/CompressionCacheStore.kt +504 -0
- package/android/src/main/java/com/customcamera/CompressionFileNameUtils.kt +7 -0
- package/android/src/main/java/com/customcamera/CompressionProfile.kt +195 -0
- package/android/src/main/java/com/customcamera/CustomCameraActivity.kt +7291 -0
- package/android/src/main/java/com/customcamera/CustomCameraFileProvider.kt +9 -0
- package/android/src/main/java/com/customcamera/CustomCameraModule.kt +2479 -0
- package/android/src/main/java/com/customcamera/CustomCameraPackage.kt +53 -0
- package/android/src/main/java/com/customcamera/DisplayUtils.kt +9 -0
- package/android/src/main/java/com/customcamera/DocumentPreviewView.kt +846 -0
- package/android/src/main/java/com/customcamera/DrawingView.kt +214 -0
- package/android/src/main/java/com/customcamera/EmojiPickerData.kt +228 -0
- package/android/src/main/java/com/customcamera/FilterStripAdapter.kt +158 -0
- package/android/src/main/java/com/customcamera/GifSupport.kt +215 -0
- package/android/src/main/java/com/customcamera/GlobalAudioPlayer.kt +320 -0
- package/android/src/main/java/com/customcamera/ImageExporter.kt +1394 -0
- package/android/src/main/java/com/customcamera/ImageFormatSupport.kt +163 -0
- package/android/src/main/java/com/customcamera/Localization.kt +206 -0
- package/android/src/main/java/com/customcamera/MediaAdapters.kt +758 -0
- package/android/src/main/java/com/customcamera/MediaCacheManager.kt +782 -0
- package/android/src/main/java/com/customcamera/MediaExportFormats.kt +201 -0
- package/android/src/main/java/com/customcamera/MediaFileIo.kt +310 -0
- package/android/src/main/java/com/customcamera/MediaMetadataStore.kt +494 -0
- package/android/src/main/java/com/customcamera/MediaPreviewEditor.kt +14865 -0
- package/android/src/main/java/com/customcamera/MediaPublisher.kt +423 -0
- package/android/src/main/java/com/customcamera/MediaStorageWorkflow.kt +722 -0
- package/android/src/main/java/com/customcamera/MediaStoreRepository.kt +378 -0
- package/android/src/main/java/com/customcamera/MediaThumbnailView.kt +286 -0
- package/android/src/main/java/com/customcamera/MediaUtils.kt +37 -0
- package/android/src/main/java/com/customcamera/NativeVideoViewManager.kt +845 -0
- package/android/src/main/java/com/customcamera/OfficeDocumentConverter.kt +2791 -0
- package/android/src/main/java/com/customcamera/PendingCaptureCrops.kt +88 -0
- package/android/src/main/java/com/customcamera/PowerPointPreview.kt +4077 -0
- package/android/src/main/java/com/customcamera/RecentPickerStore.kt +74 -0
- package/android/src/main/java/com/customcamera/SecureFileEraser.kt +411 -0
- package/android/src/main/java/com/customcamera/SecureKeyModule.kt +88 -0
- package/android/src/main/java/com/customcamera/SecureMediaDeleter.kt +493 -0
- package/android/src/main/java/com/customcamera/StickerPickerData.kt +107 -0
- package/android/src/main/java/com/customcamera/SvgDrawable.kt +175 -0
- package/android/src/main/java/com/customcamera/SvgIconViewManager.kt +77 -0
- package/android/src/main/java/com/customcamera/TextEntryController.kt +553 -0
- package/android/src/main/java/com/customcamera/ThumbnailLoader.kt +455 -0
- package/android/src/main/java/com/customcamera/UploadService.kt +909 -0
- package/android/src/main/java/com/customcamera/VideoEditUtils.kt +210 -0
- package/android/src/main/java/com/customcamera/VideoRotationTranscoder.kt +1992 -0
- package/android/src/main/java/com/customcamera/gif/AnimatedGifEncoder.kt +473 -0
- package/android/src/main/java/com/customcamera/gif/GifDecoder.kt +690 -0
- package/android/src/main/java/com/customcamera/gif/GifFramesDrawable.kt +160 -0
- package/android/src/main/java/com/customcamera/gif/GifHttp.kt +112 -0
- package/android/src/main/java/com/customcamera/gif/GifImageLoader.kt +186 -0
- package/android/src/main/java/com/customcamera/gif/GifModels.kt +74 -0
- package/android/src/main/java/com/customcamera/gif/GifProvider.kt +49 -0
- package/android/src/main/java/com/customcamera/gif/GifProviders.kt +27 -0
- package/android/src/main/java/com/customcamera/gif/GifRepository.kt +124 -0
- package/android/src/main/java/com/customcamera/gif/GifSafety.kt +33 -0
- package/android/src/main/java/com/customcamera/gif/GiphyGifProvider.kt +146 -0
- package/android/src/main/java/com/customcamera/gif/LZWEncoder.kt +258 -0
- package/android/src/main/java/com/customcamera/gif/NeuQuant.kt +435 -0
- package/android/src/main/java/com/customcamera/gif/TenorGifProvider.kt +147 -0
- package/android/src/main/res/drawable/bg_brush_selected.xml +5 -0
- package/android/src/main/res/drawable/bg_color_swatch.xml +5 -0
- package/android/src/main/res/drawable/bg_editor_toolbar.xml +6 -0
- package/android/src/main/res/drawable/bg_icon_circle.xml +5 -0
- package/android/src/main/res/drawable/bg_icon_circle_light.xml +5 -0
- package/android/src/main/res/drawable/bg_mode_tab_active.xml +6 -0
- package/android/src/main/res/drawable/bg_pill.xml +6 -0
- package/android/src/main/res/drawable/bg_recording_dot.xml +6 -0
- package/android/src/main/res/drawable/bg_selection_badge.xml +8 -0
- package/android/src/main/res/drawable/bg_send_button.xml +5 -0
- package/android/src/main/res/drawable/bg_shutter_inner.xml +5 -0
- package/android/src/main/res/drawable/bg_shutter_recording.xml +6 -0
- package/android/src/main/res/drawable/bg_shutter_ring.xml +8 -0
- package/android/src/main/res/drawable/bg_shutter_video_idle.xml +5 -0
- package/android/src/main/res/drawable/bg_thumbnail.xml +9 -0
- package/android/src/main/res/drawable/ic_aspect_ratio.xml +17 -0
- package/android/src/main/res/drawable/ic_audio.xml +10 -0
- package/android/src/main/res/drawable/ic_back.xml +10 -0
- package/android/src/main/res/drawable/ic_brush_medium.xml +12 -0
- package/android/src/main/res/drawable/ic_brush_thick.xml +12 -0
- package/android/src/main/res/drawable/ic_brush_thin.xml +12 -0
- package/android/src/main/res/drawable/ic_check.xml +17 -0
- package/android/src/main/res/drawable/ic_chevron_down.xml +10 -0
- package/android/src/main/res/drawable/ic_chevron_up.xml +13 -0
- package/android/src/main/res/drawable/ic_clear_drawing.xml +10 -0
- package/android/src/main/res/drawable/ic_close.xml +10 -0
- package/android/src/main/res/drawable/ic_cover.xml +9 -0
- package/android/src/main/res/drawable/ic_crop.xml +10 -0
- package/android/src/main/res/drawable/ic_delete.xml +9 -0
- package/android/src/main/res/drawable/ic_document.xml +10 -0
- package/android/src/main/res/drawable/ic_done.xml +10 -0
- package/android/src/main/res/drawable/ic_download.xml +10 -0
- package/android/src/main/res/drawable/ic_emoji.xml +10 -0
- package/android/src/main/res/drawable/ic_filter.xml +10 -0
- package/android/src/main/res/drawable/ic_flash_auto.xml +7 -0
- package/android/src/main/res/drawable/ic_flash_off.xml +23 -0
- package/android/src/main/res/drawable/ic_flash_on.xml +16 -0
- package/android/src/main/res/drawable/ic_flip.xml +15 -0
- package/android/src/main/res/drawable/ic_gallery.xml +10 -0
- package/android/src/main/res/drawable/ic_gif.xml +24 -0
- package/android/src/main/res/drawable/ic_lock.xml +19 -0
- package/android/src/main/res/drawable/ic_lock_open.xml +22 -0
- package/android/src/main/res/drawable/ic_marker.xml +10 -0
- package/android/src/main/res/drawable/ic_more_vert.xml +18 -0
- package/android/src/main/res/drawable/ic_overlay_close.xml +19 -0
- package/android/src/main/res/drawable/ic_overlay_resize.xml +18 -0
- package/android/src/main/res/drawable/ic_pause.xml +10 -0
- package/android/src/main/res/drawable/ic_photo_mode.xml +10 -0
- package/android/src/main/res/drawable/ic_play.xml +10 -0
- package/android/src/main/res/drawable/ic_redo.xml +10 -0
- package/android/src/main/res/drawable/ic_rotate.xml +9 -0
- package/android/src/main/res/drawable/ic_send.xml +10 -0
- package/android/src/main/res/drawable/ic_share.xml +10 -0
- package/android/src/main/res/drawable/ic_stat_upload.xml +10 -0
- package/android/src/main/res/drawable/ic_sticker.xml +24 -0
- package/android/src/main/res/drawable/ic_text.xml +10 -0
- package/android/src/main/res/drawable/ic_text_align_center.xml +11 -0
- package/android/src/main/res/drawable/ic_text_align_left.xml +11 -0
- package/android/src/main/res/drawable/ic_text_align_right.xml +11 -0
- package/android/src/main/res/drawable/ic_text_bg.xml +11 -0
- package/android/src/main/res/drawable/ic_trim.xml +23 -0
- package/android/src/main/res/drawable/ic_undo.xml +10 -0
- package/android/src/main/res/drawable/ic_video_mode.xml +10 -0
- package/android/src/main/res/drawable/ic_video_note.xml +13 -0
- package/android/src/main/res/drawable/ic_volume_off.xml +9 -0
- package/android/src/main/res/drawable/ic_volume_on.xml +9 -0
- package/android/src/main/res/drawable/sticker_heart.xml +9 -0
- package/android/src/main/res/drawable/sticker_smile.xml +9 -0
- package/android/src/main/res/drawable/sticker_star.xml +9 -0
- package/android/src/main/res/drawable/sticker_thumbsup.xml +9 -0
- package/android/src/main/res/values/colors.xml +34 -0
- package/android/src/main/res/values/ids.xml +15 -0
- package/android/src/main/res/values/strings.xml +15 -0
- package/android/src/main/res/values/styles.xml +20 -0
- package/android/src/main/res/xml/file_paths.xml +34 -0
- package/ios/CustomCamera.h +5 -0
- package/ios/CustomCamera.mm +214 -0
- package/lib/module/AudioPlayerContext.js +233 -0
- package/lib/module/AudioPlayerContext.js.map +1 -0
- package/lib/module/CameraPreview.js +304 -0
- package/lib/module/CameraPreview.js.map +1 -0
- package/lib/module/NativeAnimatedImageView.js +41 -0
- package/lib/module/NativeAnimatedImageView.js.map +1 -0
- package/lib/module/NativeAudioPlayerView.js +33 -0
- package/lib/module/NativeAudioPlayerView.js.map +1 -0
- package/lib/module/NativeBlurView.js +35 -0
- package/lib/module/NativeBlurView.js.map +1 -0
- package/lib/module/NativeCustomCamera.js +128 -0
- package/lib/module/NativeCustomCamera.js.map +1 -0
- package/lib/module/NativeCustomCamera.web.js +143 -0
- package/lib/module/NativeCustomCamera.web.js.map +1 -0
- package/lib/module/NativeCustomVideoView.js +5 -0
- package/lib/module/NativeCustomVideoView.js.map +1 -0
- package/lib/module/NativeDocumentPreviewView.js +156 -0
- package/lib/module/NativeDocumentPreviewView.js.map +1 -0
- package/lib/module/NativeMediaThumbnailView.js +110 -0
- package/lib/module/NativeMediaThumbnailView.js.map +1 -0
- package/lib/module/NativeSvgIcon.js +32 -0
- package/lib/module/NativeSvgIcon.js.map +1 -0
- package/lib/module/ResponsivePreviewContainer.js +171 -0
- package/lib/module/ResponsivePreviewContainer.js.map +1 -0
- package/lib/module/accessibility.js +61 -0
- package/lib/module/accessibility.js.map +1 -0
- package/lib/module/config.js +352 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/cpuOptimization.js +296 -0
- package/lib/module/cpuOptimization.js.map +1 -0
- package/lib/module/db/DatabaseManager.js +191 -0
- package/lib/module/db/DatabaseManager.js.map +1 -0
- package/lib/module/db/MediaMapper.js +284 -0
- package/lib/module/db/MediaMapper.js.map +1 -0
- package/lib/module/db/MediaRepository.js +242 -0
- package/lib/module/db/MediaRepository.js.map +1 -0
- package/lib/module/db/index.js +41 -0
- package/lib/module/db/index.js.map +1 -0
- package/lib/module/db/migrations.js +180 -0
- package/lib/module/db/migrations.js.map +1 -0
- package/lib/module/db/react-native-sqlcipher.d.js +2 -0
- package/lib/module/db/react-native-sqlcipher.d.js.map +1 -0
- package/lib/module/db/secureKey.js +87 -0
- package/lib/module/db/secureKey.js.map +1 -0
- package/lib/module/db/types.js +57 -0
- package/lib/module/db/types.js.map +1 -0
- package/lib/module/db/useMediaDatabase.js +123 -0
- package/lib/module/db/useMediaDatabase.js.map +1 -0
- package/lib/module/defaultIcons.js +2259 -0
- package/lib/module/defaultIcons.js.map +1 -0
- package/lib/module/deviceCapabilities.js +408 -0
- package/lib/module/deviceCapabilities.js.map +1 -0
- package/lib/module/errors.js +267 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/iconConfig.js +796 -0
- package/lib/module/iconConfig.js.map +1 -0
- package/lib/module/icons.js +514 -0
- package/lib/module/icons.js.map +1 -0
- package/lib/module/index.js +251 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/jest-globals.d.js +2 -0
- package/lib/module/jest-globals.d.js.map +1 -0
- package/lib/module/library/MediaLibrary.js +107 -0
- package/lib/module/library/MediaLibrary.js.map +1 -0
- package/lib/module/library/MediaLibraryScreen.js +2597 -0
- package/lib/module/library/MediaLibraryScreen.js.map +1 -0
- package/lib/module/library/components/AudioPreview.js +74 -0
- package/lib/module/library/components/AudioPreview.js.map +1 -0
- package/lib/module/library/components/DocumentPreview.js +331 -0
- package/lib/module/library/components/DocumentPreview.js.map +1 -0
- package/lib/module/library/components/ExpandableFab.js +343 -0
- package/lib/module/library/components/ExpandableFab.js.map +1 -0
- package/lib/module/library/components/FileTiles.js +251 -0
- package/lib/module/library/components/FileTiles.js.map +1 -0
- package/lib/module/library/components/ImagePreview.js +70 -0
- package/lib/module/library/components/ImagePreview.js.map +1 -0
- package/lib/module/library/components/MediaMetadataHeader.js +175 -0
- package/lib/module/library/components/MediaMetadataHeader.js.map +1 -0
- package/lib/module/library/components/RecordAudioScreen.js +816 -0
- package/lib/module/library/components/RecordAudioScreen.js.map +1 -0
- package/lib/module/library/components/VideoPreview.js +453 -0
- package/lib/module/library/components/VideoPreview.js.map +1 -0
- package/lib/module/library/components/ViewerCounter.js +55 -0
- package/lib/module/library/components/ViewerCounter.js.map +1 -0
- package/lib/module/library/compressionEstimate.js +507 -0
- package/lib/module/library/compressionEstimate.js.map +1 -0
- package/lib/module/library/fileMetadata.js +196 -0
- package/lib/module/library/fileMetadata.js.map +1 -0
- package/lib/module/library/index.js +42 -0
- package/lib/module/library/index.js.map +1 -0
- package/lib/module/library/libraryIconRegistry.js +69 -0
- package/lib/module/library/libraryIconRegistry.js.map +1 -0
- package/lib/module/library/libraryIcons.js +858 -0
- package/lib/module/library/libraryIcons.js.map +1 -0
- package/lib/module/library/localizedFormat.js +181 -0
- package/lib/module/library/localizedFormat.js.map +1 -0
- package/lib/module/library/palette.js +225 -0
- package/lib/module/library/palette.js.map +1 -0
- package/lib/module/library/responsive.js +53 -0
- package/lib/module/library/responsive.js.map +1 -0
- package/lib/module/locales.js +416 -0
- package/lib/module/locales.js.map +1 -0
- package/lib/module/localization.js +1064 -0
- package/lib/module/localization.js.map +1 -0
- package/lib/module/logger.js +20 -0
- package/lib/module/logger.js.map +1 -0
- package/lib/module/lowMemoryCache.js +206 -0
- package/lib/module/lowMemoryCache.js.map +1 -0
- package/lib/module/mediaHistory.js +33 -0
- package/lib/module/mediaHistory.js.map +1 -0
- package/lib/module/mediaStorageService.js +402 -0
- package/lib/module/mediaStorageService.js.map +1 -0
- package/lib/module/memory.js +79 -0
- package/lib/module/memory.js.map +1 -0
- package/lib/module/nativeActions.js +307 -0
- package/lib/module/nativeActions.js.map +1 -0
- package/lib/module/nativeComponentSupport.js +61 -0
- package/lib/module/nativeComponentSupport.js.map +1 -0
- package/lib/module/networkPerformance.js +24 -0
- package/lib/module/networkPerformance.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/performance.js +104 -0
- package/lib/module/performance.js.map +1 -0
- package/lib/module/performanceMonitor.js +155 -0
- package/lib/module/performanceMonitor.js.map +1 -0
- package/lib/module/permissions.js +370 -0
- package/lib/module/permissions.js.map +1 -0
- package/lib/module/rendering.js +40 -0
- package/lib/module/rendering.js.map +1 -0
- package/lib/module/security.js +18 -0
- package/lib/module/security.js.map +1 -0
- package/lib/module/state.js +266 -0
- package/lib/module/state.js.map +1 -0
- package/lib/module/storage.js +400 -0
- package/lib/module/storage.js.map +1 -0
- package/lib/module/theme.js +390 -0
- package/lib/module/theme.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils.js +115 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/module/validation.js +288 -0
- package/lib/module/validation.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AudioPlayerContext.d.ts +27 -0
- package/lib/typescript/src/AudioPlayerContext.d.ts.map +1 -0
- package/lib/typescript/src/CameraPreview.d.ts +13 -0
- package/lib/typescript/src/CameraPreview.d.ts.map +1 -0
- package/lib/typescript/src/NativeAnimatedImageView.d.ts +33 -0
- package/lib/typescript/src/NativeAnimatedImageView.d.ts.map +1 -0
- package/lib/typescript/src/NativeAudioPlayerView.d.ts +12 -0
- package/lib/typescript/src/NativeAudioPlayerView.d.ts.map +1 -0
- package/lib/typescript/src/NativeBlurView.d.ts +12 -0
- package/lib/typescript/src/NativeBlurView.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomCamera.d.ts +167 -0
- package/lib/typescript/src/NativeCustomCamera.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomCamera.web.d.ts +47 -0
- package/lib/typescript/src/NativeCustomCamera.web.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomVideoView.d.ts +30 -0
- package/lib/typescript/src/NativeCustomVideoView.d.ts.map +1 -0
- package/lib/typescript/src/NativeDocumentPreviewView.d.ts +48 -0
- package/lib/typescript/src/NativeDocumentPreviewView.d.ts.map +1 -0
- package/lib/typescript/src/NativeMediaThumbnailView.d.ts +54 -0
- package/lib/typescript/src/NativeMediaThumbnailView.d.ts.map +1 -0
- package/lib/typescript/src/NativeSvgIcon.d.ts +25 -0
- package/lib/typescript/src/NativeSvgIcon.d.ts.map +1 -0
- package/lib/typescript/src/ResponsivePreviewContainer.d.ts +92 -0
- package/lib/typescript/src/ResponsivePreviewContainer.d.ts.map +1 -0
- package/lib/typescript/src/accessibility.d.ts +20 -0
- package/lib/typescript/src/accessibility.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +145 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/cpuOptimization.d.ts +63 -0
- package/lib/typescript/src/cpuOptimization.d.ts.map +1 -0
- package/lib/typescript/src/db/DatabaseManager.d.ts +48 -0
- package/lib/typescript/src/db/DatabaseManager.d.ts.map +1 -0
- package/lib/typescript/src/db/MediaMapper.d.ts +77 -0
- package/lib/typescript/src/db/MediaMapper.d.ts.map +1 -0
- package/lib/typescript/src/db/MediaRepository.d.ts +66 -0
- package/lib/typescript/src/db/MediaRepository.d.ts.map +1 -0
- package/lib/typescript/src/db/index.d.ts +23 -0
- package/lib/typescript/src/db/index.d.ts.map +1 -0
- package/lib/typescript/src/db/migrations.d.ts +31 -0
- package/lib/typescript/src/db/migrations.d.ts.map +1 -0
- package/lib/typescript/src/db/secureKey.d.ts +10 -0
- package/lib/typescript/src/db/secureKey.d.ts.map +1 -0
- package/lib/typescript/src/db/types.d.ts +115 -0
- package/lib/typescript/src/db/types.d.ts.map +1 -0
- package/lib/typescript/src/db/useMediaDatabase.d.ts +41 -0
- package/lib/typescript/src/db/useMediaDatabase.d.ts.map +1 -0
- package/lib/typescript/src/defaultIcons.d.ts +139 -0
- package/lib/typescript/src/defaultIcons.d.ts.map +1 -0
- package/lib/typescript/src/deviceCapabilities.d.ts +92 -0
- package/lib/typescript/src/deviceCapabilities.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +95 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/iconConfig.d.ts +176 -0
- package/lib/typescript/src/iconConfig.d.ts.map +1 -0
- package/lib/typescript/src/icons.d.ts +217 -0
- package/lib/typescript/src/icons.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +104 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/library/MediaLibrary.d.ts +57 -0
- package/lib/typescript/src/library/MediaLibrary.d.ts.map +1 -0
- package/lib/typescript/src/library/MediaLibraryScreen.d.ts +51 -0
- package/lib/typescript/src/library/MediaLibraryScreen.d.ts.map +1 -0
- package/lib/typescript/src/library/components/AudioPreview.d.ts +6 -0
- package/lib/typescript/src/library/components/AudioPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/DocumentPreview.d.ts +10 -0
- package/lib/typescript/src/library/components/DocumentPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ExpandableFab.d.ts +39 -0
- package/lib/typescript/src/library/components/ExpandableFab.d.ts.map +1 -0
- package/lib/typescript/src/library/components/FileTiles.d.ts +8 -0
- package/lib/typescript/src/library/components/FileTiles.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ImagePreview.d.ts +5 -0
- package/lib/typescript/src/library/components/ImagePreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts +16 -0
- package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts.map +1 -0
- package/lib/typescript/src/library/components/RecordAudioScreen.d.ts +8 -0
- package/lib/typescript/src/library/components/RecordAudioScreen.d.ts.map +1 -0
- package/lib/typescript/src/library/components/VideoPreview.d.ts +20 -0
- package/lib/typescript/src/library/components/VideoPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ViewerCounter.d.ts +13 -0
- package/lib/typescript/src/library/components/ViewerCounter.d.ts.map +1 -0
- package/lib/typescript/src/library/compressionEstimate.d.ts +313 -0
- package/lib/typescript/src/library/compressionEstimate.d.ts.map +1 -0
- package/lib/typescript/src/library/fileMetadata.d.ts +33 -0
- package/lib/typescript/src/library/fileMetadata.d.ts.map +1 -0
- package/lib/typescript/src/library/index.d.ts +40 -0
- package/lib/typescript/src/library/index.d.ts.map +1 -0
- package/lib/typescript/src/library/libraryIconRegistry.d.ts +35 -0
- package/lib/typescript/src/library/libraryIconRegistry.d.ts.map +1 -0
- package/lib/typescript/src/library/libraryIcons.d.ts +85 -0
- package/lib/typescript/src/library/libraryIcons.d.ts.map +1 -0
- package/lib/typescript/src/library/localizedFormat.d.ts +49 -0
- package/lib/typescript/src/library/localizedFormat.d.ts.map +1 -0
- package/lib/typescript/src/library/palette.d.ts +104 -0
- package/lib/typescript/src/library/palette.d.ts.map +1 -0
- package/lib/typescript/src/library/responsive.d.ts +15 -0
- package/lib/typescript/src/library/responsive.d.ts.map +1 -0
- package/lib/typescript/src/locales.d.ts +103 -0
- package/lib/typescript/src/locales.d.ts.map +1 -0
- package/lib/typescript/src/localization.d.ts +1051 -0
- package/lib/typescript/src/localization.d.ts.map +1 -0
- package/lib/typescript/src/logger.d.ts +4 -0
- package/lib/typescript/src/logger.d.ts.map +1 -0
- package/lib/typescript/src/lowMemoryCache.d.ts +64 -0
- package/lib/typescript/src/lowMemoryCache.d.ts.map +1 -0
- package/lib/typescript/src/mediaHistory.d.ts +3 -0
- package/lib/typescript/src/mediaHistory.d.ts.map +1 -0
- package/lib/typescript/src/mediaStorageService.d.ts +155 -0
- package/lib/typescript/src/mediaStorageService.d.ts.map +1 -0
- package/lib/typescript/src/memory.d.ts +21 -0
- package/lib/typescript/src/memory.d.ts.map +1 -0
- package/lib/typescript/src/nativeActions.d.ts +174 -0
- package/lib/typescript/src/nativeActions.d.ts.map +1 -0
- package/lib/typescript/src/nativeComponentSupport.d.ts +30 -0
- package/lib/typescript/src/nativeComponentSupport.d.ts.map +1 -0
- package/lib/typescript/src/networkPerformance.d.ts +12 -0
- package/lib/typescript/src/networkPerformance.d.ts.map +1 -0
- package/lib/typescript/src/performance.d.ts +32 -0
- package/lib/typescript/src/performance.d.ts.map +1 -0
- package/lib/typescript/src/performanceMonitor.d.ts +45 -0
- package/lib/typescript/src/performanceMonitor.d.ts.map +1 -0
- package/lib/typescript/src/permissions.d.ts +132 -0
- package/lib/typescript/src/permissions.d.ts.map +1 -0
- package/lib/typescript/src/rendering.d.ts +9 -0
- package/lib/typescript/src/rendering.d.ts.map +1 -0
- package/lib/typescript/src/security.d.ts +6 -0
- package/lib/typescript/src/security.d.ts.map +1 -0
- package/lib/typescript/src/state.d.ts +102 -0
- package/lib/typescript/src/state.d.ts.map +1 -0
- package/lib/typescript/src/storage.d.ts +117 -0
- package/lib/typescript/src/storage.d.ts.map +1 -0
- package/lib/typescript/src/theme.d.ts +15 -0
- package/lib/typescript/src/theme.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +802 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils.d.ts +50 -0
- package/lib/typescript/src/utils.d.ts.map +1 -0
- package/lib/typescript/src/validation.d.ts +52 -0
- package/lib/typescript/src/validation.d.ts.map +1 -0
- package/package.json +204 -0
- package/react-native.config.js +17 -0
- package/src/AudioPlayerContext.tsx +220 -0
- package/src/CameraPreview.tsx +352 -0
- package/src/NativeAnimatedImageView.tsx +71 -0
- package/src/NativeAudioPlayerView.tsx +39 -0
- package/src/NativeBlurView.tsx +40 -0
- package/src/NativeCustomCamera.ts +305 -0
- package/src/NativeCustomCamera.web.ts +192 -0
- package/src/NativeCustomVideoView.ts +30 -0
- package/src/NativeDocumentPreviewView.tsx +196 -0
- package/src/NativeMediaThumbnailView.tsx +167 -0
- package/src/NativeSvgIcon.tsx +46 -0
- package/src/ResponsivePreviewContainer.tsx +227 -0
- package/src/accessibility.ts +80 -0
- package/src/config.ts +406 -0
- package/src/cpuOptimization.ts +365 -0
- package/src/db/DatabaseManager.ts +212 -0
- package/src/db/MediaMapper.ts +377 -0
- package/src/db/MediaRepository.ts +313 -0
- package/src/db/index.ts +73 -0
- package/src/db/migrations.ts +222 -0
- package/src/db/react-native-sqlcipher.d.ts +76 -0
- package/src/db/secureKey.ts +100 -0
- package/src/db/types.ts +123 -0
- package/src/db/useMediaDatabase.ts +191 -0
- package/src/defaultIcons.tsx +2390 -0
- package/src/deviceCapabilities.ts +438 -0
- package/src/errors.ts +350 -0
- package/src/iconConfig.tsx +962 -0
- package/src/icons.tsx +788 -0
- package/src/index.tsx +763 -0
- package/src/jest-globals.d.ts +9 -0
- package/src/library/MediaLibrary.tsx +141 -0
- package/src/library/MediaLibraryScreen.tsx +3003 -0
- package/src/library/components/AudioPreview.tsx +73 -0
- package/src/library/components/DocumentPreview.tsx +361 -0
- package/src/library/components/ExpandableFab.tsx +342 -0
- package/src/library/components/FileTiles.tsx +252 -0
- package/src/library/components/ImagePreview.tsx +72 -0
- package/src/library/components/MediaMetadataHeader.tsx +208 -0
- package/src/library/components/RecordAudioScreen.tsx +845 -0
- package/src/library/components/VideoPreview.tsx +478 -0
- package/src/library/components/ViewerCounter.tsx +71 -0
- package/src/library/compressionEstimate.ts +662 -0
- package/src/library/fileMetadata.ts +212 -0
- package/src/library/index.ts +66 -0
- package/src/library/libraryIconRegistry.tsx +86 -0
- package/src/library/libraryIcons.tsx +928 -0
- package/src/library/localizedFormat.ts +207 -0
- package/src/library/palette.ts +281 -0
- package/src/library/responsive.ts +49 -0
- package/src/locales.ts +381 -0
- package/src/localization.ts +1947 -0
- package/src/logger.ts +20 -0
- package/src/lowMemoryCache.ts +218 -0
- package/src/mediaHistory.ts +42 -0
- package/src/mediaStorageService.ts +546 -0
- package/src/memory.ts +102 -0
- package/src/nativeActions.ts +422 -0
- package/src/nativeComponentSupport.ts +60 -0
- package/src/networkPerformance.ts +30 -0
- package/src/performance.ts +137 -0
- package/src/performanceMonitor.ts +219 -0
- package/src/permissions.ts +403 -0
- package/src/rendering.ts +47 -0
- package/src/security.ts +19 -0
- package/src/state.ts +333 -0
- package/src/storage.ts +453 -0
- package/src/theme.tsx +467 -0
- package/src/types.ts +946 -0
- package/src/utils.ts +177 -0
- package/src/validation.ts +372 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MediaMapper — the single translation boundary between the UI-facing
|
|
3
|
+
* `CameraAsset` type and the persisted `MediaRecord`/SQL row.
|
|
4
|
+
*
|
|
5
|
+
* Keeping all field mapping here means the rest of the app (screens, hooks) and
|
|
6
|
+
* the database schema can evolve independently. Two directions are supported:
|
|
7
|
+
* - `assetToRecordInput` CameraAsset → MediaRecordInput (for writes)
|
|
8
|
+
* - `rowToRecord` / `recordToAsset` SQL row → MediaRecord → CameraAsset (reads)
|
|
9
|
+
*/
|
|
10
|
+
import type { CameraAsset } from '../types';
|
|
11
|
+
import {
|
|
12
|
+
MediaColumns,
|
|
13
|
+
type MediaRecord,
|
|
14
|
+
type MediaRecordInput,
|
|
15
|
+
type MediaSource,
|
|
16
|
+
type MediaType,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* UI uses `photo`; the database normalizes photos to `image`. Videos, documents
|
|
21
|
+
* and audio are preserved as-is so a picked PDF or track keeps its true kind
|
|
22
|
+
* instead of collapsing into `image` (the historical cause of documents/audio
|
|
23
|
+
* showing up under the Photos filter).
|
|
24
|
+
*/
|
|
25
|
+
export function assetTypeToMediaType(type: CameraAsset['type']): MediaType {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case 'video':
|
|
28
|
+
return 'video';
|
|
29
|
+
case 'document':
|
|
30
|
+
return 'document';
|
|
31
|
+
case 'audio':
|
|
32
|
+
return 'audio';
|
|
33
|
+
default:
|
|
34
|
+
return 'image';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Inverse of {@link assetTypeToMediaType}. */
|
|
39
|
+
export function mediaTypeToAssetType(type: MediaType): CameraAsset['type'] {
|
|
40
|
+
switch (type) {
|
|
41
|
+
case 'video':
|
|
42
|
+
return 'video';
|
|
43
|
+
case 'document':
|
|
44
|
+
return 'document';
|
|
45
|
+
case 'audio':
|
|
46
|
+
return 'audio';
|
|
47
|
+
default:
|
|
48
|
+
return 'photo';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Extension → kind tables. Used as a fallback (and cross-check) when the MIME
|
|
53
|
+
// type is missing or generic, so classification never relies on a single signal.
|
|
54
|
+
const IMAGE_EXTS = new Set([
|
|
55
|
+
'jpg',
|
|
56
|
+
'jpeg',
|
|
57
|
+
'png',
|
|
58
|
+
'gif',
|
|
59
|
+
'webp',
|
|
60
|
+
'bmp',
|
|
61
|
+
'heic',
|
|
62
|
+
'heif',
|
|
63
|
+
'hif',
|
|
64
|
+
'avif',
|
|
65
|
+
'avifs',
|
|
66
|
+
'tif',
|
|
67
|
+
'tiff',
|
|
68
|
+
]);
|
|
69
|
+
const VIDEO_EXTS = new Set([
|
|
70
|
+
'mp4',
|
|
71
|
+
'mov',
|
|
72
|
+
'mkv',
|
|
73
|
+
'webm',
|
|
74
|
+
'avi',
|
|
75
|
+
'3gp',
|
|
76
|
+
'3gpp',
|
|
77
|
+
'm4v',
|
|
78
|
+
'mpg',
|
|
79
|
+
'mpeg',
|
|
80
|
+
]);
|
|
81
|
+
const AUDIO_EXTS = new Set([
|
|
82
|
+
'mp3',
|
|
83
|
+
'wav',
|
|
84
|
+
'aac',
|
|
85
|
+
'm4a',
|
|
86
|
+
'ogg',
|
|
87
|
+
'oga',
|
|
88
|
+
'flac',
|
|
89
|
+
'opus',
|
|
90
|
+
'amr',
|
|
91
|
+
'mid',
|
|
92
|
+
'midi',
|
|
93
|
+
]);
|
|
94
|
+
const DOCUMENT_EXTS = new Set([
|
|
95
|
+
'pdf',
|
|
96
|
+
'doc',
|
|
97
|
+
'docx',
|
|
98
|
+
'xls',
|
|
99
|
+
'xlsx',
|
|
100
|
+
'ppt',
|
|
101
|
+
'pptx',
|
|
102
|
+
'txt',
|
|
103
|
+
'csv',
|
|
104
|
+
'rtf',
|
|
105
|
+
'odt',
|
|
106
|
+
'ods',
|
|
107
|
+
'odp',
|
|
108
|
+
'epub',
|
|
109
|
+
'md',
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
function extensionOf(fileName?: string | null): string | null {
|
|
113
|
+
if (!fileName) return null;
|
|
114
|
+
const dot = fileName.lastIndexOf('.');
|
|
115
|
+
if (dot < 0 || dot === fileName.length - 1) return null;
|
|
116
|
+
return fileName.slice(dot + 1).toLowerCase();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The extension a path/URI ends in, ignoring any query string and the directories above it. */
|
|
120
|
+
function extensionOfPath(path?: string | null): string | null {
|
|
121
|
+
if (!path) return null;
|
|
122
|
+
const withoutQuery = path.split(/[?#]/)[0] ?? '';
|
|
123
|
+
return extensionOf(withoutQuery.split(/[\\/]/).pop());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Image MIME types whose container is worth preserving in a synthesized file name. Mirrors the
|
|
127
|
+
// native ImageFormatSupport table: naming a GIF `.jpg` puts JPEG's name on bytes that
|
|
128
|
+
// start with GIF89a, and consumers then fail to render it.
|
|
129
|
+
const MIME_TO_EXTENSION: Record<string, string> = {
|
|
130
|
+
'image/gif': 'gif',
|
|
131
|
+
'image/png': 'png',
|
|
132
|
+
'image/webp': 'webp',
|
|
133
|
+
'image/heic': 'heic',
|
|
134
|
+
'image/heif': 'heif',
|
|
135
|
+
'image/heic-sequence': 'heic',
|
|
136
|
+
'image/heif-sequence': 'heif',
|
|
137
|
+
'image/avif': 'avif',
|
|
138
|
+
'image/bmp': 'bmp',
|
|
139
|
+
'image/jpeg': 'jpg',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The file extension a synthesized name should carry for `mimeType`, defaulting to the given
|
|
144
|
+
* fallback when the type is missing or unrecognised.
|
|
145
|
+
*/
|
|
146
|
+
export function extensionForMimeType(
|
|
147
|
+
mimeType: string | null | undefined,
|
|
148
|
+
fallback: string
|
|
149
|
+
): string {
|
|
150
|
+
if (!mimeType) return fallback;
|
|
151
|
+
return MIME_TO_EXTENSION[mimeType.toLowerCase()] ?? fallback;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolves the canonical persisted {@link MediaType} for an asset using every
|
|
156
|
+
* available signal — the caller-provided `type`, the MIME type, then the file
|
|
157
|
+
* extension — so a mis-tagged or MIME-less item is still classified correctly.
|
|
158
|
+
* Order of precedence: a document path → MIME family → extension → the UI `type`
|
|
159
|
+
* fallback.
|
|
160
|
+
*/
|
|
161
|
+
export function classifyMediaType(asset: CameraAsset): MediaType {
|
|
162
|
+
const mime = asset.mimeType?.toLowerCase() ?? '';
|
|
163
|
+
|
|
164
|
+
// The FILE outranks an `image/*` MIME type when the file itself names a document container.
|
|
165
|
+
// "Export By → PDF" turns a photo into a PDF, and an asset that carries the source photo's
|
|
166
|
+
// metadata — because it was written before the export re-stated it — says `image/jpeg` while
|
|
167
|
+
// its path ends in `.pdf`. Trusting the MIME there stored the PDF as an image: it was filed
|
|
168
|
+
// under Photos and handed to the image decoder, which has no idea what a PDF is, so the
|
|
169
|
+
// preview came up blank.
|
|
170
|
+
const pathExtension = extensionOfPath(resolveDocumentsPath(asset) ?? asset.uri);
|
|
171
|
+
if (
|
|
172
|
+
pathExtension &&
|
|
173
|
+
DOCUMENT_EXTS.has(pathExtension) &&
|
|
174
|
+
(!mime || mime.startsWith('image/'))
|
|
175
|
+
) {
|
|
176
|
+
return 'document';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (mime.startsWith('image/')) return 'image';
|
|
180
|
+
if (mime.startsWith('video/')) return 'video';
|
|
181
|
+
if (mime.startsWith('audio/')) return 'audio';
|
|
182
|
+
if (isDocumentMime(mime)) return 'document';
|
|
183
|
+
|
|
184
|
+
const ext = extensionOf(asset.fileName);
|
|
185
|
+
if (ext) {
|
|
186
|
+
if (IMAGE_EXTS.has(ext)) return 'image';
|
|
187
|
+
if (VIDEO_EXTS.has(ext)) return 'video';
|
|
188
|
+
if (AUDIO_EXTS.has(ext)) return 'audio';
|
|
189
|
+
if (DOCUMENT_EXTS.has(ext)) return 'document';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Nothing conclusive from mime/extension: trust the type the picker assigned.
|
|
193
|
+
return assetTypeToMediaType(asset.type);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** True for MIME strings that denote a document (non image/video/audio) file. */
|
|
197
|
+
function isDocumentMime(mime: string): boolean {
|
|
198
|
+
if (!mime) return false;
|
|
199
|
+
if (
|
|
200
|
+
mime.startsWith('image/') ||
|
|
201
|
+
mime.startsWith('video/') ||
|
|
202
|
+
mime.startsWith('audio/')
|
|
203
|
+
) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return (
|
|
207
|
+
mime === 'application/pdf' ||
|
|
208
|
+
mime.startsWith('application/vnd.') ||
|
|
209
|
+
mime.startsWith('application/msword') ||
|
|
210
|
+
mime.startsWith('application/rtf') ||
|
|
211
|
+
mime === 'application/epub+zip' ||
|
|
212
|
+
mime.startsWith('text/')
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Normalizes an arbitrary source hint onto the allowed enum. */
|
|
217
|
+
export function normalizeSource(source: unknown): MediaSource {
|
|
218
|
+
return source === 'gallery' ? 'gallery' : 'camera';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resolves the FINAL, persistent Documents path for an asset — the path that
|
|
223
|
+
* must be stored in SQLCipher — or `null` if the asset is not (yet) in the
|
|
224
|
+
* Documents directory.
|
|
225
|
+
*
|
|
226
|
+
* The native Send workflow moves a confirmed file from cache to
|
|
227
|
+
* `.../Documents/media_documents/...`, clears `cachePath`, and sets
|
|
228
|
+
* `documentUri`. This helper therefore prefers `documentUri`, then falls back to
|
|
229
|
+
* `uri` ONLY when that uri is itself a published Documents path (not a cache or
|
|
230
|
+
* `content://` gallery path). A still-cached / publish-failed asset returns
|
|
231
|
+
* `null`, so callers can skip it and never persist a temporary location.
|
|
232
|
+
*/
|
|
233
|
+
export function resolveDocumentsPath(asset: CameraAsset): string | null {
|
|
234
|
+
const documentUri = (asset as { documentUri?: string }).documentUri;
|
|
235
|
+
if (documentUri && documentUri.length > 0) {
|
|
236
|
+
return documentUri;
|
|
237
|
+
}
|
|
238
|
+
const uri = asset.uri ?? '';
|
|
239
|
+
if (uri.length > 0) {
|
|
240
|
+
return uri;
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* True when an asset has been successfully moved to the Documents directory and
|
|
247
|
+
* is therefore eligible to be persisted in SQLCipher. Cache-only or
|
|
248
|
+
* publish-failed assets return false.
|
|
249
|
+
*/
|
|
250
|
+
export function isPublishedToDocuments(asset: CameraAsset): boolean {
|
|
251
|
+
return resolveDocumentsPath(asset) !== null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Auto-detects the origin of an asset. Gallery-selected media retains its
|
|
256
|
+
* original `content://`/gallery uri in `originalUri`; camera captures do not.
|
|
257
|
+
* An explicit hint always wins.
|
|
258
|
+
*/
|
|
259
|
+
export function detectSource(
|
|
260
|
+
asset: CameraAsset,
|
|
261
|
+
hint?: MediaSource
|
|
262
|
+
): MediaSource {
|
|
263
|
+
const explicit = (asset as { source?: unknown }).source;
|
|
264
|
+
if (explicit === 'gallery' || explicit === 'camera') return explicit;
|
|
265
|
+
if (hint) return hint;
|
|
266
|
+
const originalUri = (asset as { originalUri?: string }).originalUri;
|
|
267
|
+
return originalUri && originalUri.length > 0 ? 'gallery' : 'camera';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Builds a `MediaRecordInput` from a UI asset, storing the FINAL Documents path.
|
|
272
|
+
*
|
|
273
|
+
* @param source optional origin hint; when omitted the origin is auto-detected
|
|
274
|
+
* (gallery vs camera). The stored `filePath` is always the resolved Documents
|
|
275
|
+
* path — callers must only pass assets for which {@link isPublishedToDocuments}
|
|
276
|
+
* is true (the repository enforces this).
|
|
277
|
+
*/
|
|
278
|
+
export function assetToRecordInput(
|
|
279
|
+
asset: CameraAsset,
|
|
280
|
+
source?: MediaSource
|
|
281
|
+
): MediaRecordInput {
|
|
282
|
+
// Classify from every available signal (type + MIME + extension) so a picked
|
|
283
|
+
// document/audio file is stored under its true kind, never collapsed to image.
|
|
284
|
+
const mediaType = classifyMediaType(asset);
|
|
285
|
+
const now = Date.now();
|
|
286
|
+
// Persist the Documents path only. Fall back to uri defensively; the
|
|
287
|
+
// repository will have already filtered out un-published assets.
|
|
288
|
+
const documentsPath = resolveDocumentsPath(asset) ?? asset.uri ?? '';
|
|
289
|
+
return {
|
|
290
|
+
id: asset.id,
|
|
291
|
+
mediaType,
|
|
292
|
+
source: detectSource(asset, source),
|
|
293
|
+
filePath: documentsPath,
|
|
294
|
+
fileName:
|
|
295
|
+
asset.fileName ??
|
|
296
|
+
(mediaType === 'video'
|
|
297
|
+
? `video_${now}.mp4`
|
|
298
|
+
: `photo_${now}.${extensionForMimeType(asset.mimeType, 'jpg')}`),
|
|
299
|
+
fileSize: asset.size ?? null,
|
|
300
|
+
width: asset.width ?? null,
|
|
301
|
+
height: asset.height ?? null,
|
|
302
|
+
// Videos and audio carry a duration; images/documents do not.
|
|
303
|
+
duration:
|
|
304
|
+
mediaType === 'video' || mediaType === 'audio'
|
|
305
|
+
? asset.duration ?? null
|
|
306
|
+
: null,
|
|
307
|
+
mimeType: asset.mimeType ?? null,
|
|
308
|
+
createdAt: asset.dateTaken ?? now,
|
|
309
|
+
updatedAt: now,
|
|
310
|
+
// A compressed video quotes a proportional display size, not its bytes on
|
|
311
|
+
// disk; persist the flag so the preview / Info panel keeps that figure.
|
|
312
|
+
sizeIsDisplayEstimate: asset.sizeIsDisplayEstimate ?? null,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Normalizes a raw stored media_type string onto the {@link MediaType} enum. */
|
|
317
|
+
function normalizeMediaType(value: unknown): MediaType {
|
|
318
|
+
return value === 'video' || value === 'document' || value === 'audio'
|
|
319
|
+
? value
|
|
320
|
+
: 'image';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Maps a raw SQL row (snake_case columns) into a typed `MediaRecord`. */
|
|
324
|
+
export function rowToRecord(row: Record<string, any>): MediaRecord {
|
|
325
|
+
return {
|
|
326
|
+
id: String(row[MediaColumns.id]),
|
|
327
|
+
mediaType: normalizeMediaType(row[MediaColumns.mediaType]),
|
|
328
|
+
source: normalizeSource(row[MediaColumns.source]),
|
|
329
|
+
filePath: row[MediaColumns.filePath] ?? '',
|
|
330
|
+
fileName: row[MediaColumns.fileName] ?? '',
|
|
331
|
+
fileSize: toNumberOrNull(row[MediaColumns.fileSize]),
|
|
332
|
+
width: toNumberOrNull(row[MediaColumns.width]),
|
|
333
|
+
height: toNumberOrNull(row[MediaColumns.height]),
|
|
334
|
+
duration: toNumberOrNull(row[MediaColumns.duration]),
|
|
335
|
+
mimeType: row[MediaColumns.mimeType] ?? null,
|
|
336
|
+
createdAt: Number(row[MediaColumns.createdAt]) || 0,
|
|
337
|
+
updatedAt: Number(row[MediaColumns.updatedAt]) || 0,
|
|
338
|
+
sizeIsDisplayEstimate: toBoolean(row[MediaColumns.sizeIsDisplayEstimate]),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Maps a persisted record back to the UI `CameraAsset` shape so the existing
|
|
344
|
+
* Media Library renders identically — only the data source changed.
|
|
345
|
+
*/
|
|
346
|
+
export function recordToAsset(record: MediaRecord): CameraAsset {
|
|
347
|
+
return {
|
|
348
|
+
id: record.id,
|
|
349
|
+
uri: record.filePath,
|
|
350
|
+
type: mediaTypeToAssetType(record.mediaType),
|
|
351
|
+
fileName: record.fileName,
|
|
352
|
+
mimeType: record.mimeType ?? undefined,
|
|
353
|
+
width: record.width ?? undefined,
|
|
354
|
+
height: record.height ?? undefined,
|
|
355
|
+
duration: record.duration ?? undefined,
|
|
356
|
+
size: record.fileSize ?? undefined,
|
|
357
|
+
dateTaken: record.createdAt,
|
|
358
|
+
needsThumbnail: record.mediaType === 'video',
|
|
359
|
+
sizeIsDisplayEstimate: record.sizeIsDisplayEstimate,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function toNumberOrNull(value: unknown): number | null {
|
|
364
|
+
if (value === null || value === undefined || value === '') return null;
|
|
365
|
+
const n = Number(value);
|
|
366
|
+
return Number.isFinite(n) ? n : null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Coerces a stored INTEGER flag (1/0), or a driver-native boolean, to a boolean.
|
|
371
|
+
* A null/absent column (legacy rows predating the column) reads as false.
|
|
372
|
+
*/
|
|
373
|
+
function toBoolean(value: unknown): boolean {
|
|
374
|
+
if (value === null || value === undefined || value === '') return false;
|
|
375
|
+
if (typeof value === 'boolean') return value;
|
|
376
|
+
return Number(value) === 1;
|
|
377
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MediaRepository — the CRUD/service layer over the `media` table.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY module that writes SQL for media rows. UI code and hooks talk
|
|
5
|
+
* to strongly-typed `MediaRecord`/`CameraAsset` objects and never see a query
|
|
6
|
+
* string, keeping a clean separation between presentation and persistence.
|
|
7
|
+
*
|
|
8
|
+
* All operations delegate raw execution to the centralized `DatabaseManager`.
|
|
9
|
+
*/
|
|
10
|
+
import type { CameraAsset } from '../types';
|
|
11
|
+
import {
|
|
12
|
+
getDatabaseManager,
|
|
13
|
+
type BatchStatement,
|
|
14
|
+
type DatabaseManager,
|
|
15
|
+
} from './DatabaseManager';
|
|
16
|
+
import {
|
|
17
|
+
assetToRecordInput,
|
|
18
|
+
isPublishedToDocuments,
|
|
19
|
+
recordToAsset,
|
|
20
|
+
rowToRecord,
|
|
21
|
+
} from './MediaMapper';
|
|
22
|
+
import {
|
|
23
|
+
MEDIA_TABLE,
|
|
24
|
+
MediaColumns as C,
|
|
25
|
+
type MediaCounts,
|
|
26
|
+
type MediaFilter,
|
|
27
|
+
type MediaQueryOptions,
|
|
28
|
+
type MediaRecord,
|
|
29
|
+
type MediaRecordInput,
|
|
30
|
+
type MediaSource,
|
|
31
|
+
} from './types';
|
|
32
|
+
|
|
33
|
+
const ALL_COLUMNS = [
|
|
34
|
+
C.id,
|
|
35
|
+
C.mediaType,
|
|
36
|
+
C.source,
|
|
37
|
+
C.filePath,
|
|
38
|
+
C.fileName,
|
|
39
|
+
C.fileSize,
|
|
40
|
+
C.width,
|
|
41
|
+
C.height,
|
|
42
|
+
C.duration,
|
|
43
|
+
C.mimeType,
|
|
44
|
+
C.createdAt,
|
|
45
|
+
C.updatedAt,
|
|
46
|
+
C.sizeIsDisplayEstimate,
|
|
47
|
+
].join(', ');
|
|
48
|
+
|
|
49
|
+
export class MediaRepository {
|
|
50
|
+
constructor(private readonly db: DatabaseManager = getDatabaseManager()) {}
|
|
51
|
+
|
|
52
|
+
/** Builds an idempotent UPSERT statement for one record. */
|
|
53
|
+
private upsertStatement(input: MediaRecordInput): BatchStatement {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const createdAt = input.createdAt ?? now;
|
|
56
|
+
const updatedAt = input.updatedAt ?? now;
|
|
57
|
+
return {
|
|
58
|
+
sql: `INSERT INTO ${MEDIA_TABLE} (
|
|
59
|
+
${ALL_COLUMNS}
|
|
60
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
61
|
+
ON CONFLICT(${C.id}) DO UPDATE SET
|
|
62
|
+
${C.mediaType} = excluded.${C.mediaType},
|
|
63
|
+
${C.source} = excluded.${C.source},
|
|
64
|
+
${C.filePath} = excluded.${C.filePath},
|
|
65
|
+
${C.fileName} = excluded.${C.fileName},
|
|
66
|
+
${C.fileSize} = excluded.${C.fileSize},
|
|
67
|
+
${C.width} = excluded.${C.width},
|
|
68
|
+
${C.height} = excluded.${C.height},
|
|
69
|
+
${C.duration} = excluded.${C.duration},
|
|
70
|
+
${C.mimeType} = excluded.${C.mimeType},
|
|
71
|
+
${C.sizeIsDisplayEstimate} = excluded.${C.sizeIsDisplayEstimate},
|
|
72
|
+
${C.updatedAt} = excluded.${C.updatedAt};`,
|
|
73
|
+
params: [
|
|
74
|
+
input.id,
|
|
75
|
+
input.mediaType,
|
|
76
|
+
input.source,
|
|
77
|
+
input.filePath,
|
|
78
|
+
input.fileName,
|
|
79
|
+
input.fileSize ?? null,
|
|
80
|
+
input.width ?? null,
|
|
81
|
+
input.height ?? null,
|
|
82
|
+
input.duration ?? null,
|
|
83
|
+
input.mimeType ?? null,
|
|
84
|
+
createdAt,
|
|
85
|
+
updatedAt,
|
|
86
|
+
input.sizeIsDisplayEstimate == null
|
|
87
|
+
? null
|
|
88
|
+
: input.sizeIsDisplayEstimate
|
|
89
|
+
? 1
|
|
90
|
+
: 0,
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Inserts (or updates on id/path conflict) a single record. */
|
|
96
|
+
async insert(input: MediaRecordInput): Promise<void> {
|
|
97
|
+
const stmt = this.upsertStatement(input);
|
|
98
|
+
await this.db.exec(stmt.sql, stmt.params);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Inserts many records atomically in one transaction. */
|
|
102
|
+
async insertMany(inputs: MediaRecordInput[]): Promise<void> {
|
|
103
|
+
if (inputs.length === 0) return;
|
|
104
|
+
await this.db.batch(inputs.map((i) => this.upsertStatement(i)));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Persists confirmed, Documents-backed media from the capture/gallery Send
|
|
109
|
+
* flows. This is the ONLY path the app uses after a successful Send.
|
|
110
|
+
*
|
|
111
|
+
* Contract enforced here (so callers can't create inconsistent records):
|
|
112
|
+
* 1. Only assets that have been successfully moved to the Documents
|
|
113
|
+
* directory are persisted — see {@link isPublishedToDocuments}. A
|
|
114
|
+
* cache-only or publish-failed asset is skipped, never stored.
|
|
115
|
+
* 2. The stored `filePath` is always the final Documents path, never cache.
|
|
116
|
+
* 3. The write is atomic; on failure nothing is committed (all-or-nothing),
|
|
117
|
+
* so a partial batch can't leave dangling rows.
|
|
118
|
+
*
|
|
119
|
+
* @param source optional origin hint; when omitted, each asset's origin is
|
|
120
|
+
* auto-detected (gallery vs camera).
|
|
121
|
+
* @returns the records actually persisted (may be fewer than `assets`).
|
|
122
|
+
*/
|
|
123
|
+
async insertAssets(
|
|
124
|
+
assets: CameraAsset[],
|
|
125
|
+
source?: MediaSource
|
|
126
|
+
): Promise<MediaRecord[]> {
|
|
127
|
+
// Gate: keep only well-formed assets that are confirmed in Documents.
|
|
128
|
+
const publishable = assets.filter(
|
|
129
|
+
(a) => a && a.id && a.uri && isPublishedToDocuments(a)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const skipped = assets.length - publishable.length;
|
|
133
|
+
if (skipped > 0) {
|
|
134
|
+
console.warn(
|
|
135
|
+
`[MediaRepository] Skipped ${skipped} asset(s) not yet in the ` +
|
|
136
|
+
`Documents directory; they will not be persisted.`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (publishable.length === 0) return [];
|
|
141
|
+
|
|
142
|
+
const inputs = publishable.map((a) => assetToRecordInput(a, source));
|
|
143
|
+
|
|
144
|
+
// Atomic insert. If it throws, the transaction rolls back and we surface the
|
|
145
|
+
// error to the caller without having written any partial state.
|
|
146
|
+
await this.insertMany(inputs);
|
|
147
|
+
|
|
148
|
+
return inputs.map((i) => ({
|
|
149
|
+
...i,
|
|
150
|
+
fileSize: i.fileSize ?? null,
|
|
151
|
+
width: i.width ?? null,
|
|
152
|
+
height: i.height ?? null,
|
|
153
|
+
duration: i.duration ?? null,
|
|
154
|
+
mimeType: i.mimeType ?? null,
|
|
155
|
+
createdAt: i.createdAt ?? Date.now(),
|
|
156
|
+
updatedAt: i.updatedAt ?? Date.now(),
|
|
157
|
+
sizeIsDisplayEstimate: i.sizeIsDisplayEstimate ?? false,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private buildWhere(filter?: MediaFilter): { clause: string; args: any[] } {
|
|
162
|
+
if (
|
|
163
|
+
filter === 'image' ||
|
|
164
|
+
filter === 'video' ||
|
|
165
|
+
filter === 'document' ||
|
|
166
|
+
filter === 'audio'
|
|
167
|
+
) {
|
|
168
|
+
return { clause: `WHERE ${C.mediaType} = ?`, args: [filter] };
|
|
169
|
+
}
|
|
170
|
+
return { clause: '', args: [] };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Returns records matching the query, newest first by default. */
|
|
174
|
+
async getAll(options: MediaQueryOptions = {}): Promise<MediaRecord[]> {
|
|
175
|
+
const {
|
|
176
|
+
filter = 'all',
|
|
177
|
+
orderBy = 'createdAt',
|
|
178
|
+
direction = 'DESC',
|
|
179
|
+
limit,
|
|
180
|
+
offset,
|
|
181
|
+
} = options;
|
|
182
|
+
|
|
183
|
+
const orderColumn =
|
|
184
|
+
orderBy === 'updatedAt'
|
|
185
|
+
? C.updatedAt
|
|
186
|
+
: orderBy === 'fileName'
|
|
187
|
+
? C.fileName
|
|
188
|
+
: C.createdAt;
|
|
189
|
+
const dir = direction === 'ASC' ? 'ASC' : 'DESC';
|
|
190
|
+
const { clause, args } = this.buildWhere(filter);
|
|
191
|
+
|
|
192
|
+
let sql = `SELECT ${ALL_COLUMNS} FROM ${MEDIA_TABLE} ${clause} ORDER BY ${orderColumn} ${dir}`;
|
|
193
|
+
const params = [...args];
|
|
194
|
+
if (typeof limit === 'number') {
|
|
195
|
+
sql += ' LIMIT ?';
|
|
196
|
+
params.push(limit);
|
|
197
|
+
if (typeof offset === 'number') {
|
|
198
|
+
sql += ' OFFSET ?';
|
|
199
|
+
params.push(offset);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const rows = await this.db.query<Record<string, any>>(sql, params);
|
|
204
|
+
return rows.map(rowToRecord);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Returns only image records (backs the "Photos" filter). */
|
|
208
|
+
getImages(options: Omit<MediaQueryOptions, 'filter'> = {}) {
|
|
209
|
+
return this.getAll({ ...options, filter: 'image' });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Returns only video records (backs the "Videos" filter). */
|
|
213
|
+
getVideos(options: Omit<MediaQueryOptions, 'filter'> = {}) {
|
|
214
|
+
return this.getAll({ ...options, filter: 'video' });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Returns records mapped straight to UI `CameraAsset`s. */
|
|
218
|
+
async getAssets(options: MediaQueryOptions = {}): Promise<CameraAsset[]> {
|
|
219
|
+
const records = await this.getAll(options);
|
|
220
|
+
return records.map(recordToAsset);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Fetches a single record by id, or null. */
|
|
224
|
+
async getById(id: string): Promise<MediaRecord | null> {
|
|
225
|
+
const rows = await this.db.query<Record<string, any>>(
|
|
226
|
+
`SELECT ${ALL_COLUMNS} FROM ${MEDIA_TABLE} WHERE ${C.id} = ? LIMIT 1`,
|
|
227
|
+
[id]
|
|
228
|
+
);
|
|
229
|
+
return rows.length > 0 ? rowToRecord(rows[0]!) : null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Computes the filter-card counts directly from the database in one query,
|
|
234
|
+
* one bucket per media kind (image / video / document / audio).
|
|
235
|
+
*/
|
|
236
|
+
async getCounts(): Promise<MediaCounts> {
|
|
237
|
+
const rows = await this.db.query<{ media_type: string; c: number }>(
|
|
238
|
+
`SELECT ${C.mediaType} AS ${C.mediaType}, COUNT(*) AS c
|
|
239
|
+
FROM ${MEDIA_TABLE} GROUP BY ${C.mediaType}`
|
|
240
|
+
);
|
|
241
|
+
const counts: MediaCounts = {
|
|
242
|
+
all: 0,
|
|
243
|
+
image: 0,
|
|
244
|
+
video: 0,
|
|
245
|
+
document: 0,
|
|
246
|
+
audio: 0,
|
|
247
|
+
};
|
|
248
|
+
for (const row of rows) {
|
|
249
|
+
const c = Number(row.c) || 0;
|
|
250
|
+
switch (row.media_type) {
|
|
251
|
+
case 'video':
|
|
252
|
+
counts.video += c;
|
|
253
|
+
break;
|
|
254
|
+
case 'document':
|
|
255
|
+
counts.document += c;
|
|
256
|
+
break;
|
|
257
|
+
case 'audio':
|
|
258
|
+
counts.audio += c;
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
counts.image += c;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
counts.all += c;
|
|
265
|
+
}
|
|
266
|
+
return counts;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Deletes a record by id. Returns true if a row was removed. */
|
|
270
|
+
async deleteById(id: string): Promise<boolean> {
|
|
271
|
+
const result = await this.db.exec(
|
|
272
|
+
`DELETE FROM ${MEDIA_TABLE} WHERE ${C.id} = ?`,
|
|
273
|
+
[id]
|
|
274
|
+
);
|
|
275
|
+
return result.rowsAffected > 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Deletes a record by its file path/URI. Returns true if a row was removed. */
|
|
279
|
+
async deleteByPath(filePath: string): Promise<boolean> {
|
|
280
|
+
const result = await this.db.exec(
|
|
281
|
+
`DELETE FROM ${MEDIA_TABLE} WHERE ${C.filePath} = ?`,
|
|
282
|
+
[filePath]
|
|
283
|
+
);
|
|
284
|
+
return result.rowsAffected > 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Deletes multiple records by id atomically. Returns the number requested. */
|
|
288
|
+
async deleteMany(ids: string[]): Promise<void> {
|
|
289
|
+
if (ids.length === 0) return;
|
|
290
|
+
await this.db.batch(
|
|
291
|
+
ids.map((id) => ({
|
|
292
|
+
sql: `DELETE FROM ${MEDIA_TABLE} WHERE ${C.id} = ?`,
|
|
293
|
+
params: [id],
|
|
294
|
+
}))
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Removes every media row. */
|
|
299
|
+
async clear(): Promise<void> {
|
|
300
|
+
await this.db.exec(`DELETE FROM ${MEDIA_TABLE}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Shared singleton repository over the singleton manager.
|
|
305
|
+
let repositoryInstance: MediaRepository | null = null;
|
|
306
|
+
|
|
307
|
+
/** Returns the shared `MediaRepository` singleton. */
|
|
308
|
+
export function getMediaRepository(): MediaRepository {
|
|
309
|
+
if (!repositoryInstance) {
|
|
310
|
+
repositoryInstance = new MediaRepository();
|
|
311
|
+
}
|
|
312
|
+
return repositoryInstance;
|
|
313
|
+
}
|