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,758 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.Typeface
|
|
6
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
|
+
import android.os.Handler
|
|
8
|
+
import android.os.Looper
|
|
9
|
+
import android.view.Gravity
|
|
10
|
+
import android.view.View
|
|
11
|
+
import android.view.ViewGroup
|
|
12
|
+
import android.widget.FrameLayout
|
|
13
|
+
import android.widget.ImageView
|
|
14
|
+
import android.widget.TextView
|
|
15
|
+
import androidx.core.content.ContextCompat
|
|
16
|
+
import androidx.recyclerview.widget.DiffUtil
|
|
17
|
+
import androidx.recyclerview.widget.RecyclerView
|
|
18
|
+
import com.customcamera.CameraAsset
|
|
19
|
+
import com.customcamera.ThumbnailLoader
|
|
20
|
+
import java.util.Date
|
|
21
|
+
import kotlin.math.roundToInt
|
|
22
|
+
|
|
23
|
+
// Helper extension functions
|
|
24
|
+
fun android.content.Context.dp(value: Int): Int =
|
|
25
|
+
(value * resources.displayMetrics.density).roundToInt()
|
|
26
|
+
|
|
27
|
+
private fun Long.formatDuration(): String {
|
|
28
|
+
val seconds = this / 1000
|
|
29
|
+
// Localization.format, not string concatenation: locales with their own
|
|
30
|
+
// numerals (Arabic-Indic, Bengali) render the digits themselves.
|
|
31
|
+
return Localization.format("%d:%02d", seconds / 60, seconds % 60)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class MediaStripAdapter(
|
|
35
|
+
private val thumbnailLoader: ThumbnailLoader,
|
|
36
|
+
private val onAssetPress: (CameraAsset) -> Unit,
|
|
37
|
+
private val onAssetLongPress: (CameraAsset) -> Unit = {}
|
|
38
|
+
) : RecyclerView.Adapter<MediaAssetViewHolder>() {
|
|
39
|
+
private val assets = mutableListOf<CameraAsset>()
|
|
40
|
+
private val selectedIds = linkedSetOf<String>()
|
|
41
|
+
private var lastUpdateTime = 0L
|
|
42
|
+
private val updateDebounceMs = 100L
|
|
43
|
+
// The most recent list handed to submit() while inside the debounce window, plus the
|
|
44
|
+
// scheduled rebind that will apply it. Together they turn the debounce from "drop" into
|
|
45
|
+
// "coalesce" — see submit().
|
|
46
|
+
private var pendingAssets: List<CameraAsset>? = null
|
|
47
|
+
private var pendingFlush: Runnable? = null
|
|
48
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
49
|
+
|
|
50
|
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaAssetViewHolder =
|
|
51
|
+
MediaAssetViewHolder(parent, thumbnailLoader, sizeDp = 68)
|
|
52
|
+
|
|
53
|
+
override fun onBindViewHolder(holder: MediaAssetViewHolder, position: Int) {
|
|
54
|
+
val asset = assets.getOrNull(position) ?: return
|
|
55
|
+
val order = selectedIds.indexOf(asset.id) + 1
|
|
56
|
+
holder.bind(asset, if (order > 0) order else 0)
|
|
57
|
+
holder.itemView.setOnClickListener {
|
|
58
|
+
onAssetPress(asset)
|
|
59
|
+
}
|
|
60
|
+
holder.itemView.setOnLongClickListener {
|
|
61
|
+
onAssetLongPress(asset)
|
|
62
|
+
true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun getItemCount(): Int = assets.size
|
|
67
|
+
|
|
68
|
+
override fun onViewRecycled(holder: MediaAssetViewHolder) {
|
|
69
|
+
super.onViewRecycled(holder)
|
|
70
|
+
holder.clear()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun submit(next: List<CameraAsset>) {
|
|
74
|
+
// Coalesce rapid updates rather than DROPPING them. The previous implementation returned
|
|
75
|
+
// early inside the debounce window, so a page that arrived just after another (paging and a
|
|
76
|
+
// ContentObserver refresh routinely land together) was discarded outright and its assets
|
|
77
|
+
// never appeared in the strip until something else forced a rebind — one of the ways
|
|
78
|
+
// legitimately-discovered images went missing. Posting the newest list keeps the debounce's
|
|
79
|
+
// purpose (at most one rebind per window) without ever losing the data.
|
|
80
|
+
pendingAssets = next
|
|
81
|
+
if (pendingFlush != null) return
|
|
82
|
+
|
|
83
|
+
val elapsed = System.currentTimeMillis() - lastUpdateTime
|
|
84
|
+
if (elapsed >= updateDebounceMs) {
|
|
85
|
+
flushPending()
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
val flush = Runnable {
|
|
90
|
+
pendingFlush = null
|
|
91
|
+
flushPending()
|
|
92
|
+
}
|
|
93
|
+
pendingFlush = flush
|
|
94
|
+
handler.postDelayed(flush, updateDebounceMs - elapsed)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private fun flushPending() {
|
|
98
|
+
val next = pendingAssets ?: return
|
|
99
|
+
pendingAssets = null
|
|
100
|
+
lastUpdateTime = System.currentTimeMillis()
|
|
101
|
+
assets.clear()
|
|
102
|
+
assets.addAll(next)
|
|
103
|
+
notifyDataSetChanged()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fun prepend(asset: CameraAsset) {
|
|
107
|
+
// Apply any list still waiting in the debounce window FIRST, so the freshly-captured asset
|
|
108
|
+
// is prepended onto the newest data and cannot be wiped out moments later by a flush that
|
|
109
|
+
// predates it.
|
|
110
|
+
cancelPendingFlush()
|
|
111
|
+
flushPending()
|
|
112
|
+
|
|
113
|
+
val existingIndex = assets.indexOfFirst { it.id == asset.id || it.uri == asset.uri }
|
|
114
|
+
if (existingIndex >= 0) {
|
|
115
|
+
assets.removeAt(existingIndex)
|
|
116
|
+
notifyItemRemoved(existingIndex)
|
|
117
|
+
}
|
|
118
|
+
assets.add(0, asset)
|
|
119
|
+
notifyItemInserted(0)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private fun cancelPendingFlush() {
|
|
123
|
+
pendingFlush?.let { handler.removeCallbacks(it) }
|
|
124
|
+
pendingFlush = null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fun setSelected(nextSelectedIds: Set<String>) {
|
|
128
|
+
if (selectedIds == nextSelectedIds) return
|
|
129
|
+
selectedIds.clear()
|
|
130
|
+
selectedIds.addAll(nextSelectedIds)
|
|
131
|
+
notifyDataSetChanged()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class MediaGridAdapter(
|
|
136
|
+
private val thumbnailLoader: ThumbnailLoader,
|
|
137
|
+
private val onAssetPress: (CameraAsset) -> Unit,
|
|
138
|
+
private val onAssetLongPress: (CameraAsset) -> Unit = {},
|
|
139
|
+
private val onNearEnd: () -> Unit
|
|
140
|
+
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
141
|
+
private val rows = mutableListOf<Row>()
|
|
142
|
+
private val selectedIds = linkedSetOf<String>()
|
|
143
|
+
private var lastLoadTrigger = 0L
|
|
144
|
+
|
|
145
|
+
override fun getItemViewType(position: Int): Int =
|
|
146
|
+
when (rows.getOrNull(position)) {
|
|
147
|
+
is Row.Header -> VIEW_HEADER
|
|
148
|
+
is Row.Asset -> VIEW_ASSET
|
|
149
|
+
null -> VIEW_ASSET
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
|
|
153
|
+
if (viewType == VIEW_HEADER) {
|
|
154
|
+
DateHeaderViewHolder(parent)
|
|
155
|
+
} else {
|
|
156
|
+
MediaAssetViewHolder(parent, thumbnailLoader, sizeDp = null)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
override fun onBindViewHolder(
|
|
160
|
+
holder: RecyclerView.ViewHolder,
|
|
161
|
+
position: Int,
|
|
162
|
+
payloads: MutableList<Any>
|
|
163
|
+
) {
|
|
164
|
+
// Selection-only update (from setSelected): refresh just the badge/highlight without
|
|
165
|
+
// re-triggering the thumbnail load or reattaching click listeners. This is the cheap path
|
|
166
|
+
// that makes selecting/deselecting a tile instant instead of a full rebind of the row.
|
|
167
|
+
if (payloads.contains(PAYLOAD_SELECTION)) {
|
|
168
|
+
val row = rows.getOrNull(position)
|
|
169
|
+
if (row is Row.Asset && holder is MediaAssetViewHolder) {
|
|
170
|
+
val order = selectedIds.indexOf(row.asset.id) + 1
|
|
171
|
+
holder.updateSelection(if (order > 0) order else 0)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
super.onBindViewHolder(holder, position, payloads)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
179
|
+
// Trigger load more when within 12 items of end, but debounce
|
|
180
|
+
if (position >= rows.size - 12) {
|
|
181
|
+
val now = System.currentTimeMillis()
|
|
182
|
+
if (now - lastLoadTrigger > 500L) {
|
|
183
|
+
lastLoadTrigger = now
|
|
184
|
+
onNearEnd()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
val row = rows.getOrNull(position) ?: return
|
|
189
|
+
when (row) {
|
|
190
|
+
is Row.Header -> (holder as DateHeaderViewHolder).bind(row.label)
|
|
191
|
+
is Row.Asset -> {
|
|
192
|
+
val assetHolder = holder as MediaAssetViewHolder
|
|
193
|
+
val order = selectedIds.indexOf(row.asset.id) + 1
|
|
194
|
+
assetHolder.bind(row.asset, if (order > 0) order else 0)
|
|
195
|
+
assetHolder.itemView.setOnClickListener {
|
|
196
|
+
onAssetPress(row.asset)
|
|
197
|
+
}
|
|
198
|
+
assetHolder.itemView.setOnLongClickListener {
|
|
199
|
+
onAssetLongPress(row.asset)
|
|
200
|
+
true
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
override fun getItemCount(): Int = rows.size
|
|
207
|
+
|
|
208
|
+
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
|
209
|
+
super.onViewRecycled(holder)
|
|
210
|
+
if (holder is MediaAssetViewHolder) {
|
|
211
|
+
holder.clear()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fun submit(assets: List<CameraAsset>) {
|
|
216
|
+
val newRows = groupRows(assets)
|
|
217
|
+
// Compute the MINIMAL set of changes instead of blasting notifyDataSetChanged() on every
|
|
218
|
+
// paged append. The gallery calls submit() with the full accumulated list each time a new
|
|
219
|
+
// page loads (and on capture prepend); notifyDataSetChanged() rebinds every visible holder
|
|
220
|
+
// and defeats item reuse/animation, causing scroll jank on large libraries. DiffUtil emits
|
|
221
|
+
// only the actual insertions/moves/changes, so appending a page touches just the new rows.
|
|
222
|
+
// Row is a data class, so structural equality drives an accurate diff. The list is small
|
|
223
|
+
// per page and diffing is linear here, run on the main thread exactly where the old
|
|
224
|
+
// notifyDataSetChanged() ran — no threading/behaviour change, identical final contents.
|
|
225
|
+
val oldRows = ArrayList(rows)
|
|
226
|
+
val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
|
227
|
+
override fun getOldListSize(): Int = oldRows.size
|
|
228
|
+
override fun getNewListSize(): Int = newRows.size
|
|
229
|
+
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
|
230
|
+
val o = oldRows[oldItemPosition]
|
|
231
|
+
val n = newRows[newItemPosition]
|
|
232
|
+
return when {
|
|
233
|
+
o is Row.Asset && n is Row.Asset -> o.asset.id == n.asset.id
|
|
234
|
+
o is Row.Header && n is Row.Header -> o.label == n.label
|
|
235
|
+
else -> false
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
|
239
|
+
oldRows[oldItemPosition] == newRows[newItemPosition]
|
|
240
|
+
})
|
|
241
|
+
rows.clear()
|
|
242
|
+
rows.addAll(newRows)
|
|
243
|
+
diff.dispatchUpdatesTo(this)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fun setSelected(nextSelectedIds: Set<String>) {
|
|
247
|
+
if (selectedIds == nextSelectedIds) return
|
|
248
|
+
// Only the selection BADGE/highlight of affected rows changes, never the row set or its
|
|
249
|
+
// order. Rebind just the asset rows whose membership actually flipped (added or removed
|
|
250
|
+
// from the selection) via a lightweight payload, instead of notifyDataSetChanged() which
|
|
251
|
+
// rebinds and reloads every visible tile. Falls back to nothing when a row isn't visible
|
|
252
|
+
// (RecyclerView skips it) — the correct new state is applied on its next bind.
|
|
253
|
+
val changed = selectedIds.toMutableSet().apply {
|
|
254
|
+
// symmetric difference: ids that were selected xor are now selected
|
|
255
|
+
val union = selectedIds + nextSelectedIds
|
|
256
|
+
clear()
|
|
257
|
+
union.forEach { id ->
|
|
258
|
+
if ((id in selectedIds) != (id in nextSelectedIds)) add(id)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
selectedIds.clear()
|
|
262
|
+
selectedIds.addAll(nextSelectedIds)
|
|
263
|
+
if (changed.isEmpty()) return
|
|
264
|
+
rows.forEachIndexed { index, row ->
|
|
265
|
+
if (row is Row.Asset && row.asset.id in changed) {
|
|
266
|
+
notifyItemChanged(index, PAYLOAD_SELECTION)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private fun groupRows(assets: List<CameraAsset>): List<Row> {
|
|
272
|
+
val formatter = Localization.dateFormatter()
|
|
273
|
+
val grouped = mutableListOf<Row>()
|
|
274
|
+
var currentLabel: String? = null
|
|
275
|
+
assets.forEach { asset ->
|
|
276
|
+
val label = asset.dateTaken?.let {
|
|
277
|
+
runCatching { formatter.format(Date(it)) }.getOrNull()
|
|
278
|
+
} ?: Localization.getString("unknownDate", "Unknown date")
|
|
279
|
+
if (label != currentLabel) {
|
|
280
|
+
currentLabel = label
|
|
281
|
+
grouped.add(Row.Header(label))
|
|
282
|
+
}
|
|
283
|
+
grouped.add(Row.Asset(asset))
|
|
284
|
+
}
|
|
285
|
+
return grouped
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
sealed class Row {
|
|
289
|
+
data class Header(val label: String) : Row()
|
|
290
|
+
data class Asset(val asset: CameraAsset) : Row()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
companion object {
|
|
294
|
+
const val VIEW_HEADER = 1
|
|
295
|
+
const val VIEW_ASSET = 2
|
|
296
|
+
// Marker payload for a selection-only rebind (badge/highlight), so the thumbnail isn't
|
|
297
|
+
// reloaded when a tile is (de)selected. See onBindViewHolder(..., payloads).
|
|
298
|
+
private val PAYLOAD_SELECTION = Any()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
class DateHeaderViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
|
|
303
|
+
TextView(parent.context).apply {
|
|
304
|
+
val theme = CameraTheme.get(context)
|
|
305
|
+
setTextColor(theme.componentColor("mediaGrid", "headerText", Color.WHITE))
|
|
306
|
+
setTextSize(theme.componentValue("mediaGrid", "headerTextSize", 12f))
|
|
307
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
308
|
+
setPadding(parent.context.dp(16), parent.context.dp(12), 0, parent.context.dp(6))
|
|
309
|
+
setBackgroundColor(theme.componentColor("mediaGrid", "headerBackground", 0x66000000))
|
|
310
|
+
}
|
|
311
|
+
) {
|
|
312
|
+
private val textView = itemView as TextView
|
|
313
|
+
|
|
314
|
+
fun bind(label: String) {
|
|
315
|
+
textView.text = label
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
class MediaAssetViewHolder(
|
|
320
|
+
parent: ViewGroup,
|
|
321
|
+
private val thumbnailLoader: ThumbnailLoader,
|
|
322
|
+
sizeDp: Int?
|
|
323
|
+
) : RecyclerView.ViewHolder(SquareFrameLayout(parent.context)) {
|
|
324
|
+
private val imageView = ImageView(parent.context)
|
|
325
|
+
private val playIconView = ImageView(parent.context)
|
|
326
|
+
private val durationView = TextView(parent.context)
|
|
327
|
+
// A GIF tile shows a static first frame, exactly like a still photo, so without a marker the
|
|
328
|
+
// two are indistinguishable in the grid. Mirrors the video duration badge's placement and
|
|
329
|
+
// styling — same corner, same treatment — so the tile reads as one consistent system.
|
|
330
|
+
private val gifBadgeView = TextView(parent.context)
|
|
331
|
+
// Centred selection checkmark, shown on any selected tile (strip + grid). The checkmark glyph
|
|
332
|
+
// comes from the theme's customizable `selectionCheck` icon so a host app can swap it out.
|
|
333
|
+
private val selectedView = ImageView(parent.context)
|
|
334
|
+
// Shown only for document/audio assets, which have no visual thumbnail: overlays the
|
|
335
|
+
// file name beneath a type icon so the tile is identifiable.
|
|
336
|
+
private val fileLabelView = TextView(parent.context)
|
|
337
|
+
private val theme = CameraTheme.get(parent.context)
|
|
338
|
+
private val fixedSizePx = sizeDp?.let { parent.context.dp(it) }
|
|
339
|
+
private val fallbackSizePx = fixedSizePx ?: (parent.resources.displayMetrics.widthPixels / 3)
|
|
340
|
+
|
|
341
|
+
init {
|
|
342
|
+
val root = itemView as FrameLayout
|
|
343
|
+
val itemWidth = fixedSizePx ?: ViewGroup.LayoutParams.MATCH_PARENT
|
|
344
|
+
val itemHeight = fixedSizePx ?: ViewGroup.LayoutParams.WRAP_CONTENT
|
|
345
|
+
root.layoutParams = RecyclerView.LayoutParams(itemWidth, itemHeight).apply {
|
|
346
|
+
setMargins(parent.context.dp(3), parent.context.dp(3), parent.context.dp(3), parent.context.dp(3))
|
|
347
|
+
}
|
|
348
|
+
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
|
349
|
+
imageView.adjustViewBounds = false
|
|
350
|
+
imageView.cropToPadding = true
|
|
351
|
+
root.addView(
|
|
352
|
+
imageView,
|
|
353
|
+
FrameLayout.LayoutParams(
|
|
354
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
355
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
playIconView.setImageDrawable(theme.config.iconDrawable(parent.context, "play", R.drawable.ic_play))
|
|
360
|
+
theme.config.iconTint("play")?.let { playIconView.setColorFilter(it) }
|
|
361
|
+
playIconView.scaleType = ImageView.ScaleType.FIT_CENTER
|
|
362
|
+
val playSize = parent.context.dp(theme.config.iconSizeDp("play") ?: 28)
|
|
363
|
+
root.addView(
|
|
364
|
+
playIconView,
|
|
365
|
+
FrameLayout.LayoutParams(
|
|
366
|
+
playSize,
|
|
367
|
+
playSize,
|
|
368
|
+
Gravity.CENTER
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
durationView.setTextColor(theme.componentColor("mediaGrid", "durationText", Color.WHITE))
|
|
373
|
+
durationView.textSize = theme.componentValue("mediaGrid", "durationTextSize", 11f)
|
|
374
|
+
durationView.typeface = Typeface.DEFAULT_BOLD
|
|
375
|
+
durationView.setBackgroundColor(theme.componentColor("mediaGrid", "durationBackground", 0x66000000))
|
|
376
|
+
durationView.setPadding(parent.context.dp(4), 0, parent.context.dp(4), 0)
|
|
377
|
+
root.addView(
|
|
378
|
+
durationView,
|
|
379
|
+
FrameLayout.LayoutParams(
|
|
380
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
381
|
+
parent.context.dp(20),
|
|
382
|
+
Gravity.BOTTOM or Gravity.END
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
gifBadgeView.text = Localization.getString("gifBadge", "GIF")
|
|
387
|
+
gifBadgeView.setTextColor(theme.componentColor("mediaGrid", "gifBadgeText", Color.WHITE))
|
|
388
|
+
gifBadgeView.textSize = theme.componentValue("mediaGrid", "gifBadgeTextSize", 11f)
|
|
389
|
+
gifBadgeView.typeface = Typeface.DEFAULT_BOLD
|
|
390
|
+
gifBadgeView.setBackgroundColor(theme.componentColor("mediaGrid", "gifBadgeBackground", 0x66000000))
|
|
391
|
+
gifBadgeView.setPadding(parent.context.dp(4), 0, parent.context.dp(4), 0)
|
|
392
|
+
gifBadgeView.visibility = View.GONE
|
|
393
|
+
root.addView(
|
|
394
|
+
gifBadgeView,
|
|
395
|
+
FrameLayout.LayoutParams(
|
|
396
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
397
|
+
parent.context.dp(20),
|
|
398
|
+
Gravity.BOTTOM or Gravity.END
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
// The selected-media checkmark stays centered on the thumbnail, but the circular backdrop is
|
|
403
|
+
// intentionally removed so the badge reads as a clean glyph instead of a colored disc.
|
|
404
|
+
selectedView.scaleType = ImageView.ScaleType.FIT_CENTER
|
|
405
|
+
selectedView.setBackgroundColor(Color.TRANSPARENT)
|
|
406
|
+
selectedView.setImageDrawable(theme.config.iconDrawable(parent.context, "selectionCheck", R.drawable.ic_check))
|
|
407
|
+
theme.config.iconTint("selectionCheck")?.let { selectedView.setColorFilter(it) }
|
|
408
|
+
selectedView.visibility = View.GONE
|
|
409
|
+
root.addView(
|
|
410
|
+
selectedView,
|
|
411
|
+
FrameLayout.LayoutParams(
|
|
412
|
+
parent.context.dp(26),
|
|
413
|
+
parent.context.dp(26),
|
|
414
|
+
Gravity.CENTER
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
fileLabelView.setTextColor(theme.componentColor("mediaGrid", "fileLabelText", Color.WHITE))
|
|
419
|
+
fileLabelView.textSize = theme.componentValue("mediaGrid", "fileLabelTextSize", 9f)
|
|
420
|
+
fileLabelView.gravity = Gravity.CENTER
|
|
421
|
+
fileLabelView.maxLines = 2
|
|
422
|
+
fileLabelView.ellipsize = android.text.TextUtils.TruncateAt.MIDDLE
|
|
423
|
+
fileLabelView.setBackgroundColor(theme.componentColor("mediaGrid", "fileLabelBackground", 0x66000000))
|
|
424
|
+
fileLabelView.setPadding(parent.context.dp(4), parent.context.dp(2), parent.context.dp(4), parent.context.dp(4))
|
|
425
|
+
fileLabelView.visibility = View.GONE
|
|
426
|
+
root.addView(
|
|
427
|
+
fileLabelView,
|
|
428
|
+
FrameLayout.LayoutParams(
|
|
429
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
430
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
431
|
+
Gravity.BOTTOM
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
fun bind(asset: CameraAsset, selectionOrder: Int) {
|
|
437
|
+
val isVideo = asset.type == "video"
|
|
438
|
+
val isDocument = asset.type == "document"
|
|
439
|
+
val isAudio = asset.type == "audio"
|
|
440
|
+
|
|
441
|
+
if (isDocument || isAudio) {
|
|
442
|
+
// No pixel thumbnail exists for these — render a centered type icon plus the file name.
|
|
443
|
+
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
|
|
444
|
+
val pad = itemView.context.dp(if (fixedSizePx != null) 14 else 22)
|
|
445
|
+
imageView.setPadding(pad, pad, pad, pad)
|
|
446
|
+
imageView.setBackgroundColor(theme.componentColor("mediaGrid", "fileTileBackground", 0xFF1A1A1A.toInt()))
|
|
447
|
+
val iconName = if (isAudio) "audio" else "document"
|
|
448
|
+
val builtin = if (isAudio) R.drawable.ic_audio else R.drawable.ic_document
|
|
449
|
+
val fileTileIcon = theme.config.iconDrawable(itemView.context, iconName, builtin)
|
|
450
|
+
imageView.setImageDrawable(fileTileIcon)
|
|
451
|
+
// Config tint wins; else a host SVG keeps its own colours (null → clear) and a
|
|
452
|
+
// built-in glyph gets the neutral file-tile tint.
|
|
453
|
+
theme.config.iconTintFor(
|
|
454
|
+
iconName,
|
|
455
|
+
fileTileIcon,
|
|
456
|
+
theme.componentColor("mediaGrid", "fileTileIconTint", 0xFFBFBFBF.toInt())
|
|
457
|
+
)?.let { imageView.setColorFilter(it) } ?: imageView.clearColorFilter()
|
|
458
|
+
fileLabelView.visibility = View.VISIBLE
|
|
459
|
+
fileLabelView.text = asset.fileName
|
|
460
|
+
?: if (isAudio) Localization.getString("audio", "Audio")
|
|
461
|
+
else Localization.getString("document", "Document")
|
|
462
|
+
} else {
|
|
463
|
+
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
|
464
|
+
imageView.setPadding(0, 0, 0, 0)
|
|
465
|
+
imageView.background = null
|
|
466
|
+
imageView.clearColorFilter()
|
|
467
|
+
fileLabelView.visibility = View.GONE
|
|
468
|
+
val loadSize = itemView.width.takeIf { it > 0 } ?: fallbackSizePx
|
|
469
|
+
thumbnailLoader.load(asset, imageView, loadSize)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
durationView.visibility = if (isVideo) View.VISIBLE else View.GONE
|
|
473
|
+
durationView.text = asset.duration?.formatDuration()
|
|
474
|
+
?: Localization.getString("durationZero", "0:00")
|
|
475
|
+
playIconView.visibility = if (isVideo) View.VISIBLE else View.GONE
|
|
476
|
+
// Only an image tile can be a GIF, and the badge shares the duration badge's corner, so it
|
|
477
|
+
// is mutually exclusive with the video treatment above.
|
|
478
|
+
gifBadgeView.visibility =
|
|
479
|
+
if (!isVideo && !isDocument && !isAudio && GifSupport.isGif(asset)) View.VISIBLE
|
|
480
|
+
else View.GONE
|
|
481
|
+
updateSelection(selectionOrder)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Update ONLY the selection badge + highlight for this tile. Split out of [bind] so a
|
|
486
|
+
* selection change can be applied via a payload rebind without reloading the thumbnail or
|
|
487
|
+
* re-running the (more expensive) media-type framing logic above.
|
|
488
|
+
*/
|
|
489
|
+
fun updateSelection(selectionOrder: Int) {
|
|
490
|
+
// Every selected tile shows the SAME centred checkmark — the order is only used to decide
|
|
491
|
+
// selected vs. not, never displayed, so multi-selection never shows a number.
|
|
492
|
+
selectedView.visibility = if (selectionOrder > 0) View.VISIBLE else View.GONE
|
|
493
|
+
itemView.foreground =
|
|
494
|
+
if (selectionOrder > 0) {
|
|
495
|
+
GradientDrawable().apply {
|
|
496
|
+
setColor(theme.componentColor("mediaGrid", "selectionOverlay", 0x2200FF66))
|
|
497
|
+
setStroke(
|
|
498
|
+
itemView.context.dp(3),
|
|
499
|
+
theme.componentColor("mediaGrid", "selectionAccent", 0xFF25D366.toInt())
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
null
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
fun clear() {
|
|
508
|
+
// Clear image to release memory
|
|
509
|
+
imageView.setImageDrawable(null)
|
|
510
|
+
// Reset the document/audio tile styling so a recycled holder rebinding to an
|
|
511
|
+
// image/video shows a normal cropped thumbnail rather than a padded icon.
|
|
512
|
+
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
|
513
|
+
imageView.setPadding(0, 0, 0, 0)
|
|
514
|
+
imageView.background = null
|
|
515
|
+
imageView.clearColorFilter()
|
|
516
|
+
fileLabelView.visibility = View.GONE
|
|
517
|
+
fileLabelView.text = ""
|
|
518
|
+
playIconView.visibility = View.GONE
|
|
519
|
+
durationView.text = ""
|
|
520
|
+
gifBadgeView.visibility = View.GONE
|
|
521
|
+
selectedView.visibility = View.GONE
|
|
522
|
+
itemView.foreground = null
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private class SquareFrameLayout(context: Context) : FrameLayout(context) {
|
|
527
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
528
|
+
val width = View.MeasureSpec.getSize(widthMeasureSpec)
|
|
529
|
+
val squareSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
|
|
530
|
+
super.onMeasure(widthMeasureSpec, squareSpec)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
class SendingModeThumbnailAdapter(
|
|
535
|
+
private val thumbnailLoader: ThumbnailLoader,
|
|
536
|
+
private val assets: List<CameraAsset>,
|
|
537
|
+
private var currentIndex: Int,
|
|
538
|
+
private val thumbnailSize: Int,
|
|
539
|
+
private val onAssetSelect: (Int) -> Unit,
|
|
540
|
+
private val onRemoveAsset: (Int) -> Unit
|
|
541
|
+
) : RecyclerView.Adapter<SendingModeThumbnailViewHolder>() {
|
|
542
|
+
|
|
543
|
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SendingModeThumbnailViewHolder =
|
|
544
|
+
SendingModeThumbnailViewHolder(parent, thumbnailLoader, thumbnailSize)
|
|
545
|
+
|
|
546
|
+
override fun onBindViewHolder(holder: SendingModeThumbnailViewHolder, position: Int) {
|
|
547
|
+
val asset = assets.getOrNull(position) ?: return
|
|
548
|
+
val isSelected = position == currentIndex
|
|
549
|
+
holder.bind(asset, isSelected, {
|
|
550
|
+
onAssetSelect(position)
|
|
551
|
+
}, {
|
|
552
|
+
onRemoveAsset(position)
|
|
553
|
+
})
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
override fun getItemCount(): Int = assets.size
|
|
557
|
+
|
|
558
|
+
fun getCurrentIndex(): Int = currentIndex
|
|
559
|
+
|
|
560
|
+
override fun onViewRecycled(holder: SendingModeThumbnailViewHolder) {
|
|
561
|
+
super.onViewRecycled(holder)
|
|
562
|
+
holder.clear()
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
fun updateCurrentIndex(index: Int) {
|
|
566
|
+
currentIndex = index
|
|
567
|
+
notifyDataSetChanged()
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
class SendingModeThumbnailViewHolder(
|
|
572
|
+
parent: ViewGroup,
|
|
573
|
+
private val thumbnailLoader: ThumbnailLoader,
|
|
574
|
+
private val thumbnailSize: Int
|
|
575
|
+
) : RecyclerView.ViewHolder(FrameLayout(parent.context)) {
|
|
576
|
+
private val container = itemView as FrameLayout
|
|
577
|
+
private val theme = CameraTheme.get(parent.context)
|
|
578
|
+
private val cornerRadius = parent.context.dp(10)
|
|
579
|
+
private val thumbnail = ImageView(parent.context).apply {
|
|
580
|
+
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
581
|
+
adjustViewBounds = false
|
|
582
|
+
cropToPadding = true
|
|
583
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
584
|
+
thumbnailSize,
|
|
585
|
+
thumbnailSize
|
|
586
|
+
)
|
|
587
|
+
}
|
|
588
|
+
private val border = View(parent.context).apply {
|
|
589
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
590
|
+
thumbnailSize,
|
|
591
|
+
thumbnailSize
|
|
592
|
+
)
|
|
593
|
+
background = thumbnailBorder(false)
|
|
594
|
+
}
|
|
595
|
+
private val playIconView = ImageView(parent.context).apply {
|
|
596
|
+
setImageDrawable(theme.config.iconDrawable(parent.context, "play", R.drawable.ic_play))
|
|
597
|
+
theme.config.iconTint("play")?.let { setColorFilter(it) }
|
|
598
|
+
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
599
|
+
}
|
|
600
|
+
private val durationView = TextView(parent.context).apply {
|
|
601
|
+
setTextColor(theme.componentColor("sendingTray", "durationText", Color.WHITE))
|
|
602
|
+
textSize = theme.componentValue("sendingTray", "durationTextSize", 9f)
|
|
603
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
604
|
+
setBackgroundColor(theme.componentColor("sendingTray", "durationBackground", 0x66000000))
|
|
605
|
+
setPadding(parent.context.dp(2), 0, parent.context.dp(2), 0)
|
|
606
|
+
}
|
|
607
|
+
// Marks an animated-capable tile in the sending tray, matching the grid/strip badge so a GIF
|
|
608
|
+
// stays identifiable right up to the moment it is sent.
|
|
609
|
+
private val gifBadgeView = TextView(parent.context).apply {
|
|
610
|
+
text = Localization.getString("gifBadge", "GIF")
|
|
611
|
+
setTextColor(theme.componentColor("sendingTray", "gifBadgeText", Color.WHITE))
|
|
612
|
+
textSize = theme.componentValue("sendingTray", "gifBadgeTextSize", 9f)
|
|
613
|
+
typeface = Typeface.DEFAULT_BOLD
|
|
614
|
+
setBackgroundColor(theme.componentColor("sendingTray", "gifBadgeBackground", 0x66000000))
|
|
615
|
+
setPadding(parent.context.dp(2), 0, parent.context.dp(2), 0)
|
|
616
|
+
visibility = View.GONE
|
|
617
|
+
}
|
|
618
|
+
private val removeBtn = ImageView(parent.context).apply {
|
|
619
|
+
// Dustbin icon to delete this media from the selection before sending. Shown
|
|
620
|
+
// CENTERED on EVERY thumbnail as a bare glyph — no dark circular backdrop. The
|
|
621
|
+
// tint toggles with the active index: red on the currently previewed thumbnail,
|
|
622
|
+
// white on the rest (see deleteTint / bind()).
|
|
623
|
+
setImageDrawable(theme.config.iconDrawable(parent.context, "delete", R.drawable.ic_delete))
|
|
624
|
+
setColorFilter(deleteTint(false))
|
|
625
|
+
scaleType = ImageView.ScaleType.CENTER_INSIDE
|
|
626
|
+
contentDescription = Localization.getString("delete", "Delete")
|
|
627
|
+
isClickable = true
|
|
628
|
+
isFocusable = true
|
|
629
|
+
visibility = View.GONE
|
|
630
|
+
setPadding(
|
|
631
|
+
parent.context.dp(6),
|
|
632
|
+
parent.context.dp(6),
|
|
633
|
+
parent.context.dp(6),
|
|
634
|
+
parent.context.dp(6)
|
|
635
|
+
)
|
|
636
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
637
|
+
parent.context.dp(32),
|
|
638
|
+
parent.context.dp(32),
|
|
639
|
+
Gravity.CENTER
|
|
640
|
+
)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
init {
|
|
644
|
+
container.layoutParams = RecyclerView.LayoutParams(
|
|
645
|
+
thumbnailSize,
|
|
646
|
+
thumbnailSize
|
|
647
|
+
).apply {
|
|
648
|
+
marginEnd = parent.context.dp(8)
|
|
649
|
+
}
|
|
650
|
+
container.addView(thumbnail)
|
|
651
|
+
container.addView(border)
|
|
652
|
+
|
|
653
|
+
val playSize = parent.context.dp(16)
|
|
654
|
+
container.addView(
|
|
655
|
+
playIconView,
|
|
656
|
+
FrameLayout.LayoutParams(
|
|
657
|
+
playSize,
|
|
658
|
+
playSize,
|
|
659
|
+
Gravity.CENTER
|
|
660
|
+
)
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
container.addView(
|
|
664
|
+
durationView,
|
|
665
|
+
FrameLayout.LayoutParams(
|
|
666
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
667
|
+
parent.context.dp(14),
|
|
668
|
+
Gravity.BOTTOM or Gravity.END
|
|
669
|
+
).apply {
|
|
670
|
+
bottomMargin = parent.context.dp(4)
|
|
671
|
+
marginEnd = parent.context.dp(4)
|
|
672
|
+
}
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
container.addView(
|
|
676
|
+
gifBadgeView,
|
|
677
|
+
FrameLayout.LayoutParams(
|
|
678
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
679
|
+
parent.context.dp(14),
|
|
680
|
+
Gravity.BOTTOM or Gravity.END
|
|
681
|
+
).apply {
|
|
682
|
+
bottomMargin = parent.context.dp(4)
|
|
683
|
+
marginEnd = parent.context.dp(4)
|
|
684
|
+
}
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
container.addView(removeBtn)
|
|
688
|
+
removeBtn.bringToFront()
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
fun bind(asset: CameraAsset, isSelected: Boolean, onSelect: () -> Unit, onRemove: () -> Unit) {
|
|
692
|
+
// Always load the original unfiltered image for thumbnails
|
|
693
|
+
// Use a cache key that bypasses any filtered versions
|
|
694
|
+
thumbnailLoader.load(asset, thumbnail, thumbnailSize)
|
|
695
|
+
border.background = thumbnailBorder(isSelected)
|
|
696
|
+
// Clear any color filter to ensure thumbnail shows original image
|
|
697
|
+
thumbnail.clearColorFilter()
|
|
698
|
+
val isVideo = asset.type == "video"
|
|
699
|
+
// The trash glyph now sits centered on EVERY thumbnail, so the centered play
|
|
700
|
+
// glyph stays hidden in the sending tray — the duration badge alone marks videos.
|
|
701
|
+
playIconView.visibility = View.GONE
|
|
702
|
+
durationView.visibility = if (isVideo) View.VISIBLE else View.GONE
|
|
703
|
+
durationView.text = asset.duration?.formatDuration()
|
|
704
|
+
?: Localization.getString("durationZero", "0:00")
|
|
705
|
+
gifBadgeView.visibility =
|
|
706
|
+
if (!isVideo && GifSupport.isGif(asset)) View.VISIBLE else View.GONE
|
|
707
|
+
container.setOnClickListener { onSelect() }
|
|
708
|
+
// Trash shows on ALL thumbnails; the tint toggles with selection — red on the
|
|
709
|
+
// active (currently previewed) thumbnail, white on the rest. Only the active
|
|
710
|
+
// tile's trash is tappable: on inactive tiles the tap falls through to the
|
|
711
|
+
// container and selects the thumbnail, exactly as before.
|
|
712
|
+
removeBtn.animate().cancel()
|
|
713
|
+
removeBtn.alpha = 1f
|
|
714
|
+
removeBtn.visibility = View.VISIBLE
|
|
715
|
+
removeBtn.setColorFilter(deleteTint(isSelected))
|
|
716
|
+
if (isSelected) {
|
|
717
|
+
removeBtn.isClickable = true
|
|
718
|
+
removeBtn.setOnClickListener { onRemove() }
|
|
719
|
+
} else {
|
|
720
|
+
removeBtn.isClickable = false
|
|
721
|
+
removeBtn.setOnClickListener(null)
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
fun clear() {
|
|
726
|
+
thumbnail.setImageDrawable(null)
|
|
727
|
+
playIconView.visibility = View.GONE
|
|
728
|
+
durationView.visibility = View.GONE
|
|
729
|
+
gifBadgeView.visibility = View.GONE
|
|
730
|
+
container.setOnClickListener(null)
|
|
731
|
+
removeBtn.animate().cancel()
|
|
732
|
+
removeBtn.visibility = View.GONE
|
|
733
|
+
removeBtn.setOnClickListener(null)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Tint for the centered dustbin glyph: red on the currently previewed (active)
|
|
738
|
+
* thumbnail, white on the others. Both sides are themeable via the sendingTray
|
|
739
|
+
* component colors; a config iconTint("delete") still overrides the inactive white
|
|
740
|
+
* so existing white-label configs keep their custom base tint.
|
|
741
|
+
*/
|
|
742
|
+
private fun deleteTint(isSelected: Boolean): Int =
|
|
743
|
+
if (isSelected) theme.componentColor("sendingTray", "activeDeleteTint", 0xFFF44336.toInt())
|
|
744
|
+
else theme.componentColor("sendingTray", "inactiveDeleteTint", theme.config.iconTint("delete") ?: Color.WHITE)
|
|
745
|
+
|
|
746
|
+
private fun thumbnailBorder(isSelected: Boolean): GradientDrawable =
|
|
747
|
+
GradientDrawable().apply {
|
|
748
|
+
setColor(Color.TRANSPARENT)
|
|
749
|
+
cornerRadius = cornerRadius.toFloat()
|
|
750
|
+
// The selected highlight marks the currently previewed thumbnail; the rest keep a
|
|
751
|
+
// faint hairline so they still read as tappable tiles.
|
|
752
|
+
setStroke(
|
|
753
|
+
container.context.dp(if (isSelected) 3 else 1),
|
|
754
|
+
if (isSelected) theme.componentColor("sendingTray", "activeBorder", Color.WHITE)
|
|
755
|
+
else theme.componentColor("sendingTray", "inactiveBorder", 0x55FFFFFF)
|
|
756
|
+
)
|
|
757
|
+
}
|
|
758
|
+
}
|