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,845 @@
1
+ /**
2
+ * RecordAudioScreen — a full-screen modal for recording audio.
3
+ *
4
+ * Handles the complete lifecycle:
5
+ * 1. Permission request (RECORD_AUDIO)
6
+ * 2. Start / pause / resume / stop recording
7
+ * 3. Cancel (deletes temp file)
8
+ * 4. On stop: validates → moves to Documents → returns finalized asset
9
+ *
10
+ * Reuses:
11
+ * - checkMicrophonePermission / requestMicrophonePermission from permissions.ts
12
+ * - startAudioRecording / stopAndPublishAudioRecording / cancelAudioRecording bridges
13
+ * - onAudioAmplitude DeviceEvent for waveform visualisation
14
+ */
15
+ import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
16
+ import {
17
+ View,
18
+ Text,
19
+ TouchableOpacity,
20
+ StyleSheet,
21
+ Modal,
22
+ StatusBar,
23
+ Platform,
24
+ Animated,
25
+ Easing,
26
+ DeviceEventEmitter,
27
+ AppState,
28
+ Linking,
29
+ type AppStateStatus,
30
+ } from 'react-native';
31
+ import { wp, ms, fs } from '../responsive';
32
+ import { useLibraryPalette, type LibraryPalette } from '../palette';
33
+ import {
34
+ checkMicrophonePermission,
35
+ requestMicrophonePermission,
36
+ } from '../../permissions';
37
+ import {
38
+ startAudioRecording,
39
+ stopAndPublishAudioRecording,
40
+ cancelAudioRecording,
41
+ pauseAudioRecording,
42
+ resumeAudioRecording,
43
+ } from '../../nativeActions';
44
+ import { useCameraLocalization } from '../../localization';
45
+ import { RenderIcon } from '../../icons';
46
+ import type { CameraAsset } from '../../types';
47
+
48
+ // ── Constants ──────────────────────────────────────────────────────
49
+ const METERING_INTERVAL_MS = 60;
50
+ const MIN_RECORDING_MS = 500; // Minimum 500ms recording to be valid
51
+ const WAVEFORM_BARS = 48;
52
+ const STATUS_BAR_INSET = Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 44;
53
+
54
+ // ── Types ──────────────────────────────────────────────────────────
55
+ type RecordingState = 'idle' | 'requesting-permission' | 'ready' | 'recording' | 'paused' | 'stopping' | 'error';
56
+
57
+ export interface RecordAudioScreenProps {
58
+ visible: boolean;
59
+ onClose: () => void;
60
+ onRecorded: (asset: CameraAsset) => void;
61
+ }
62
+
63
+ // ── Helpers ────────────────────────────────────────────────────────
64
+ function formatDuration(totalMs: number): string {
65
+ const totalSec = Math.floor(totalMs / 1000);
66
+ const h = Math.floor(totalSec / 3600);
67
+ const m = Math.floor((totalSec % 3600) / 60);
68
+ const s = totalSec % 60;
69
+ if (h > 0) {
70
+ return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
71
+ }
72
+ return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
73
+ }
74
+
75
+ // ── Component ──────────────────────────────────────────────────────
76
+ export const RecordAudioScreen = ({
77
+ visible,
78
+ onClose,
79
+ onRecorded,
80
+ }: RecordAudioScreenProps) => {
81
+ const { t } = useCameraLocalization();
82
+ const palette = useLibraryPalette();
83
+ const styles = useMemo(() => makeStyles(palette), [palette]);
84
+ const [state, setState] = useState<RecordingState>('idle');
85
+ const [durationMs, setDurationMs] = useState(0);
86
+ const [amplitudes, setAmplitudes] = useState<number[]>(() => new Array(WAVEFORM_BARS).fill(0));
87
+ const [isPauseSupported, setIsPauseSupported] = useState(false);
88
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
89
+
90
+ // ── Derived states ───────────────────────────────────────────────
91
+ // Declared here, immediately after the state they derive from, because the
92
+ // lifecycle effects below list them in their dependency arrays — and a
93
+ // dependency array is evaluated DURING render, so a `const` declared further
94
+ // down would still be in its temporal dead zone and throw.
95
+ const isRecording = state === 'recording';
96
+ const isPaused = state === 'paused';
97
+ const isStopping = state === 'stopping';
98
+ const isActive = isRecording || isPaused;
99
+ const isBusy = isRecording || isPaused || isStopping;
100
+ const canRecord = state === 'ready';
101
+ const hasError = state === 'error';
102
+
103
+ const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
104
+ const startTimeRef = useRef(0);
105
+ const pausedDurationRef = useRef(0);
106
+ const pauseStartRef = useRef(0);
107
+ const amplitudeSubRef = useRef<any>(null);
108
+ const amplitudeBufferRef = useRef<number[]>(new Array(WAVEFORM_BARS).fill(0));
109
+ const amplitudeWriteIdx = useRef(0);
110
+ const pulseAnim = useRef(new Animated.Value(1)).current;
111
+ const pulseLoopRef = useRef<Animated.CompositeAnimation | null>(null);
112
+ // Guard to prevent double-stop from AppState + onAudioRecorderState firing together.
113
+ const stoppingRef = useRef(false);
114
+ // Ref for durationMs so handleStop doesn't depend on the rapidly-updating state.
115
+ const durationMsRef = useRef(0);
116
+
117
+ // ── Cleanup on unmount or close ──────────────────────────────────
118
+ const cleanup = useCallback(() => {
119
+ if (timerRef.current) {
120
+ clearInterval(timerRef.current);
121
+ timerRef.current = null;
122
+ }
123
+ if (amplitudeSubRef.current) {
124
+ amplitudeSubRef.current.remove();
125
+ amplitudeSubRef.current = null;
126
+ }
127
+ if (pulseLoopRef.current) {
128
+ pulseLoopRef.current.stop();
129
+ pulseLoopRef.current = null;
130
+ }
131
+ pulseAnim.setValue(1);
132
+ startTimeRef.current = 0;
133
+ pausedDurationRef.current = 0;
134
+ pauseStartRef.current = 0;
135
+ amplitudeBufferRef.current = new Array(WAVEFORM_BARS).fill(0);
136
+ amplitudeWriteIdx.current = 0;
137
+ }, [pulseAnim]);
138
+
139
+ // ── Permission ───────────────────────────────────────────────────
140
+ const ensurePermission = useCallback(async (): Promise<boolean> => {
141
+ setState('requesting-permission');
142
+ setErrorMessage(null);
143
+
144
+ try {
145
+ const granted = await checkMicrophonePermission();
146
+ if (granted) {
147
+ setState('ready');
148
+ return true;
149
+ }
150
+
151
+ const result = await requestMicrophonePermission({
152
+ title: t('permissionRationaleMicrophoneTitle'),
153
+ message: t('micRationaleMessage'),
154
+ buttonPositive: t('permissionGrant'),
155
+ buttonNegative: t('permissionDeny'),
156
+ });
157
+
158
+ if (result === 'granted') {
159
+ setState('ready');
160
+ return true;
161
+ }
162
+
163
+ if (result === 'never_ask_again') {
164
+ setErrorMessage(t('micPermanentlyDenied'));
165
+ } else {
166
+ setErrorMessage(t('micPermissionDenied'));
167
+ }
168
+ setState('error');
169
+ return false;
170
+ } catch (e: any) {
171
+ setErrorMessage(e?.message ?? t('micPermissionRequestFailed'));
172
+ setState('error');
173
+ return false;
174
+ }
175
+ }, [t]);
176
+
177
+ // ── Start recording ──────────────────────────────────────────────
178
+ const startRecording = useCallback(async () => {
179
+ setErrorMessage(null);
180
+
181
+ try {
182
+ // Throws on failure, so reaching the next line means the recorder is live.
183
+ // The resolved temp path isn't needed here — stopAndPublishAudioRecording
184
+ // looks it up natively.
185
+ await startAudioRecording({
186
+ outputFormat: 'mpeg4',
187
+ audioEncoder: 'aac',
188
+ audioSource: 'mic',
189
+ bitRate: 128000,
190
+ sampleRate: 44100,
191
+ meteringIntervalMs: METERING_INTERVAL_MS,
192
+ });
193
+
194
+ // Reset amplitude buffer from any previous session.
195
+ amplitudeBufferRef.current = new Array(WAVEFORM_BARS).fill(0);
196
+ amplitudeWriteIdx.current = 0;
197
+ setAmplitudes(new Array(WAVEFORM_BARS).fill(0));
198
+
199
+ // Start the elapsed-duration timer.
200
+ startTimeRef.current = Date.now();
201
+ pausedDurationRef.current = 0;
202
+ pauseStartRef.current = 0;
203
+ setDurationMs(0);
204
+
205
+ timerRef.current = setInterval(() => {
206
+ const now = Date.now();
207
+ const paused = pausedDurationRef.current + (pauseStartRef.current > 0 ? now - pauseStartRef.current : 0);
208
+ setDurationMs(now - startTimeRef.current - paused);
209
+ }, 50);
210
+
211
+ // Subscribe to amplitude events for waveform visualisation.
212
+ if (amplitudeSubRef.current) amplitudeSubRef.current.remove();
213
+ amplitudeSubRef.current = DeviceEventEmitter.addListener(
214
+ 'onAudioAmplitude',
215
+ (amp: number) => {
216
+ const normalised = Math.min(1, Math.max(0, amp / 32768));
217
+ const buf = amplitudeBufferRef.current;
218
+ buf[amplitudeWriteIdx.current % WAVEFORM_BARS] = normalised;
219
+ amplitudeWriteIdx.current += 1;
220
+ setAmplitudes([...buf]);
221
+ }
222
+ );
223
+
224
+ // Start pulse animation on the record button.
225
+ pulseLoopRef.current = Animated.loop(
226
+ Animated.sequence([
227
+ Animated.timing(pulseAnim, {
228
+ toValue: 0.4,
229
+ duration: 800,
230
+ easing: Easing.inOut(Easing.ease),
231
+ useNativeDriver: true,
232
+ }),
233
+ Animated.timing(pulseAnim, {
234
+ toValue: 1,
235
+ duration: 800,
236
+ easing: Easing.inOut(Easing.ease),
237
+ useNativeDriver: true,
238
+ }),
239
+ ])
240
+ );
241
+ pulseLoopRef.current.start();
242
+
243
+ setState('recording');
244
+ } catch (e: any) {
245
+ const msg = e?.message ?? t('failedToStartRecording');
246
+ setErrorMessage(msg.includes('permission') ? t('micPermissionNotGranted') : msg);
247
+ setState('error');
248
+ }
249
+ }, [pulseAnim, t]);
250
+
251
+ // ── Pause ────────────────────────────────────────────────────────
252
+ const handlePause = useCallback(async () => {
253
+ try {
254
+ await pauseAudioRecording();
255
+ pauseStartRef.current = Date.now();
256
+ if (pulseLoopRef.current) pulseLoopRef.current.stop();
257
+ pulseAnim.setValue(0.4);
258
+ setState('paused');
259
+ } catch {
260
+ // Pause not supported on this device — ignore.
261
+ }
262
+ }, [pulseAnim]);
263
+
264
+ // ── Resume ───────────────────────────────────────────────────────
265
+ const handleResume = useCallback(async () => {
266
+ try {
267
+ await resumeAudioRecording();
268
+ if (pauseStartRef.current > 0) {
269
+ pausedDurationRef.current += Date.now() - pauseStartRef.current;
270
+ pauseStartRef.current = 0;
271
+ }
272
+ pulseLoopRef.current?.start();
273
+ setState('recording');
274
+ } catch {
275
+ // Resume not supported — ignore.
276
+ }
277
+ }, []);
278
+
279
+ // ── Stop & publish ───────────────────────────────────────────────
280
+ const handleStop = useCallback(async () => {
281
+ // Guard: prevent double-stop from AppState + onAudioRecorderState.
282
+ if (stoppingRef.current) return;
283
+ stoppingRef.current = true;
284
+
285
+ cleanup();
286
+ setState('stopping');
287
+
288
+ try {
289
+ // Check minimum duration (use ref so this callback is stable).
290
+ // Do this BEFORE the native call to avoid moving an invalid file.
291
+ if (durationMsRef.current < MIN_RECORDING_MS) {
292
+ setErrorMessage(t('recordingTooShort'));
293
+ setState('ready');
294
+ stoppingRef.current = false;
295
+ return;
296
+ }
297
+
298
+ const asset = await stopAndPublishAudioRecording();
299
+ onRecorded(asset);
300
+ onClose();
301
+ } catch (e: any) {
302
+ const msg = e?.message ?? t('failedToSaveRecording');
303
+ setErrorMessage(msg);
304
+ setState('ready');
305
+ stoppingRef.current = false;
306
+ }
307
+ }, [cleanup, onRecorded, onClose, t]);
308
+
309
+ // ── Cancel ───────────────────────────────────────────────────────
310
+ const handleCancel = useCallback(async () => {
311
+ if (stoppingRef.current) return;
312
+ stoppingRef.current = true;
313
+ cleanup();
314
+ try {
315
+ await cancelAudioRecording();
316
+ } catch {
317
+ // Best-effort cleanup.
318
+ }
319
+ onClose();
320
+ }, [cleanup, onClose]);
321
+
322
+ // ── Keep durationMsRef in sync with the state ───────────────────
323
+ useEffect(() => {
324
+ durationMsRef.current = durationMs;
325
+ }, [durationMs]);
326
+
327
+ // ── Request permission on first open ─────────────────────────────
328
+ useEffect(() => {
329
+ if (visible && state === 'idle') {
330
+ ensurePermission();
331
+ }
332
+ }, [visible, state, ensurePermission]);
333
+
334
+ // ── Reset state when modal closes ────────────────────────────────
335
+ useEffect(() => {
336
+ if (!visible) {
337
+ cleanup();
338
+ setState('idle');
339
+ setDurationMs(0);
340
+ setAmplitudes(new Array(WAVEFORM_BARS).fill(0));
341
+ setErrorMessage(null);
342
+ }
343
+ }, [visible, cleanup]);
344
+
345
+ // ── Check pause support on mount ─────────────────────────────────
346
+ useEffect(() => {
347
+ // Pause/resume is supported on API 24+. The native side exposes this via getAudioRecordingState.
348
+ // We set it optimistically and the native error on pause will be caught.
349
+ setIsPauseSupported(Platform.OS === 'android' && (Platform.Version as number) >= 24);
350
+ }, []);
351
+
352
+ // ── Lifecycle: auto-stop on background / screen lock ─────────────
353
+ useEffect(() => {
354
+ const handleAppState = (next: AppStateStatus) => {
355
+ // When the app moves to background while actively recording, stop and
356
+ // publish so the user doesn't lose their recording. The native
357
+ // MediaRecorder can be suspended or killed by the OS at any time the
358
+ // app is backgrounded, so it's safest to finalise immediately.
359
+ if (next !== 'active' && (isRecording || isPaused)) {
360
+ handleStop();
361
+ }
362
+ };
363
+ const sub = AppState.addEventListener('change', handleAppState);
364
+ return () => sub.remove();
365
+ }, [isRecording, isPaused, handleStop]);
366
+
367
+ // Reset the stopping guard when the modal closes so it can be used again.
368
+ useEffect(() => {
369
+ if (!visible) {
370
+ stoppingRef.current = false;
371
+ }
372
+ }, [visible]);
373
+
374
+ // ── Listen for unexpected native state changes ───────────────────
375
+ // The native side emits onAudioRecorderState when the recorder is
376
+ // interrupted externally (e.g. phone call, audio focus loss, mic
377
+ // conflict). React to these so the UI never gets out of sync.
378
+ useEffect(() => {
379
+ const sub = DeviceEventEmitter.addListener(
380
+ 'onAudioRecorderState',
381
+ (event: { state: string }) => {
382
+ const nativeState = event.state;
383
+ // If the native side reports idle while we think we're recording,
384
+ // the recorder was taken away (phone call, audio focus loss, etc.).
385
+ // Auto-save whatever was recorded so the user doesn't lose data.
386
+ if (nativeState === 'idle' && isActive) {
387
+ handleStop();
388
+ }
389
+ }
390
+ );
391
+ return () => sub.remove();
392
+ }, [isActive, handleStop]);
393
+
394
+ // ── Waveform bars ────────────────────────────────────────────────
395
+ const waveformBars = useMemo(() => {
396
+ return amplitudes.map((amp, i) => {
397
+ // Each bar's height is proportional to the amplitude, with a minimum height.
398
+ const height = Math.max(ms(4), amp * ms(80));
399
+ const isActive = isRecording && amp > 0.05;
400
+ return (
401
+ <View
402
+ key={i}
403
+ style={[
404
+ styles.waveformBar,
405
+ {
406
+ height,
407
+ backgroundColor: isActive ? palette.primary : palette.surfaceMuted,
408
+ },
409
+ ]}
410
+ />
411
+ );
412
+ });
413
+ }, [amplitudes, isRecording, palette, styles]);
414
+
415
+ // ── Render ───────────────────────────────────────────────────────
416
+ return (
417
+ <Modal
418
+ visible={visible}
419
+ animationType="slide"
420
+ onRequestClose={isBusy ? undefined : handleCancel}
421
+ transparent={false}
422
+ statusBarTranslucent
423
+ >
424
+ <View style={styles.container}>
425
+ <StatusBar
426
+ barStyle={palette.isDark ? 'light-content' : 'dark-content'}
427
+ translucent
428
+ backgroundColor="transparent"
429
+ />
430
+
431
+ {/* ── Header ── */}
432
+ <View style={[styles.header, { paddingTop: STATUS_BAR_INSET + ms(12) }]}>
433
+ <TouchableOpacity
434
+ onPress={handleCancel}
435
+ style={styles.headerButton}
436
+ activeOpacity={0.7}
437
+ disabled={isBusy}
438
+ >
439
+ <Text style={styles.headerButtonText}>{t('cancel')}</Text>
440
+ </TouchableOpacity>
441
+ <Text style={styles.headerTitle}>{t('recordAudio')}</Text>
442
+ <View style={styles.headerButton} />
443
+ </View>
444
+
445
+ {/* ── Main Content ── */}
446
+ <View style={styles.content}>
447
+ {/* Permission requesting state */}
448
+ {state === 'requesting-permission' && (
449
+ <View style={styles.centerContent}>
450
+ <View style={styles.permissionIcon}>
451
+ <RenderIcon name="mic" size={ms(44)} color={palette.textBright} />
452
+ </View>
453
+ <Text style={styles.permissionTitle}>{t('requestingPermission')}</Text>
454
+ <Text style={styles.permissionSubtitle}>
455
+ {t('allowMicAccess')}
456
+ </Text>
457
+ </View>
458
+ )}
459
+
460
+ {/* Error state */}
461
+ {hasError && (
462
+ <View style={styles.centerContent}>
463
+ <View style={[styles.permissionIcon, { backgroundColor: palette.error + '20' }]}>
464
+ <RenderIcon name="warning" size={ms(44)} color={palette.error} />
465
+ </View>
466
+ <Text style={[styles.permissionTitle, { color: palette.error }]}>
467
+ {t('recordingError')}
468
+ </Text>
469
+ <Text style={styles.permissionSubtitle}>{errorMessage}</Text>
470
+ {errorMessage?.includes('Settings') ? (
471
+ <TouchableOpacity
472
+ style={[styles.retryButton, { backgroundColor: palette.primary }]}
473
+ onPress={() => {
474
+ Linking.openSettings();
475
+ }}
476
+ activeOpacity={0.8}
477
+ >
478
+ <Text style={styles.retryButtonText}>{t('openSettings')}</Text>
479
+ </TouchableOpacity>
480
+ ) : (
481
+ <TouchableOpacity
482
+ style={[styles.retryButton, { backgroundColor: palette.primary }]}
483
+ onPress={() => {
484
+ setErrorMessage(null);
485
+ ensurePermission();
486
+ }}
487
+ activeOpacity={0.8}
488
+ >
489
+ <Text style={styles.retryButtonText}>{t('tryAgain')}</Text>
490
+ </TouchableOpacity>
491
+ )}
492
+ </View>
493
+ )}
494
+
495
+ {/* Ready / Recording / Paused / Stopping */}
496
+ {(canRecord || isRecording || isPaused || isStopping) && (
497
+ <>
498
+ {/* Duration */}
499
+ <View style={styles.durationContainer}>
500
+ <Text style={[
501
+ styles.durationText,
502
+ (isRecording || isPaused) && styles.durationTextActive,
503
+ ]}>
504
+ {formatDuration(durationMs)}
505
+ </Text>
506
+ {isPaused && (
507
+ <View style={styles.pausedBadge}>
508
+ <Text style={styles.pausedBadgeText}>{t('pausedBadge')}</Text>
509
+ </View>
510
+ )}
511
+ </View>
512
+
513
+ {/* Waveform */}
514
+ <View style={styles.waveformContainer}>
515
+ <View style={styles.waveformRow}>
516
+ {waveformBars}
517
+ </View>
518
+ </View>
519
+
520
+ {/* Status text */}
521
+ <Text style={styles.statusText}>
522
+ {isStopping
523
+ ? t('saving')
524
+ : isRecording
525
+ ? t('recording')
526
+ : isPaused
527
+ ? t('paused')
528
+ : t('tapToStartRecording')}
529
+ </Text>
530
+
531
+ {/* Error message (inline, non-fatal) */}
532
+ {errorMessage && !hasError && (
533
+ <Text style={styles.inlineError}>{errorMessage}</Text>
534
+ )}
535
+
536
+ {/* Controls */}
537
+ <View style={styles.controls}>
538
+ {canRecord && (
539
+ <TouchableOpacity
540
+ style={styles.recordButton}
541
+ onPress={startRecording}
542
+ activeOpacity={0.8}
543
+ >
544
+ <View style={styles.recordButtonOuter}>
545
+ <View style={styles.recordButtonInner} />
546
+ </View>
547
+ </TouchableOpacity>
548
+ )}
549
+
550
+ {isRecording && (
551
+ <View style={styles.activeControls}>
552
+ {/* Cancel / discard */}
553
+ <TouchableOpacity
554
+ style={styles.controlButton}
555
+ onPress={handleCancel}
556
+ activeOpacity={0.7}
557
+ >
558
+ <View style={[styles.controlIcon, { backgroundColor: palette.error }]}>
559
+ <RenderIcon name="close" size={ms(22)} color={palette.white} strokeWidth={2.4} />
560
+ </View>
561
+ <Text style={styles.controlLabel}>{t('discard')}</Text>
562
+ </TouchableOpacity>
563
+
564
+ {/* Pause / Resume */}
565
+ {isPauseSupported && (
566
+ <TouchableOpacity
567
+ style={styles.controlButton}
568
+ onPress={handlePause}
569
+ activeOpacity={0.7}
570
+ >
571
+ <View style={[styles.controlIcon, { backgroundColor: palette.accentOrange }]}>
572
+ <RenderIcon name="pause" size={ms(22)} color={palette.white} />
573
+ </View>
574
+ <Text style={styles.controlLabel}>{t('pause')}</Text>
575
+ </TouchableOpacity>
576
+ )}
577
+
578
+ {/* Stop & save */}
579
+ <TouchableOpacity
580
+ style={styles.controlButton}
581
+ onPress={handleStop}
582
+ activeOpacity={0.7}
583
+ >
584
+ <Animated.View style={[styles.controlIcon, { backgroundColor: palette.success, opacity: pulseAnim }]}>
585
+ <RenderIcon name="stop" size={ms(20)} color={palette.white} />
586
+ </Animated.View>
587
+ <Text style={styles.controlLabel}>{t('save')}</Text>
588
+ </TouchableOpacity>
589
+ </View>
590
+ )}
591
+
592
+ {isPaused && (
593
+ <View style={styles.activeControls}>
594
+ {/* Cancel / discard */}
595
+ <TouchableOpacity
596
+ style={styles.controlButton}
597
+ onPress={handleCancel}
598
+ activeOpacity={0.7}
599
+ >
600
+ <View style={[styles.controlIcon, { backgroundColor: palette.error }]}>
601
+ <RenderIcon name="close" size={ms(22)} color={palette.white} strokeWidth={2.4} />
602
+ </View>
603
+ <Text style={styles.controlLabel}>{t('discard')}</Text>
604
+ </TouchableOpacity>
605
+
606
+ {/* Resume */}
607
+ <TouchableOpacity
608
+ style={styles.controlButton}
609
+ onPress={handleResume}
610
+ activeOpacity={0.7}
611
+ >
612
+ <View style={[styles.controlIcon, { backgroundColor: palette.primary }]}>
613
+ <RenderIcon name="play" size={ms(22)} color={palette.white} />
614
+ </View>
615
+ <Text style={styles.controlLabel}>{t('resume')}</Text>
616
+ </TouchableOpacity>
617
+
618
+ {/* Stop & save */}
619
+ <TouchableOpacity
620
+ style={styles.controlButton}
621
+ onPress={handleStop}
622
+ activeOpacity={0.7}
623
+ >
624
+ <View style={[styles.controlIcon, { backgroundColor: palette.success }]}>
625
+ <RenderIcon name="stop" size={ms(20)} color={palette.white} />
626
+ </View>
627
+ <Text style={styles.controlLabel}>{t('save')}</Text>
628
+ </TouchableOpacity>
629
+ </View>
630
+ )}
631
+
632
+ {isStopping && (
633
+ <View style={styles.stoppingIndicator}>
634
+ <Text style={styles.stoppingText}>{t('savingRecording')}</Text>
635
+ </View>
636
+ )}
637
+ </View>
638
+ </>
639
+ )}
640
+ </View>
641
+ </View>
642
+ </Modal>
643
+ );
644
+ };
645
+
646
+ // ── Styles ─────────────────────────────────────────────────────────
647
+ const makeStyles = (palette: LibraryPalette) =>
648
+ StyleSheet.create({
649
+ container: {
650
+ flex: 1,
651
+ backgroundColor: palette.appBackgroundDeepest,
652
+ },
653
+ header: {
654
+ flexDirection: 'row',
655
+ alignItems: 'center',
656
+ justifyContent: 'space-between',
657
+ paddingHorizontal: wp(16),
658
+ paddingBottom: ms(12),
659
+ },
660
+ headerButton: {
661
+ width: ms(72),
662
+ alignItems: 'center',
663
+ justifyContent: 'center',
664
+ paddingVertical: ms(8),
665
+ },
666
+ headerButtonText: {
667
+ color: palette.primary,
668
+ fontSize: fs(15),
669
+ fontWeight: '600',
670
+ },
671
+ headerTitle: {
672
+ color: palette.textBright,
673
+ fontSize: fs(17),
674
+ fontWeight: '700',
675
+ },
676
+ content: {
677
+ flex: 1,
678
+ justifyContent: 'center',
679
+ alignItems: 'center',
680
+ paddingHorizontal: wp(24),
681
+ },
682
+ centerContent: {
683
+ alignItems: 'center',
684
+ justifyContent: 'center',
685
+ },
686
+ permissionIcon: {
687
+ width: ms(96),
688
+ height: ms(96),
689
+ borderRadius: ms(48),
690
+ backgroundColor: palette.surface,
691
+ alignItems: 'center',
692
+ justifyContent: 'center',
693
+ marginBottom: ms(24),
694
+ },
695
+ permissionIconText: {
696
+ fontSize: ms(40),
697
+ },
698
+ permissionTitle: {
699
+ color: palette.textBright,
700
+ fontSize: fs(20),
701
+ fontWeight: '700',
702
+ marginBottom: ms(8),
703
+ textAlign: 'center',
704
+ },
705
+ permissionSubtitle: {
706
+ color: palette.textFaded,
707
+ fontSize: fs(14),
708
+ fontWeight: '400',
709
+ textAlign: 'center',
710
+ lineHeight: fs(20),
711
+ paddingHorizontal: wp(20),
712
+ },
713
+ retryButton: {
714
+ marginTop: ms(24),
715
+ paddingHorizontal: wp(32),
716
+ paddingVertical: ms(12),
717
+ borderRadius: ms(12),
718
+ },
719
+ retryButtonText: {
720
+ color: palette.white,
721
+ fontSize: fs(15),
722
+ fontWeight: '700',
723
+ },
724
+ durationContainer: {
725
+ alignItems: 'center',
726
+ marginBottom: ms(32),
727
+ },
728
+ durationText: {
729
+ color: palette.textDim,
730
+ fontSize: fs(44),
731
+ fontWeight: '200',
732
+ fontVariant: ['tabular-nums'],
733
+ letterSpacing: 2,
734
+ },
735
+ durationTextActive: {
736
+ color: palette.textBright,
737
+ },
738
+ pausedBadge: {
739
+ marginTop: ms(8),
740
+ paddingHorizontal: ms(12),
741
+ paddingVertical: ms(4),
742
+ borderRadius: ms(6),
743
+ backgroundColor: palette.accentOrange + '30',
744
+ },
745
+ pausedBadgeText: {
746
+ color: palette.accentOrange,
747
+ fontSize: fs(11),
748
+ fontWeight: '700',
749
+ letterSpacing: 1.5,
750
+ },
751
+ waveformContainer: {
752
+ width: '100%',
753
+ height: ms(100),
754
+ justifyContent: 'center',
755
+ alignItems: 'center',
756
+ marginBottom: ms(24),
757
+ },
758
+ waveformRow: {
759
+ flexDirection: 'row',
760
+ alignItems: 'center',
761
+ justifyContent: 'center',
762
+ gap: ms(3),
763
+ height: ms(80),
764
+ },
765
+ waveformBar: {
766
+ width: ms(4),
767
+ borderRadius: ms(2),
768
+ minHeight: ms(4),
769
+ },
770
+ statusText: {
771
+ color: palette.textFaded,
772
+ fontSize: fs(13),
773
+ fontWeight: '500',
774
+ textAlign: 'center',
775
+ marginBottom: ms(40),
776
+ },
777
+ inlineError: {
778
+ color: palette.error,
779
+ fontSize: fs(12),
780
+ fontWeight: '500',
781
+ textAlign: 'center',
782
+ marginBottom: ms(16),
783
+ paddingHorizontal: wp(20),
784
+ },
785
+ controls: {
786
+ alignItems: 'center',
787
+ width: '100%',
788
+ },
789
+ recordButton: {
790
+ alignItems: 'center',
791
+ justifyContent: 'center',
792
+ },
793
+ recordButtonOuter: {
794
+ width: ms(80),
795
+ height: ms(80),
796
+ borderRadius: ms(40),
797
+ borderWidth: ms(4),
798
+ // Ring around the red dot — must contrast with the theme-flipping screen background.
799
+ borderColor: palette.textBright,
800
+ alignItems: 'center',
801
+ justifyContent: 'center',
802
+ },
803
+ recordButtonInner: {
804
+ width: ms(56),
805
+ height: ms(56),
806
+ borderRadius: ms(28),
807
+ backgroundColor: palette.error,
808
+ },
809
+ activeControls: {
810
+ flexDirection: 'row',
811
+ alignItems: 'center',
812
+ justifyContent: 'center',
813
+ gap: wp(32),
814
+ },
815
+ controlButton: {
816
+ alignItems: 'center',
817
+ justifyContent: 'center',
818
+ },
819
+ controlIcon: {
820
+ width: ms(56),
821
+ height: ms(56),
822
+ borderRadius: ms(28),
823
+ alignItems: 'center',
824
+ justifyContent: 'center',
825
+ marginBottom: ms(6),
826
+ },
827
+ controlIconText: {
828
+ color: palette.white,
829
+ fontSize: fs(20),
830
+ fontWeight: '700',
831
+ },
832
+ controlLabel: {
833
+ color: palette.textFaded,
834
+ fontSize: fs(11),
835
+ fontWeight: '600',
836
+ },
837
+ stoppingIndicator: {
838
+ alignItems: 'center',
839
+ },
840
+ stoppingText: {
841
+ color: palette.textFaded,
842
+ fontSize: fs(14),
843
+ fontWeight: '500',
844
+ },
845
+ });