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,662 @@
1
+ // ── Compression countdown: measured remaining time, seeded by an estimate ───────────────────
2
+ // The library shell's viewer shows a countdown ("Compressing (5s left)") instead of an
3
+ // indeterminate spinner while the displayed image or video is being compressed. Nothing here
4
+ // touches the compression: these are pure functions the screen calls to decide what the line
5
+ // should read, plus the size-band arithmetic the level list quotes.
6
+ //
7
+ // The number is driven by the compressor's OWN progress. Native reports measured progress on the
8
+ // `onCompressionProgress` device event as it works (VideoRotationTranscoder.compress from the
9
+ // encoder's output timestamps, ImageExporter.compress from the encodes its quality search spends),
10
+ // and each report re-derives how long the whole job will take from how long it has taken to reach
11
+ // that point: `total = elapsed / fraction`. The line then reads `total - elapsed`.
12
+ //
13
+ // That is what makes the figure honest in BOTH directions, which counting a fixed up-front
14
+ // estimate down cannot be:
15
+ // • a job slower than predicted has its total re-derived UPWARD, so the count no longer reaches
16
+ // zero while the encoder is still running;
17
+ // • a job faster than predicted has it re-derived DOWNWARD, so the count is not still promising
18
+ // seconds when the compressed file is already on screen.
19
+ //
20
+ // The line opens by counting UP to the figure — a short, evenly paced ramp that reads as the app
21
+ // working out how long the job will take — and turns around into the countdown the moment the two
22
+ // meet. See the calculation-ramp section further down.
23
+ //
24
+ // The estimate below is still computed up front, but only to seed the first seconds — before
25
+ // there is any measurement to derive a total from — and it is blended out as soon as there is. It
26
+ // is also the whole figure for a source that reports no progress at all (a clip whose container
27
+ // declares no duration has no denominator), which is the one case that still ends in
28
+ // "Finalizing...".
29
+ //
30
+ // Every constant here mirrors the native editor's for the same overlay (MediaPreviewEditor.kt), so
31
+ // both surfaces behave identically for the same media.
32
+
33
+ import type { CameraTranslationFunction } from '../localization';
34
+
35
+ /** MB of SOURCE video demuxed + decoded + re-encoded per second. */
36
+ export const COMPRESSION_SOURCE_MB_PER_SECOND = 1.0;
37
+
38
+ /** Megapixels of decoded frame data processed per second (≈ 2 s per second of 1080p). */
39
+ export const COMPRESSION_MEGAPIXELS_PER_SECOND = 4.0;
40
+
41
+ /**
42
+ * Deprecated per-level discount, kept at 1 (no discount).
43
+ *
44
+ * They used to model the native transcoder downscaling the frame once the quality ratio dropped
45
+ * under 0.6, which made a heavy level a cheaper pass. That downscale is gone — every level now
46
+ * re-encodes at FULL resolution so quality is protected rather than traded away
47
+ * (`MediaPreviewEditor.estimateCompressionSeconds` dropped its matching discount for the same
48
+ * reason) — so the pass costs the same at 5% as at 95% and the factor must not shorten the seed.
49
+ * Left in place, and exported, because they are part of this module's public surface.
50
+ */
51
+ export const COMPRESSION_DOWNSCALE_QUALITY_CEILING = 60;
52
+ export const COMPRESSION_DOWNSCALED_LEVEL_FACTOR = 1;
53
+
54
+ /**
55
+ * Floor: below this there is no countdown worth showing, and it doubles as the allowance for the
56
+ * fixed codec + muxer setup that no amount of video changes.
57
+ */
58
+ export const MIN_COMPRESSION_COUNTDOWN_SECONDS = 2;
59
+
60
+ /** Ceiling: a very long clip shows a bounded number and finishes in "Finalizing..." if need be. */
61
+ export const MAX_COMPRESSION_COUNTDOWN_SECONDS = 180;
62
+
63
+ /**
64
+ * Megapixels of STILL image compressed per second, behind {@link estimateImageCompressionSeconds}.
65
+ *
66
+ * Much lower than a plain "encode one JPEG" rate because a still compression is not one encode:
67
+ * the native compressor decodes once and then re-encodes several times while it searches for the
68
+ * quality that hits the requested size.
69
+ */
70
+ export const IMAGE_COMPRESSION_MEGAPIXELS_PER_SECOND = 3.0;
71
+
72
+ /** Ceiling for a still: even a 100 MP source is bounded well below the video ceiling. */
73
+ export const MAX_IMAGE_COMPRESSION_COUNTDOWN_SECONDS = 30;
74
+
75
+ /** Caption shown once there is no measurable remaining time left to quote. */
76
+ export const COMPRESSION_FINALIZING_LABEL = 'Finalizing...';
77
+
78
+ /** Caption shown when there is no countdown to display (estimate unavailable). */
79
+ export const COMPRESSION_FALLBACK_LABEL = 'Compressing...';
80
+
81
+ /**
82
+ * The quality percentage a dropdown option asks to KEEP (5..99), or 0 for anything that is not a
83
+ * level (the "Original Video" / "Original Image" entries, which never start a compression).
84
+ */
85
+ export const parseCompressionPercent = (option: string): number => {
86
+ const match = /^\s*(\d+(?:\.\d+)?)\s*%\s*$/.exec(option);
87
+ if (!match) return 0;
88
+ const percent = Number(match[1]);
89
+ return Number.isFinite(percent) ? percent : 0;
90
+ };
91
+
92
+ /**
93
+ * Durations reach this screen in milliseconds from native metadata but in seconds from some
94
+ * host-supplied assets, so normalise the same way the media list does: a value large enough to be
95
+ * an implausible number of seconds is milliseconds.
96
+ */
97
+ const durationToSeconds = (duration?: number): number => {
98
+ if (!duration || duration <= 0) return 0;
99
+ return duration > 1000 ? duration / 1000 : duration;
100
+ };
101
+
102
+ export interface VideoCompressionEstimateInput {
103
+ /** Size of the ORIGINAL file in bytes (never a previously compressed copy's). */
104
+ sizeBytes?: number;
105
+ /** Duration of the original, in milliseconds (seconds are accepted and normalised). */
106
+ duration?: number;
107
+ /** The original's own pixel dimensions. */
108
+ width?: number;
109
+ height?: number;
110
+ /** The selected level: the percentage of the original QUALITY to keep (5..99). Lower = stronger. */
111
+ percent: number;
112
+ }
113
+
114
+ /**
115
+ * How long compressing a clip is expected to take, in whole seconds — the value the countdown
116
+ * starts at, and only that: measured progress takes the figure over within the first second or so
117
+ * (see {@link foldCompressionProgress}).
118
+ *
119
+ * Two independent predictors are computed and the LARGER is taken, because either one alone is
120
+ * wrong for a whole class of clip:
121
+ *
122
+ * - **Bytes.** The whole file is demuxed, decoded and re-encoded, so throughput measured in MB of
123
+ * SOURCE per second is the most stable single predictor across devices. On its own it
124
+ * under-predicts a long, low-bitrate clip (lots of frames, few bytes).
125
+ * - **Pixels × frames.** Duration times megapixels per frame is what the codec actually has to
126
+ * work on. On its own it under-predicts a short, very high-bitrate clip (few frames, huge
127
+ * bytes).
128
+ *
129
+ * Every input must come from the ORIGINAL, so picking a second level after a first estimates
130
+ * against the untouched file rather than the shrunken copy. A clip with no readable metadata at
131
+ * all falls back to the floor rather than to zero.
132
+ */
133
+ export const estimateVideoCompressionSeconds = ({
134
+ sizeBytes,
135
+ duration,
136
+ width,
137
+ height,
138
+ percent,
139
+ }: VideoCompressionEstimateInput): number => {
140
+ const megabytes = sizeBytes && sizeBytes > 0 ? sizeBytes / (1024 * 1024) : 0;
141
+ const fromBytes =
142
+ megabytes > 0 ? megabytes / COMPRESSION_SOURCE_MB_PER_SECOND : 0;
143
+
144
+ const durationSeconds = durationToSeconds(duration);
145
+ const megapixelsPerFrame =
146
+ width && height && width > 0 && height > 0
147
+ ? (width * height) / 1_000_000
148
+ : 0;
149
+ const fromPixels =
150
+ durationSeconds > 0 && megapixelsPerFrame > 0
151
+ ? (durationSeconds * megapixelsPerFrame) / COMPRESSION_MEGAPIXELS_PER_SECOND
152
+ : 0;
153
+
154
+ const levelFactor =
155
+ percent < COMPRESSION_DOWNSCALE_QUALITY_CEILING
156
+ ? COMPRESSION_DOWNSCALED_LEVEL_FACTOR
157
+ : 1;
158
+ const estimate = Math.max(fromBytes, fromPixels) * levelFactor;
159
+
160
+ return Math.min(
161
+ MAX_COMPRESSION_COUNTDOWN_SECONDS,
162
+ Math.max(MIN_COMPRESSION_COUNTDOWN_SECONDS, Math.ceil(estimate))
163
+ );
164
+ };
165
+
166
+ export interface ImageCompressionEstimateInput {
167
+ /** The ORIGINAL still's own pixel dimensions. */
168
+ width?: number;
169
+ height?: number;
170
+ }
171
+
172
+ /**
173
+ * How long compressing a still is expected to take, in whole seconds — the seed for its countdown.
174
+ *
175
+ * A single predictor, unlike the video estimate's two: a still has no duration and no bit rate, so
176
+ * the only thing that varies is how many pixels there are to decode and re-encode. The requested
177
+ * level does not enter into it either — the native compressor searches for the quality that hits
178
+ * the target, so a light level costs the same handful of encodes as a heavy one.
179
+ *
180
+ * A still with no readable dimensions falls back to the floor rather than to zero.
181
+ */
182
+ export const estimateImageCompressionSeconds = ({
183
+ width,
184
+ height,
185
+ }: ImageCompressionEstimateInput): number => {
186
+ const megapixels =
187
+ width && height && width > 0 && height > 0
188
+ ? (width * height) / 1_000_000
189
+ : 0;
190
+ return Math.min(
191
+ MAX_IMAGE_COMPRESSION_COUNTDOWN_SECONDS,
192
+ Math.max(
193
+ MIN_COMPRESSION_COUNTDOWN_SECONDS,
194
+ Math.ceil(megapixels / IMAGE_COMPRESSION_MEGAPIXELS_PER_SECOND)
195
+ )
196
+ );
197
+ };
198
+
199
+ /** The countdown's text for a given number of seconds remaining, e.g. `Compressing (5s left)`. */
200
+ export const formatCompressionCountdown = (
201
+ secondsLeft: number,
202
+ t?: CameraTranslationFunction
203
+ ): string => {
204
+ const seconds = Math.max(1, Math.ceil(secondsLeft));
205
+ const translated = t?.('compressingSecondsLeft', seconds);
206
+ return typeof translated === 'string' && translated.length > 0
207
+ ? translated
208
+ : `Compressing (${seconds}s left)`;
209
+ };
210
+
211
+ // ── Measured progress → remaining time ──────────────────────────────────────────────────────
212
+ // The three functions below are the whole countdown: create a tracker when the compression starts,
213
+ // fold each native progress report into it, and read the line off it as often as you like. They
214
+ // are pure — `now` is always passed in — so the screen owns the clock and the ticking, and the
215
+ // model itself is trivially testable.
216
+
217
+ /**
218
+ * Below this fraction, `elapsed / fraction` is dominated by fixed setup cost (codec configuration,
219
+ * the first decode) and would predict a wildly pessimistic total. Under it the prediction is
220
+ * blended with the up-front estimate in proportion to how far along it is, so the number is steady
221
+ * at the start and fully measurement-driven from here on.
222
+ */
223
+ export const MIN_TRUSTED_COMPRESSION_PROGRESS = 0.08;
224
+
225
+ /**
226
+ * At or above this the encoder has finished its frames and the pass is in its tail — flushing,
227
+ * muxing, reading the output back — real work with no measurable length, which the line reports as
228
+ * "Finalizing..." rather than quoting seconds it cannot justify.
229
+ */
230
+ export const COMPRESSION_FINALIZING_PROGRESS = 0.985;
231
+
232
+ /**
233
+ * Weight given to a freshly derived total when smoothing. Low enough that a burst of
234
+ * cheap-to-encode frames cannot make the figure jump, high enough that a genuinely slower job has
235
+ * the number corrected within a few seconds.
236
+ */
237
+ export const COMPRESSION_PREDICTION_SMOOTHING = 0.4;
238
+
239
+ /** How often the screen should re-read the line. Twice a second; the read is one subtraction. */
240
+ export const COMPRESSION_COUNTDOWN_TICK_MS = 500;
241
+
242
+ // ── The opening "working out how long this will take" ramp ──────────────────────────────────
243
+ // The countdown does not appear at its full figure: it counts UP to it first, one readable step at
244
+ // a time, and only then turns around and counts down. That opening ramp is what tells the user the
245
+ // app is sizing the job up rather than freezing.
246
+ //
247
+ // It is a DISPLAY ramp — it advances on a fixed cadence of its own, never on how fast the encoder
248
+ // happens to report. The measured prediction keeps being re-derived underneath it the whole time
249
+ // (see {@link foldCompressionProgress}); the ramp simply decides what is shown until the two meet.
250
+ // That separation is the point: before it, the figure was whatever the prediction happened to be
251
+ // re-derived to, which early on moves several seconds per report and flickers through numbers far
252
+ // too quickly to read.
253
+
254
+ /**
255
+ * One step of the opening ramp: how long each number stays on screen while the total is being
256
+ * worked out. About three numbers a second — slow enough to read every one of them, quick enough
257
+ * that the ramp is over in a moment.
258
+ */
259
+ export const COMPRESSION_CALCULATION_STEP_MS = 300;
260
+
261
+ /**
262
+ * The most steps the ramp may take, which is what bounds it in wall-clock time
263
+ * (`COMPRESSION_CALCULATION_STEP_MS` × this ≈ 3 s). A long job strides in larger increments rather
264
+ * than taking longer to count up — the ramp must never become the wait it is describing.
265
+ */
266
+ export const COMPRESSION_CALCULATION_MAX_STEPS = 10;
267
+
268
+ /**
269
+ * And it may never spend more than this share of the predicted job counting up either, so a job
270
+ * that is over in two seconds does not spend most of it ramping.
271
+ */
272
+ export const COMPRESSION_CALCULATION_MAX_ELAPSED_FRACTION = 0.4;
273
+
274
+ export interface CompressionProgressTracker {
275
+ /** `Date.now()` when the compression started. */
276
+ readonly startedAtMs: number;
277
+ /** The up-front estimate in seconds, which seeds — and early on partly carries — the total. */
278
+ readonly estimateSeconds: number;
279
+ /**
280
+ * The largest fraction the compressor has reported, or 0 while it has reported nothing at all.
281
+ * 0 is a real state, not just an initial one: a source with no readable duration reports nothing,
282
+ * and then the estimate is all there is.
283
+ */
284
+ readonly progress: number;
285
+ /** How long the whole compression is now expected to take, in seconds from its start. */
286
+ readonly predictedTotalSeconds: number;
287
+ /**
288
+ * Seconds the opening ramp adds per step. 1 for anything the ramp can count up to one second at
289
+ * a time within its step budget, larger for a job long enough that it has to stride.
290
+ */
291
+ readonly calculationStepSeconds: number;
292
+ /**
293
+ * Where the ramp stops climbing no matter what: the first multiple of
294
+ * [calculationStepSeconds] at or above the up-front estimate. Normally the ramp ends before this
295
+ * by meeting the real remaining time; this is the backstop for a prediction that runs away
296
+ * upward while the ramp is still climbing.
297
+ */
298
+ readonly calculationCeilingSeconds: number;
299
+ }
300
+
301
+ /** A tracker for a compression starting now, seeded by its up-front [estimateSeconds]. */
302
+ export const createCompressionProgressTracker = (
303
+ estimateSeconds: number,
304
+ nowMs: number
305
+ ): CompressionProgressTracker => {
306
+ const seconds = Math.max(1, Math.ceil(estimateSeconds));
307
+ // Step budget: capped both absolutely and as a share of the job, then divided into the estimate
308
+ // so that every step is the same size and the whole ramp is over within it.
309
+ const budgetMs = Math.min(
310
+ COMPRESSION_CALCULATION_STEP_MS * COMPRESSION_CALCULATION_MAX_STEPS,
311
+ seconds * 1000 * COMPRESSION_CALCULATION_MAX_ELAPSED_FRACTION
312
+ );
313
+ const steps = Math.max(
314
+ 1,
315
+ Math.floor(budgetMs / COMPRESSION_CALCULATION_STEP_MS)
316
+ );
317
+ const calculationStepSeconds = Math.max(1, Math.ceil(seconds / steps));
318
+ return {
319
+ startedAtMs: nowMs,
320
+ estimateSeconds: seconds,
321
+ progress: 0,
322
+ predictedTotalSeconds: seconds,
323
+ calculationStepSeconds,
324
+ calculationCeilingSeconds:
325
+ calculationStepSeconds * Math.ceil(seconds / calculationStepSeconds),
326
+ };
327
+ };
328
+
329
+ /**
330
+ * Fold one measured progress report (0..1) into [tracker], re-deriving how long the whole
331
+ * compression will take from how long it has taken to get this far.
332
+ *
333
+ * `total = elapsed / fraction` is the entire idea: it is why the displayed figure can stretch as
334
+ * well as shrink, and therefore why it neither hits zero mid-compression nor lingers after the
335
+ * work is done. Out-of-order or repeated reports cannot walk the fraction backwards.
336
+ *
337
+ * Reports during the opening ramp are folded in exactly like any other: the ramp is a DISPLAY
338
+ * concern only, so nothing here has to be held back or paused for it. That is what makes the
339
+ * hand-over accurate — by the time the ramp meets the countdown, the figure it meets has already
340
+ * had every report so far folded into it.
341
+ */
342
+ export const foldCompressionProgress = (
343
+ tracker: CompressionProgressTracker,
344
+ progress: number,
345
+ nowMs: number
346
+ ): CompressionProgressTracker => {
347
+ const clamped = Math.min(1, Math.max(0, progress));
348
+ const nextProgress = Math.max(tracker.progress, clamped);
349
+
350
+ const elapsedSeconds = Math.max(0, (nowMs - tracker.startedAtMs) / 1000);
351
+ if (nextProgress <= 0 || elapsedSeconds <= 0) {
352
+ return { ...tracker, progress: nextProgress };
353
+ }
354
+
355
+ const measuredTotal = elapsedSeconds / nextProgress;
356
+ const trust = Math.min(1, nextProgress / MIN_TRUSTED_COMPRESSION_PROGRESS);
357
+ const blendedTotal =
358
+ trust * measuredTotal + (1 - trust) * tracker.estimateSeconds;
359
+ return {
360
+ ...tracker,
361
+ progress: nextProgress,
362
+ predictedTotalSeconds:
363
+ tracker.predictedTotalSeconds +
364
+ COMPRESSION_PREDICTION_SMOOTHING *
365
+ (blendedTotal - tracker.predictedTotalSeconds),
366
+ };
367
+ };
368
+
369
+ /** Seconds still to go by the current prediction — the figure the countdown proper reads. */
370
+ const remainingSeconds = (
371
+ tracker: CompressionProgressTracker,
372
+ nowMs: number
373
+ ): number =>
374
+ tracker.predictedTotalSeconds -
375
+ Math.max(0, (nowMs - tracker.startedAtMs) / 1000);
376
+ /** Where the opening ramp has climbed to by [nowMs], in whole seconds. */
377
+ const calculationValueSeconds = (
378
+ tracker: CompressionProgressTracker,
379
+ nowMs: number
380
+ ): number => {
381
+ const elapsedMs = Math.max(0, nowMs - tracker.startedAtMs);
382
+ const stepsTaken = Math.floor(elapsedMs / COMPRESSION_CALCULATION_STEP_MS);
383
+ return Math.min(
384
+ tracker.calculationCeilingSeconds,
385
+ tracker.calculationStepSeconds * (stepsTaken + 1)
386
+ );
387
+ };
388
+
389
+ /**
390
+ * The figure the opening ramp is showing right now, or `null` once it is over and the countdown
391
+ * proper owns the line.
392
+ *
393
+ * The ramp ends the moment its climbing figure REACHES the real remaining time, which is what
394
+ * makes the hand-over seamless: the last number it shows and the first number the countdown shows
395
+ * are the same one, arrived at from below. (`remaining` is falling in real time while the ramp
396
+ * climbs, so the two always meet — the ceiling is only a backstop for a prediction re-derived
397
+ * upward faster than the ramp can climb.)
398
+ */
399
+ export const compressionCalculationSeconds = (
400
+ tracker: CompressionProgressTracker,
401
+ nowMs: number
402
+ ): number | null => {
403
+ if (tracker.calculationStepSeconds <= 0) return null;
404
+ const value = calculationValueSeconds(tracker, nowMs);
405
+ if (value >= tracker.calculationCeilingSeconds) return null;
406
+ if (value >= remainingSeconds(tracker, nowMs)) return null;
407
+ return value;
408
+ };
409
+
410
+ /**
411
+ * How long the screen should wait before re-reading the line.
412
+ *
413
+ * While the ramp is climbing this is the time to its NEXT step, so every number it shows gets the
414
+ * same {@link COMPRESSION_CALCULATION_STEP_MS} on screen — an even cadence rather than one
415
+ * quantised to a tick that does not divide it. Afterwards it is the ordinary countdown tick.
416
+ */
417
+ export const compressionCountdownTickMs = (
418
+ tracker: CompressionProgressTracker | null,
419
+ nowMs: number
420
+ ): number => {
421
+ if (!tracker) return COMPRESSION_COUNTDOWN_TICK_MS;
422
+ if (compressionCalculationSeconds(tracker, nowMs) === null) {
423
+ return COMPRESSION_COUNTDOWN_TICK_MS;
424
+ }
425
+ const elapsedMs = Math.max(0, nowMs - tracker.startedAtMs);
426
+ return (
427
+ COMPRESSION_CALCULATION_STEP_MS -
428
+ (elapsedMs % COMPRESSION_CALCULATION_STEP_MS)
429
+ );
430
+ };
431
+
432
+ /**
433
+ * What the overlay's line should read right now.
434
+ *
435
+ * Four cases, in order:
436
+ *
437
+ * 1. **The encoder is done and the pass is in its tail** (progress at or above
438
+ * {@link COMPRESSION_FINALIZING_PROGRESS}) — "Finalizing...", because flushing and muxing are
439
+ * real work of unmeasurable length.
440
+ * 2. **The opening ramp is still climbing** — its figure, one readable step at a time, until it
441
+ * meets the real remaining time (see {@link compressionCalculationSeconds}).
442
+ * 3. **The prediction is spent and nothing was ever measured** — "Finalizing..." as well: a
443
+ * compressor that reports no progress leaves only the estimate, and counting through zero into
444
+ * negative numbers would misrepresent what is happening.
445
+ * 4. **Otherwise** — `predicted total - elapsed`, floored at one second. The floor is reached only
446
+ * within about a second of the end (it takes progress above `elapsed / (elapsed + 1)`), and
447
+ * case 1 takes over from there.
448
+ */
449
+ export const compressionCountdownLabel = (
450
+ tracker: CompressionProgressTracker | null,
451
+ nowMs: number,
452
+ t?: CameraTranslationFunction
453
+ ): string => {
454
+ const finalizingLabel = () => {
455
+ const out = t?.('finalizingCompression');
456
+ return typeof out === 'string' && out.length > 0
457
+ ? out
458
+ : COMPRESSION_FINALIZING_LABEL;
459
+ };
460
+ if (!tracker) {
461
+ const fallback = t?.('compressing');
462
+ return typeof fallback === 'string' && fallback.length > 0
463
+ ? fallback
464
+ : COMPRESSION_FALLBACK_LABEL;
465
+ }
466
+ if (tracker.progress >= COMPRESSION_FINALIZING_PROGRESS) {
467
+ return finalizingLabel();
468
+ }
469
+ const calculating = compressionCalculationSeconds(tracker, nowMs);
470
+ if (calculating !== null) return formatCompressionCountdown(calculating, t);
471
+ const remaining = remainingSeconds(tracker, nowMs);
472
+ if (remaining < 1 && tracker.progress <= 0) return finalizingLabel();
473
+ return formatCompressionCountdown(Math.max(1, remaining), t);
474
+ };
475
+
476
+ // ── The level ladder and the sizes its rows quote ───────────────────────────────────────────
477
+ // The rows the viewer's compression sheet lists, and the size band each of them promises. Both are
478
+ // the native editor's own (MediaPreviewEditor.estimatedCompressedSize / proportionalTargetRange /
479
+ // ImageExporter.estimateCompressedByteRange), reproduced here so the shell's sheet and the native
480
+ // editor quote the SAME figures for the same file at the same level.
481
+
482
+ /**
483
+ * The QUALITY levels the sheet offers, highest quality first — the percentage of the original to
484
+ * KEEP, which is exactly what the bridge takes (`compressVideo(uri, '45%')`). So the number a row
485
+ * displays is the number handed to the encoder: no inversion anywhere between the two.
486
+ *
487
+ * The native editor's ladder is the same rungs expressed the other way round (percent to SHAVE), so
488
+ * `95%` quality here is its `5% smaller` row and `5%` quality here is its `95% smaller` row.
489
+ */
490
+ export const COMPRESSION_QUALITY_LEVELS: readonly number[] = [
491
+ 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5,
492
+ ];
493
+
494
+ /**
495
+ * How far under its ceiling a compression routinely lands, which is the bottom of every quoted band
496
+ * — for stills because the encoder's size search stops at the highest quality at or under the
497
+ * target (quality quantisation, a profile pre-cap, a fallback when the target is out of reach), and
498
+ * for clips because the same fraction is used so a video band and an image band open by the same
499
+ * amount. Shared verbatim with `ImageExporter.ESTIMATE_SIZE_UNDERSHOOT`.
500
+ */
501
+ export const ESTIMATE_SIZE_UNDERSHOOT = 0.85;
502
+
503
+ /**
504
+ * The quality floor, mirrored from `VideoRotationTranscoder.MIN_BITS_PER_PIXEL_AVC` — the fewest
505
+ * bits per pixel per frame the native encoder will spend on a full-size frame, however heavy the
506
+ * level asked for. Kotlin is the source of truth for the value; this copy exists so the sheet does
507
+ * not quote a size the pipeline deliberately refuses to produce.
508
+ *
509
+ * AVC's figure rather than HEVC's on purpose: it is the HIGHER of the two, and the shell cannot know
510
+ * which codec the device will pick, so assuming AVC keeps every quoted row at or above what will
511
+ * actually be written. A row that promises slightly less reduction than it delivers is a pleasant
512
+ * surprise; the other way round is a broken promise.
513
+ */
514
+ export const MIN_BITS_PER_PIXEL_AVC = 0.065;
515
+
516
+ /**
517
+ * How far the floor may push into the clip's own rate, mirroring
518
+ * `VideoRotationTranscoder.MAX_FLOOR_SOURCE_SHARE`. A clip that already sits below the floor is
519
+ * honestly cheap rather than under-encoded, so the floor gives way to half its own average rate:
520
+ * every clip can always be made about twice as small, and past that the picture is protected.
521
+ */
522
+ export const MAX_FLOOR_SOURCE_SHARE = 0.5;
523
+
524
+ /**
525
+ * Frames per second assumed when quoting the floor. The shell has no access to a clip's real frame
526
+ * rate (nothing on a `CameraAsset` carries it), and 30 is both the overwhelmingly common capture
527
+ * rate and native's own fallback (`VideoRotationTranscoder.DEFAULT_FRAME_RATE`).
528
+ */
529
+ export const ASSUMED_VIDEO_FRAME_RATE = 30;
530
+
531
+ /** The clip facts the quality floor is measured from. All optional: missing any of them drops it. */
532
+ export interface CompressedSizeFrame {
533
+ /** The clip's pixel dimensions. Orientation does not matter — only the pixel COUNT is used. */
534
+ readonly width?: number;
535
+ readonly height?: number;
536
+ /** Duration in milliseconds (seconds are accepted and normalised). */
537
+ readonly duration?: number;
538
+ }
539
+
540
+ /**
541
+ * The smallest size a full-resolution re-encode of [frame] can honestly come out at, or 0 when the
542
+ * clip does not describe itself well enough to say.
543
+ *
544
+ * `bits-per-pixel × pixels × fps × duration`, capped at {@link MAX_FLOOR_SOURCE_SHARE} of the file's
545
+ * own average rate. An APPROXIMATION of native's figure rather than a byte-identical copy of it: the
546
+ * shell cannot read the track's frame rate, nor the split between the video track and the audio the
547
+ * transcoder copies through untouched. It is close enough for the one job it has — keeping a row from
548
+ * quoting a reduction the encoder will not perform.
549
+ */
550
+ const qualityFloorBytes = (
551
+ originalBytes: number,
552
+ frame?: CompressedSizeFrame
553
+ ): number => {
554
+ const width = frame?.width ?? 0;
555
+ const height = frame?.height ?? 0;
556
+ const seconds = durationToSeconds(frame?.duration);
557
+ if (width <= 0 || height <= 0 || seconds <= 0) return 0;
558
+ const floorBits =
559
+ MIN_BITS_PER_PIXEL_AVC * width * height * ASSUMED_VIDEO_FRAME_RATE * seconds;
560
+ const floor = Math.floor(floorBits / 8);
561
+ if (floor <= 0) return 0;
562
+ return Math.min(floor, Math.floor(originalBytes * MAX_FLOOR_SOURCE_SHARE));
563
+ };
564
+
565
+ /** A quoted output-size band: [low]..[high], the pair a level row shows as "1.8 – 2.1 MB". */
566
+ export interface CompressedSizeBand {
567
+ readonly low: number;
568
+ readonly high: number;
569
+ }
570
+
571
+ /**
572
+ * The CEILING a compression at [qualityPercent] is aiming at: [originalBytes] with the shaved share
573
+ * removed. The single figure a compressed clip is quoted at afterwards, and the top of the band a
574
+ * row promises beforehand.
575
+ *
576
+ * `shave = 100 - qualityPercent`, pinned to the same range the encoder pins it to — 1..99 for a
577
+ * clip, 1..95 for a still (`ImageExporter.compress`'s own clamp) — and the arithmetic is integer
578
+ * truncating, exactly as native's is, so the two agree to the byte.
579
+ *
580
+ * For a CLIP the result is then raised to the quality floor when [frame] describes the video well
581
+ * enough to measure one (see {@link qualityFloorBytes}): the native encoder will not take a
582
+ * full-size frame below that rate, so the heavy rungs all land on the same size and a row that went
583
+ * on quoting `1% of the original` would be promising a file that is never written. Omit [frame] and
584
+ * the figure stays purely proportional, as it was before the floor existed.
585
+ */
586
+ export const compressedCeilingBytes = (
587
+ originalBytes: number,
588
+ qualityPercent: number,
589
+ kind: 'video' | 'image',
590
+ frame?: CompressedSizeFrame
591
+ ): number => {
592
+ if (!originalBytes || originalBytes <= 0) return 0;
593
+ const maxShave = kind === 'video' ? 99 : 95;
594
+ const shave = Math.min(maxShave, Math.max(1, Math.round(100 - qualityPercent)));
595
+ const proportional = Math.max(1, Math.floor((originalBytes * (100 - shave)) / 100));
596
+ if (kind !== 'video') return proportional;
597
+ // Never above the original: a "compressed" row may not read as growth.
598
+ const floor = Math.min(originalBytes, qualityFloorBytes(originalBytes, frame));
599
+ return Math.max(proportional, floor);
600
+ };
601
+
602
+ /**
603
+ * The band of output sizes a compression at [qualityPercent] is expected to land in, or null when
604
+ * [originalBytes] is unknown — which drops the size from the row rather than inventing one.
605
+ *
606
+ * The top is {@link compressedCeilingBytes}; the bottom is that ceiling scaled by
607
+ * {@link ESTIMATE_SIZE_UNDERSHOOT}. Both media kinds share the shape deliberately: a still's band is
608
+ * the encoder's own target arithmetic, and a clip's is the display convention the native menu uses
609
+ * so a video row and an image row read alike at every rung. [frame] is the clip's own geometry and
610
+ * duration, which is what lets a video band respect the encoder's quality floor.
611
+ */
612
+ export const compressedSizeBand = (
613
+ originalBytes: number,
614
+ qualityPercent: number,
615
+ kind: 'video' | 'image',
616
+ frame?: CompressedSizeFrame
617
+ ): CompressedSizeBand | null => {
618
+ const ceiling = compressedCeilingBytes(originalBytes, qualityPercent, kind, frame);
619
+ if (ceiling <= 0) return null;
620
+ const low = Math.min(
621
+ ceiling,
622
+ Math.max(1, Math.round(ceiling * ESTIMATE_SIZE_UNDERSHOOT))
623
+ );
624
+ return { low, high: ceiling };
625
+ };
626
+
627
+ /**
628
+ * The option string the bridge is called with for a quality level — the level itself, as a
629
+ * percentage. `compressVideo(uri, '45%')` keeps 45% of the quality.
630
+ */
631
+ export const compressionOptionForQuality = (qualityPercent: number): string =>
632
+ `${Math.round(qualityPercent)}%`;
633
+
634
+ /**
635
+ * The sentinel option for "leave it alone". Native resolves any option starting with `Original`
636
+ * straight back to the input URI (quality ratio 1.0), and {@link parseCompressionPercent} reads it
637
+ * as 0, so it never starts a compression on either side. Never displayed — the row's label comes
638
+ * from the localized `originalVideo` / `originalImage` keys.
639
+ */
640
+ export const originalCompressionOption = (kind: 'video' | 'image'): string =>
641
+ kind === 'video' ? 'Original Video' : 'Original Image';
642
+
643
+ /** True for the {@link originalCompressionOption} sentinels — the rows that compress nothing. */
644
+ export const isOriginalCompressionOption = (option: string): boolean =>
645
+ /^\s*Original\b/i.test(option);
646
+
647
+ /**
648
+ * How much smaller a finished compression came out, as a whole percentage of the original — the
649
+ * `{1}` of the `compressedToSize` line. Returns null when there is nothing honest to claim (either
650
+ * size unknown, or an output that did not actually shrink), and the caller falls back to
651
+ * `compressedToSizeOnly`.
652
+ */
653
+ export const compressionSavingPercent = (
654
+ originalBytes: number,
655
+ compressedBytes: number
656
+ ): number | null => {
657
+ if (!originalBytes || originalBytes <= 0) return null;
658
+ if (!compressedBytes || compressedBytes <= 0) return null;
659
+ if (compressedBytes >= originalBytes) return null;
660
+ const saved = Math.round((1 - compressedBytes / originalBytes) * 100);
661
+ return saved >= 1 ? saved : null;
662
+ };