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,722 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.ContentValues
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.os.Environment
|
|
8
|
+
import android.provider.MediaStore
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import kotlinx.coroutines.*
|
|
11
|
+
import java.io.File
|
|
12
|
+
import java.io.FileOutputStream
|
|
13
|
+
import java.io.IOException
|
|
14
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* MediaStorageWorkflow orchestrates the complete media storage lifecycle:
|
|
18
|
+
* 1. Initial capture/selection -> Cache Directory
|
|
19
|
+
* 2. User confirms -> Move to Document Directory
|
|
20
|
+
* 3. Metadata persistence and tracking
|
|
21
|
+
* 4. Automatic cleanup of old cache files
|
|
22
|
+
*
|
|
23
|
+
* Thread-safe and optimized for responsive operation
|
|
24
|
+
*/
|
|
25
|
+
class MediaStorageWorkflow private constructor(private val context: Context) {
|
|
26
|
+
|
|
27
|
+
companion object {
|
|
28
|
+
private const val TAG = "MediaStorageWorkflow"
|
|
29
|
+
private const val MAX_RETRY_ATTEMPTS = 3
|
|
30
|
+
|
|
31
|
+
@Volatile
|
|
32
|
+
private var instance: MediaStorageWorkflow? = null
|
|
33
|
+
|
|
34
|
+
fun getInstance(context: Context): MediaStorageWorkflow {
|
|
35
|
+
return instance ?: synchronized(this) {
|
|
36
|
+
instance ?: MediaStorageWorkflow(context.applicationContext).also { instance = it }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private val cacheManager = MediaCacheManager.getInstance(context)
|
|
42
|
+
private val metadataStore = MediaMetadataStore.getInstance(context)
|
|
43
|
+
private val mediaPublisher = MediaPublisher.getInstance(context)
|
|
44
|
+
private val cleanupManager = CacheCleanupManager.getInstance(context)
|
|
45
|
+
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
46
|
+
|
|
47
|
+
private val activeTransfers = ConcurrentHashMap<String, TransferJob>()
|
|
48
|
+
private val failedTransfers = ConcurrentHashMap<String, FailedTransfer>()
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initialize the workflow - setup directories and load metadata
|
|
52
|
+
*/
|
|
53
|
+
suspend fun initialize() {
|
|
54
|
+
withContext(Dispatchers.IO) {
|
|
55
|
+
// Runs at host-app startup. It must NEVER crash the app: catch Throwable (to also
|
|
56
|
+
// stop a StackOverflowError/OutOfMemoryError from persisted-state handling) and do
|
|
57
|
+
// NOT rethrow. Degrading to an empty/uninitialized store is always preferable to a
|
|
58
|
+
// launch crash — new captures and publishing still work regardless.
|
|
59
|
+
try {
|
|
60
|
+
ensureDirectoriesExist()
|
|
61
|
+
metadataStore.loadMetadata()
|
|
62
|
+
// Perform initial cleanup
|
|
63
|
+
cleanupManager.performCleanup()
|
|
64
|
+
Log.i(TAG, "Workflow initialized successfully")
|
|
65
|
+
} catch (t: Throwable) {
|
|
66
|
+
Log.e(TAG, "Failed to initialize workflow; continuing with degraded storage", t)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Start media capture workflow - save to cache initially
|
|
73
|
+
*
|
|
74
|
+
* @param allowMove set by capture paths whose [mediaUri] is a temp file they own and delete
|
|
75
|
+
* immediately afterwards. It lets the file be moved into the cache rather than copied, which
|
|
76
|
+
* for a long recording is the difference between an instant handoff and a full read+write
|
|
77
|
+
* pass over hundreds of megabytes (plus the free space that second copy needs).
|
|
78
|
+
*/
|
|
79
|
+
suspend fun startCaptureWorkflow(
|
|
80
|
+
mediaUri: Uri,
|
|
81
|
+
mediaType: String,
|
|
82
|
+
fileName: String? = null,
|
|
83
|
+
allowMove: Boolean = false
|
|
84
|
+
): CacheMediaResult {
|
|
85
|
+
return withContext(Dispatchers.IO) {
|
|
86
|
+
try {
|
|
87
|
+
val cacheResult = cacheManager.cacheMediaForStorage(
|
|
88
|
+
uri = mediaUri,
|
|
89
|
+
fileType = mediaType,
|
|
90
|
+
fileName = fileName,
|
|
91
|
+
allowMove = allowMove
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
// Store metadata
|
|
95
|
+
if (cacheResult.isSuccess) {
|
|
96
|
+
metadataStore.addCachedMedia(
|
|
97
|
+
id = cacheResult.mediaId,
|
|
98
|
+
fileName = fileName ?: cacheResult.fileName,
|
|
99
|
+
cachePath = cacheResult.cachePath,
|
|
100
|
+
fileType = mediaType,
|
|
101
|
+
fileSize = cacheResult.fileSize,
|
|
102
|
+
width = cacheResult.width,
|
|
103
|
+
height = cacheResult.height,
|
|
104
|
+
duration = cacheResult.duration,
|
|
105
|
+
mimeType = cacheResult.mimeType,
|
|
106
|
+
fileHash = computeFileHash(File(cacheResult.cachePath))
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
cacheResult
|
|
111
|
+
} catch (e: Exception) {
|
|
112
|
+
Log.e(TAG, "Capture workflow failed", e)
|
|
113
|
+
CacheMediaResult(
|
|
114
|
+
isSuccess = false,
|
|
115
|
+
mediaId = "",
|
|
116
|
+
fileName = "",
|
|
117
|
+
cachePath = "",
|
|
118
|
+
error = e.message
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Publish media to document directory (called when user sends media)
|
|
126
|
+
* Moves from cache to documents with metadata persistence
|
|
127
|
+
*/
|
|
128
|
+
suspend fun publishMedia(
|
|
129
|
+
mediaId: String,
|
|
130
|
+
moveMode: Boolean = true // true = move, false = copy
|
|
131
|
+
): PublishMediaResult {
|
|
132
|
+
return withContext(Dispatchers.IO) {
|
|
133
|
+
try {
|
|
134
|
+
// A recorded clip's aspect-crop re-encode now runs in the background after the editor opens
|
|
135
|
+
// (so the stop -> editor transition is instant — see CustomCameraActivity.scheduleBackgroundVideoCrop).
|
|
136
|
+
// If the user sends before that crop lands, wait for it here so the published file is the
|
|
137
|
+
// cropped clip, not the raw recording. No-op for images and any media without a pending crop.
|
|
138
|
+
PendingCaptureCrops.await(mediaId)
|
|
139
|
+
|
|
140
|
+
var metadata = metadataStore.getMediaMetadata(mediaId)
|
|
141
|
+
?: return@withContext PublishMediaResult(
|
|
142
|
+
success = false,
|
|
143
|
+
mediaId = mediaId,
|
|
144
|
+
error = "Media metadata not found"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
val cacheFile = File(metadata.cachePath)
|
|
148
|
+
if (!cacheFile.exists()) {
|
|
149
|
+
return@withContext PublishMediaResult(
|
|
150
|
+
success = false,
|
|
151
|
+
mediaId = mediaId,
|
|
152
|
+
error = "Cache file not found"
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Uniquify the published name when either (a) another tracked media already has these
|
|
157
|
+
// exact contents, or (b) something is already sitting at the target path.
|
|
158
|
+
//
|
|
159
|
+
// (b) is new, and it is what makes (a) safe to fix. isDuplicate used to match the media
|
|
160
|
+
// against ITS OWN signature — which is in the index from the moment it was cached — so it
|
|
161
|
+
// returned true for literally every publish and every file got a timestamp suffix. That
|
|
162
|
+
// masked the real collision question. Now (a) answers "same bytes, different media" and
|
|
163
|
+
// (b) independently guarantees a publish can never overwrite an unrelated file, which is
|
|
164
|
+
// the property the blanket rename was accidentally providing.
|
|
165
|
+
if (isDuplicate(cacheFile, metadata) ||
|
|
166
|
+
mediaPublisher.documentFileExists(metadata.fileName, metadata.fileType)
|
|
167
|
+
) {
|
|
168
|
+
// Generate a unique file name by adding timestamp to avoid duplicates
|
|
169
|
+
val baseName = metadata.fileName
|
|
170
|
+
val lastDotIndex = baseName.lastIndexOf(".")
|
|
171
|
+
val extension = if (lastDotIndex >= 0) {
|
|
172
|
+
baseName.substring(lastDotIndex)
|
|
173
|
+
} else {
|
|
174
|
+
if (metadata.fileType == "video") ".mp4" else ".jpg"
|
|
175
|
+
}
|
|
176
|
+
val nameWithoutExtension = if (lastDotIndex >= 0) {
|
|
177
|
+
baseName.substring(0, lastDotIndex)
|
|
178
|
+
} else {
|
|
179
|
+
baseName
|
|
180
|
+
}
|
|
181
|
+
val timestamp = System.currentTimeMillis()
|
|
182
|
+
val uniqueFileName = "${nameWithoutExtension}_${timestamp}${extension}"
|
|
183
|
+
|
|
184
|
+
// Update metadata with unique file name
|
|
185
|
+
metadata = metadata.copy(fileName = uniqueFileName)
|
|
186
|
+
Log.i(TAG, "Handled duplicate file by generating unique name: $uniqueFileName")
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// The signature was already computed when this media entered the cache, so hand it to the
|
|
190
|
+
// publisher instead of letting it read the whole file again. On a large clip that removes
|
|
191
|
+
// an entire redundant pass from the send path.
|
|
192
|
+
val knownHash = metadata.fileHash?.takeIf { it.isNotEmpty() }
|
|
193
|
+
|
|
194
|
+
val publishResult = if (moveMode) {
|
|
195
|
+
mediaPublisher.moveToDocuments(
|
|
196
|
+
sourceFile = cacheFile,
|
|
197
|
+
fileName = metadata.fileName,
|
|
198
|
+
mediaType = metadata.fileType,
|
|
199
|
+
metadata = metadata,
|
|
200
|
+
knownHash = knownHash
|
|
201
|
+
)
|
|
202
|
+
} else {
|
|
203
|
+
mediaPublisher.copyToDocuments(
|
|
204
|
+
sourceFile = cacheFile,
|
|
205
|
+
fileName = metadata.fileName,
|
|
206
|
+
mediaType = metadata.fileType,
|
|
207
|
+
metadata = metadata,
|
|
208
|
+
knownHash = knownHash
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (publishResult.success) {
|
|
213
|
+
// Update metadata with published info
|
|
214
|
+
metadataStore.updateMediaStatus(
|
|
215
|
+
mediaId = mediaId,
|
|
216
|
+
status = "published",
|
|
217
|
+
publishedUri = publishResult.targetUri,
|
|
218
|
+
publishedAt = System.currentTimeMillis(),
|
|
219
|
+
documentUri = publishResult.targetUri
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
// If move mode, remove from cache
|
|
223
|
+
if (moveMode) {
|
|
224
|
+
try {
|
|
225
|
+
cacheFile.delete()
|
|
226
|
+
metadataStore.removeFromCache(mediaId)
|
|
227
|
+
} catch (e: Exception) {
|
|
228
|
+
Log.w(TAG, "Failed to cleanup cache after publish", e)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Log.i(TAG, "Media $mediaId published successfully")
|
|
233
|
+
} else {
|
|
234
|
+
recordFailedTransfer(mediaId, publishResult.error ?: "Unknown error")
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
PublishMediaResult(
|
|
238
|
+
success = publishResult.success,
|
|
239
|
+
mediaId = mediaId,
|
|
240
|
+
targetUri = publishResult.targetUri,
|
|
241
|
+
fileHash = publishResult.fileHash,
|
|
242
|
+
error = publishResult.error
|
|
243
|
+
)
|
|
244
|
+
} catch (e: Exception) {
|
|
245
|
+
Log.e(TAG, "Publish workflow failed for media $mediaId", e)
|
|
246
|
+
recordFailedTransfer(mediaId, e.message ?: "Unknown error")
|
|
247
|
+
PublishMediaResult(
|
|
248
|
+
success = false,
|
|
249
|
+
mediaId = mediaId,
|
|
250
|
+
error = e.message
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Publish media to the document directory starting from a known cache file.
|
|
258
|
+
*
|
|
259
|
+
* This guarantees a consistent lifecycle for BOTH images and videos even when
|
|
260
|
+
* an asset was never registered through [startCaptureWorkflow] (for example the
|
|
261
|
+
* video-capture fallback, which writes a raw temp file directly to the cache
|
|
262
|
+
* without metadata). If the media is not yet tracked, its cache file is
|
|
263
|
+
* registered here first; publishing then reuses [publishMedia] so the same
|
|
264
|
+
* move (cache -> documents), metadata update, and cache cleanup apply in every
|
|
265
|
+
* case — with correct path updates returned to the caller.
|
|
266
|
+
*/
|
|
267
|
+
suspend fun publishMediaFile(
|
|
268
|
+
mediaId: String,
|
|
269
|
+
cachePath: String?,
|
|
270
|
+
fileName: String,
|
|
271
|
+
mediaType: String,
|
|
272
|
+
width: Int? = null,
|
|
273
|
+
height: Int? = null,
|
|
274
|
+
duration: Long? = null,
|
|
275
|
+
moveMode: Boolean = true
|
|
276
|
+
): PublishMediaResult {
|
|
277
|
+
return withContext(Dispatchers.IO) {
|
|
278
|
+
try {
|
|
279
|
+
// Register the cache file so publishMedia can locate and move it when the
|
|
280
|
+
// asset is not already tracked in the metadata store.
|
|
281
|
+
if (metadataStore.getMediaMetadata(mediaId) == null) {
|
|
282
|
+
if (cachePath.isNullOrBlank()) {
|
|
283
|
+
return@withContext PublishMediaResult(
|
|
284
|
+
success = false,
|
|
285
|
+
mediaId = mediaId,
|
|
286
|
+
error = "Media metadata not found"
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
val cacheFile = File(cachePath)
|
|
290
|
+
if (!cacheFile.exists()) {
|
|
291
|
+
return@withContext PublishMediaResult(
|
|
292
|
+
success = false,
|
|
293
|
+
mediaId = mediaId,
|
|
294
|
+
error = "Cache file not found"
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
metadataStore.addCachedMedia(
|
|
298
|
+
id = mediaId,
|
|
299
|
+
fileName = if (fileName.isNotBlank()) fileName else cacheFile.name,
|
|
300
|
+
cachePath = cachePath,
|
|
301
|
+
fileType = mediaType,
|
|
302
|
+
fileSize = cacheFile.length(),
|
|
303
|
+
width = width,
|
|
304
|
+
height = height,
|
|
305
|
+
duration = duration,
|
|
306
|
+
// Read the container off the file being registered rather than assuming JPEG for
|
|
307
|
+
// every image, so a .gif published through this path is tracked as image/gif.
|
|
308
|
+
mimeType = MediaCacheManager.MediaFormat.ofFile(cacheFile, mediaType).mimeType,
|
|
309
|
+
fileHash = computeFileHash(cacheFile)
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
publishMedia(mediaId, moveMode)
|
|
314
|
+
} catch (e: Exception) {
|
|
315
|
+
Log.e(TAG, "publishMediaFile failed for media $mediaId", e)
|
|
316
|
+
recordFailedTransfer(mediaId, e.message ?: "Unknown error")
|
|
317
|
+
PublishMediaResult(
|
|
318
|
+
success = false,
|
|
319
|
+
mediaId = mediaId,
|
|
320
|
+
error = e.message
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Publish multiple media files in batch (optimized for performance)
|
|
328
|
+
*/
|
|
329
|
+
suspend fun publishBatch(
|
|
330
|
+
mediaIds: List<String>,
|
|
331
|
+
moveMode: Boolean = true
|
|
332
|
+
): BatchPublishResult {
|
|
333
|
+
return withContext(Dispatchers.IO) {
|
|
334
|
+
try {
|
|
335
|
+
val results = mutableListOf<PublishMediaResult>()
|
|
336
|
+
var successCount = 0
|
|
337
|
+
var failureCount = 0
|
|
338
|
+
|
|
339
|
+
mediaIds.forEach { mediaId ->
|
|
340
|
+
val result = publishMedia(mediaId, moveMode)
|
|
341
|
+
results.add(result)
|
|
342
|
+
if (result.success) successCount++ else failureCount++
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
BatchPublishResult(
|
|
346
|
+
totalCount = mediaIds.size,
|
|
347
|
+
successCount = successCount,
|
|
348
|
+
failureCount = failureCount,
|
|
349
|
+
results = results,
|
|
350
|
+
timestamp = System.currentTimeMillis()
|
|
351
|
+
)
|
|
352
|
+
} catch (e: Exception) {
|
|
353
|
+
Log.e(TAG, "Batch publish failed", e)
|
|
354
|
+
BatchPublishResult(
|
|
355
|
+
totalCount = mediaIds.size,
|
|
356
|
+
successCount = 0,
|
|
357
|
+
failureCount = mediaIds.size,
|
|
358
|
+
results = emptyList(),
|
|
359
|
+
error = e.message,
|
|
360
|
+
timestamp = System.currentTimeMillis()
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get all cached media that hasn't been published yet
|
|
368
|
+
*/
|
|
369
|
+
suspend fun getCachedMedia(): List<MediaStorageMetadata> {
|
|
370
|
+
return withContext(Dispatchers.IO) {
|
|
371
|
+
try {
|
|
372
|
+
metadataStore.getCachedMedia()
|
|
373
|
+
} catch (e: Exception) {
|
|
374
|
+
Log.e(TAG, "Failed to get cached media", e)
|
|
375
|
+
emptyList()
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get all published media
|
|
382
|
+
*/
|
|
383
|
+
suspend fun getPublishedMedia(): List<MediaStorageMetadata> {
|
|
384
|
+
return withContext(Dispatchers.IO) {
|
|
385
|
+
try {
|
|
386
|
+
metadataStore.getPublishedMedia()
|
|
387
|
+
} catch (e: Exception) {
|
|
388
|
+
Log.e(TAG, "Failed to get published media", e)
|
|
389
|
+
emptyList()
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get storage statistics
|
|
396
|
+
*/
|
|
397
|
+
suspend fun getStorageStats(): StorageStats {
|
|
398
|
+
return withContext(Dispatchers.IO) {
|
|
399
|
+
try {
|
|
400
|
+
val cacheDir = getCacheDir()
|
|
401
|
+
val documentDir = getDocumentDir()
|
|
402
|
+
|
|
403
|
+
val cacheUsedMB = cacheDir.totalSize() / (1024 * 1024)
|
|
404
|
+
val documentUsedMB = documentDir.totalSize() / (1024 * 1024)
|
|
405
|
+
val totalAvailableMB = getAvailableStorageMB()
|
|
406
|
+
|
|
407
|
+
val cachedItems = metadataStore.getCachedMedia()
|
|
408
|
+
val publishedItems = metadataStore.getPublishedMedia()
|
|
409
|
+
val duplicates = cachedItems.filter { it.isDuplicate == true }
|
|
410
|
+
|
|
411
|
+
val oldestAge = cachedItems.minOfOrNull { System.currentTimeMillis() - it.cacheCreatedAt } ?: 0
|
|
412
|
+
val newestAge = cachedItems.maxOfOrNull { System.currentTimeMillis() - it.cacheCreatedAt } ?: 0
|
|
413
|
+
|
|
414
|
+
StorageStats(
|
|
415
|
+
cacheUsedMB = cacheUsedMB,
|
|
416
|
+
documentUsedMB = documentUsedMB,
|
|
417
|
+
totalAvailableMB = totalAvailableMB,
|
|
418
|
+
cachedItemsCount = cachedItems.size,
|
|
419
|
+
publishedItemsCount = publishedItems.size,
|
|
420
|
+
duplicateItemsCount = duplicates.size,
|
|
421
|
+
oldestCacheItemAge = oldestAge,
|
|
422
|
+
newestCacheItemAge = newestAge
|
|
423
|
+
)
|
|
424
|
+
} catch (e: Exception) {
|
|
425
|
+
Log.e(TAG, "Failed to get storage stats", e)
|
|
426
|
+
StorageStats(
|
|
427
|
+
cacheUsedMB = 0,
|
|
428
|
+
documentUsedMB = 0,
|
|
429
|
+
totalAvailableMB = 0,
|
|
430
|
+
cachedItemsCount = 0,
|
|
431
|
+
publishedItemsCount = 0,
|
|
432
|
+
duplicateItemsCount = 0,
|
|
433
|
+
oldestCacheItemAge = 0,
|
|
434
|
+
newestCacheItemAge = 0
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Cleanup old cache files
|
|
442
|
+
*/
|
|
443
|
+
suspend fun cleanupOldCache(olderThanDays: Double = 7.0): CleanupOperationResult {
|
|
444
|
+
return withContext(Dispatchers.IO) {
|
|
445
|
+
try {
|
|
446
|
+
val days = olderThanDays.toInt().coerceAtLeast(0)
|
|
447
|
+
val result = cleanupManager.cleanupOldFiles(days)
|
|
448
|
+
Log.i(TAG, "Cleanup completed: ${result.filesRemoved} files removed, ${result.spaceFreedMB}MB freed")
|
|
449
|
+
CleanupOperationResult(
|
|
450
|
+
success = result.success,
|
|
451
|
+
filesRemoved = result.filesRemoved,
|
|
452
|
+
spaceFreedMB = result.spaceFreedMB,
|
|
453
|
+
error = result.error
|
|
454
|
+
)
|
|
455
|
+
} catch (e: Exception) {
|
|
456
|
+
Log.e(TAG, "Cleanup failed", e)
|
|
457
|
+
CleanupOperationResult(
|
|
458
|
+
success = false,
|
|
459
|
+
filesRemoved = 0,
|
|
460
|
+
spaceFreedMB = 0.0,
|
|
461
|
+
error = e.message
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Securely delete a specific media (from cache or document).
|
|
469
|
+
*
|
|
470
|
+
* The file's bytes are overwritten with encrypted CSPRNG garbage and flushed to storage before
|
|
471
|
+
* it is unlinked, and every cached copy / derived variant / metadata reference for the id is
|
|
472
|
+
* swept — see [SecureMediaDeleter]. A plain `File.delete()` here would only detach the name,
|
|
473
|
+
* leaving the media recoverable from the raw partition.
|
|
474
|
+
*
|
|
475
|
+
* Returns true only when the file is actually gone. A file that was deleted but could NOT be
|
|
476
|
+
* securely overwritten returns true (it IS deleted) and logs the shortfall at ERROR — callers
|
|
477
|
+
* needing the security verdict should go through `CustomCameraModule.secureDeleteMedia`, which
|
|
478
|
+
* surfaces the full report instead of collapsing it.
|
|
479
|
+
*/
|
|
480
|
+
suspend fun deleteMedia(mediaId: String): Boolean {
|
|
481
|
+
return withContext(Dispatchers.IO) {
|
|
482
|
+
try {
|
|
483
|
+
val metadata = metadataStore.getMediaMetadata(mediaId) ?: return@withContext false
|
|
484
|
+
|
|
485
|
+
// Prefer the published document as the primary, and hand the cache path over as an extra
|
|
486
|
+
// copy: a published item usually STILL has its cache file, and each of the two is a
|
|
487
|
+
// complete copy of the media, so erasing only one leaves the other recoverable.
|
|
488
|
+
val primaryUri = metadata.documentUri ?: metadata.cachePath
|
|
489
|
+
?: return@withContext false
|
|
490
|
+
val extraUris = listOfNotNull(
|
|
491
|
+
metadata.cachePath?.takeIf { it != primaryUri }
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
val report = SecureMediaDeleter.secureDelete(
|
|
495
|
+
context = context,
|
|
496
|
+
uriString = primaryUri,
|
|
497
|
+
assetId = mediaId,
|
|
498
|
+
fileName = metadata.fileName,
|
|
499
|
+
extraUris = extraUris
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
if (!report.fullySecure) {
|
|
503
|
+
Log.e(
|
|
504
|
+
TAG,
|
|
505
|
+
"Media $mediaId was not securely erased — deleted=${report.deleted}, " +
|
|
506
|
+
"overwritten=${report.securelyOverwritten}, " +
|
|
507
|
+
"derivedFailures=${report.derivedFilesFailed}, error=${report.error ?: "none"}"
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// secureDelete already clears this id's metadata; the explicit call covers the case where
|
|
512
|
+
// the record exists but its file did not, so a dangling entry cannot survive a delete.
|
|
513
|
+
if (report.deleted) {
|
|
514
|
+
metadataStore.removeMedia(mediaId)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
report.deleted
|
|
518
|
+
} catch (e: Exception) {
|
|
519
|
+
Log.e(TAG, "Failed to delete media $mediaId", e)
|
|
520
|
+
false
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Check if a file is a duplicate.
|
|
527
|
+
*
|
|
528
|
+
* Reuses the signature recorded when the media was cached rather than reading the file again.
|
|
529
|
+
* Publishing a long recording used to compute a whole-file SHA-256 here and then a second one
|
|
530
|
+
* inside the publisher, on top of the one already taken at capture — three full passes over the
|
|
531
|
+
* same gigabyte to answer one equality question. Only media that somehow reached this point
|
|
532
|
+
* without a signature pays for computing one.
|
|
533
|
+
*
|
|
534
|
+
* [metadataStore.getMediaByHash] resolves a hash back to its own entry, so a match against THIS
|
|
535
|
+
* media is not a duplicate; only a different id is.
|
|
536
|
+
*/
|
|
537
|
+
private suspend fun isDuplicate(file: File, metadata: MediaStorageMetadata): Boolean {
|
|
538
|
+
return withContext(Dispatchers.IO) {
|
|
539
|
+
try {
|
|
540
|
+
val fileHash = metadata.fileHash?.takeIf { it.isNotEmpty() } ?: computeFileHash(file)
|
|
541
|
+
if (fileHash.isEmpty()) return@withContext false
|
|
542
|
+
val existingMedia = metadataStore.getMediaByHash(fileHash)
|
|
543
|
+
existingMedia != null && existingMedia.id != metadata.id
|
|
544
|
+
} catch (e: Exception) {
|
|
545
|
+
Log.w(TAG, "Failed to check for duplicates", e)
|
|
546
|
+
false
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Content signature used for duplicate detection.
|
|
553
|
+
*
|
|
554
|
+
* Bounded for large files — see [MediaFileIo.contentSignature] for why a video is not hashed in
|
|
555
|
+
* full and why that is safe for de-duplication.
|
|
556
|
+
*/
|
|
557
|
+
private fun computeFileHash(file: File): String = MediaFileIo.contentSignature(file)
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Record a failed transfer for retry
|
|
561
|
+
*/
|
|
562
|
+
private fun recordFailedTransfer(mediaId: String, error: String) {
|
|
563
|
+
failedTransfers[mediaId] = FailedTransfer(
|
|
564
|
+
mediaId = mediaId,
|
|
565
|
+
error = error,
|
|
566
|
+
failedAt = System.currentTimeMillis(),
|
|
567
|
+
retryCount = (failedTransfers[mediaId]?.retryCount ?: 0) + 1
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Get failed transfers
|
|
573
|
+
*/
|
|
574
|
+
suspend fun getFailedTransfers(): List<FailedTransfer> {
|
|
575
|
+
return withContext(Dispatchers.IO) {
|
|
576
|
+
failedTransfers.values.toList()
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Retry failed transfers
|
|
582
|
+
*/
|
|
583
|
+
suspend fun retryFailedTransfers(): List<PublishMediaResult> {
|
|
584
|
+
return withContext(Dispatchers.IO) {
|
|
585
|
+
val results = mutableListOf<PublishMediaResult>()
|
|
586
|
+
val failed = failedTransfers.keys.toList()
|
|
587
|
+
|
|
588
|
+
failed.forEach { mediaId ->
|
|
589
|
+
if (failedTransfers[mediaId]?.retryCount ?: 0 < MAX_RETRY_ATTEMPTS) {
|
|
590
|
+
try {
|
|
591
|
+
val result = publishMedia(mediaId, moveMode = true)
|
|
592
|
+
results.add(result)
|
|
593
|
+
if (result.success) {
|
|
594
|
+
failedTransfers.remove(mediaId)
|
|
595
|
+
}
|
|
596
|
+
} catch (e: Exception) {
|
|
597
|
+
Log.w(TAG, "Retry failed for $mediaId", e)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
results
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Ensure all required directories exist
|
|
607
|
+
*/
|
|
608
|
+
private fun ensureDirectoriesExist() {
|
|
609
|
+
getCacheDir().mkdirs()
|
|
610
|
+
getDocumentDir().mkdirs()
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Get cache directory
|
|
615
|
+
*/
|
|
616
|
+
private fun getCacheDir(): File {
|
|
617
|
+
return File(context.cacheDir, "media_cache").apply { mkdirs() }
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Get document directory
|
|
622
|
+
*/
|
|
623
|
+
private fun getDocumentDir(): File {
|
|
624
|
+
return File(
|
|
625
|
+
context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS),
|
|
626
|
+
"media_documents"
|
|
627
|
+
).apply { mkdirs() }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Get available storage in MB
|
|
632
|
+
*/
|
|
633
|
+
private fun getAvailableStorageMB(): Long {
|
|
634
|
+
return try {
|
|
635
|
+
val stat = android.os.StatFs(Environment.getDataDirectory().absolutePath)
|
|
636
|
+
stat.availableBlocksLong * stat.blockSizeLong / (1024 * 1024)
|
|
637
|
+
} catch (e: Exception) {
|
|
638
|
+
0
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Cleanup and shutdown
|
|
644
|
+
*/
|
|
645
|
+
fun shutdown() {
|
|
646
|
+
coroutineScope.cancel()
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
data class CacheMediaResult(
|
|
650
|
+
val isSuccess: Boolean,
|
|
651
|
+
val mediaId: String,
|
|
652
|
+
val fileName: String,
|
|
653
|
+
val cachePath: String,
|
|
654
|
+
val fileSize: Long = 0,
|
|
655
|
+
val width: Int? = null,
|
|
656
|
+
val height: Int? = null,
|
|
657
|
+
val duration: Long? = null,
|
|
658
|
+
val mimeType: String? = null,
|
|
659
|
+
val error: String? = null
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
data class PublishMediaResult(
|
|
663
|
+
val success: Boolean,
|
|
664
|
+
val mediaId: String,
|
|
665
|
+
val targetUri: String? = null,
|
|
666
|
+
val fileHash: String? = null,
|
|
667
|
+
val error: String? = null,
|
|
668
|
+
val isDuplicate: Boolean = false
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
data class BatchPublishResult(
|
|
672
|
+
val totalCount: Int,
|
|
673
|
+
val successCount: Int,
|
|
674
|
+
val failureCount: Int,
|
|
675
|
+
val results: List<PublishMediaResult>,
|
|
676
|
+
val error: String? = null,
|
|
677
|
+
val timestamp: Long
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
data class FailedTransfer(
|
|
681
|
+
val mediaId: String,
|
|
682
|
+
val error: String,
|
|
683
|
+
val failedAt: Long,
|
|
684
|
+
val retryCount: Int
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
data class TransferJob(
|
|
688
|
+
val mediaId: String,
|
|
689
|
+
val status: String, // "pending", "in_progress", "completed", "failed"
|
|
690
|
+
val progress: Float = 0f,
|
|
691
|
+
val startedAt: Long = System.currentTimeMillis(),
|
|
692
|
+
val error: String? = null
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
data class StorageStats(
|
|
696
|
+
val cacheUsedMB: Long,
|
|
697
|
+
val documentUsedMB: Long,
|
|
698
|
+
val totalAvailableMB: Long,
|
|
699
|
+
val cachedItemsCount: Int,
|
|
700
|
+
val publishedItemsCount: Int,
|
|
701
|
+
val duplicateItemsCount: Int,
|
|
702
|
+
val oldestCacheItemAge: Long,
|
|
703
|
+
val newestCacheItemAge: Long
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
data class CleanupOperationResult(
|
|
707
|
+
val success: Boolean,
|
|
708
|
+
val filesRemoved: Int,
|
|
709
|
+
val spaceFreedMB: Double,
|
|
710
|
+
val error: String? = null,
|
|
711
|
+
val timestamp: Long = System.currentTimeMillis()
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
private fun File.totalSize(): Long {
|
|
717
|
+
return if (isDirectory) {
|
|
718
|
+
listFiles()?.sumOf { it.totalSize() } ?: 0L
|
|
719
|
+
} else {
|
|
720
|
+
length()
|
|
721
|
+
}
|
|
722
|
+
}
|