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.
Files changed (506) hide show
  1. package/CustomCamera.podspec +20 -0
  2. package/LICENSE +20 -0
  3. package/README.md +833 -0
  4. package/android/build.gradle +135 -0
  5. package/android/consumer-rules.pro +47 -0
  6. package/android/src/main/AndroidManifest.xml +72 -0
  7. package/android/src/main/java/android/print/PdfPrint.java +51 -0
  8. package/android/src/main/java/com/customcamera/AnimatedImageViewManager.kt +199 -0
  9. package/android/src/main/java/com/customcamera/AudioPlayerView.kt +322 -0
  10. package/android/src/main/java/com/customcamera/AudioRecorder.kt +506 -0
  11. package/android/src/main/java/com/customcamera/BarcodeScannerActivity.kt +361 -0
  12. package/android/src/main/java/com/customcamera/BlurBackgroundView.kt +199 -0
  13. package/android/src/main/java/com/customcamera/CacheCleanupManager.kt +386 -0
  14. package/android/src/main/java/com/customcamera/CameraAspectRatioMode.kt +77 -0
  15. package/android/src/main/java/com/customcamera/CameraAsset.kt +211 -0
  16. package/android/src/main/java/com/customcamera/CameraTheme.kt +479 -0
  17. package/android/src/main/java/com/customcamera/CompressionCacheStore.kt +504 -0
  18. package/android/src/main/java/com/customcamera/CompressionFileNameUtils.kt +7 -0
  19. package/android/src/main/java/com/customcamera/CompressionProfile.kt +195 -0
  20. package/android/src/main/java/com/customcamera/CustomCameraActivity.kt +7291 -0
  21. package/android/src/main/java/com/customcamera/CustomCameraFileProvider.kt +9 -0
  22. package/android/src/main/java/com/customcamera/CustomCameraModule.kt +2479 -0
  23. package/android/src/main/java/com/customcamera/CustomCameraPackage.kt +53 -0
  24. package/android/src/main/java/com/customcamera/DisplayUtils.kt +9 -0
  25. package/android/src/main/java/com/customcamera/DocumentPreviewView.kt +846 -0
  26. package/android/src/main/java/com/customcamera/DrawingView.kt +214 -0
  27. package/android/src/main/java/com/customcamera/EmojiPickerData.kt +228 -0
  28. package/android/src/main/java/com/customcamera/FilterStripAdapter.kt +158 -0
  29. package/android/src/main/java/com/customcamera/GifSupport.kt +215 -0
  30. package/android/src/main/java/com/customcamera/GlobalAudioPlayer.kt +320 -0
  31. package/android/src/main/java/com/customcamera/ImageExporter.kt +1394 -0
  32. package/android/src/main/java/com/customcamera/ImageFormatSupport.kt +163 -0
  33. package/android/src/main/java/com/customcamera/Localization.kt +206 -0
  34. package/android/src/main/java/com/customcamera/MediaAdapters.kt +758 -0
  35. package/android/src/main/java/com/customcamera/MediaCacheManager.kt +782 -0
  36. package/android/src/main/java/com/customcamera/MediaExportFormats.kt +201 -0
  37. package/android/src/main/java/com/customcamera/MediaFileIo.kt +310 -0
  38. package/android/src/main/java/com/customcamera/MediaMetadataStore.kt +494 -0
  39. package/android/src/main/java/com/customcamera/MediaPreviewEditor.kt +14865 -0
  40. package/android/src/main/java/com/customcamera/MediaPublisher.kt +423 -0
  41. package/android/src/main/java/com/customcamera/MediaStorageWorkflow.kt +722 -0
  42. package/android/src/main/java/com/customcamera/MediaStoreRepository.kt +378 -0
  43. package/android/src/main/java/com/customcamera/MediaThumbnailView.kt +286 -0
  44. package/android/src/main/java/com/customcamera/MediaUtils.kt +37 -0
  45. package/android/src/main/java/com/customcamera/NativeVideoViewManager.kt +845 -0
  46. package/android/src/main/java/com/customcamera/OfficeDocumentConverter.kt +2791 -0
  47. package/android/src/main/java/com/customcamera/PendingCaptureCrops.kt +88 -0
  48. package/android/src/main/java/com/customcamera/PowerPointPreview.kt +4077 -0
  49. package/android/src/main/java/com/customcamera/RecentPickerStore.kt +74 -0
  50. package/android/src/main/java/com/customcamera/SecureFileEraser.kt +411 -0
  51. package/android/src/main/java/com/customcamera/SecureKeyModule.kt +88 -0
  52. package/android/src/main/java/com/customcamera/SecureMediaDeleter.kt +493 -0
  53. package/android/src/main/java/com/customcamera/StickerPickerData.kt +107 -0
  54. package/android/src/main/java/com/customcamera/SvgDrawable.kt +175 -0
  55. package/android/src/main/java/com/customcamera/SvgIconViewManager.kt +77 -0
  56. package/android/src/main/java/com/customcamera/TextEntryController.kt +553 -0
  57. package/android/src/main/java/com/customcamera/ThumbnailLoader.kt +455 -0
  58. package/android/src/main/java/com/customcamera/UploadService.kt +909 -0
  59. package/android/src/main/java/com/customcamera/VideoEditUtils.kt +210 -0
  60. package/android/src/main/java/com/customcamera/VideoRotationTranscoder.kt +1992 -0
  61. package/android/src/main/java/com/customcamera/gif/AnimatedGifEncoder.kt +473 -0
  62. package/android/src/main/java/com/customcamera/gif/GifDecoder.kt +690 -0
  63. package/android/src/main/java/com/customcamera/gif/GifFramesDrawable.kt +160 -0
  64. package/android/src/main/java/com/customcamera/gif/GifHttp.kt +112 -0
  65. package/android/src/main/java/com/customcamera/gif/GifImageLoader.kt +186 -0
  66. package/android/src/main/java/com/customcamera/gif/GifModels.kt +74 -0
  67. package/android/src/main/java/com/customcamera/gif/GifProvider.kt +49 -0
  68. package/android/src/main/java/com/customcamera/gif/GifProviders.kt +27 -0
  69. package/android/src/main/java/com/customcamera/gif/GifRepository.kt +124 -0
  70. package/android/src/main/java/com/customcamera/gif/GifSafety.kt +33 -0
  71. package/android/src/main/java/com/customcamera/gif/GiphyGifProvider.kt +146 -0
  72. package/android/src/main/java/com/customcamera/gif/LZWEncoder.kt +258 -0
  73. package/android/src/main/java/com/customcamera/gif/NeuQuant.kt +435 -0
  74. package/android/src/main/java/com/customcamera/gif/TenorGifProvider.kt +147 -0
  75. package/android/src/main/res/drawable/bg_brush_selected.xml +5 -0
  76. package/android/src/main/res/drawable/bg_color_swatch.xml +5 -0
  77. package/android/src/main/res/drawable/bg_editor_toolbar.xml +6 -0
  78. package/android/src/main/res/drawable/bg_icon_circle.xml +5 -0
  79. package/android/src/main/res/drawable/bg_icon_circle_light.xml +5 -0
  80. package/android/src/main/res/drawable/bg_mode_tab_active.xml +6 -0
  81. package/android/src/main/res/drawable/bg_pill.xml +6 -0
  82. package/android/src/main/res/drawable/bg_recording_dot.xml +6 -0
  83. package/android/src/main/res/drawable/bg_selection_badge.xml +8 -0
  84. package/android/src/main/res/drawable/bg_send_button.xml +5 -0
  85. package/android/src/main/res/drawable/bg_shutter_inner.xml +5 -0
  86. package/android/src/main/res/drawable/bg_shutter_recording.xml +6 -0
  87. package/android/src/main/res/drawable/bg_shutter_ring.xml +8 -0
  88. package/android/src/main/res/drawable/bg_shutter_video_idle.xml +5 -0
  89. package/android/src/main/res/drawable/bg_thumbnail.xml +9 -0
  90. package/android/src/main/res/drawable/ic_aspect_ratio.xml +17 -0
  91. package/android/src/main/res/drawable/ic_audio.xml +10 -0
  92. package/android/src/main/res/drawable/ic_back.xml +10 -0
  93. package/android/src/main/res/drawable/ic_brush_medium.xml +12 -0
  94. package/android/src/main/res/drawable/ic_brush_thick.xml +12 -0
  95. package/android/src/main/res/drawable/ic_brush_thin.xml +12 -0
  96. package/android/src/main/res/drawable/ic_check.xml +17 -0
  97. package/android/src/main/res/drawable/ic_chevron_down.xml +10 -0
  98. package/android/src/main/res/drawable/ic_chevron_up.xml +13 -0
  99. package/android/src/main/res/drawable/ic_clear_drawing.xml +10 -0
  100. package/android/src/main/res/drawable/ic_close.xml +10 -0
  101. package/android/src/main/res/drawable/ic_cover.xml +9 -0
  102. package/android/src/main/res/drawable/ic_crop.xml +10 -0
  103. package/android/src/main/res/drawable/ic_delete.xml +9 -0
  104. package/android/src/main/res/drawable/ic_document.xml +10 -0
  105. package/android/src/main/res/drawable/ic_done.xml +10 -0
  106. package/android/src/main/res/drawable/ic_download.xml +10 -0
  107. package/android/src/main/res/drawable/ic_emoji.xml +10 -0
  108. package/android/src/main/res/drawable/ic_filter.xml +10 -0
  109. package/android/src/main/res/drawable/ic_flash_auto.xml +7 -0
  110. package/android/src/main/res/drawable/ic_flash_off.xml +23 -0
  111. package/android/src/main/res/drawable/ic_flash_on.xml +16 -0
  112. package/android/src/main/res/drawable/ic_flip.xml +15 -0
  113. package/android/src/main/res/drawable/ic_gallery.xml +10 -0
  114. package/android/src/main/res/drawable/ic_gif.xml +24 -0
  115. package/android/src/main/res/drawable/ic_lock.xml +19 -0
  116. package/android/src/main/res/drawable/ic_lock_open.xml +22 -0
  117. package/android/src/main/res/drawable/ic_marker.xml +10 -0
  118. package/android/src/main/res/drawable/ic_more_vert.xml +18 -0
  119. package/android/src/main/res/drawable/ic_overlay_close.xml +19 -0
  120. package/android/src/main/res/drawable/ic_overlay_resize.xml +18 -0
  121. package/android/src/main/res/drawable/ic_pause.xml +10 -0
  122. package/android/src/main/res/drawable/ic_photo_mode.xml +10 -0
  123. package/android/src/main/res/drawable/ic_play.xml +10 -0
  124. package/android/src/main/res/drawable/ic_redo.xml +10 -0
  125. package/android/src/main/res/drawable/ic_rotate.xml +9 -0
  126. package/android/src/main/res/drawable/ic_send.xml +10 -0
  127. package/android/src/main/res/drawable/ic_share.xml +10 -0
  128. package/android/src/main/res/drawable/ic_stat_upload.xml +10 -0
  129. package/android/src/main/res/drawable/ic_sticker.xml +24 -0
  130. package/android/src/main/res/drawable/ic_text.xml +10 -0
  131. package/android/src/main/res/drawable/ic_text_align_center.xml +11 -0
  132. package/android/src/main/res/drawable/ic_text_align_left.xml +11 -0
  133. package/android/src/main/res/drawable/ic_text_align_right.xml +11 -0
  134. package/android/src/main/res/drawable/ic_text_bg.xml +11 -0
  135. package/android/src/main/res/drawable/ic_trim.xml +23 -0
  136. package/android/src/main/res/drawable/ic_undo.xml +10 -0
  137. package/android/src/main/res/drawable/ic_video_mode.xml +10 -0
  138. package/android/src/main/res/drawable/ic_video_note.xml +13 -0
  139. package/android/src/main/res/drawable/ic_volume_off.xml +9 -0
  140. package/android/src/main/res/drawable/ic_volume_on.xml +9 -0
  141. package/android/src/main/res/drawable/sticker_heart.xml +9 -0
  142. package/android/src/main/res/drawable/sticker_smile.xml +9 -0
  143. package/android/src/main/res/drawable/sticker_star.xml +9 -0
  144. package/android/src/main/res/drawable/sticker_thumbsup.xml +9 -0
  145. package/android/src/main/res/values/colors.xml +34 -0
  146. package/android/src/main/res/values/ids.xml +15 -0
  147. package/android/src/main/res/values/strings.xml +15 -0
  148. package/android/src/main/res/values/styles.xml +20 -0
  149. package/android/src/main/res/xml/file_paths.xml +34 -0
  150. package/ios/CustomCamera.h +5 -0
  151. package/ios/CustomCamera.mm +214 -0
  152. package/lib/module/AudioPlayerContext.js +233 -0
  153. package/lib/module/AudioPlayerContext.js.map +1 -0
  154. package/lib/module/CameraPreview.js +304 -0
  155. package/lib/module/CameraPreview.js.map +1 -0
  156. package/lib/module/NativeAnimatedImageView.js +41 -0
  157. package/lib/module/NativeAnimatedImageView.js.map +1 -0
  158. package/lib/module/NativeAudioPlayerView.js +33 -0
  159. package/lib/module/NativeAudioPlayerView.js.map +1 -0
  160. package/lib/module/NativeBlurView.js +35 -0
  161. package/lib/module/NativeBlurView.js.map +1 -0
  162. package/lib/module/NativeCustomCamera.js +128 -0
  163. package/lib/module/NativeCustomCamera.js.map +1 -0
  164. package/lib/module/NativeCustomCamera.web.js +143 -0
  165. package/lib/module/NativeCustomCamera.web.js.map +1 -0
  166. package/lib/module/NativeCustomVideoView.js +5 -0
  167. package/lib/module/NativeCustomVideoView.js.map +1 -0
  168. package/lib/module/NativeDocumentPreviewView.js +156 -0
  169. package/lib/module/NativeDocumentPreviewView.js.map +1 -0
  170. package/lib/module/NativeMediaThumbnailView.js +110 -0
  171. package/lib/module/NativeMediaThumbnailView.js.map +1 -0
  172. package/lib/module/NativeSvgIcon.js +32 -0
  173. package/lib/module/NativeSvgIcon.js.map +1 -0
  174. package/lib/module/ResponsivePreviewContainer.js +171 -0
  175. package/lib/module/ResponsivePreviewContainer.js.map +1 -0
  176. package/lib/module/accessibility.js +61 -0
  177. package/lib/module/accessibility.js.map +1 -0
  178. package/lib/module/config.js +352 -0
  179. package/lib/module/config.js.map +1 -0
  180. package/lib/module/cpuOptimization.js +296 -0
  181. package/lib/module/cpuOptimization.js.map +1 -0
  182. package/lib/module/db/DatabaseManager.js +191 -0
  183. package/lib/module/db/DatabaseManager.js.map +1 -0
  184. package/lib/module/db/MediaMapper.js +284 -0
  185. package/lib/module/db/MediaMapper.js.map +1 -0
  186. package/lib/module/db/MediaRepository.js +242 -0
  187. package/lib/module/db/MediaRepository.js.map +1 -0
  188. package/lib/module/db/index.js +41 -0
  189. package/lib/module/db/index.js.map +1 -0
  190. package/lib/module/db/migrations.js +180 -0
  191. package/lib/module/db/migrations.js.map +1 -0
  192. package/lib/module/db/react-native-sqlcipher.d.js +2 -0
  193. package/lib/module/db/react-native-sqlcipher.d.js.map +1 -0
  194. package/lib/module/db/secureKey.js +87 -0
  195. package/lib/module/db/secureKey.js.map +1 -0
  196. package/lib/module/db/types.js +57 -0
  197. package/lib/module/db/types.js.map +1 -0
  198. package/lib/module/db/useMediaDatabase.js +123 -0
  199. package/lib/module/db/useMediaDatabase.js.map +1 -0
  200. package/lib/module/defaultIcons.js +2259 -0
  201. package/lib/module/defaultIcons.js.map +1 -0
  202. package/lib/module/deviceCapabilities.js +408 -0
  203. package/lib/module/deviceCapabilities.js.map +1 -0
  204. package/lib/module/errors.js +267 -0
  205. package/lib/module/errors.js.map +1 -0
  206. package/lib/module/iconConfig.js +796 -0
  207. package/lib/module/iconConfig.js.map +1 -0
  208. package/lib/module/icons.js +514 -0
  209. package/lib/module/icons.js.map +1 -0
  210. package/lib/module/index.js +251 -0
  211. package/lib/module/index.js.map +1 -0
  212. package/lib/module/jest-globals.d.js +2 -0
  213. package/lib/module/jest-globals.d.js.map +1 -0
  214. package/lib/module/library/MediaLibrary.js +107 -0
  215. package/lib/module/library/MediaLibrary.js.map +1 -0
  216. package/lib/module/library/MediaLibraryScreen.js +2597 -0
  217. package/lib/module/library/MediaLibraryScreen.js.map +1 -0
  218. package/lib/module/library/components/AudioPreview.js +74 -0
  219. package/lib/module/library/components/AudioPreview.js.map +1 -0
  220. package/lib/module/library/components/DocumentPreview.js +331 -0
  221. package/lib/module/library/components/DocumentPreview.js.map +1 -0
  222. package/lib/module/library/components/ExpandableFab.js +343 -0
  223. package/lib/module/library/components/ExpandableFab.js.map +1 -0
  224. package/lib/module/library/components/FileTiles.js +251 -0
  225. package/lib/module/library/components/FileTiles.js.map +1 -0
  226. package/lib/module/library/components/ImagePreview.js +70 -0
  227. package/lib/module/library/components/ImagePreview.js.map +1 -0
  228. package/lib/module/library/components/MediaMetadataHeader.js +175 -0
  229. package/lib/module/library/components/MediaMetadataHeader.js.map +1 -0
  230. package/lib/module/library/components/RecordAudioScreen.js +816 -0
  231. package/lib/module/library/components/RecordAudioScreen.js.map +1 -0
  232. package/lib/module/library/components/VideoPreview.js +453 -0
  233. package/lib/module/library/components/VideoPreview.js.map +1 -0
  234. package/lib/module/library/components/ViewerCounter.js +55 -0
  235. package/lib/module/library/components/ViewerCounter.js.map +1 -0
  236. package/lib/module/library/compressionEstimate.js +507 -0
  237. package/lib/module/library/compressionEstimate.js.map +1 -0
  238. package/lib/module/library/fileMetadata.js +196 -0
  239. package/lib/module/library/fileMetadata.js.map +1 -0
  240. package/lib/module/library/index.js +42 -0
  241. package/lib/module/library/index.js.map +1 -0
  242. package/lib/module/library/libraryIconRegistry.js +69 -0
  243. package/lib/module/library/libraryIconRegistry.js.map +1 -0
  244. package/lib/module/library/libraryIcons.js +858 -0
  245. package/lib/module/library/libraryIcons.js.map +1 -0
  246. package/lib/module/library/localizedFormat.js +181 -0
  247. package/lib/module/library/localizedFormat.js.map +1 -0
  248. package/lib/module/library/palette.js +225 -0
  249. package/lib/module/library/palette.js.map +1 -0
  250. package/lib/module/library/responsive.js +53 -0
  251. package/lib/module/library/responsive.js.map +1 -0
  252. package/lib/module/locales.js +416 -0
  253. package/lib/module/locales.js.map +1 -0
  254. package/lib/module/localization.js +1064 -0
  255. package/lib/module/localization.js.map +1 -0
  256. package/lib/module/logger.js +20 -0
  257. package/lib/module/logger.js.map +1 -0
  258. package/lib/module/lowMemoryCache.js +206 -0
  259. package/lib/module/lowMemoryCache.js.map +1 -0
  260. package/lib/module/mediaHistory.js +33 -0
  261. package/lib/module/mediaHistory.js.map +1 -0
  262. package/lib/module/mediaStorageService.js +402 -0
  263. package/lib/module/mediaStorageService.js.map +1 -0
  264. package/lib/module/memory.js +79 -0
  265. package/lib/module/memory.js.map +1 -0
  266. package/lib/module/nativeActions.js +307 -0
  267. package/lib/module/nativeActions.js.map +1 -0
  268. package/lib/module/nativeComponentSupport.js +61 -0
  269. package/lib/module/nativeComponentSupport.js.map +1 -0
  270. package/lib/module/networkPerformance.js +24 -0
  271. package/lib/module/networkPerformance.js.map +1 -0
  272. package/lib/module/package.json +1 -0
  273. package/lib/module/performance.js +104 -0
  274. package/lib/module/performance.js.map +1 -0
  275. package/lib/module/performanceMonitor.js +155 -0
  276. package/lib/module/performanceMonitor.js.map +1 -0
  277. package/lib/module/permissions.js +370 -0
  278. package/lib/module/permissions.js.map +1 -0
  279. package/lib/module/rendering.js +40 -0
  280. package/lib/module/rendering.js.map +1 -0
  281. package/lib/module/security.js +18 -0
  282. package/lib/module/security.js.map +1 -0
  283. package/lib/module/state.js +266 -0
  284. package/lib/module/state.js.map +1 -0
  285. package/lib/module/storage.js +400 -0
  286. package/lib/module/storage.js.map +1 -0
  287. package/lib/module/theme.js +390 -0
  288. package/lib/module/theme.js.map +1 -0
  289. package/lib/module/types.js +4 -0
  290. package/lib/module/types.js.map +1 -0
  291. package/lib/module/utils.js +115 -0
  292. package/lib/module/utils.js.map +1 -0
  293. package/lib/module/validation.js +288 -0
  294. package/lib/module/validation.js.map +1 -0
  295. package/lib/typescript/package.json +1 -0
  296. package/lib/typescript/src/AudioPlayerContext.d.ts +27 -0
  297. package/lib/typescript/src/AudioPlayerContext.d.ts.map +1 -0
  298. package/lib/typescript/src/CameraPreview.d.ts +13 -0
  299. package/lib/typescript/src/CameraPreview.d.ts.map +1 -0
  300. package/lib/typescript/src/NativeAnimatedImageView.d.ts +33 -0
  301. package/lib/typescript/src/NativeAnimatedImageView.d.ts.map +1 -0
  302. package/lib/typescript/src/NativeAudioPlayerView.d.ts +12 -0
  303. package/lib/typescript/src/NativeAudioPlayerView.d.ts.map +1 -0
  304. package/lib/typescript/src/NativeBlurView.d.ts +12 -0
  305. package/lib/typescript/src/NativeBlurView.d.ts.map +1 -0
  306. package/lib/typescript/src/NativeCustomCamera.d.ts +167 -0
  307. package/lib/typescript/src/NativeCustomCamera.d.ts.map +1 -0
  308. package/lib/typescript/src/NativeCustomCamera.web.d.ts +47 -0
  309. package/lib/typescript/src/NativeCustomCamera.web.d.ts.map +1 -0
  310. package/lib/typescript/src/NativeCustomVideoView.d.ts +30 -0
  311. package/lib/typescript/src/NativeCustomVideoView.d.ts.map +1 -0
  312. package/lib/typescript/src/NativeDocumentPreviewView.d.ts +48 -0
  313. package/lib/typescript/src/NativeDocumentPreviewView.d.ts.map +1 -0
  314. package/lib/typescript/src/NativeMediaThumbnailView.d.ts +54 -0
  315. package/lib/typescript/src/NativeMediaThumbnailView.d.ts.map +1 -0
  316. package/lib/typescript/src/NativeSvgIcon.d.ts +25 -0
  317. package/lib/typescript/src/NativeSvgIcon.d.ts.map +1 -0
  318. package/lib/typescript/src/ResponsivePreviewContainer.d.ts +92 -0
  319. package/lib/typescript/src/ResponsivePreviewContainer.d.ts.map +1 -0
  320. package/lib/typescript/src/accessibility.d.ts +20 -0
  321. package/lib/typescript/src/accessibility.d.ts.map +1 -0
  322. package/lib/typescript/src/config.d.ts +145 -0
  323. package/lib/typescript/src/config.d.ts.map +1 -0
  324. package/lib/typescript/src/cpuOptimization.d.ts +63 -0
  325. package/lib/typescript/src/cpuOptimization.d.ts.map +1 -0
  326. package/lib/typescript/src/db/DatabaseManager.d.ts +48 -0
  327. package/lib/typescript/src/db/DatabaseManager.d.ts.map +1 -0
  328. package/lib/typescript/src/db/MediaMapper.d.ts +77 -0
  329. package/lib/typescript/src/db/MediaMapper.d.ts.map +1 -0
  330. package/lib/typescript/src/db/MediaRepository.d.ts +66 -0
  331. package/lib/typescript/src/db/MediaRepository.d.ts.map +1 -0
  332. package/lib/typescript/src/db/index.d.ts +23 -0
  333. package/lib/typescript/src/db/index.d.ts.map +1 -0
  334. package/lib/typescript/src/db/migrations.d.ts +31 -0
  335. package/lib/typescript/src/db/migrations.d.ts.map +1 -0
  336. package/lib/typescript/src/db/secureKey.d.ts +10 -0
  337. package/lib/typescript/src/db/secureKey.d.ts.map +1 -0
  338. package/lib/typescript/src/db/types.d.ts +115 -0
  339. package/lib/typescript/src/db/types.d.ts.map +1 -0
  340. package/lib/typescript/src/db/useMediaDatabase.d.ts +41 -0
  341. package/lib/typescript/src/db/useMediaDatabase.d.ts.map +1 -0
  342. package/lib/typescript/src/defaultIcons.d.ts +139 -0
  343. package/lib/typescript/src/defaultIcons.d.ts.map +1 -0
  344. package/lib/typescript/src/deviceCapabilities.d.ts +92 -0
  345. package/lib/typescript/src/deviceCapabilities.d.ts.map +1 -0
  346. package/lib/typescript/src/errors.d.ts +95 -0
  347. package/lib/typescript/src/errors.d.ts.map +1 -0
  348. package/lib/typescript/src/iconConfig.d.ts +176 -0
  349. package/lib/typescript/src/iconConfig.d.ts.map +1 -0
  350. package/lib/typescript/src/icons.d.ts +217 -0
  351. package/lib/typescript/src/icons.d.ts.map +1 -0
  352. package/lib/typescript/src/index.d.ts +104 -0
  353. package/lib/typescript/src/index.d.ts.map +1 -0
  354. package/lib/typescript/src/library/MediaLibrary.d.ts +57 -0
  355. package/lib/typescript/src/library/MediaLibrary.d.ts.map +1 -0
  356. package/lib/typescript/src/library/MediaLibraryScreen.d.ts +51 -0
  357. package/lib/typescript/src/library/MediaLibraryScreen.d.ts.map +1 -0
  358. package/lib/typescript/src/library/components/AudioPreview.d.ts +6 -0
  359. package/lib/typescript/src/library/components/AudioPreview.d.ts.map +1 -0
  360. package/lib/typescript/src/library/components/DocumentPreview.d.ts +10 -0
  361. package/lib/typescript/src/library/components/DocumentPreview.d.ts.map +1 -0
  362. package/lib/typescript/src/library/components/ExpandableFab.d.ts +39 -0
  363. package/lib/typescript/src/library/components/ExpandableFab.d.ts.map +1 -0
  364. package/lib/typescript/src/library/components/FileTiles.d.ts +8 -0
  365. package/lib/typescript/src/library/components/FileTiles.d.ts.map +1 -0
  366. package/lib/typescript/src/library/components/ImagePreview.d.ts +5 -0
  367. package/lib/typescript/src/library/components/ImagePreview.d.ts.map +1 -0
  368. package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts +16 -0
  369. package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts.map +1 -0
  370. package/lib/typescript/src/library/components/RecordAudioScreen.d.ts +8 -0
  371. package/lib/typescript/src/library/components/RecordAudioScreen.d.ts.map +1 -0
  372. package/lib/typescript/src/library/components/VideoPreview.d.ts +20 -0
  373. package/lib/typescript/src/library/components/VideoPreview.d.ts.map +1 -0
  374. package/lib/typescript/src/library/components/ViewerCounter.d.ts +13 -0
  375. package/lib/typescript/src/library/components/ViewerCounter.d.ts.map +1 -0
  376. package/lib/typescript/src/library/compressionEstimate.d.ts +313 -0
  377. package/lib/typescript/src/library/compressionEstimate.d.ts.map +1 -0
  378. package/lib/typescript/src/library/fileMetadata.d.ts +33 -0
  379. package/lib/typescript/src/library/fileMetadata.d.ts.map +1 -0
  380. package/lib/typescript/src/library/index.d.ts +40 -0
  381. package/lib/typescript/src/library/index.d.ts.map +1 -0
  382. package/lib/typescript/src/library/libraryIconRegistry.d.ts +35 -0
  383. package/lib/typescript/src/library/libraryIconRegistry.d.ts.map +1 -0
  384. package/lib/typescript/src/library/libraryIcons.d.ts +85 -0
  385. package/lib/typescript/src/library/libraryIcons.d.ts.map +1 -0
  386. package/lib/typescript/src/library/localizedFormat.d.ts +49 -0
  387. package/lib/typescript/src/library/localizedFormat.d.ts.map +1 -0
  388. package/lib/typescript/src/library/palette.d.ts +104 -0
  389. package/lib/typescript/src/library/palette.d.ts.map +1 -0
  390. package/lib/typescript/src/library/responsive.d.ts +15 -0
  391. package/lib/typescript/src/library/responsive.d.ts.map +1 -0
  392. package/lib/typescript/src/locales.d.ts +103 -0
  393. package/lib/typescript/src/locales.d.ts.map +1 -0
  394. package/lib/typescript/src/localization.d.ts +1051 -0
  395. package/lib/typescript/src/localization.d.ts.map +1 -0
  396. package/lib/typescript/src/logger.d.ts +4 -0
  397. package/lib/typescript/src/logger.d.ts.map +1 -0
  398. package/lib/typescript/src/lowMemoryCache.d.ts +64 -0
  399. package/lib/typescript/src/lowMemoryCache.d.ts.map +1 -0
  400. package/lib/typescript/src/mediaHistory.d.ts +3 -0
  401. package/lib/typescript/src/mediaHistory.d.ts.map +1 -0
  402. package/lib/typescript/src/mediaStorageService.d.ts +155 -0
  403. package/lib/typescript/src/mediaStorageService.d.ts.map +1 -0
  404. package/lib/typescript/src/memory.d.ts +21 -0
  405. package/lib/typescript/src/memory.d.ts.map +1 -0
  406. package/lib/typescript/src/nativeActions.d.ts +174 -0
  407. package/lib/typescript/src/nativeActions.d.ts.map +1 -0
  408. package/lib/typescript/src/nativeComponentSupport.d.ts +30 -0
  409. package/lib/typescript/src/nativeComponentSupport.d.ts.map +1 -0
  410. package/lib/typescript/src/networkPerformance.d.ts +12 -0
  411. package/lib/typescript/src/networkPerformance.d.ts.map +1 -0
  412. package/lib/typescript/src/performance.d.ts +32 -0
  413. package/lib/typescript/src/performance.d.ts.map +1 -0
  414. package/lib/typescript/src/performanceMonitor.d.ts +45 -0
  415. package/lib/typescript/src/performanceMonitor.d.ts.map +1 -0
  416. package/lib/typescript/src/permissions.d.ts +132 -0
  417. package/lib/typescript/src/permissions.d.ts.map +1 -0
  418. package/lib/typescript/src/rendering.d.ts +9 -0
  419. package/lib/typescript/src/rendering.d.ts.map +1 -0
  420. package/lib/typescript/src/security.d.ts +6 -0
  421. package/lib/typescript/src/security.d.ts.map +1 -0
  422. package/lib/typescript/src/state.d.ts +102 -0
  423. package/lib/typescript/src/state.d.ts.map +1 -0
  424. package/lib/typescript/src/storage.d.ts +117 -0
  425. package/lib/typescript/src/storage.d.ts.map +1 -0
  426. package/lib/typescript/src/theme.d.ts +15 -0
  427. package/lib/typescript/src/theme.d.ts.map +1 -0
  428. package/lib/typescript/src/types.d.ts +802 -0
  429. package/lib/typescript/src/types.d.ts.map +1 -0
  430. package/lib/typescript/src/utils.d.ts +50 -0
  431. package/lib/typescript/src/utils.d.ts.map +1 -0
  432. package/lib/typescript/src/validation.d.ts +52 -0
  433. package/lib/typescript/src/validation.d.ts.map +1 -0
  434. package/package.json +204 -0
  435. package/react-native.config.js +17 -0
  436. package/src/AudioPlayerContext.tsx +220 -0
  437. package/src/CameraPreview.tsx +352 -0
  438. package/src/NativeAnimatedImageView.tsx +71 -0
  439. package/src/NativeAudioPlayerView.tsx +39 -0
  440. package/src/NativeBlurView.tsx +40 -0
  441. package/src/NativeCustomCamera.ts +305 -0
  442. package/src/NativeCustomCamera.web.ts +192 -0
  443. package/src/NativeCustomVideoView.ts +30 -0
  444. package/src/NativeDocumentPreviewView.tsx +196 -0
  445. package/src/NativeMediaThumbnailView.tsx +167 -0
  446. package/src/NativeSvgIcon.tsx +46 -0
  447. package/src/ResponsivePreviewContainer.tsx +227 -0
  448. package/src/accessibility.ts +80 -0
  449. package/src/config.ts +406 -0
  450. package/src/cpuOptimization.ts +365 -0
  451. package/src/db/DatabaseManager.ts +212 -0
  452. package/src/db/MediaMapper.ts +377 -0
  453. package/src/db/MediaRepository.ts +313 -0
  454. package/src/db/index.ts +73 -0
  455. package/src/db/migrations.ts +222 -0
  456. package/src/db/react-native-sqlcipher.d.ts +76 -0
  457. package/src/db/secureKey.ts +100 -0
  458. package/src/db/types.ts +123 -0
  459. package/src/db/useMediaDatabase.ts +191 -0
  460. package/src/defaultIcons.tsx +2390 -0
  461. package/src/deviceCapabilities.ts +438 -0
  462. package/src/errors.ts +350 -0
  463. package/src/iconConfig.tsx +962 -0
  464. package/src/icons.tsx +788 -0
  465. package/src/index.tsx +763 -0
  466. package/src/jest-globals.d.ts +9 -0
  467. package/src/library/MediaLibrary.tsx +141 -0
  468. package/src/library/MediaLibraryScreen.tsx +3003 -0
  469. package/src/library/components/AudioPreview.tsx +73 -0
  470. package/src/library/components/DocumentPreview.tsx +361 -0
  471. package/src/library/components/ExpandableFab.tsx +342 -0
  472. package/src/library/components/FileTiles.tsx +252 -0
  473. package/src/library/components/ImagePreview.tsx +72 -0
  474. package/src/library/components/MediaMetadataHeader.tsx +208 -0
  475. package/src/library/components/RecordAudioScreen.tsx +845 -0
  476. package/src/library/components/VideoPreview.tsx +478 -0
  477. package/src/library/components/ViewerCounter.tsx +71 -0
  478. package/src/library/compressionEstimate.ts +662 -0
  479. package/src/library/fileMetadata.ts +212 -0
  480. package/src/library/index.ts +66 -0
  481. package/src/library/libraryIconRegistry.tsx +86 -0
  482. package/src/library/libraryIcons.tsx +928 -0
  483. package/src/library/localizedFormat.ts +207 -0
  484. package/src/library/palette.ts +281 -0
  485. package/src/library/responsive.ts +49 -0
  486. package/src/locales.ts +381 -0
  487. package/src/localization.ts +1947 -0
  488. package/src/logger.ts +20 -0
  489. package/src/lowMemoryCache.ts +218 -0
  490. package/src/mediaHistory.ts +42 -0
  491. package/src/mediaStorageService.ts +546 -0
  492. package/src/memory.ts +102 -0
  493. package/src/nativeActions.ts +422 -0
  494. package/src/nativeComponentSupport.ts +60 -0
  495. package/src/networkPerformance.ts +30 -0
  496. package/src/performance.ts +137 -0
  497. package/src/performanceMonitor.ts +219 -0
  498. package/src/permissions.ts +403 -0
  499. package/src/rendering.ts +47 -0
  500. package/src/security.ts +19 -0
  501. package/src/state.ts +333 -0
  502. package/src/storage.ts +453 -0
  503. package/src/theme.tsx +467 -0
  504. package/src/types.ts +946 -0
  505. package/src/utils.ts +177 -0
  506. 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
+ }