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,160 @@
|
|
|
1
|
+
package com.customcamera.gif
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.ColorFilter
|
|
6
|
+
import android.graphics.Paint
|
|
7
|
+
import android.graphics.PixelFormat
|
|
8
|
+
import android.graphics.Rect
|
|
9
|
+
import android.graphics.drawable.Animatable
|
|
10
|
+
import android.graphics.drawable.Drawable
|
|
11
|
+
import android.os.Handler
|
|
12
|
+
import android.os.Looper
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An [Animatable] [Drawable] that cycles through a list of pre-decoded GIF frames.
|
|
16
|
+
*
|
|
17
|
+
* This is the all-API animation path for placed GIF overlays: on API < 28 there is no
|
|
18
|
+
* [android.graphics.drawable.AnimatedImageDrawable], and even on API 28+ we prefer feeding the
|
|
19
|
+
* exporter the exact same frames it will re-encode. Frames are decoded once (see the GIF decoder)
|
|
20
|
+
* and owned by this drawable; call [recycle] when the overlay is removed.
|
|
21
|
+
*
|
|
22
|
+
* Timing is driven by a main-thread [Handler]: each frame is shown for its own delay, then the
|
|
23
|
+
* next frame is scheduled. Looping honours the source GIF's loop count (0 == loop forever, the
|
|
24
|
+
* common case). Drawing scales the current frame to the drawable [bounds], so the same instance
|
|
25
|
+
* works for a small grid thumbnail and a large placed overlay.
|
|
26
|
+
*/
|
|
27
|
+
class GifFramesDrawable(
|
|
28
|
+
private val frames: List<Bitmap>,
|
|
29
|
+
delaysMs: List<Int>,
|
|
30
|
+
private val loopCount: Int
|
|
31
|
+
) : Drawable(), Animatable, Runnable {
|
|
32
|
+
|
|
33
|
+
/** Per-frame display duration, sanitised so a zero/negative delay never busy-loops. */
|
|
34
|
+
private val delays: IntArray = IntArray(frames.size) { i ->
|
|
35
|
+
val d = delaysMs.getOrElse(i) { DEFAULT_DELAY_MS }
|
|
36
|
+
if (d <= 0) DEFAULT_DELAY_MS else d
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private val paint = Paint(Paint.FILTER_BITMAP_FLAG)
|
|
40
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
41
|
+
private val srcRect = Rect()
|
|
42
|
+
|
|
43
|
+
private var currentFrame = 0
|
|
44
|
+
private var loopsDone = 0
|
|
45
|
+
private var running = false
|
|
46
|
+
|
|
47
|
+
/** Number of decoded frames; 1 (or 0) means this is effectively a static image. */
|
|
48
|
+
val frameCount: Int get() = frames.size
|
|
49
|
+
|
|
50
|
+
/** Total time for one full pass through every frame, in milliseconds. */
|
|
51
|
+
val totalDurationMs: Int get() = delays.sum()
|
|
52
|
+
|
|
53
|
+
override fun draw(canvas: Canvas) {
|
|
54
|
+
if (frames.isEmpty()) return
|
|
55
|
+
val bmp = frames[currentFrame.coerceIn(0, frames.size - 1)]
|
|
56
|
+
if (bmp.isRecycled) return
|
|
57
|
+
srcRect.set(0, 0, bmp.width, bmp.height)
|
|
58
|
+
canvas.drawBitmap(bmp, srcRect, bounds, paint)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
override fun run() {
|
|
62
|
+
if (!running || frames.size <= 1) return
|
|
63
|
+
val next = currentFrame + 1
|
|
64
|
+
if (next >= frames.size) {
|
|
65
|
+
// Completed one full pass through the frames.
|
|
66
|
+
if (loopCount != 0) {
|
|
67
|
+
loopsDone++
|
|
68
|
+
if (loopsDone >= loopCount) {
|
|
69
|
+
currentFrame = frames.size - 1
|
|
70
|
+
running = false
|
|
71
|
+
invalidateSelf()
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
currentFrame = 0
|
|
76
|
+
} else {
|
|
77
|
+
currentFrame = next
|
|
78
|
+
}
|
|
79
|
+
invalidateSelf()
|
|
80
|
+
scheduleNext()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private fun scheduleNext() {
|
|
84
|
+
if (!running || frames.size <= 1) return
|
|
85
|
+
val delay = delays[currentFrame.coerceIn(0, delays.size - 1)].toLong()
|
|
86
|
+
handler.postDelayed(this, delay)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override fun start() {
|
|
90
|
+
if (frames.size <= 1) { invalidateSelf(); return }
|
|
91
|
+
if (running) return
|
|
92
|
+
running = true
|
|
93
|
+
loopsDone = 0
|
|
94
|
+
scheduleNext()
|
|
95
|
+
invalidateSelf()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override fun stop() {
|
|
99
|
+
running = false
|
|
100
|
+
handler.removeCallbacks(this)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun isRunning(): Boolean = running
|
|
104
|
+
|
|
105
|
+
override fun setAlpha(alpha: Int) { paint.alpha = alpha; invalidateSelf() }
|
|
106
|
+
|
|
107
|
+
override fun setColorFilter(colorFilter: ColorFilter?) {
|
|
108
|
+
paint.colorFilter = colorFilter
|
|
109
|
+
invalidateSelf()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Deprecated("Deprecated in Java", ReplaceWith("PixelFormat.TRANSLUCENT"))
|
|
113
|
+
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
|
114
|
+
|
|
115
|
+
override fun getIntrinsicWidth(): Int = frames.firstOrNull()?.width ?: -1
|
|
116
|
+
|
|
117
|
+
override fun getIntrinsicHeight(): Int = frames.firstOrNull()?.height ?: -1
|
|
118
|
+
|
|
119
|
+
/** Reset to the first frame without freeing bitmaps; safe to call before re-[start]. */
|
|
120
|
+
fun reset() {
|
|
121
|
+
stop()
|
|
122
|
+
currentFrame = 0
|
|
123
|
+
loopsDone = 0
|
|
124
|
+
invalidateSelf()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Deterministically position the drawable at the frame visible [timeMs] into a forever-looping
|
|
129
|
+
* playback (independent of [loopCount]). Used by the exporter to step frames by hand; the caller
|
|
130
|
+
* must [stop] the handler-driven animation first so the two don't fight over [currentFrame].
|
|
131
|
+
*/
|
|
132
|
+
fun seekToTime(timeMs: Long) {
|
|
133
|
+
if (frames.size <= 1) { currentFrame = 0; invalidateSelf(); return }
|
|
134
|
+
val total = totalDurationMs
|
|
135
|
+
if (total <= 0) { currentFrame = 0; invalidateSelf(); return }
|
|
136
|
+
var t = (timeMs % total).toInt()
|
|
137
|
+
if (t < 0) t += total
|
|
138
|
+
var acc = 0
|
|
139
|
+
var idx = 0
|
|
140
|
+
for (i in delays.indices) {
|
|
141
|
+
acc += delays[i]
|
|
142
|
+
if (t < acc) { idx = i; break }
|
|
143
|
+
idx = i
|
|
144
|
+
}
|
|
145
|
+
currentFrame = idx.coerceIn(0, frames.size - 1)
|
|
146
|
+
invalidateSelf()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Stop animating and free every frame bitmap. The drawable is unusable afterwards. */
|
|
150
|
+
fun recycle() {
|
|
151
|
+
stop()
|
|
152
|
+
for (f in frames) {
|
|
153
|
+
if (!f.isRecycled) f.recycle()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
companion object {
|
|
158
|
+
private const val DEFAULT_DELAY_MS = 100
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package com.customcamera.gif
|
|
2
|
+
|
|
3
|
+
import java.io.IOException
|
|
4
|
+
import java.net.SocketTimeoutException
|
|
5
|
+
import java.net.UnknownHostException
|
|
6
|
+
import java.util.concurrent.TimeUnit
|
|
7
|
+
import okhttp3.OkHttpClient
|
|
8
|
+
import okhttp3.Request
|
|
9
|
+
import org.json.JSONException
|
|
10
|
+
import org.json.JSONObject
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal blocking HTTP helper shared by every [GifProvider].
|
|
14
|
+
*
|
|
15
|
+
* Providers run on a background thread and must never throw, so [getJson] performs a synchronous
|
|
16
|
+
* GET and folds every failure mode -- transport, HTTP status, and JSON parsing -- into a typed
|
|
17
|
+
* [GifResult.Failure]. A single [OkHttpClient] is shared across all providers so its connection
|
|
18
|
+
* pool and dispatcher threads are reused.
|
|
19
|
+
*/
|
|
20
|
+
class GifHttp {
|
|
21
|
+
|
|
22
|
+
companion object {
|
|
23
|
+
/** Shared, lazily-created client: 15s to connect, 20s waiting for the response body. */
|
|
24
|
+
private val client: OkHttpClient by lazy {
|
|
25
|
+
OkHttpClient.Builder()
|
|
26
|
+
.connectTimeout(15, TimeUnit.SECONDS)
|
|
27
|
+
.readTimeout(20, TimeUnit.SECONDS)
|
|
28
|
+
.build()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Perform a blocking GET and parse the response body as a JSON object.
|
|
33
|
+
*
|
|
34
|
+
* Never throws. Failures map to: [GifError.TIMEOUT] (socket timeout), [GifError.NETWORK]
|
|
35
|
+
* (DNS / connectivity / other IO), [GifError.RATE_LIMITED] (HTTP 429), [GifError.SERVER]
|
|
36
|
+
* (HTTP 5xx), [GifError.PARSE] (empty or malformed JSON), and [GifError.UNKNOWN] (any other
|
|
37
|
+
* non-2xx status or unexpected error).
|
|
38
|
+
*/
|
|
39
|
+
fun getJson(url: String): GifResult<JSONObject> {
|
|
40
|
+
return try {
|
|
41
|
+
val request = Request.Builder()
|
|
42
|
+
.url(url)
|
|
43
|
+
.header("Accept", "application/json")
|
|
44
|
+
.get()
|
|
45
|
+
.build()
|
|
46
|
+
client.newCall(request).execute().use { response ->
|
|
47
|
+
val code = response.code
|
|
48
|
+
when {
|
|
49
|
+
response.isSuccessful -> {
|
|
50
|
+
val bodyString = response.body?.string()
|
|
51
|
+
if (bodyString.isNullOrBlank()) {
|
|
52
|
+
GifResult.Failure(GifError.PARSE)
|
|
53
|
+
} else {
|
|
54
|
+
try {
|
|
55
|
+
GifResult.Success(JSONObject(bodyString))
|
|
56
|
+
} catch (e: JSONException) {
|
|
57
|
+
GifResult.Failure(GifError.PARSE, e)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
code == 429 -> GifResult.Failure(GifError.RATE_LIMITED)
|
|
62
|
+
code in 500..599 -> GifResult.Failure(GifError.SERVER)
|
|
63
|
+
else -> GifResult.Failure(GifError.UNKNOWN)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch (e: SocketTimeoutException) {
|
|
67
|
+
GifResult.Failure(GifError.TIMEOUT, e)
|
|
68
|
+
} catch (e: UnknownHostException) {
|
|
69
|
+
GifResult.Failure(GifError.NETWORK, e)
|
|
70
|
+
} catch (e: IOException) {
|
|
71
|
+
GifResult.Failure(GifError.NETWORK, e)
|
|
72
|
+
} catch (e: Exception) {
|
|
73
|
+
GifResult.Failure(GifError.UNKNOWN, e)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Blocking GET that returns the raw response body bytes — used to download a GIF's bytes
|
|
79
|
+
* (grid thumbnail or the full content GIF) for decoding. Same failure folding as [getJson],
|
|
80
|
+
* except the body is returned verbatim rather than parsed. Never throws.
|
|
81
|
+
*/
|
|
82
|
+
fun getBytes(url: String): GifResult<ByteArray> {
|
|
83
|
+
return try {
|
|
84
|
+
val request = Request.Builder().url(url).get().build()
|
|
85
|
+
client.newCall(request).execute().use { response ->
|
|
86
|
+
val code = response.code
|
|
87
|
+
when {
|
|
88
|
+
response.isSuccessful -> {
|
|
89
|
+
val bytes = response.body?.bytes()
|
|
90
|
+
if (bytes == null || bytes.isEmpty()) {
|
|
91
|
+
GifResult.Failure(GifError.PARSE)
|
|
92
|
+
} else {
|
|
93
|
+
GifResult.Success(bytes)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
code == 429 -> GifResult.Failure(GifError.RATE_LIMITED)
|
|
97
|
+
code in 500..599 -> GifResult.Failure(GifError.SERVER)
|
|
98
|
+
else -> GifResult.Failure(GifError.UNKNOWN)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (e: SocketTimeoutException) {
|
|
102
|
+
GifResult.Failure(GifError.TIMEOUT, e)
|
|
103
|
+
} catch (e: UnknownHostException) {
|
|
104
|
+
GifResult.Failure(GifError.NETWORK, e)
|
|
105
|
+
} catch (e: IOException) {
|
|
106
|
+
GifResult.Failure(GifError.NETWORK, e)
|
|
107
|
+
} catch (e: Exception) {
|
|
108
|
+
GifResult.Failure(GifError.UNKNOWN, e)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
package com.customcamera.gif
|
|
2
|
+
|
|
3
|
+
import android.graphics.BitmapFactory
|
|
4
|
+
import android.graphics.drawable.AnimatedImageDrawable
|
|
5
|
+
import android.graphics.drawable.BitmapDrawable
|
|
6
|
+
import android.graphics.drawable.Drawable
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
10
|
+
import android.util.LruCache
|
|
11
|
+
import android.widget.ImageView
|
|
12
|
+
import java.nio.ByteBuffer
|
|
13
|
+
import java.util.WeakHashMap
|
|
14
|
+
import java.util.concurrent.Executors
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loads GIF thumbnails into the picker grid's [ImageView]s, and downloads raw GIF bytes for the
|
|
18
|
+
* placed overlay. Deliberately dependency-free (uses only [GifHttp] + the platform decoders) so
|
|
19
|
+
* the grid works without the frame decoder/encoder.
|
|
20
|
+
*
|
|
21
|
+
* Thumbnails animate on API 28+ via [AnimatedImageDrawable] (native, lazy, memory-friendly) and
|
|
22
|
+
* fall back to a downsampled static first frame on API 24–27 — a deliberate low-RAM degradation
|
|
23
|
+
* for old devices; the *placed* overlay still animates on every API via the frame decoder.
|
|
24
|
+
*
|
|
25
|
+
* Downloaded bytes are held in a bounded LRU cache keyed by URL so re-scrolling the grid and then
|
|
26
|
+
* picking a GIF never re-downloads. Decoded thumbnails are additionally held in a small drawable
|
|
27
|
+
* LRU so a GIF that scrolls back into view is re-shown instantly (no re-decode, no blank flash). A
|
|
28
|
+
* per-[ImageView] "pending url" guard (a [WeakHashMap]) makes the loader safe against RecyclerView
|
|
29
|
+
* cell recycling: a slow response for a recycled cell is dropped instead of painting the wrong GIF.
|
|
30
|
+
*/
|
|
31
|
+
class GifImageLoader(cacheSizeBytes: Int = DEFAULT_CACHE_BYTES) {
|
|
32
|
+
|
|
33
|
+
private val executor = Executors.newFixedThreadPool(3) { r ->
|
|
34
|
+
Thread(r, "gif-img").apply { isDaemon = true }
|
|
35
|
+
}
|
|
36
|
+
private val main = Handler(Looper.getMainLooper())
|
|
37
|
+
|
|
38
|
+
private val byteCache = object : LruCache<String, ByteArray>(cacheSizeBytes) {
|
|
39
|
+
override fun sizeOf(key: String, value: ByteArray): Int = value.size
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The URL each live ImageView currently wants; guards against recycled-cell races. */
|
|
43
|
+
private val pending = WeakHashMap<ImageView, String>()
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Decoded thumbnails, keyed by URL+size. Lets a previously-viewed GIF that scrolls back into the
|
|
47
|
+
* grid be re-shown synchronously on the main thread — no re-decode, no background hop, and no
|
|
48
|
+
* blanking of the cell — which is what removes the 1–2s blank/flicker on scroll-back. Bounded by
|
|
49
|
+
* entry count; an evicted animation is stopped (unless it is still attached to a live cell).
|
|
50
|
+
*/
|
|
51
|
+
private val drawableCache = object : LruCache<String, Drawable>(DRAWABLE_CACHE_ENTRIES) {
|
|
52
|
+
override fun entryRemoved(evicted: Boolean, key: String, oldValue: Drawable, newValue: Drawable?) {
|
|
53
|
+
if (oldValue !== newValue && oldValue.callback == null) {
|
|
54
|
+
(oldValue as? AnimatedImageDrawable)?.stop()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Volatile private var shutdown = false
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Download (or reuse cached bytes for) [url], decode, and set the result on [target]. Any
|
|
63
|
+
* previous request for the same view is superseded. [maxDim] caps the decoded thumbnail size.
|
|
64
|
+
*/
|
|
65
|
+
fun loadThumbnail(url: String, target: ImageView, maxDim: Int = DEFAULT_THUMB_MAX_DIM) {
|
|
66
|
+
if (url.isBlank()) return
|
|
67
|
+
pending[target] = url
|
|
68
|
+
val key = cacheKey(url, maxDim)
|
|
69
|
+
|
|
70
|
+
// Fast path: this GIF was decoded earlier (e.g. it was scrolled off and is now coming back).
|
|
71
|
+
// Re-show the cached drawable straight away on the main thread — no blanking, no re-decode,
|
|
72
|
+
// no background hop — so a returning cell never flashes its blank placeholder. Only reuse a
|
|
73
|
+
// drawable that isn't currently attached to a different live cell (the rare same-URL-twice
|
|
74
|
+
// case), so we never steal an animation from another visible view.
|
|
75
|
+
val cached = drawableCache.get(key)
|
|
76
|
+
if (cached != null && (cached.callback == null || cached.callback === target)) {
|
|
77
|
+
if (target.drawable !== cached) {
|
|
78
|
+
(target.drawable as? AnimatedImageDrawable)?.stop()
|
|
79
|
+
target.setImageDrawable(cached)
|
|
80
|
+
}
|
|
81
|
+
(cached as? AnimatedImageDrawable)?.start()
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Slow path: first time this GIF is shown. Blank the recycled cell and decode off-main, then
|
|
86
|
+
// cache the result so any later scroll-back takes the flicker-free fast path above.
|
|
87
|
+
(target.drawable as? AnimatedImageDrawable)?.stop()
|
|
88
|
+
target.setImageDrawable(null)
|
|
89
|
+
execute {
|
|
90
|
+
val bytes = bytesFor(url) ?: return@execute
|
|
91
|
+
val drawable = runCatching { decodeThumbnail(bytes, maxDim) }.getOrNull() ?: return@execute
|
|
92
|
+
main.post {
|
|
93
|
+
// Cache even if this cell was recycled meanwhile: the decode is done, so a future
|
|
94
|
+
// appearance of the same GIF should reuse it rather than decode again.
|
|
95
|
+
drawableCache.put(key, drawable)
|
|
96
|
+
if (shutdown || pending[target] != url) {
|
|
97
|
+
(drawable as? AnimatedImageDrawable)?.stop()
|
|
98
|
+
return@post
|
|
99
|
+
}
|
|
100
|
+
target.setImageDrawable(drawable)
|
|
101
|
+
(drawable as? AnimatedImageDrawable)?.start()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private fun cacheKey(url: String, maxDim: Int): String = "$url#$maxDim"
|
|
107
|
+
|
|
108
|
+
/** Blocking byte fetch for the overlay content GIF; caches on success. Off-main callers only. */
|
|
109
|
+
fun fetchBytes(url: String): GifResult<ByteArray> {
|
|
110
|
+
byteCache.get(url)?.let { return GifResult.Success(it) }
|
|
111
|
+
val res = GifHttp.getBytes(url)
|
|
112
|
+
if (res is GifResult.Success) byteCache.put(url, res.value)
|
|
113
|
+
return res
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Forget the pending request for [target] (e.g. when the sheet closes). */
|
|
117
|
+
fun cancel(target: ImageView) {
|
|
118
|
+
pending.remove(target)
|
|
119
|
+
(target.drawable as? AnimatedImageDrawable)?.stop()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private fun bytesFor(url: String): ByteArray? {
|
|
123
|
+
byteCache.get(url)?.let { return it }
|
|
124
|
+
val res = GifHttp.getBytes(url)
|
|
125
|
+
return if (res is GifResult.Success) {
|
|
126
|
+
byteCache.put(url, res.value)
|
|
127
|
+
res.value
|
|
128
|
+
} else {
|
|
129
|
+
null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private fun decodeThumbnail(bytes: ByteArray, maxDim: Int): Drawable? {
|
|
134
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
135
|
+
val source = android.graphics.ImageDecoder.createSource(ByteBuffer.wrap(bytes))
|
|
136
|
+
return android.graphics.ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
|
|
137
|
+
val longest = maxOf(info.size.width, info.size.height)
|
|
138
|
+
if (maxDim > 0 && longest > maxDim) {
|
|
139
|
+
decoder.setTargetSampleSize(sampleSizeFor(longest, maxDim))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// API 24–27: static first frame, downsampled.
|
|
144
|
+
val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
145
|
+
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opts)
|
|
146
|
+
val longest = maxOf(opts.outWidth, opts.outHeight)
|
|
147
|
+
val decodeOpts = BitmapFactory.Options().apply {
|
|
148
|
+
inSampleSize = if (maxDim > 0 && longest > maxDim) sampleSizeFor(longest, maxDim) else 1
|
|
149
|
+
}
|
|
150
|
+
val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, decodeOpts) ?: return null
|
|
151
|
+
return BitmapDrawable(null, bmp)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Largest power-of-two sample size that keeps the longest edge >= [maxDim]. */
|
|
155
|
+
private fun sampleSizeFor(longest: Int, maxDim: Int): Int {
|
|
156
|
+
var sample = 1
|
|
157
|
+
while (longest / (sample * 2) >= maxDim) sample *= 2
|
|
158
|
+
return sample
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun execute(block: () -> Unit) {
|
|
162
|
+
try {
|
|
163
|
+
executor.execute { if (!shutdown) block() }
|
|
164
|
+
} catch (e: Exception) {
|
|
165
|
+
// Executor already shut down — nothing to do.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Release worker threads and drop the caches. Safe to call more than once. */
|
|
170
|
+
fun shutdown() {
|
|
171
|
+
shutdown = true
|
|
172
|
+
executor.shutdownNow()
|
|
173
|
+
byteCache.evictAll()
|
|
174
|
+
drawableCache.evictAll()
|
|
175
|
+
pending.clear()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
companion object {
|
|
179
|
+
private const val DEFAULT_CACHE_BYTES = 16 * 1024 * 1024
|
|
180
|
+
private const val DEFAULT_THUMB_MAX_DIM = 256
|
|
181
|
+
// How many decoded thumbnails to keep for instant, flicker-free scroll-back. Sized to a few
|
|
182
|
+
// screens of the ~104dp grid cells; thumbnails are downsampled to DEFAULT_THUMB_MAX_DIM and
|
|
183
|
+
// AnimatedImageDrawable is lazy, so this stays modest on RAM. Evicted animations are stopped.
|
|
184
|
+
private const val DRAWABLE_CACHE_ENTRIES = 48
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.customcamera.gif
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provider-agnostic data models for the GIF picker feature.
|
|
5
|
+
*
|
|
6
|
+
* Everything the UI and the editor overlay consume is expressed in these types so that the
|
|
7
|
+
* concrete provider (GIPHY, Tenor, or anything added later) stays fully behind [GifProvider].
|
|
8
|
+
* No GIPHY/Tenor-specific field ever leaks past this file.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A single GIF result. URLs point at remote animated GIF assets. */
|
|
12
|
+
data class GifItem(
|
|
13
|
+
/** Stable provider-scoped id, used for de-duplication and "recent" tracking. */
|
|
14
|
+
val id: String,
|
|
15
|
+
/** Human-readable description for accessibility / content-description. May be blank. */
|
|
16
|
+
val description: String,
|
|
17
|
+
/** Small animated GIF used for the grid thumbnail (roughly ~200 px wide). */
|
|
18
|
+
val previewUrl: String,
|
|
19
|
+
val previewWidth: Int,
|
|
20
|
+
val previewHeight: Int,
|
|
21
|
+
/** Larger animated GIF placed on the photo when the item is picked. */
|
|
22
|
+
val contentUrl: String,
|
|
23
|
+
val contentWidth: Int,
|
|
24
|
+
val contentHeight: Int
|
|
25
|
+
) {
|
|
26
|
+
/** Aspect ratio (w/h) of the preview, defaulting to 1 when dimensions are unknown. */
|
|
27
|
+
val previewAspect: Float
|
|
28
|
+
get() = if (previewWidth > 0 && previewHeight > 0) previewWidth.toFloat() / previewHeight else 1f
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A browsable category / trending term shown as a chip or tile before the user searches. */
|
|
32
|
+
data class GifCategory(
|
|
33
|
+
/** Display label (already localized/normalized by the provider). */
|
|
34
|
+
val name: String,
|
|
35
|
+
/** The query to run when the category is tapped. */
|
|
36
|
+
val searchTerm: String,
|
|
37
|
+
/** Optional still/animated preview image for the category tile. */
|
|
38
|
+
val imageUrl: String?
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
/** One page of results plus an opaque cursor for the next page (null/blank = no more). */
|
|
42
|
+
data class GifPage(
|
|
43
|
+
val items: List<GifItem>,
|
|
44
|
+
val nextCursor: String?
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
/** Why a provider call could not return data. Drives the UI's empty/error/retry states. */
|
|
48
|
+
enum class GifError {
|
|
49
|
+
/** No connectivity, DNS failure, socket error, etc. */
|
|
50
|
+
NETWORK,
|
|
51
|
+
/** The request timed out. */
|
|
52
|
+
TIMEOUT,
|
|
53
|
+
/** The provider has no API key configured, so it cannot be used at all. */
|
|
54
|
+
NO_API_KEY,
|
|
55
|
+
/** HTTP 429 / provider quota exceeded. */
|
|
56
|
+
RATE_LIMITED,
|
|
57
|
+
/** HTTP 5xx from the provider. */
|
|
58
|
+
SERVER,
|
|
59
|
+
/** The response could not be parsed into the expected shape. */
|
|
60
|
+
PARSE,
|
|
61
|
+
/** Anything else. */
|
|
62
|
+
UNKNOWN
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Result wrapper. [Success] carries the value; [Failure] carries a typed [GifError]. */
|
|
66
|
+
sealed class GifResult<out T> {
|
|
67
|
+
data class Success<T>(val value: T) : GifResult<T>()
|
|
68
|
+
data class Failure(val error: GifError, val cause: Throwable? = null) : GifResult<Nothing>()
|
|
69
|
+
|
|
70
|
+
inline fun <R> map(transform: (T) -> R): GifResult<R> = when (this) {
|
|
71
|
+
is Success -> Success(transform(value))
|
|
72
|
+
is Failure -> this
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package com.customcamera.gif
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The single seam behind which every GIF service lives.
|
|
5
|
+
*
|
|
6
|
+
* The editor and picker UI depend ONLY on this interface, never on GIPHY/Tenor directly, so a
|
|
7
|
+
* provider can be swapped (or a new one added) by implementing this and registering it in
|
|
8
|
+
* [GifProviders]. Implementations are called on a background thread by [GifRepository] and must
|
|
9
|
+
* be blocking (no callbacks of their own); they must never touch the UI thread.
|
|
10
|
+
*
|
|
11
|
+
* Implementations must be resilient: any network/parse failure is returned as a
|
|
12
|
+
* [GifResult.Failure] with a typed [GifError] rather than thrown, so the UI can render a precise
|
|
13
|
+
* empty/error/retry state.
|
|
14
|
+
*/
|
|
15
|
+
interface GifProvider {
|
|
16
|
+
/** Stable identifier, e.g. "giphy" or "tenor". Used to select the provider from config. */
|
|
17
|
+
val id: String
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Attribution string the picker must display when this provider is active (both GIPHY and
|
|
21
|
+
* Tenor require visible attribution). Null when none is required.
|
|
22
|
+
*/
|
|
23
|
+
val attributionLabel: String?
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* False when the provider cannot operate at all — almost always a missing API key. The UI
|
|
27
|
+
* shows a dedicated "not configured" message instead of a generic network error, and the
|
|
28
|
+
* repository short-circuits with [GifError.NO_API_KEY].
|
|
29
|
+
*/
|
|
30
|
+
fun isConfigured(): Boolean
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Trending / featured GIFs for the initial, no-query state.
|
|
34
|
+
* @param cursor opaque page cursor from a previous [GifPage.nextCursor]; null for the first page.
|
|
35
|
+
*/
|
|
36
|
+
fun trending(limit: Int, cursor: String?): GifResult<GifPage>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Search results for [query].
|
|
40
|
+
* @param cursor opaque page cursor from a previous [GifPage.nextCursor]; null for the first page.
|
|
41
|
+
*/
|
|
42
|
+
fun search(query: String, limit: Int, cursor: String?): GifResult<GifPage>
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Browsable categories shown before the user types. Providers without a categories endpoint
|
|
46
|
+
* may return an empty list; the UI then simply shows trending only.
|
|
47
|
+
*/
|
|
48
|
+
fun categories(): GifResult<List<GifCategory>>
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.customcamera.gif
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Factory mapping a configured provider id to a concrete [GifProvider] implementation.
|
|
5
|
+
*
|
|
6
|
+
* GIPHY is the default -- selected for a null, blank, or unrecognised id -- because Google has
|
|
7
|
+
* announced the discontinuation of the Tenor API; Tenor is therefore only used when explicitly
|
|
8
|
+
* requested by [providerId] == "tenor".
|
|
9
|
+
*/
|
|
10
|
+
object GifProviders {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create the provider selected by [providerId].
|
|
14
|
+
*
|
|
15
|
+
* @param providerId case-insensitive provider key. "tenor" -> [TenorGifProvider]; anything else
|
|
16
|
+
* (including null / blank / unknown) -> [GiphyGifProvider].
|
|
17
|
+
* @param apiKey API key handed to the selected provider; may be null (the provider then reports
|
|
18
|
+
* [GifProvider.isConfigured] == false).
|
|
19
|
+
* @return a ready-to-use [GifProvider]; never null.
|
|
20
|
+
*/
|
|
21
|
+
fun create(providerId: String?, apiKey: String?): GifProvider {
|
|
22
|
+
return when (providerId?.trim()?.lowercase()) {
|
|
23
|
+
"tenor" -> TenorGifProvider(apiKey)
|
|
24
|
+
else -> GiphyGifProvider(apiKey)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|