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