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,782 @@
1
+ package com.customcamera
2
+
3
+ import android.content.Context
4
+ import android.graphics.BitmapFactory
5
+ import android.media.MediaMetadataRetriever
6
+ import android.net.Uri
7
+ import android.os.Build
8
+ import android.provider.MediaStore
9
+ import androidx.core.content.FileProvider
10
+ import android.util.Log
11
+ import java.io.File
12
+ import java.io.IOException
13
+ import java.text.SimpleDateFormat
14
+ import java.util.Date
15
+ import java.util.Locale
16
+ import java.util.TimeZone
17
+ import java.util.concurrent.ConcurrentHashMap
18
+
19
+ /**
20
+ * MediaCacheManager handles all media caching operations.
21
+ *
22
+ * Key features:
23
+ * - Stores captured/selected media in app cache (not gallery)
24
+ * - Maintains metadata for each cached item
25
+ * - Supports overwriting edited versions (no version history)
26
+ * - Provides cache cleanup and management
27
+ * - Thread-safe operations
28
+ */
29
+ class MediaCacheManager private constructor(private val context: Context) {
30
+
31
+ companion object {
32
+ private const val TAG = "MediaCacheManager"
33
+ private const val CACHE_DIR_NAME = "camera_media_cache"
34
+ private const val METADATA_FILE_NAME = "cache_metadata.json"
35
+ private const val MAX_CACHE_SIZE_MB = 500L // Maximum cache size in MB
36
+
37
+ @Volatile
38
+ private var instance: MediaCacheManager? = null
39
+
40
+ fun getInstance(context: Context): MediaCacheManager {
41
+ return instance ?: synchronized(this) {
42
+ instance ?: MediaCacheManager(context.applicationContext).also { instance = it }
43
+ }
44
+ }
45
+ }
46
+
47
+ // Cache directory for storing media files
48
+ private val cacheDir: File by lazy {
49
+ File(context.cacheDir, CACHE_DIR_NAME).apply {
50
+ if (!exists()) {
51
+ mkdirs()
52
+ }
53
+ }
54
+ }
55
+
56
+ // Metadata file for tracking cached items
57
+ private val metadataFile: File by lazy {
58
+ File(cacheDir, METADATA_FILE_NAME)
59
+ }
60
+
61
+ // In-memory cache of metadata for fast access
62
+ private val metadataCache = ConcurrentHashMap<String, MediaCacheMetadata>()
63
+
64
+ // Date format for generating unique identifiers
65
+ private val timestampFormat = SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US).apply {
66
+ timeZone = TimeZone.getTimeZone("UTC")
67
+ }
68
+
69
+ /**
70
+ * Metadata for a cached media item
71
+ */
72
+ data class MediaCacheMetadata(
73
+ val id: String,
74
+ val fileName: String,
75
+ val cachePath: String,
76
+ val fileType: String, // "photo" or "video"
77
+ val fileSize: Long,
78
+ val width: Int?,
79
+ val height: Int?,
80
+ val duration: Long?, // For videos in milliseconds
81
+ val mimeType: String?,
82
+ val createdAt: Long,
83
+ val lastModified: Long,
84
+ val originalUri: String? = null // Original source URI if from gallery
85
+ )
86
+
87
+ init {
88
+ loadMetadata()
89
+ }
90
+
91
+ /**
92
+ * Load metadata from file into memory
93
+ */
94
+ private fun loadMetadata() {
95
+ try {
96
+ if (!metadataFile.exists()) {
97
+ return
98
+ }
99
+
100
+ val jsonContent = metadataFile.readText()
101
+ if (jsonContent.isBlank()) {
102
+ return
103
+ }
104
+
105
+ // Parse JSON and populate metadata cache
106
+ // For simplicity, we'll use a basic JSON parser
107
+ val lines = jsonContent.split("\n")
108
+ for (line in lines) {
109
+ if (line.isNotBlank()) {
110
+ try {
111
+ val metadata = parseMetadataLine(line)
112
+ metadataCache[metadata.id] = metadata
113
+ } catch (e: Exception) {
114
+ Log.w(TAG, "Failed to parse metadata line: $line", e)
115
+ }
116
+ }
117
+ }
118
+
119
+ Log.d(TAG, "Loaded ${metadataCache.size} metadata entries")
120
+ } catch (e: Exception) {
121
+ Log.e(TAG, "Failed to load metadata", e)
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Save metadata to file
127
+ */
128
+ private fun saveMetadata() {
129
+ try {
130
+ val jsonContent = StringBuilder()
131
+ for (metadata in metadataCache.values) {
132
+ jsonContent.append(metadataToJsonLine(metadata)).append("\n")
133
+ }
134
+ metadataFile.writeText(jsonContent.toString())
135
+ } catch (e: Exception) {
136
+ Log.e(TAG, "Failed to save metadata", e)
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Parse a metadata line from JSON format
142
+ */
143
+ private fun parseMetadataLine(line: String): MediaCacheMetadata {
144
+ // Simple JSON parsing for our specific format
145
+ val values = mutableMapOf<String, String>()
146
+
147
+ // Extract key-value pairs from JSON-like format
148
+ val pattern = "\"([^\"]+)\":\"?([^\"]+)\"?".toRegex()
149
+ pattern.findAll(line).forEach { match ->
150
+ val key = match.groupValues[1]
151
+ val value = match.groupValues[2].trimEnd(',')
152
+ values[key] = value
153
+ }
154
+
155
+ return MediaCacheMetadata(
156
+ id = values["id"] ?: "",
157
+ fileName = values["fileName"] ?: "",
158
+ cachePath = values["cachePath"] ?: "",
159
+ fileType = values["fileType"] ?: "photo",
160
+ fileSize = values["fileSize"]?.toLong() ?: 0L,
161
+ width = values["width"]?.toIntOrNull(),
162
+ height = values["height"]?.toIntOrNull(),
163
+ duration = values["duration"]?.toLongOrNull(),
164
+ mimeType = values["mimeType"],
165
+ createdAt = values["createdAt"]?.toLong() ?: System.currentTimeMillis(),
166
+ lastModified = values["lastModified"]?.toLong() ?: System.currentTimeMillis(),
167
+ originalUri = values["originalUri"]
168
+ )
169
+ }
170
+
171
+ /**
172
+ * Convert metadata to JSON line format
173
+ */
174
+ private fun metadataToJsonLine(metadata: MediaCacheMetadata): String {
175
+ return """{"id":"${metadata.id}","fileName":"${metadata.fileName}","cachePath":"${metadata.cachePath}","fileType":"${metadata.fileType}","fileSize":${metadata.fileSize},"width":${metadata.width ?: "null"},"height":${metadata.height ?: "null"},"duration":${metadata.duration ?: "null"},"mimeType":"${metadata.mimeType ?: ""}","createdAt":${metadata.createdAt},"lastModified":${metadata.lastModified},"originalUri":"${metadata.originalUri ?: ""}"}"""
176
+ }
177
+
178
+ /**
179
+ * Generate a unique ID based on timestamp
180
+ */
181
+ fun generateUniqueId(): String {
182
+ return "media_${timestampFormat.format(Date())}"
183
+ }
184
+
185
+ /**
186
+ * Generate a unique filename for cached media.
187
+ *
188
+ * [extension] (with the leading dot) overrides the per-type default. Callers that know the real
189
+ * container — anything copied in from the gallery — pass it so the cache file keeps the source's
190
+ * own format instead of being renamed to .jpg. See [MediaFormat.of].
191
+ */
192
+ fun generateFileName(fileType: String, id: String? = null, extension: String? = null): String {
193
+ val uniqueId = id ?: generateUniqueId()
194
+ val ext = extension ?: if (fileType == "video") ".mp4" else ".jpg"
195
+ return "${uniqueId}${ext}"
196
+ }
197
+
198
+ /**
199
+ * The container an incoming file is actually in — extension plus MIME type.
200
+ *
201
+ * Copying media into the cache is a byte-for-byte copy, so the bytes always keep their original
202
+ * format; only the NAME used to be forced to ".jpg" (with a hardcoded "image/jpeg" alongside it)
203
+ * for every non-video. That turned a picked GIF into `media_x.jpg` / image/jpeg the moment it
204
+ * entered the cache, and the lie travelled all the way to the published file and on to JS —
205
+ * consumers then failed to render a "JPEG" whose bytes start with GIF89a. Resolving the format
206
+ * here keeps a GIF a GIF end to end, and does the same for PNG/WebP/HEIC.
207
+ */
208
+ internal data class MediaFormat(val extension: String, val mimeType: String) {
209
+ companion object {
210
+ /**
211
+ * Resolve the format of [sourceUri] for a [fileType] asset. The MIME type the resolver
212
+ * reports wins (it is derived from the provider's own record); a content:// URI that
213
+ * reports nothing falls back to the extension in its path, then to the type default.
214
+ *
215
+ * The container table lives in [ImageFormatSupport] so that every path that names a file —
216
+ * caching here, publishing in [MediaPublisher], saving from the editor — agrees about what
217
+ * a given format is called. That is what keeps a HEIF a `.heif` and an AVIF an `.avif`
218
+ * instead of quietly becoming `.jpg` on the way into the cache.
219
+ */
220
+ fun of(context: Context, sourceUri: Uri?, fileType: String): MediaFormat {
221
+ if (fileType == "video") return MediaFormat(".mp4", "video/mp4")
222
+ if (fileType == "audio") return MediaFormat(".m4a", "audio/mp4")
223
+ if (sourceUri == null) return DEFAULT_IMAGE
224
+
225
+ val declaredMime = runCatching { context.contentResolver.getType(sourceUri) }
226
+ .getOrNull()
227
+ ?.lowercase(Locale.US)
228
+ ImageFormatSupport.fromMimeType(declaredMime)?.let {
229
+ // Preserve the exact MIME spelling the provider reported (image/heic vs image/heif)
230
+ // rather than normalising it — the bytes are copied verbatim, so the record should be
231
+ // verbatim too.
232
+ return MediaFormat(it.dottedExtension, declaredMime ?: it.mimeType)
233
+ }
234
+
235
+ ImageFormatSupport.fromFileName(sourceUri.path)?.let { format ->
236
+ // Keep the extension actually present in the path (.heic stays .heic) and pair it with
237
+ // that container's canonical MIME type.
238
+ val pathExtension = sourceUri.path
239
+ ?.substringAfterLast('/', "")
240
+ ?.substringAfterLast('.', "")
241
+ ?.lowercase(Locale.US)
242
+ ?.takeIf { it.isNotEmpty() }
243
+ return MediaFormat(".${pathExtension ?: format.extension}", format.mimeType)
244
+ }
245
+
246
+ return DEFAULT_IMAGE
247
+ }
248
+
249
+ /** Format of an on-disk [file], read from its extension alone. */
250
+ fun ofFile(file: File, fileType: String): MediaFormat {
251
+ if (fileType == "video") {
252
+ // A video is not automatically an MP4: the preview editor's "Export By" menu can
253
+ // produce a .webm or .3gp, and registering those as video/mp4 would put MP4's name on
254
+ // bytes that are not MP4 — the same class of bug the image branch below exists to
255
+ // prevent. Unrecognised extensions keep the historical MP4 default.
256
+ return MediaExportFormats.videoContainerFromFileName(file.name)
257
+ ?.let { MediaFormat(it.dottedExtension, it.mimeType) }
258
+ ?: MediaFormat(".mp4", "video/mp4")
259
+ }
260
+ val ext = file.extension.lowercase(Locale.US)
261
+ val format = ImageFormatSupport.fromFileName(file.name) ?: return DEFAULT_IMAGE
262
+ return MediaFormat(".$ext", format.mimeType)
263
+ }
264
+
265
+ /** Every camera capture is a JPEG, so that is the fallback for anything unrecognised. */
266
+ private val DEFAULT_IMAGE = MediaFormat(".jpg", "image/jpeg")
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Cache a media file from a source URI
272
+ *
273
+ * @param sourceUri The source URI of the media file
274
+ * @param fileType The type of media ("photo" or "video")
275
+ * @param id Optional custom ID (will generate one if not provided)
276
+ * @param allowMove Set only when the source is a throwaway file this app just wrote and will
277
+ * delete straight after caching — a freshly recorded clip or captured still. It lets the
278
+ * source be MOVED into the cache instead of copied; see [ingestUriToFile].
279
+ * @return MediaCacheMetadata containing cache information
280
+ */
281
+ fun cacheMedia(
282
+ sourceUri: Uri,
283
+ fileType: String,
284
+ id: String? = null,
285
+ allowMove: Boolean = false
286
+ ): MediaCacheMetadata {
287
+ val uniqueId = id ?: generateUniqueId()
288
+ // Keep the source's real container (a picked GIF stays a .gif / image/gif) — the copy below
289
+ // is byte-for-byte, so naming it anything else would misdescribe the bytes on disk.
290
+ val format = MediaFormat.of(context, sourceUri, fileType)
291
+ val fileName = generateFileName(fileType, uniqueId, format.extension)
292
+ val cacheFile = File(cacheDir, fileName)
293
+
294
+ // Bring the file into the cache — moved when the caller owns the source, copied otherwise.
295
+ ingestUriToFile(sourceUri, cacheFile, allowMove)
296
+
297
+ // Extract metadata
298
+ val fileSize = cacheFile.length()
299
+ val (width, height, duration) = extractMediaMetadata(cacheFile, fileType)
300
+ val mimeType = format.mimeType
301
+
302
+ // Create metadata
303
+ val metadata = MediaCacheMetadata(
304
+ id = uniqueId,
305
+ fileName = fileName,
306
+ cachePath = cacheFile.absolutePath,
307
+ fileType = fileType,
308
+ fileSize = fileSize,
309
+ width = width,
310
+ height = height,
311
+ duration = duration,
312
+ mimeType = mimeType,
313
+ createdAt = System.currentTimeMillis(),
314
+ lastModified = System.currentTimeMillis(),
315
+ originalUri = sourceUri.toString()
316
+ )
317
+
318
+ // Store metadata
319
+ metadataCache[uniqueId] = metadata
320
+ saveMetadata()
321
+
322
+ Log.d(TAG, "Cached media: $uniqueId -> ${cacheFile.absolutePath}")
323
+ return metadata
324
+ }
325
+
326
+ /**
327
+ * Cache a media file and return CacheMediaResult for the workflow layer.
328
+ */
329
+ fun cacheMediaForStorage(
330
+ uri: Uri,
331
+ fileType: String,
332
+ fileName: String? = null,
333
+ allowMove: Boolean = false
334
+ ): MediaStorageWorkflow.CacheMediaResult {
335
+ return try {
336
+ val metadata = cacheMedia(
337
+ sourceUri = uri,
338
+ fileType = fileType,
339
+ id = null,
340
+ allowMove = allowMove
341
+ )
342
+ MediaStorageWorkflow.CacheMediaResult(
343
+ isSuccess = true,
344
+ mediaId = metadata.id,
345
+ fileName = metadata.fileName,
346
+ cachePath = metadata.cachePath,
347
+ fileSize = metadata.fileSize,
348
+ width = metadata.width,
349
+ height = metadata.height,
350
+ duration = metadata.duration,
351
+ mimeType = metadata.mimeType
352
+ )
353
+ } catch (e: Exception) {
354
+ Log.e(TAG, "Failed to cache media", e)
355
+ MediaStorageWorkflow.CacheMediaResult(
356
+ isSuccess = false,
357
+ mediaId = "",
358
+ fileName = fileName ?: "",
359
+ cachePath = "",
360
+ error = e.message
361
+ )
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Cache a media file from a byte array
367
+ *
368
+ * @param data The byte array of the media file
369
+ * @param fileType The type of media ("photo" or "video")
370
+ * @param id Optional custom ID (will generate one if not provided)
371
+ * @return MediaCacheMetadata containing cache information
372
+ */
373
+ fun cacheMediaFromBytes(data: ByteArray, fileType: String, id: String? = null): MediaCacheMetadata {
374
+ val uniqueId = id ?: generateUniqueId()
375
+ // The bytes are written verbatim, so the entry must be named for what they actually are.
376
+ // Unlike cacheMedia there is no source URI to ask the resolver about, so the container is
377
+ // read from the bytes themselves — otherwise a GIF written through this path lands as
378
+ // `media_x.jpg` / image/jpeg and every consumer fails to render it (see MediaFormat).
379
+ val isGif = fileType != "video" && fileType != "audio" && GifSupport.hasGifSignature(data)
380
+ val fileName = generateFileName(fileType, uniqueId, if (isGif) ".gif" else null)
381
+ val cacheFile = File(cacheDir, fileName)
382
+
383
+ // Write bytes to cache file
384
+ cacheFile.writeBytes(data)
385
+
386
+ // Extract metadata
387
+ val fileSize = cacheFile.length()
388
+ val (width, height, duration) = extractMediaMetadata(cacheFile, fileType)
389
+ val mimeType = if (isGif) "image/gif" else getMimeType(fileType)
390
+
391
+ // Create metadata
392
+ val metadata = MediaCacheMetadata(
393
+ id = uniqueId,
394
+ fileName = fileName,
395
+ cachePath = cacheFile.absolutePath,
396
+ fileType = fileType,
397
+ fileSize = fileSize,
398
+ width = width,
399
+ height = height,
400
+ duration = duration,
401
+ mimeType = mimeType,
402
+ createdAt = System.currentTimeMillis(),
403
+ lastModified = System.currentTimeMillis()
404
+ )
405
+
406
+ // Store metadata
407
+ metadataCache[uniqueId] = metadata
408
+ saveMetadata()
409
+
410
+ Log.d(TAG, "Cached media from bytes: $uniqueId -> ${cacheFile.absolutePath}")
411
+ return metadata
412
+ }
413
+
414
+ /**
415
+ * Overwrite an existing cached media file with new data
416
+ * This replaces the existing file without creating a new entry
417
+ *
418
+ * @param id The ID of the media to overwrite
419
+ * @param newData The new byte data
420
+ * @return Updated MediaCacheMetadata
421
+ */
422
+ fun overwriteCachedMedia(id: String, newData: ByteArray): MediaCacheMetadata {
423
+ val existingMetadata = metadataCache[id]
424
+ ?: throw IllegalArgumentException("Media with id $id not found in cache")
425
+
426
+ val cacheFile = File(existingMetadata.cachePath)
427
+
428
+ // Overwrite file with new data
429
+ cacheFile.writeBytes(newData)
430
+
431
+ // Extract new metadata
432
+ val fileSize = cacheFile.length()
433
+ val (width, height, duration) = extractMediaMetadata(cacheFile, existingMetadata.fileType)
434
+
435
+ // Update metadata
436
+ val updatedMetadata = existingMetadata.copy(
437
+ fileSize = fileSize,
438
+ width = width,
439
+ height = height,
440
+ duration = duration,
441
+ lastModified = System.currentTimeMillis()
442
+ )
443
+
444
+ // Store updated metadata
445
+ metadataCache[id] = updatedMetadata
446
+ saveMetadata()
447
+
448
+ Log.d(TAG, "Overwrote cached media: $id")
449
+ return updatedMetadata
450
+ }
451
+
452
+ /**
453
+ * Overwrite an existing cached media file from a source URI
454
+ *
455
+ * @param id The ID of the media to overwrite
456
+ * @param sourceUri The source URI of the new media data
457
+ * @return Updated MediaCacheMetadata
458
+ */
459
+ fun overwriteCachedMediaFromUri(id: String, sourceUri: Uri): MediaCacheMetadata {
460
+ val existingMetadata = metadataCache[id]
461
+ ?: throw IllegalArgumentException("Media with id $id not found in cache")
462
+
463
+ var cacheFile = File(existingMetadata.cachePath)
464
+
465
+ // Copy file to cache, overwriting existing. Never a move: the source of an overwrite is an
466
+ // edit result whose lifetime belongs to the editor, not to us.
467
+ ingestUriToFile(sourceUri, cacheFile, allowMove = false)
468
+
469
+ // The new bytes may be in a DIFFERENT container than the entry they replace: baking edits
470
+ // into a GIF (or PNG/WebP) composites it to a flat JPEG, and leaving the entry named .gif
471
+ // would put JPEG bytes behind a GIF name and MIME type — the same lie, just in the other
472
+ // direction. Re-type the entry to match what was actually written. Same-format overwrites
473
+ // (trim/mute writing .mp4 over .mp4) resolve to the identical name and skip the rename.
474
+ val incoming = MediaFormat.of(context, sourceUri, existingMetadata.fileType)
475
+ var fileName = existingMetadata.fileName
476
+ var mimeType = existingMetadata.mimeType
477
+ if (!fileName.endsWith(incoming.extension, ignoreCase = true)) {
478
+ val retypedName = "${File(fileName).nameWithoutExtension}${incoming.extension}"
479
+ val retypedFile = File(cacheDir, retypedName)
480
+ if (runCatching { cacheFile.renameTo(retypedFile) }.getOrDefault(false)) {
481
+ cacheFile = retypedFile
482
+ fileName = retypedName
483
+ mimeType = incoming.mimeType
484
+ Log.d(TAG, "Re-typed cached media $id to $retypedName after format change")
485
+ }
486
+ }
487
+
488
+ // Extract new metadata
489
+ val fileSize = cacheFile.length()
490
+ val (width, height, duration) = extractMediaMetadata(cacheFile, existingMetadata.fileType)
491
+
492
+ // Update metadata
493
+ val updatedMetadata = existingMetadata.copy(
494
+ fileName = fileName,
495
+ cachePath = cacheFile.absolutePath,
496
+ mimeType = mimeType,
497
+ fileSize = fileSize,
498
+ width = width,
499
+ height = height,
500
+ duration = duration,
501
+ lastModified = System.currentTimeMillis()
502
+ )
503
+
504
+ // Store updated metadata
505
+ metadataCache[id] = updatedMetadata
506
+ saveMetadata()
507
+
508
+ Log.d(TAG, "Overwrote cached media from URI: $id")
509
+ return updatedMetadata
510
+ }
511
+
512
+ /**
513
+ * Get metadata for a cached media item
514
+ */
515
+ fun getMetadata(id: String): MediaCacheMetadata? {
516
+ return metadataCache[id]
517
+ }
518
+
519
+ /**
520
+ * Get all cached media metadata
521
+ */
522
+ fun getAllMetadata(): List<MediaCacheMetadata> {
523
+ return metadataCache.values.sortedByDescending { it.createdAt }
524
+ }
525
+
526
+ /**
527
+ * Get a cached media file
528
+ */
529
+ fun getCachedFile(id: String): File? {
530
+ val metadata = metadataCache[id] ?: return null
531
+ val file = File(metadata.cachePath)
532
+ return if (file.exists()) file else null
533
+ }
534
+
535
+ /**
536
+ * Get URI for a cached media file
537
+ */
538
+ fun getCachedUri(id: String): Uri? {
539
+ val file = getCachedFile(id) ?: return null
540
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
541
+ try {
542
+ FileProvider.getUriForFile(
543
+ context,
544
+ "${context.packageName}.camera.fileprovider",
545
+ file
546
+ )
547
+ } catch (e: Exception) {
548
+ Uri.fromFile(file)
549
+ }
550
+ } else {
551
+ Uri.fromFile(file)
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Delete a cached media item
557
+ */
558
+ fun deleteCachedMedia(id: String): Boolean {
559
+ val metadata = metadataCache[id] ?: return false
560
+ val file = File(metadata.cachePath)
561
+
562
+ val deleted = if (file.exists()) {
563
+ file.delete()
564
+ } else {
565
+ true // File doesn't exist, consider it deleted
566
+ }
567
+
568
+ if (deleted) {
569
+ metadataCache.remove(id)
570
+ saveMetadata()
571
+ Log.d(TAG, "Deleted cached media: $id")
572
+ }
573
+
574
+ return deleted
575
+ }
576
+
577
+ /**
578
+ * Clear all cached media
579
+ */
580
+ fun clearCache(): Boolean {
581
+ var success = true
582
+
583
+ // Delete all files in cache directory
584
+ cacheDir.listFiles()?.forEach { file ->
585
+ if (file.name != METADATA_FILE_NAME) {
586
+ if (!file.delete()) {
587
+ success = false
588
+ Log.w(TAG, "Failed to delete cache file: ${file.name}")
589
+ }
590
+ }
591
+ }
592
+
593
+ // Clear metadata
594
+ metadataCache.clear()
595
+ saveMetadata()
596
+
597
+ // Reclaim the persistent compression cache too — its master outputs live in a sibling cache
598
+ // dir (cache/compression_cache/) and its index is a separate encrypted DB, neither of which
599
+ // this loop above touches. Best-effort: a failure here never fails the media-cache clear.
600
+ runCatching { CompressionCacheStore.clearAll(context) }
601
+
602
+ Log.d(TAG, "Cleared cache, success: $success")
603
+ return success
604
+ }
605
+
606
+ /**
607
+ * Get total cache size in bytes
608
+ */
609
+ fun getCacheSize(): Long {
610
+ return metadataCache.values.sumOf { it.fileSize }
611
+ }
612
+
613
+ /**
614
+ * Get cache size in MB
615
+ */
616
+ fun getCacheSizeMB(): Double {
617
+ return getCacheSize() / (1024.0 * 1024.0)
618
+ }
619
+
620
+ /**
621
+ * Clean up old cache entries if cache size exceeds limit
622
+ * Removes oldest entries first
623
+ */
624
+ fun cleanupIfNeeded() {
625
+ val currentSizeMB = getCacheSizeMB()
626
+
627
+ if (currentSizeMB > MAX_CACHE_SIZE_MB) {
628
+ Log.d(TAG, "Cache size ($currentSizeMB MB) exceeds limit ($MAX_CACHE_SIZE_MB MB), cleaning up")
629
+
630
+ // Sort by creation time (oldest first)
631
+ val sortedMetadata = metadataCache.values.sortedBy { it.createdAt }
632
+ var sizeToRemove = currentSizeMB - (MAX_CACHE_SIZE_MB * 0.8) // Remove down to 80% of limit
633
+
634
+ for (metadata in sortedMetadata) {
635
+ if (sizeToRemove <= 0) break
636
+
637
+ val removedSize = metadata.fileSize / (1024.0 * 1024.0)
638
+ if (deleteCachedMedia(metadata.id)) {
639
+ sizeToRemove -= removedSize
640
+ Log.d(TAG, "Removed old cache entry: ${metadata.id} (${removedSize} MB)")
641
+ }
642
+ }
643
+ }
644
+ }
645
+
646
+ /**
647
+ * Bring the content behind [sourceUri] into [destFile].
648
+ *
649
+ * When [allowMove] is set AND the URI resolves to a plain file this app owns, the file is MOVED
650
+ * (a rename inside the cache volume) instead of copied. This is the difference between a large
651
+ * recording landing in the cache instantly and the app spending a full read+write pass over
652
+ * hundreds of megabytes — while also needing a second copy's worth of free space it does not
653
+ * have. Capture callers immediately delete their temp file afterwards, so the move is exactly
654
+ * what they meant; a deleted-by-us source simply makes their delete a no-op.
655
+ *
656
+ * Everything else (gallery picks, documents, anything behind a content provider) is copied, and
657
+ * a failed copy never leaves a truncated file behind — see [MediaFileIo.copyStream].
658
+ */
659
+ private fun ingestUriToFile(sourceUri: Uri, destFile: File, allowMove: Boolean) {
660
+ if (allowMove) {
661
+ sourceFileOrNull(sourceUri)?.let { source ->
662
+ if (MediaFileIo.relocate(source, destFile).succeeded) return
663
+ // Fall through to the stream path: the source is still intact either way, so a refused
664
+ // relocation (no room for a cross-volume copy) still gets one honest attempt that either
665
+ // works or throws a reportable IOException.
666
+ }
667
+ }
668
+ context.contentResolver.openInputStream(sourceUri)?.use { input ->
669
+ MediaFileIo.copyStream(input, destFile)
670
+ } ?: throw IOException("Failed to open input stream for URI: $sourceUri")
671
+ }
672
+
673
+ /**
674
+ * The on-disk file behind [uri], or null when there is not one we may relocate.
675
+ *
676
+ * Only `file://` URIs qualify. A `content://` URI is deliberately excluded even when it points
677
+ * at our own FileProvider: the provider is the only thing that knows how the authority maps to
678
+ * a path, and guessing wrong would move a file some other component still owns.
679
+ */
680
+ private fun sourceFileOrNull(uri: Uri): File? {
681
+ if (uri.scheme != null && uri.scheme != "file") return null
682
+ val path = uri.path?.takeIf { it.isNotBlank() } ?: return null
683
+ return File(path).takeIf { it.isFile }
684
+ }
685
+
686
+ /**
687
+ * Extract media metadata (dimensions, duration) from file
688
+ */
689
+ private fun extractMediaMetadata(file: File, fileType: String): Triple<Int?, Int?, Long?> {
690
+ return if (fileType == "video") {
691
+ extractVideoMetadata(file)
692
+ } else {
693
+ extractImageMetadata(file)
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Extract image metadata
699
+ */
700
+ private fun extractImageMetadata(file: File): Triple<Int?, Int?, Long?> {
701
+ return try {
702
+ val options = BitmapFactory.Options().apply {
703
+ inJustDecodeBounds = true
704
+ }
705
+ BitmapFactory.decodeFile(file.absolutePath, options)
706
+ Triple(options.outWidth, options.outHeight, null)
707
+ } catch (e: Exception) {
708
+ Log.w(TAG, "Failed to extract image metadata", e)
709
+ Triple(null, null, null)
710
+ }
711
+ }
712
+
713
+ /**
714
+ * Extract video metadata
715
+ */
716
+ private fun extractVideoMetadata(file: File): Triple<Int?, Int?, Long?> {
717
+ return try {
718
+ val retriever = MediaMetadataRetriever()
719
+ // release() MUST run even if setDataSource/extractMetadata throws (corrupt or
720
+ // zero-byte cache file, unsupported codec) — otherwise the native retriever handle
721
+ // leaks. Rapidly caching many videos, some malformed, would accumulate leaked native
722
+ // allocations until MediaMetadataRetriever creation fails and the process crashes.
723
+ // Use try/finally, NOT Kotlin .use{} (MediaMetadataRetriever is only AutoCloseable on
724
+ // API 29+, so .use{} crashes on API 24-28).
725
+ try {
726
+ retriever.setDataSource(file.absolutePath)
727
+
728
+ var width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull()
729
+ var height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull()
730
+ val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull()
731
+ val rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)?.toIntOrNull() ?: 0
732
+
733
+ if (rotation == 90 || rotation == 270) {
734
+ val temp = width
735
+ width = height
736
+ height = temp
737
+ }
738
+
739
+ Triple(width, height, duration)
740
+ } finally {
741
+ retriever.release()
742
+ }
743
+ } catch (e: Exception) {
744
+ Log.w(TAG, "Failed to extract video metadata", e)
745
+ Triple(null, null, null)
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Get MIME type for file type
751
+ */
752
+ private fun getMimeType(fileType: String): String {
753
+ return when (fileType) {
754
+ "video" -> "video/mp4"
755
+ else -> "image/jpeg"
756
+ }
757
+ }
758
+
759
+ /**
760
+ * Check if a media item exists in cache
761
+ */
762
+ fun existsInCache(id: String): Boolean {
763
+ return metadataCache.containsKey(id) && getCachedFile(id)?.exists() == true
764
+ }
765
+
766
+ /**
767
+ * Get cache statistics
768
+ */
769
+ fun getCacheStats(): Map<String, Any> {
770
+ val allMetadata = getAllMetadata()
771
+ val photoCount = allMetadata.count { it.fileType == "photo" }
772
+ val videoCount = allMetadata.count { it.fileType == "video" }
773
+
774
+ return mapOf(
775
+ "totalItems" to allMetadata.size,
776
+ "photoCount" to photoCount,
777
+ "videoCount" to videoCount,
778
+ "totalSizeMB" to String.format("%.2f", getCacheSizeMB()),
779
+ "maxSizeMB" to MAX_CACHE_SIZE_MB
780
+ )
781
+ }
782
+ }