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,201 @@
1
+ package com.customcamera
2
+
3
+ import android.media.MediaCodecList
4
+ import android.media.MediaMuxer
5
+ import android.os.Build
6
+ import java.util.Locale
7
+
8
+ /**
9
+ * The containers the preview editor's "Export By" menu can PRODUCE, and whether this particular
10
+ * device can actually produce them.
11
+ *
12
+ * This is the encode-side counterpart of [ImageFormatSupport], which answers the decode-side
13
+ * question ("what is this file, and can we render it?"). The two are deliberately separate: a
14
+ * device can happily *decode* AVIF from API 31 while having no AVIF *encoder* at all, and a
15
+ * format that cannot be written must never appear in the menu. Everything that decides what the
16
+ * dropdown shows reads [encodableImageFormats] / [encodableVideoContainers], so an option is
17
+ * offered only when the export behind it can genuinely be performed.
18
+ *
19
+ * Capability is resolved from the platform codec list rather than from an API-level table alone,
20
+ * because HEIC/AVIF image encoders are hardware features that vary by device at the same API
21
+ * level. Results are memoised: [MediaCodecList] enumeration is not free and the menu can be
22
+ * opened repeatedly.
23
+ */
24
+ object MediaExportFormats {
25
+
26
+ // MIME spellings of the platform's dedicated STILL-image encoders. Written as literals rather
27
+ // than referencing MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC / MIMETYPE_IMAGE_AVIF so this file
28
+ // compiles and runs identically regardless of compileSdk (they are API 28 / API 34 constants).
29
+ const val MIME_IMAGE_HEIC = "image/vnd.android.heic"
30
+ const val MIME_IMAGE_AVIF = "image/avif"
31
+
32
+ /**
33
+ * Still containers offered by "Export By", in menu order.
34
+ *
35
+ * The entries are [ImageFormatSupport.Format] values so the extension, MIME type and display
36
+ * name keep coming from the single container table — an exported `.webp` is described as
37
+ * `image/webp` by the cache, the publisher and the thumbnail loader without any of them
38
+ * learning about exporting.
39
+ *
40
+ * GIF and BMP are deliberately absent: Android ships no encoder for either, so they could
41
+ * never be produced. HEIC is absent because it is the same container as HEIF and offering
42
+ * both would present the user with one choice twice.
43
+ */
44
+ private val IMAGE_FORMAT_ORDER: List<ImageFormatSupport.Format> = listOf(
45
+ ImageFormatSupport.Format.JPEG,
46
+ ImageFormatSupport.Format.PNG,
47
+ ImageFormatSupport.Format.WEBP,
48
+ ImageFormatSupport.Format.HEIF,
49
+ ImageFormatSupport.Format.AVIF,
50
+ ImageFormatSupport.Format.PDF
51
+ )
52
+
53
+ /**
54
+ * A video container this library can write, together with everything the transcode needs:
55
+ * the video codec to encode into, the [MediaMuxer] output format that writes the container,
56
+ * and the audio codecs that container is able to carry.
57
+ *
58
+ * [supportsOrientationHint] is why a portrait clip survives the conversion. MP4 and 3GP store
59
+ * orientation as container metadata, so the frames are re-encoded in their coded orientation
60
+ * and the source's rotation is re-stamped on the copy (exactly what compression does — see
61
+ * [VideoRotationTranscoder.compress]). WebM has no such field, so for it the rotation is baked
62
+ * into the pixels instead; either way the exported clip DISPLAYS at the original's dimensions,
63
+ * aspect ratio and orientation.
64
+ *
65
+ * [audioMimeTypes] is the set the container's muxer accepts. A source track outside it cannot
66
+ * be carried through — WebM takes only Vorbis/Opus, so an AAC track is dropped rather than
67
+ * making the whole export fail. Callers are told so they can say it out loud.
68
+ */
69
+ enum class VideoContainer(
70
+ val extension: String,
71
+ val mimeType: String,
72
+ val videoMimeType: String,
73
+ val muxerOutputFormat: Int,
74
+ val minSdk: Int,
75
+ val supportsOrientationHint: Boolean,
76
+ val audioMimeTypes: Set<String>,
77
+ val alternateExtensions: Set<String> = emptySet()
78
+ ) {
79
+ MP4(
80
+ extension = "mp4",
81
+ mimeType = "video/mp4",
82
+ videoMimeType = "video/avc",
83
+ muxerOutputFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
84
+ minSdk = Build.VERSION_CODES.BASE,
85
+ supportsOrientationHint = true,
86
+ audioMimeTypes = setOf("audio/mp4a-latm", "audio/3gpp", "audio/amr-wb"),
87
+ alternateExtensions = setOf("m4v")
88
+ ),
89
+ WEBM(
90
+ extension = "webm",
91
+ mimeType = "video/webm",
92
+ videoMimeType = "video/x-vnd.on2.vp8",
93
+ muxerOutputFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM,
94
+ minSdk = Build.VERSION_CODES.BASE,
95
+ // The WebM writer has no rotation field, so orientation is baked into the pixels instead.
96
+ supportsOrientationHint = false,
97
+ audioMimeTypes = setOf("audio/vorbis", "audio/opus")
98
+ ),
99
+ THREE_GPP(
100
+ extension = "3gp",
101
+ mimeType = "video/3gpp",
102
+ videoMimeType = "video/avc",
103
+ // MUXER_OUTPUT_3GPP is API 26; the enum is filtered by minSdk before it is ever used.
104
+ muxerOutputFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP,
105
+ minSdk = Build.VERSION_CODES.O,
106
+ supportsOrientationHint = true,
107
+ audioMimeTypes = setOf("audio/mp4a-latm", "audio/3gpp", "audio/amr-wb"),
108
+ alternateExtensions = setOf("3gpp")
109
+ );
110
+
111
+ /** Every file extension that names this container, without the leading dot. */
112
+ val extensions: Set<String> get() = alternateExtensions + extension
113
+
114
+ /** The extension with its leading dot, for building file names. */
115
+ val dottedExtension: String get() = ".$extension"
116
+
117
+ /** Whether [audioMimeType] can be muxed into this container as-is (no audio re-encode). */
118
+ fun carriesAudio(audioMimeType: String?): Boolean =
119
+ audioMimeType != null && audioMimeTypes.contains(audioMimeType.lowercase(Locale.US))
120
+ }
121
+
122
+ // Encoder availability is a device property, so it is answered once and remembered.
123
+ private val encoderCache = HashMap<String, Boolean>()
124
+
125
+ /**
126
+ * Whether this device advertises an ENCODER for [mimeType]. Enumerates the regular codec list
127
+ * rather than calling `findEncoderForFormat`, which additionally matches resolution/frame-rate
128
+ * constraints — the question here is only "does this codec exist at all", and a format built
129
+ * for the probe would answer a different, narrower one.
130
+ */
131
+ @Synchronized
132
+ fun hasEncoder(mimeType: String): Boolean = encoderCache.getOrPut(mimeType) {
133
+ runCatching {
134
+ MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.any { info ->
135
+ info.isEncoder && info.supportedTypes.any { it.equals(mimeType, ignoreCase = true) }
136
+ }
137
+ }.getOrDefault(false)
138
+ }
139
+
140
+ /**
141
+ * Whether [format] can be WRITTEN on this device.
142
+ *
143
+ * JPEG, PNG and WebP go through `Bitmap.compress`, which every supported API level implements.
144
+ * HEIF and AVIF need a dedicated still-image encoder plus the HEIF muxer (API 28), so they are
145
+ * offered only where both exist — which is what keeps the menu honest instead of listing an
146
+ * option that always fails.
147
+ */
148
+ fun canEncodeImage(format: ImageFormatSupport.Format): Boolean = when (format) {
149
+ ImageFormatSupport.Format.JPEG,
150
+ ImageFormatSupport.Format.PNG,
151
+ ImageFormatSupport.Format.WEBP,
152
+ ImageFormatSupport.Format.PDF -> true
153
+ ImageFormatSupport.Format.HEIF ->
154
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && hasEncoder(MIME_IMAGE_HEIC)
155
+ ImageFormatSupport.Format.AVIF ->
156
+ // MediaFormat.MIMETYPE_IMAGE_AVIF exists from API 34; below that no encoder can be
157
+ // addressed even if the hardware has one.
158
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && hasEncoder(MIME_IMAGE_AVIF)
159
+ else -> false
160
+ }
161
+
162
+ /** The encoder MIME type used to write [format], or null when it is not encodable here. */
163
+ fun stillEncoderMimeType(format: ImageFormatSupport.Format): String? = when (format) {
164
+ ImageFormatSupport.Format.HEIF -> MIME_IMAGE_HEIC
165
+ ImageFormatSupport.Format.AVIF -> MIME_IMAGE_AVIF
166
+ else -> null
167
+ }
168
+
169
+ /** Still containers this device can export to, in menu order. */
170
+ fun encodableImageFormats(): List<ImageFormatSupport.Format> =
171
+ IMAGE_FORMAT_ORDER.filter { canEncodeImage(it) }
172
+
173
+ /** Whether [container] can be written on this device (API level + a usable video encoder). */
174
+ fun canEncodeVideo(container: VideoContainer): Boolean =
175
+ Build.VERSION.SDK_INT >= container.minSdk && hasEncoder(container.videoMimeType)
176
+
177
+ /** Video containers this device can export to, in menu order. */
178
+ fun encodableVideoContainers(): List<VideoContainer> =
179
+ VideoContainer.entries.filter { canEncodeVideo(it) }
180
+
181
+ private val VIDEO_BY_EXTENSION: Map<String, VideoContainer> =
182
+ VideoContainer.entries.flatMap { container ->
183
+ container.extensions.map { it to container }
184
+ }.toMap()
185
+
186
+ /**
187
+ * The video container the extension of [name] (a file name, path or URI) denotes, or null.
188
+ *
189
+ * Used by the cache and the publisher to describe an exported clip by its real container
190
+ * instead of assuming `video/mp4` for every video — the same class of "the name no longer
191
+ * describes the bytes" bug [ImageFormatSupport] prevents on the stills side.
192
+ */
193
+ fun videoContainerFromFileName(name: String?): VideoContainer? {
194
+ if (name.isNullOrEmpty()) return null
195
+ val path = name.substringBefore('?').substringBefore('#')
196
+ val segment = path.substringAfterLast('/', path)
197
+ val extension = segment.substringAfterLast('.', "").lowercase(Locale.US)
198
+ if (extension.isEmpty()) return null
199
+ return VIDEO_BY_EXTENSION[extension]
200
+ }
201
+ }
@@ -0,0 +1,310 @@
1
+ package com.customcamera
2
+
3
+ import android.os.StatFs
4
+ import android.util.Log
5
+ import java.io.File
6
+ import java.io.FileInputStream
7
+ import java.io.FileOutputStream
8
+ import java.io.InputStream
9
+ import java.io.RandomAccessFile
10
+ import java.security.MessageDigest
11
+
12
+ /**
13
+ * File plumbing shared by every stage that handles media which can be ARBITRARILY LARGE.
14
+ *
15
+ * A still photo is a couple of megabytes, so the historic "copy it with an 8 KiB buffer and
16
+ * SHA-256 the whole thing" approach was invisible. A recording is not: a few minutes of HD is
17
+ * hundreds of megabytes and a long take can pass a gigabyte. At that size the same code turns
18
+ * one capture into several full passes over the file — the recording→preview handoff sits on a
19
+ * frozen viewfinder for tens of seconds, the send path stalls, and each redundant copy needs
20
+ * another file's worth of free space, which is how a long recording ends in a write failure or
21
+ * an out-of-space crash rather than a video.
22
+ *
23
+ * Everything here exists to make the per-byte cost of a large clip as close to zero as the
24
+ * platform allows:
25
+ *
26
+ * - [relocate] renames when it can, so moving a 1 GB clip inside one volume costs no I/O at all
27
+ * instead of 1 GB read + 1 GB written + 1 GB of extra space held.
28
+ * - [copy] / [copyStream] move bytes in 1 MiB units through the kernel (`transferTo`) instead
29
+ * of 8 KiB user-space hops, and NEVER leave a truncated destination behind on failure.
30
+ * - [contentSignature] keeps duplicate detection O(1) in file size past a threshold, so hashing
31
+ * stops being a multi-second, whole-file read on the capture and send paths.
32
+ * - [usableSpaceBytes] lets callers refuse to start work that cannot possibly fit.
33
+ */
34
+ internal object MediaFileIo {
35
+ private const val TAG = "MediaFileIo"
36
+
37
+ /**
38
+ * 1 MiB. Copying 1 GB with the old 8 KiB buffer is ~131,000 read/write syscall pairs; at 1 MiB
39
+ * it is ~1,000. The buffer itself is short-lived and dwarfed by the bitmaps the app already
40
+ * allocates, so there is no memory trade-off worth making here.
41
+ */
42
+ const val COPY_BUFFER_BYTES: Int = 1 shl 20
43
+
44
+ /**
45
+ * Files at or below this size are hashed in FULL, exactly as before — every photo, every
46
+ * document, every short clip. Duplicate detection for them is unchanged.
47
+ *
48
+ * Above it, hashing the whole file is the single most expensive thing in the capture pipeline
49
+ * and it is pure overhead: the signature is only ever used to notice that the same bytes are
50
+ * being stored twice, and [contentSignature]'s bounded form answers that question just as well
51
+ * for a video. 16 MiB reads in well under a frame's worth of time on any device this library
52
+ * supports.
53
+ */
54
+ private const val FULL_HASH_MAX_BYTES: Long = 16L * 1024L * 1024L
55
+
56
+ /** Bytes sampled at each of the three probe points of a large file's signature. */
57
+ private const val SIGNATURE_WINDOW_BYTES: Int = 2 * 1024 * 1024
58
+
59
+ /** Head-room kept free by default when a relocation has to write a second copy. */
60
+ const val DEFAULT_SPACE_MARGIN_BYTES: Long = 32L * 1024L * 1024L
61
+
62
+ /** How a [relocate] / [duplicate] request actually resolved. */
63
+ enum class Outcome {
64
+ /** Directory entry moved. No bytes read or written, no free space consumed. */
65
+ RENAMED,
66
+
67
+ /** Bytes were streamed to the destination (and, for [relocate], the source unlinked). */
68
+ COPIED,
69
+
70
+ /** The destination volume cannot hold another copy. The source is untouched. */
71
+ INSUFFICIENT_SPACE,
72
+
73
+ /** The operation failed. The source is untouched and no partial destination remains. */
74
+ FAILED;
75
+
76
+ val succeeded: Boolean get() = this == RENAMED || this == COPIED
77
+ }
78
+
79
+ /**
80
+ * Move [source] to [dest], preferring a rename.
81
+ *
82
+ * A rename is a directory-entry update: it does not read or write a single byte of content and
83
+ * needs NO free space, so relocating a large recording within one volume becomes instant and
84
+ * works even on a nearly full device. Renames only work within one filesystem, so the
85
+ * cross-volume case (internal cache → external app files dir) falls back to [copy] plus
86
+ * unlinking the source — still a genuine move as far as the caller and the disk are concerned.
87
+ *
88
+ * The free-space check is deliberately applied ONLY on the copy path, and only after the rename
89
+ * has been tried. Checking up front would refuse a 2 GB clip that a rename could have relocated
90
+ * on a device with 1 GB free — exactly the case this class exists to make work.
91
+ *
92
+ * [source] is never touched unless the relocation completed, so a caller cannot mistake a
93
+ * failure for a success and lose the only copy.
94
+ */
95
+ fun relocate(
96
+ source: File,
97
+ dest: File,
98
+ spaceMarginBytes: Long = DEFAULT_SPACE_MARGIN_BYTES
99
+ ): Outcome {
100
+ if (!source.exists()) return Outcome.FAILED
101
+ if (source.absolutePath == dest.absolutePath) return Outcome.RENAMED
102
+
103
+ dest.parentFile?.mkdirs()
104
+ // A stale destination makes renameTo fail on some filesystems, and nothing here appends to
105
+ // one, so clear it first.
106
+ if (dest.exists()) runCatching { dest.delete() }
107
+
108
+ if (runCatching { source.renameTo(dest) }.getOrDefault(false)) return Outcome.RENAMED
109
+
110
+ val required = source.length() + spaceMarginBytes
111
+ val parent = dest.parentFile
112
+ if (parent != null && !hasRoomFor(parent, required)) {
113
+ Log.w(TAG, "No room to copy ${source.name} (${source.length()} bytes) to ${parent.path}")
114
+ return Outcome.INSUFFICIENT_SPACE
115
+ }
116
+
117
+ if (!copy(source, dest)) return Outcome.FAILED
118
+ // The bytes are safely at the destination; failing to unlink the source only wastes cache
119
+ // space (the orphan sweep collects it later), so it is not part of the success contract.
120
+ runCatching { source.delete() }
121
+ return Outcome.COPIED
122
+ }
123
+
124
+ /**
125
+ * Copy [source] to [dest], keeping the source. Unlike [relocate] this ALWAYS writes a second
126
+ * copy, so the free-space check is unconditional.
127
+ */
128
+ fun duplicate(
129
+ source: File,
130
+ dest: File,
131
+ spaceMarginBytes: Long = DEFAULT_SPACE_MARGIN_BYTES
132
+ ): Outcome {
133
+ if (!source.exists()) return Outcome.FAILED
134
+
135
+ dest.parentFile?.mkdirs()
136
+ val parent = dest.parentFile
137
+ if (parent != null && !hasRoomFor(parent, source.length() + spaceMarginBytes)) {
138
+ Log.w(TAG, "No room to copy ${source.name} (${source.length()} bytes) to ${parent.path}")
139
+ return Outcome.INSUFFICIENT_SPACE
140
+ }
141
+ return if (copy(source, dest)) Outcome.COPIED else Outcome.FAILED
142
+ }
143
+
144
+ /**
145
+ * Copy [source] to [dest] via the kernel, returning false on failure.
146
+ *
147
+ * `FileChannel.transferTo` hands the copy to the kernel, which moves the data without bouncing
148
+ * it through a user-space buffer at all. It is allowed to transfer less than requested, so the
149
+ * loop continues until the whole file has moved, and it can legitimately refuse to progress on
150
+ * some filesystems — that case finishes the job with an ordinary buffered read/write loop
151
+ * rather than abandoning a half-written file.
152
+ *
153
+ * On ANY failure the partial destination is deleted. That matters more than the speed: the old
154
+ * code left whatever bytes it had managed to write in place, so a copy interrupted by a full
155
+ * disk produced a truncated video file that every later stage (metadata read, thumbnail decode,
156
+ * playback, upload) treated as real and failed on.
157
+ */
158
+ fun copy(source: File, dest: File): Boolean {
159
+ dest.parentFile?.mkdirs()
160
+ return try {
161
+ FileInputStream(source).use { input ->
162
+ FileOutputStream(dest).use { output ->
163
+ val inChannel = input.channel
164
+ val outChannel = output.channel
165
+ val size = inChannel.size()
166
+ var position = 0L
167
+ var stalled = false
168
+ while (position < size) {
169
+ val transferred = inChannel.transferTo(position, size - position, outChannel)
170
+ if (transferred <= 0L) {
171
+ stalled = true
172
+ break
173
+ }
174
+ position += transferred
175
+ }
176
+ if (stalled) {
177
+ // FileInputStream/FileOutputStream share their position with their channel, so
178
+ // resuming the streams from `position` continues exactly where transferTo stopped.
179
+ inChannel.position(position)
180
+ outChannel.position(position)
181
+ val buffer = ByteArray(COPY_BUFFER_BYTES)
182
+ while (true) {
183
+ val read = input.read(buffer)
184
+ if (read < 0) break
185
+ output.write(buffer, 0, read)
186
+ }
187
+ }
188
+ }
189
+ }
190
+ true
191
+ } catch (t: Throwable) {
192
+ Log.e(TAG, "Copy failed: ${source.absolutePath} -> ${dest.absolutePath}", t)
193
+ runCatching { dest.delete() }
194
+ false
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Stream [input] into [dest] with the large buffer, deleting a partial [dest] and rethrowing if
200
+ * the write fails.
201
+ *
202
+ * Rethrowing is deliberate: callers distinguish "the media could not be stored" from "the media
203
+ * was stored" by whether this threw, and a truncated file left on disk would make an
204
+ * out-of-space failure look like a successful capture.
205
+ */
206
+ fun copyStream(input: InputStream, dest: File): Long {
207
+ dest.parentFile?.mkdirs()
208
+ return try {
209
+ FileOutputStream(dest).use { output -> input.copyTo(output, COPY_BUFFER_BYTES) }
210
+ } catch (t: Throwable) {
211
+ runCatching { dest.delete() }
212
+ throw t
213
+ }
214
+ }
215
+
216
+ /**
217
+ * A stable content signature used for duplicate detection.
218
+ *
219
+ * Small files (≤ [FULL_HASH_MAX_BYTES]) are hashed in full — identical semantics to the plain
220
+ * SHA-256 this replaces. Larger files are described by their exact length plus three 2 MiB
221
+ * windows (head, middle, tail), which is bounded work regardless of whether the clip is 30 MB
222
+ * or 3 GB.
223
+ *
224
+ * This is a de-duplication key, not a security primitive: two DIFFERENT videos that share an
225
+ * identical length AND identical head, middle and tail windows would collide, which cannot
226
+ * happen for independently encoded clips. What it buys is that recording a long video no longer
227
+ * spends multiple seconds (and a full extra read pass, twice — once when caching and once when
228
+ * publishing) computing a hash that is only ever compared for equality.
229
+ *
230
+ * Returns "" when the file cannot be read, matching the previous behaviour: callers treat an
231
+ * empty signature as "unknown", which simply skips duplicate detection.
232
+ */
233
+ fun contentSignature(file: File): String {
234
+ return try {
235
+ val length = file.length()
236
+ val digest = MessageDigest.getInstance("SHA-256")
237
+
238
+ if (length <= FULL_HASH_MAX_BYTES) {
239
+ FileInputStream(file).use { input ->
240
+ val buffer = ByteArray(COPY_BUFFER_BYTES)
241
+ while (true) {
242
+ val read = input.read(buffer)
243
+ if (read < 0) break
244
+ digest.update(buffer, 0, read)
245
+ }
246
+ }
247
+ } else {
248
+ // Fold the length in first so two clips that happen to share a sampled window are still
249
+ // separated by their size, and so the sampled form can never collide with the full-hash
250
+ // form of a different file.
251
+ digest.update(length.toString().toByteArray(Charsets.US_ASCII))
252
+ RandomAccessFile(file, "r").use { raf ->
253
+ val window = ByteArray(SIGNATURE_WINDOW_BYTES)
254
+ val offsets = longArrayOf(
255
+ 0L,
256
+ (length / 2) - (SIGNATURE_WINDOW_BYTES / 2),
257
+ length - SIGNATURE_WINDOW_BYTES
258
+ )
259
+ for (offset in offsets) {
260
+ raf.seek(offset.coerceAtLeast(0L))
261
+ raf.readFully(window)
262
+ digest.update(window)
263
+ }
264
+ }
265
+ }
266
+
267
+ digest.digest().joinToString("") { "%02x".format(it) }
268
+ } catch (t: Throwable) {
269
+ Log.w(TAG, "Failed to compute content signature for ${file.name}", t)
270
+ ""
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Bytes still writable on the volume that holds [dir].
276
+ *
277
+ * Returns [Long.MAX_VALUE] when the platform will not answer, so an unknown figure never blocks
278
+ * a capture the device could actually have completed.
279
+ */
280
+ fun usableSpaceBytes(dir: File): Long {
281
+ runCatching {
282
+ val stat = StatFs(dir.absolutePath)
283
+ val available = stat.availableBlocksLong * stat.blockSizeLong
284
+ if (available > 0L) return available
285
+ }
286
+ return runCatching { dir.usableSpace }.getOrNull()?.takeIf { it > 0L } ?: Long.MAX_VALUE
287
+ }
288
+
289
+ /** True when [dir]'s volume has room for [requiredBytes] plus a small working margin. */
290
+ fun hasRoomFor(dir: File, requiredBytes: Long): Boolean =
291
+ usableSpaceBytes(dir) >= requiredBytes
292
+
293
+ /**
294
+ * Format [bytes] for a figure that is REDRAWN CONTINUOUSLY — the live size of a recording in
295
+ * flight, updated once a second.
296
+ *
297
+ * Fixed at megabytes on purpose. An adaptive formatter (B → KB → MB) is the right call for a
298
+ * static label, but a ticking one would change unit under the user in the first seconds of every
299
+ * recording (`820 KB` → `1.0 MB`), and each unit change resizes the centred pill it sits in. One
300
+ * unit, one decimal, all the way up to a gigabyte.
301
+ */
302
+ fun formatMegabytes(bytes: Long): String {
303
+ val mb = bytes.coerceAtLeast(0L) / (1024.0 * 1024.0)
304
+ return if (mb >= 1024.0) {
305
+ Localization.formatUnit("fileSizeGb", "{0} GB", "%.2f", mb / 1024.0)
306
+ } else {
307
+ Localization.formatUnit("fileSizeMb", "{0} MB", "%.1f", mb)
308
+ }
309
+ }
310
+ }