react-native-inapp-inspector 2.4.2 → 2.5.0

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 (202) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +523 -2
  2. package/dist/commonjs/capture.d.ts +2 -0
  3. package/dist/commonjs/capture.js +1 -1
  4. package/dist/commonjs/components/AnalyticsDetail.js +23 -23
  5. package/dist/commonjs/components/AnalyticsEventCard.js +1 -1
  6. package/dist/commonjs/components/AnalyticsGraph.js +44 -44
  7. package/dist/commonjs/components/AnimatedBrandOwl.js +32 -32
  8. package/dist/commonjs/components/AppHeaderLogo.js +6 -6
  9. package/dist/commonjs/components/BrandCircleIcon.js +28 -28
  10. package/dist/commonjs/components/BrandSquareIcon.js +33 -33
  11. package/dist/commonjs/components/ConsoleLogCard.js +1 -1
  12. package/dist/commonjs/components/Inspector/AnalyticsTab.js +2 -2
  13. package/dist/commonjs/components/Inspector/ConfirmationModal.js +23 -25
  14. package/dist/commonjs/components/Inspector/ConsoleTab.js +5 -5
  15. package/dist/commonjs/components/Inspector/CrashTab.js +44 -11
  16. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +1 -1
  17. package/dist/commonjs/components/Inspector/FabLauncher.js +30 -25
  18. package/dist/commonjs/components/Inspector/FloatingCaptureWidget.d.ts +3 -0
  19. package/dist/commonjs/components/Inspector/FloatingCaptureWidget.js +54 -0
  20. package/dist/commonjs/components/Inspector/GlobalCrashScreenModal.d.ts +11 -0
  21. package/dist/commonjs/components/Inspector/GlobalCrashScreenModal.js +234 -0
  22. package/dist/commonjs/components/Inspector/InspectorHeader.js +137 -127
  23. package/dist/commonjs/components/Inspector/LanguageSelectorModal.d.ts +1 -1
  24. package/dist/commonjs/components/Inspector/LanguageSelectorModal.js +114 -32
  25. package/dist/commonjs/components/Inspector/MainScreen.js +26 -19
  26. package/dist/commonjs/components/Inspector/MediaGalleryTab.js +38 -35
  27. package/dist/commonjs/components/Inspector/MediaPreviewModal.d.ts +36 -0
  28. package/dist/commonjs/components/Inspector/MediaPreviewModal.js +345 -123
  29. package/dist/commonjs/components/Inspector/NetworkTab.js +14 -14
  30. package/dist/commonjs/components/Inspector/PushCard.js +1 -1
  31. package/dist/commonjs/components/Inspector/PushDetail.js +9 -9
  32. package/dist/commonjs/components/Inspector/PushTab.js +3 -3
  33. package/dist/commonjs/components/Inspector/ReduxTab.js +9 -7
  34. package/dist/commonjs/components/Inspector/SecondaryTelemetryStrip.d.ts +2 -0
  35. package/dist/commonjs/components/Inspector/SecondaryTelemetryStrip.js +43 -0
  36. package/dist/commonjs/components/Inspector/SettingsPanel.js +102 -73
  37. package/dist/commonjs/components/Inspector/SocketCard.js +1 -1
  38. package/dist/commonjs/components/Inspector/SocketDetail.js +73 -63
  39. package/dist/commonjs/components/Inspector/SocketTab.js +4 -4
  40. package/dist/commonjs/components/Inspector/StorageTab.js +10 -10
  41. package/dist/commonjs/components/Inspector/SupportModal.js +2 -2
  42. package/dist/commonjs/components/Inspector/SupportPage.js +1 -1
  43. package/dist/commonjs/components/JsonViewer.js +47 -45
  44. package/dist/commonjs/components/LogCard.js +22 -20
  45. package/dist/commonjs/components/ModuleErrorBoundary.js +5 -5
  46. package/dist/commonjs/components/NetworkIcons.d.ts +27 -0
  47. package/dist/commonjs/components/NetworkIcons.js +103 -5
  48. package/dist/commonjs/constants/version.d.ts +1 -1
  49. package/dist/commonjs/constants/version.js +1 -1
  50. package/dist/commonjs/crash.d.ts +1 -1
  51. package/dist/commonjs/crash.js +1 -1
  52. package/dist/commonjs/customHooks/crashHandler.d.ts +2 -1
  53. package/dist/commonjs/customHooks/crashHandler.js +7 -7
  54. package/dist/commonjs/editor.d.ts +13 -0
  55. package/dist/commonjs/editor.js +1 -0
  56. package/dist/commonjs/helpers/index.d.ts +1 -0
  57. package/dist/commonjs/helpers/index.js +1 -1
  58. package/dist/commonjs/helpers/settingsStore.js +1 -1
  59. package/dist/commonjs/helpers/telemetry.js +1 -1
  60. package/dist/commonjs/i18n/index.js +1 -1
  61. package/dist/commonjs/i18n/locales/ar.json +1 -1
  62. package/dist/commonjs/i18n/locales/bn.json +1 -1
  63. package/dist/commonjs/i18n/locales/de.json +1 -1
  64. package/dist/commonjs/i18n/locales/en.json +1 -1
  65. package/dist/commonjs/i18n/locales/es.json +1 -1
  66. package/dist/commonjs/i18n/locales/fr.json +1 -1
  67. package/dist/commonjs/i18n/locales/gu.json +1 -1
  68. package/dist/commonjs/i18n/locales/hi.json +1 -1
  69. package/dist/commonjs/i18n/locales/id.json +1 -1
  70. package/dist/commonjs/i18n/locales/it.json +1 -1
  71. package/dist/commonjs/i18n/locales/ja.json +1 -1
  72. package/dist/commonjs/i18n/locales/kn.json +1 -1
  73. package/dist/commonjs/i18n/locales/ko.json +1 -1
  74. package/dist/commonjs/i18n/locales/ml.json +1 -1
  75. package/dist/commonjs/i18n/locales/mr.json +1 -1
  76. package/dist/commonjs/i18n/locales/nl.json +1 -1
  77. package/dist/commonjs/i18n/locales/pa.json +1 -1
  78. package/dist/commonjs/i18n/locales/pl.json +1 -1
  79. package/dist/commonjs/i18n/locales/pt.json +1 -1
  80. package/dist/commonjs/i18n/locales/ru.json +1 -1
  81. package/dist/commonjs/i18n/locales/ta.json +1 -1
  82. package/dist/commonjs/i18n/locales/te.json +1 -1
  83. package/dist/commonjs/i18n/locales/tr.json +1 -1
  84. package/dist/commonjs/i18n/locales/vi.json +1 -1
  85. package/dist/commonjs/i18n/locales/zh.json +1 -1
  86. package/dist/commonjs/index.d.ts +4 -1
  87. package/dist/commonjs/index.js +3 -3
  88. package/dist/commonjs/native/NativeInspector.d.ts +3 -0
  89. package/dist/commonjs/native/NativeInspector.js +1 -1
  90. package/dist/commonjs/native/NativeNetworkInspector.d.ts +4 -0
  91. package/dist/commonjs/styles/AppColors.d.ts +42 -2
  92. package/dist/commonjs/styles/AppColors.js +1 -1
  93. package/dist/commonjs/styles/common.js +1 -1
  94. package/dist/commonjs/styles/index.d.ts +7 -0
  95. package/dist/commonjs/styles/index.js +1 -1
  96. package/dist/commonjs/types/editor.d.ts +96 -0
  97. package/dist/commonjs/types/editor.js +1 -0
  98. package/dist/commonjs/types/interfaces.d.ts +21 -0
  99. package/dist/esm/capture.d.ts +2 -0
  100. package/dist/esm/capture.js +1 -1
  101. package/dist/esm/components/AnalyticsDetail.js +31 -31
  102. package/dist/esm/components/AnalyticsEventCard.js +1 -1
  103. package/dist/esm/components/AnalyticsGraph.js +74 -74
  104. package/dist/esm/components/AnimatedBrandOwl.js +114 -114
  105. package/dist/esm/components/AppHeaderLogo.js +7 -7
  106. package/dist/esm/components/BrandCircleIcon.js +70 -70
  107. package/dist/esm/components/BrandSquareIcon.js +61 -61
  108. package/dist/esm/components/ConsoleLogCard.js +1 -1
  109. package/dist/esm/components/Inspector/AnalyticsTab.js +14 -14
  110. package/dist/esm/components/Inspector/ConfirmationModal.js +23 -25
  111. package/dist/esm/components/Inspector/ConsoleTab.js +17 -17
  112. package/dist/esm/components/Inspector/CrashTab.js +202 -169
  113. package/dist/esm/components/Inspector/DeviceInfoTab.js +75 -75
  114. package/dist/esm/components/Inspector/FabLauncher.js +56 -51
  115. package/dist/esm/components/Inspector/FloatingCaptureWidget.d.ts +3 -0
  116. package/dist/esm/components/Inspector/FloatingCaptureWidget.js +54 -0
  117. package/dist/esm/components/Inspector/GlobalCrashScreenModal.d.ts +11 -0
  118. package/dist/esm/components/Inspector/GlobalCrashScreenModal.js +234 -0
  119. package/dist/esm/components/Inspector/InspectorHeader.js +162 -152
  120. package/dist/esm/components/Inspector/LanguageSelectorModal.d.ts +1 -1
  121. package/dist/esm/components/Inspector/LanguageSelectorModal.js +140 -58
  122. package/dist/esm/components/Inspector/MainScreen.js +61 -54
  123. package/dist/esm/components/Inspector/MediaGalleryTab.js +55 -52
  124. package/dist/esm/components/Inspector/MediaPreviewModal.d.ts +36 -0
  125. package/dist/esm/components/Inspector/MediaPreviewModal.js +345 -123
  126. package/dist/esm/components/Inspector/NetworkTab.js +74 -74
  127. package/dist/esm/components/Inspector/PushCard.js +1 -1
  128. package/dist/esm/components/Inspector/PushDetail.js +18 -18
  129. package/dist/esm/components/Inspector/PushTab.js +1 -1
  130. package/dist/esm/components/Inspector/ReduxTab.js +18 -16
  131. package/dist/esm/components/Inspector/SecondaryTelemetryStrip.d.ts +2 -0
  132. package/dist/esm/components/Inspector/SecondaryTelemetryStrip.js +43 -0
  133. package/dist/esm/components/Inspector/SettingsPanel.js +224 -195
  134. package/dist/esm/components/Inspector/SocketCard.js +8 -8
  135. package/dist/esm/components/Inspector/SocketDetail.js +106 -96
  136. package/dist/esm/components/Inspector/SocketTab.js +18 -18
  137. package/dist/esm/components/Inspector/StorageTab.js +47 -47
  138. package/dist/esm/components/Inspector/SupportModal.js +64 -64
  139. package/dist/esm/components/Inspector/SupportPage.js +5 -5
  140. package/dist/esm/components/JsonViewer.js +51 -49
  141. package/dist/esm/components/LogCard.js +41 -39
  142. package/dist/esm/components/ModuleErrorBoundary.js +10 -10
  143. package/dist/esm/components/NetworkIcons.d.ts +27 -0
  144. package/dist/esm/components/NetworkIcons.js +101 -3
  145. package/dist/esm/constants/version.d.ts +1 -1
  146. package/dist/esm/constants/version.js +1 -1
  147. package/dist/esm/crash.d.ts +1 -1
  148. package/dist/esm/crash.js +1 -1
  149. package/dist/esm/customHooks/crashHandler.d.ts +2 -1
  150. package/dist/esm/customHooks/crashHandler.js +7 -7
  151. package/dist/esm/editor.d.ts +13 -0
  152. package/dist/esm/editor.js +1 -0
  153. package/dist/esm/helpers/index.d.ts +1 -0
  154. package/dist/esm/helpers/index.js +4 -4
  155. package/dist/esm/helpers/settingsStore.js +1 -1
  156. package/dist/esm/helpers/telemetry.js +1 -1
  157. package/dist/esm/i18n/index.js +1 -1
  158. package/dist/esm/i18n/locales/ar.json +1 -1
  159. package/dist/esm/i18n/locales/bn.json +1 -1
  160. package/dist/esm/i18n/locales/de.json +1 -1
  161. package/dist/esm/i18n/locales/en.json +1 -1
  162. package/dist/esm/i18n/locales/es.json +1 -1
  163. package/dist/esm/i18n/locales/fr.json +1 -1
  164. package/dist/esm/i18n/locales/gu.json +1 -1
  165. package/dist/esm/i18n/locales/hi.json +1 -1
  166. package/dist/esm/i18n/locales/id.json +1 -1
  167. package/dist/esm/i18n/locales/it.json +1 -1
  168. package/dist/esm/i18n/locales/ja.json +1 -1
  169. package/dist/esm/i18n/locales/kn.json +1 -1
  170. package/dist/esm/i18n/locales/ko.json +1 -1
  171. package/dist/esm/i18n/locales/ml.json +1 -1
  172. package/dist/esm/i18n/locales/mr.json +1 -1
  173. package/dist/esm/i18n/locales/nl.json +1 -1
  174. package/dist/esm/i18n/locales/pa.json +1 -1
  175. package/dist/esm/i18n/locales/pl.json +1 -1
  176. package/dist/esm/i18n/locales/pt.json +1 -1
  177. package/dist/esm/i18n/locales/ru.json +1 -1
  178. package/dist/esm/i18n/locales/ta.json +1 -1
  179. package/dist/esm/i18n/locales/te.json +1 -1
  180. package/dist/esm/i18n/locales/tr.json +1 -1
  181. package/dist/esm/i18n/locales/vi.json +1 -1
  182. package/dist/esm/i18n/locales/zh.json +1 -1
  183. package/dist/esm/index.d.ts +4 -1
  184. package/dist/esm/index.js +5 -5
  185. package/dist/esm/native/NativeInspector.d.ts +3 -0
  186. package/dist/esm/native/NativeInspector.js +1 -1
  187. package/dist/esm/native/NativeNetworkInspector.d.ts +4 -0
  188. package/dist/esm/styles/AppColors.d.ts +42 -2
  189. package/dist/esm/styles/AppColors.js +1 -1
  190. package/dist/esm/styles/common.js +1 -1
  191. package/dist/esm/styles/index.d.ts +7 -0
  192. package/dist/esm/styles/index.js +1 -1
  193. package/dist/esm/types/editor.d.ts +96 -0
  194. package/dist/esm/types/editor.js +1 -0
  195. package/dist/esm/types/interfaces.d.ts +21 -0
  196. package/editor.d.ts +1 -0
  197. package/editor.js +1 -0
  198. package/ios/NetworkInspectorModule.mm +429 -0
  199. package/package.json +10 -1
  200. package/react-native-inapp-inspector.podspec +1 -1
  201. package/src/native/NativeInspector.ts +57 -4
  202. package/src/native/NativeNetworkInspector.ts +4 -0
@@ -1259,13 +1259,534 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
1259
1259
  promise.resolve(map)
1260
1260
  }
1261
1261
  }
1262
+ // ─────────────────────────────────────────────────────────────────────────
1263
+ // 100% Native Media Editor: Photo Editing (Bitmap + ColorMatrix + Canvas)
1264
+ // ─────────────────────────────────────────────────────────────────────────
1262
1265
 
1266
+ @ReactMethod
1267
+ fun editPhoto(options: ReadableMap, promise: Promise) {
1268
+ captureExecutor.execute {
1269
+ try {
1270
+ val rawUri = if (options.hasKey("uri")) options.getString("uri") else null
1271
+ if (rawUri.isNullOrEmpty()) {
1272
+ promise.reject("INVALID_URI", "Image URI is missing or empty")
1273
+ return@execute
1274
+ }
1275
+
1276
+ val cleanPath = when {
1277
+ rawUri.startsWith("file://") -> Uri.parse(rawUri).path ?: rawUri.substring(7)
1278
+ else -> rawUri
1279
+ }
1280
+
1281
+ val sourceBitmap = if (cleanPath.startsWith("content://")) {
1282
+ val input = reactContext.contentResolver.openInputStream(Uri.parse(cleanPath))
1283
+ android.graphics.BitmapFactory.decodeStream(input)
1284
+ } else {
1285
+ android.graphics.BitmapFactory.decodeFile(cleanPath)
1286
+ }
1287
+
1288
+ if (sourceBitmap == null) {
1289
+ promise.reject("IMAGE_LOAD_FAILED", "Failed to decode source image")
1290
+ return@execute
1291
+ }
1292
+
1293
+ // 1. Compute Matrix for Rotation and Flip
1294
+ val matrix = Matrix()
1295
+ val rotation = if (options.hasKey("rotation")) options.getInt("rotation") else 0
1296
+ val flipH = if (options.hasKey("flipHorizontal")) options.getBoolean("flipHorizontal") else false
1297
+ val flipV = if (options.hasKey("flipVertical")) options.getBoolean("flipVertical") else false
1298
+
1299
+ if (rotation != 0) {
1300
+ matrix.postRotate(rotation.toFloat())
1301
+ }
1302
+ val scaleX = if (flipH) -1f else 1f
1303
+ val scaleY = if (flipV) -1f else 1f
1304
+ if (flipH || flipV) {
1305
+ matrix.postScale(scaleX, scaleY)
1306
+ }
1307
+
1308
+ // 2. Compute Crop Bounds
1309
+ var cropX = 0
1310
+ var cropY = 0
1311
+ var cropW = sourceBitmap.width
1312
+ var cropH = sourceBitmap.height
1313
+
1314
+ if (options.hasKey("crop")) {
1315
+ val cropMap = options.getMap("crop")
1316
+ if (cropMap != null) {
1317
+ val isNorm = if (cropMap.hasKey("isNormalized")) cropMap.getBoolean("isNormalized") else false
1318
+ var x = if (cropMap.hasKey("x")) cropMap.getDouble("x").toFloat() else 0f
1319
+ var y = if (cropMap.hasKey("y")) cropMap.getDouble("y").toFloat() else 0f
1320
+ var w = if (cropMap.hasKey("width")) cropMap.getDouble("width").toFloat() else cropW.toFloat()
1321
+ var h = if (cropMap.hasKey("height")) cropMap.getDouble("height").toFloat() else cropH.toFloat()
1322
+
1323
+ if (isNorm) {
1324
+ x *= cropW
1325
+ y *= cropH
1326
+ w *= cropW
1327
+ h *= cropH
1328
+ }
1329
+
1330
+ cropX = x.toInt().coerceIn(0, cropW - 1)
1331
+ cropY = y.toInt().coerceIn(0, cropH - 1)
1332
+ cropW = w.toInt().coerceIn(1, sourceBitmap.width - cropX)
1333
+ cropH = h.toInt().coerceIn(1, sourceBitmap.height - cropY)
1334
+ }
1335
+ }
1336
+
1337
+ val croppedBitmap = Bitmap.createBitmap(sourceBitmap, cropX, cropY, cropW, cropH, matrix, true)
1338
+
1339
+ // 3. Color Grading & Adjustments via ColorMatrix
1340
+ val finalColorMatrix = android.graphics.ColorMatrix()
1341
+ var hasColorTransforms = false
1342
+
1343
+ if (options.hasKey("adjustments")) {
1344
+ val adj = options.getMap("adjustments")
1345
+ if (adj != null) {
1346
+ val brightness = if (adj.hasKey("brightness")) adj.getDouble("brightness").toFloat() else 0f
1347
+ val contrast = if (adj.hasKey("contrast")) adj.getDouble("contrast").toFloat() else 1f
1348
+ val saturation = if (adj.hasKey("saturation")) adj.getDouble("saturation").toFloat() else 1f
1349
+ val temperature = if (adj.hasKey("temperature")) adj.getDouble("temperature").toFloat() else 0f
1350
+
1351
+ if (saturation != 1f) {
1352
+ finalColorMatrix.setSaturation(saturation)
1353
+ hasColorTransforms = true
1354
+ }
1355
+ if (contrast != 1f || brightness != 0f) {
1356
+ val scale = contrast
1357
+ val translate = (brightness * 255f) + (1f - scale) * 128f
1358
+ val cm = android.graphics.ColorMatrix(floatArrayOf(
1359
+ scale, 0f, 0f, 0f, translate,
1360
+ 0f, scale, 0f, 0f, translate,
1361
+ 0f, 0f, scale, 0f, translate,
1362
+ 0f, 0f, 0f, 1f, 0f
1363
+ ))
1364
+ finalColorMatrix.postConcat(cm)
1365
+ hasColorTransforms = true
1366
+ }
1367
+ if (temperature != 0f) {
1368
+ val warmScaleR = 1f + (temperature * 0.15f)
1369
+ val coolScaleB = 1f - (temperature * 0.15f)
1370
+ val tempCm = android.graphics.ColorMatrix(floatArrayOf(
1371
+ warmScaleR, 0f, 0f, 0f, 0f,
1372
+ 0f, 1f, 0f, 0f, 0f,
1373
+ 0f, 0f, coolScaleB, 0f, 0f,
1374
+ 0f, 0f, 0f, 1f, 0f
1375
+ ))
1376
+ finalColorMatrix.postConcat(tempCm)
1377
+ hasColorTransforms = true
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ // 4. Preset Filters
1383
+ val filterPreset = if (options.hasKey("filterPreset")) options.getString("filterPreset") else "none"
1384
+ if (!filterPreset.isNullOrEmpty() && filterPreset != "none") {
1385
+ val presetCm = android.graphics.ColorMatrix()
1386
+ when (filterPreset) {
1387
+ "mono", "noir" -> {
1388
+ presetCm.setSaturation(0f)
1389
+ if (filterPreset == "noir") {
1390
+ presetCm.postConcat(android.graphics.ColorMatrix(floatArrayOf(
1391
+ 1.2f, 0f, 0f, 0f, -20f,
1392
+ 0f, 1.2f, 0f, 0f, -20f,
1393
+ 0f, 0f, 1.2f, 0f, -20f,
1394
+ 0f, 0f, 0f, 1f, 0f
1395
+ )))
1396
+ }
1397
+ hasColorTransforms = true
1398
+ }
1399
+ "sepia" -> {
1400
+ val sepiaMatrix = floatArrayOf(
1401
+ 0.393f, 0.769f, 0.189f, 0f, 0f,
1402
+ 0.349f, 0.686f, 0.168f, 0f, 0f,
1403
+ 0.272f, 0.534f, 0.131f, 0f, 0f,
1404
+ 0f, 0f, 0f, 1f, 0f
1405
+ )
1406
+ presetCm.set(sepiaMatrix)
1407
+ hasColorTransforms = true
1408
+ }
1409
+ "vibrant" -> {
1410
+ presetCm.setSaturation(1.4f)
1411
+ hasColorTransforms = true
1412
+ }
1413
+ "warm" -> {
1414
+ presetCm.set(floatArrayOf(
1415
+ 1.15f, 0f, 0f, 0f, 0f,
1416
+ 0f, 1.05f, 0f, 0f, 0f,
1417
+ 0f, 0f, 0.88f, 0f, 0f,
1418
+ 0f, 0f, 0f, 1f, 0f
1419
+ ))
1420
+ hasColorTransforms = true
1421
+ }
1422
+ "cool" -> {
1423
+ presetCm.set(floatArrayOf(
1424
+ 0.90f, 0f, 0f, 0f, 0f,
1425
+ 0f, 1.0f, 0f, 0f, 0f,
1426
+ 0f, 0f, 1.20f, 0f, 0f,
1427
+ 0f, 0f, 0f, 1f, 0f
1428
+ ))
1429
+ hasColorTransforms = true
1430
+ }
1431
+ "fade" -> {
1432
+ presetCm.set(floatArrayOf(
1433
+ 0.9f, 0f, 0f, 0f, 25f,
1434
+ 0f, 0.9f, 0f, 0f, 25f,
1435
+ 0f, 0f, 0.9f, 0f, 25f,
1436
+ 0f, 0f, 0f, 1f, 0f
1437
+ ))
1438
+ hasColorTransforms = true
1439
+ }
1440
+ }
1441
+ finalColorMatrix.postConcat(presetCm)
1442
+ }
1443
+
1444
+ // Render into final Bitmap
1445
+ val finalBitmap: Bitmap
1446
+ if (hasColorTransforms) {
1447
+ finalBitmap = Bitmap.createBitmap(croppedBitmap.width, croppedBitmap.height, Bitmap.Config.ARGB_8888)
1448
+ val canvas = Canvas(finalBitmap)
1449
+ val paint = android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG).apply {
1450
+ colorFilter = android.graphics.ColorMatrixColorFilter(finalColorMatrix)
1451
+ }
1452
+ canvas.drawBitmap(croppedBitmap, 0f, 0f, paint)
1453
+ } else {
1454
+ finalBitmap = croppedBitmap
1455
+ }
1456
+
1457
+ // 5. Apply Redactions & Annotations Overlay if provided
1458
+ val mutableBitmap = if (finalBitmap.isMutable) finalBitmap else finalBitmap.copy(Bitmap.Config.ARGB_8888, true)
1459
+ val canvas = android.graphics.Canvas(mutableBitmap)
1460
+ val overlayPaint = android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG)
1461
+
1462
+ if (options.hasKey("redactions")) {
1463
+ val redactions = options.getArray("redactions")
1464
+ if (redactions != null) {
1465
+ overlayPaint.color = android.graphics.Color.BLACK
1466
+ overlayPaint.style = android.graphics.Paint.Style.FILL
1467
+ for (i in 0 until redactions.size()) {
1468
+ val box = redactions.getMap(i) ?: continue
1469
+ var x = if (box.hasKey("x")) box.getDouble("x").toFloat() else 0f
1470
+ var y = if (box.hasKey("y")) box.getDouble("y").toFloat() else 0f
1471
+ var w = if (box.hasKey("width")) box.getDouble("width").toFloat() else 0f
1472
+ var h = if (box.hasKey("height")) box.getDouble("height").toFloat() else 0f
1473
+ val isNorm = if (box.hasKey("isNormalized")) box.getBoolean("isNormalized") else false
1474
+ if (isNorm) {
1475
+ x *= mutableBitmap.width
1476
+ y *= mutableBitmap.height
1477
+ w *= mutableBitmap.width
1478
+ h *= mutableBitmap.height
1479
+ }
1480
+ canvas.drawRect(x, y, x + w, y + h, overlayPaint)
1481
+ }
1482
+ }
1483
+ }
1484
+
1485
+ if (options.hasKey("annotations")) {
1486
+ val annotations = options.getArray("annotations")
1487
+ if (annotations != null) {
1488
+ overlayPaint.color = android.graphics.Color.RED
1489
+ overlayPaint.style = android.graphics.Paint.Style.STROKE
1490
+ overlayPaint.strokeWidth = 6f
1491
+ for (i in 0 until annotations.size()) {
1492
+ val ann = annotations.getMap(i) ?: continue
1493
+ var x = if (ann.hasKey("x")) ann.getDouble("x").toFloat() else 0f
1494
+ var y = if (ann.hasKey("y")) ann.getDouble("y").toFloat() else 0f
1495
+ var w = if (ann.hasKey("width")) ann.getDouble("width").toFloat() else 0f
1496
+ var h = if (ann.hasKey("height")) ann.getDouble("height").toFloat() else 0f
1497
+ val isNorm = if (ann.hasKey("isNormalized")) ann.getBoolean("isNormalized") else false
1498
+ if (isNorm) {
1499
+ x *= mutableBitmap.width
1500
+ y *= mutableBitmap.height
1501
+ w *= mutableBitmap.width
1502
+ h *= mutableBitmap.height
1503
+ }
1504
+ canvas.drawRect(x, y, x + w, y + h, overlayPaint)
1505
+ }
1506
+ }
1507
+ }
1508
+
1509
+ val format = if (options.hasKey("format")) options.getString("format")?.lowercase() ?: "jpeg" else "jpeg"
1510
+ val quality = if (options.hasKey("quality")) options.getDouble("quality").toFloat() else 0.9f
1511
+ val qualityInt = (quality * 100).toInt().coerceIn(1, 100)
1512
+
1513
+ val compressFormat: Bitmap.CompressFormat
1514
+ val ext: String
1515
+ val mimeType: String
1516
+
1517
+ when (format) {
1518
+ "png" -> {
1519
+ compressFormat = Bitmap.CompressFormat.PNG
1520
+ ext = "png"
1521
+ mimeType = "image/png"
1522
+ }
1523
+ "webp" -> {
1524
+ compressFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
1525
+ Bitmap.CompressFormat.WEBP_LOSSY
1526
+ } else {
1527
+ @Suppress("DEPRECATION")
1528
+ Bitmap.CompressFormat.WEBP
1529
+ }
1530
+ ext = "webp"
1531
+ mimeType = "image/webp"
1532
+ }
1533
+ else -> {
1534
+ compressFormat = Bitmap.CompressFormat.JPEG
1535
+ ext = "jpg"
1536
+ mimeType = "image/jpeg"
1537
+ }
1538
+ }
1539
+
1540
+ val timestamp = java.text.SimpleDateFormat("yyyyMMdd_HHmmss_SSS", java.util.Locale.US).format(java.util.Date())
1541
+ val outFile = File(getCapturesDirectory(), "edit_${timestamp}.$ext")
1542
+ FileOutputStream(outFile).use { out ->
1543
+ mutableBitmap.compress(compressFormat, qualityInt, out)
1544
+ out.flush()
1545
+ }
1546
+
1547
+ val resultMap = Arguments.createMap().apply {
1548
+ putString("uri", Uri.fromFile(outFile).toString())
1549
+ putInt("width", mutableBitmap.width)
1550
+ putInt("height", mutableBitmap.height)
1551
+ putDouble("size", outFile.length().toDouble())
1552
+ putString("mimeType", mimeType)
1553
+ putString("format", format)
1554
+ }
1555
+ promise.resolve(resultMap)
1556
+ } catch (e: Exception) {
1557
+ promise.reject("PHOTO_EDIT_ERROR", e.message ?: "Photo editing failed", e)
1558
+ }
1559
+ }
1560
+ }
1561
+
1562
+ // ─────────────────────────────────────────────────────────────────────────
1563
+ // 100% Native Media Editor: Video Trimming (MediaExtractor + MediaMuxer)
1564
+ // ─────────────────────────────────────────────────────────────────────────
1263
1565
 
1264
1566
  @ReactMethod
1265
- fun addListener(eventName: String) {
1266
- // Required for React Native NativeEventEmitter
1567
+ fun trimVideo(options: ReadableMap, promise: Promise) {
1568
+ captureExecutor.execute {
1569
+ var extractor: android.media.MediaExtractor? = null
1570
+ var muxer: android.media.MediaMuxer? = null
1571
+ try {
1572
+ val rawUri = if (options.hasKey("uri")) options.getString("uri") else null
1573
+ if (rawUri.isNullOrEmpty()) {
1574
+ promise.reject("INVALID_URI", "Video URI is missing or empty")
1575
+ return@execute
1576
+ }
1577
+
1578
+ val cleanPath = when {
1579
+ rawUri.startsWith("file://") -> Uri.parse(rawUri).path ?: rawUri.substring(7)
1580
+ else -> rawUri
1581
+ }
1582
+
1583
+ var startMs = if (options.hasKey("startTimeMs")) options.getDouble("startTimeMs").toLong() else 0L
1584
+ var endMs = if (options.hasKey("endTimeMs")) options.getDouble("endTimeMs").toLong() else 0L
1585
+ val muteAudio = (if (options.hasKey("mute")) options.getBoolean("mute") else false) ||
1586
+ (if (options.hasKey("muteAudio")) options.getBoolean("muteAudio") else false)
1587
+
1588
+ extractor = android.media.MediaExtractor()
1589
+ if (cleanPath.startsWith("content://")) {
1590
+ extractor.setDataSource(reactContext, Uri.parse(cleanPath), null)
1591
+ } else {
1592
+ extractor.setDataSource(cleanPath)
1593
+ }
1594
+
1595
+ var totalDurationUs = 0L
1596
+ val trackCount = extractor.trackCount
1597
+ for (i in 0 until trackCount) {
1598
+ val format = extractor.getTrackFormat(i)
1599
+ if (format.containsKey(android.media.MediaFormat.KEY_DURATION)) {
1600
+ val dur = format.getLong(android.media.MediaFormat.KEY_DURATION)
1601
+ if (dur > totalDurationUs) {
1602
+ totalDurationUs = dur
1603
+ }
1604
+ }
1605
+ }
1606
+
1607
+ val totalDurationMs = totalDurationUs / 1000L
1608
+ if (endMs <= 0 || (totalDurationMs > 0 && endMs > totalDurationMs)) {
1609
+ endMs = if (totalDurationMs > 0) totalDurationMs else endMs
1610
+ }
1611
+ if (startMs >= endMs && endMs > 0) {
1612
+ startMs = 0L
1613
+ }
1614
+
1615
+ val startUs = startMs * 1000L
1616
+ val endUs = if (endMs > 0) endMs * 1000L else Long.MAX_VALUE
1617
+
1618
+ val timestamp = java.text.SimpleDateFormat("yyyyMMdd_HHmmss_SSS", java.util.Locale.US).format(java.util.Date())
1619
+ val outFile = File(getCapturesDirectory(), "trim_${timestamp}.mp4")
1620
+
1621
+ muxer = android.media.MediaMuxer(outFile.absolutePath, android.media.MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
1622
+
1623
+ val trackIndexMap = HashMap<Int, Int>()
1624
+ var videoWidth = 1080
1625
+ var videoHeight = 1920
1626
+
1627
+ for (i in 0 until trackCount) {
1628
+ val format = extractor.getTrackFormat(i)
1629
+ val mime = format.getString(android.media.MediaFormat.KEY_MIME) ?: ""
1630
+ if (mime.startsWith("video/")) {
1631
+ extractor.selectTrack(i)
1632
+ val muxerTrackIndex = muxer.addTrack(format)
1633
+ trackIndexMap[i] = muxerTrackIndex
1634
+ if (format.containsKey(android.media.MediaFormat.KEY_WIDTH)) {
1635
+ videoWidth = format.getInteger(android.media.MediaFormat.KEY_WIDTH)
1636
+ }
1637
+ if (format.containsKey(android.media.MediaFormat.KEY_HEIGHT)) {
1638
+ videoHeight = format.getInteger(android.media.MediaFormat.KEY_HEIGHT)
1639
+ }
1640
+ } else if (mime.startsWith("audio/") && !muteAudio) {
1641
+ extractor.selectTrack(i)
1642
+ val muxerTrackIndex = muxer.addTrack(format)
1643
+ trackIndexMap[i] = muxerTrackIndex
1644
+ }
1645
+ }
1646
+
1647
+ muxer.start()
1648
+
1649
+ extractor.seekTo(startUs, android.media.MediaExtractor.SEEK_TO_CLOSEST_SYNC)
1650
+
1651
+ val bufferSize = 1024 * 1024
1652
+ val buffer = java.nio.ByteBuffer.allocateDirect(bufferSize)
1653
+ val bufferInfo = android.media.MediaCodec.BufferInfo()
1654
+
1655
+ while (true) {
1656
+ val trackIndex = extractor.sampleTrackIndex
1657
+ if (trackIndex < 0) break
1658
+
1659
+ val sampleTime = extractor.sampleTime
1660
+ if (sampleTime > endUs) break
1661
+
1662
+ if (sampleTime >= startUs) {
1663
+ bufferInfo.offset = 0
1664
+ bufferInfo.size = extractor.readSampleData(buffer, 0)
1665
+ if (bufferInfo.size > 0) {
1666
+ bufferInfo.presentationTimeUs = sampleTime - startUs
1667
+ bufferInfo.flags = extractor.sampleFlags
1668
+ val muxerTrackIndex = trackIndexMap[trackIndex]
1669
+ if (muxerTrackIndex != null) {
1670
+ muxer.writeSampleData(muxerTrackIndex, buffer, bufferInfo)
1671
+ }
1672
+ }
1673
+ }
1674
+ extractor.advance()
1675
+ }
1676
+
1677
+ muxer.stop()
1678
+ muxer.release()
1679
+ muxer = null
1680
+
1681
+ extractor.release()
1682
+ extractor = null
1683
+
1684
+ val durationMs = if (endMs > startMs) (endMs - startMs) else totalDurationMs
1685
+ val resultMap = Arguments.createMap().apply {
1686
+ putString("uri", Uri.fromFile(outFile).toString())
1687
+ putDouble("durationMs", durationMs.toDouble())
1688
+ putDouble("size", outFile.length().toDouble())
1689
+ putInt("width", videoWidth)
1690
+ putInt("height", videoHeight)
1691
+ putString("format", "mp4")
1692
+ }
1693
+ promise.resolve(resultMap)
1694
+ } catch (e: Exception) {
1695
+ try { muxer?.release() } catch (_: Exception) {}
1696
+ try { extractor?.release() } catch (_: Exception) {}
1697
+ promise.reject("VIDEO_TRIM_ERROR", e.message ?: "Video trim failed", e)
1698
+ }
1699
+ }
1267
1700
  }
1268
1701
 
1702
+ // ─────────────────────────────────────────────────────────────────────────
1703
+ // 100% Native Media Editor: Filmstrip Generator (MediaMetadataRetriever)
1704
+ // ─────────────────────────────────────────────────────────────────────────
1705
+
1706
+ @ReactMethod
1707
+ fun generateFilmstrip(options: ReadableMap, promise: Promise) {
1708
+ captureExecutor.execute {
1709
+ var retriever: MediaMetadataRetriever? = null
1710
+ try {
1711
+ val rawUri = if (options.hasKey("uri")) options.getString("uri") else null
1712
+ if (rawUri.isNullOrEmpty()) {
1713
+ promise.reject("INVALID_URI", "Video URI is missing or empty")
1714
+ return@execute
1715
+ }
1716
+
1717
+ val cleanPath = when {
1718
+ rawUri.startsWith("file://") -> Uri.parse(rawUri).path ?: rawUri.substring(7)
1719
+ else -> rawUri
1720
+ }
1721
+
1722
+ val count = if (options.hasKey("count")) options.getInt("count").coerceIn(1, 50) else 10
1723
+ val targetW = if (options.hasKey("maxWidth")) options.getInt("maxWidth")
1724
+ else if (options.hasKey("targetWidth")) options.getInt("targetWidth") else 120
1725
+ val targetH = if (options.hasKey("maxHeight")) options.getInt("maxHeight")
1726
+ else if (options.hasKey("targetHeight")) options.getInt("targetHeight") else 120
1727
+ val qualityDouble = if (options.hasKey("quality")) options.getDouble("quality") else 0.7
1728
+ val qualityInt = (qualityDouble * 100).toInt().coerceIn(10, 100)
1729
+
1730
+ retriever = MediaMetadataRetriever()
1731
+ if (cleanPath.startsWith("content://")) {
1732
+ retriever.setDataSource(reactContext, Uri.parse(cleanPath))
1733
+ } else {
1734
+ retriever.setDataSource(cleanPath)
1735
+ }
1736
+
1737
+ val durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
1738
+ val durationMs = durationStr?.toLongOrNull() ?: 0L
1739
+ if (durationMs <= 0L) {
1740
+ promise.reject("INVALID_DURATION", "Unable to determine video duration")
1741
+ return@execute
1742
+ }
1743
+
1744
+ val durationUs = durationMs * 1000L
1745
+ val stepUs = durationUs / count.coerceAtLeast(1)
1746
+ val thumbnailsArray = Arguments.createArray()
1747
+
1748
+ for (i in 0 until count) {
1749
+ val targetUs = i * stepUs
1750
+ val frameBitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
1751
+ retriever.getScaledFrameAtTime(targetUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC, targetW, targetH)
1752
+ } else {
1753
+ val original = retriever.getFrameAtTime(targetUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
1754
+ if (original != null) {
1755
+ Bitmap.createScaledBitmap(original, targetW, targetH, true)
1756
+ } else null
1757
+ }
1758
+
1759
+ if (frameBitmap != null) {
1760
+ val stream = ByteArrayOutputStream()
1761
+ frameBitmap.compress(Bitmap.CompressFormat.JPEG, qualityInt, stream)
1762
+ val b64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP)
1763
+ val dataUrl = "data:image/jpeg;base64,$b64"
1764
+
1765
+ val thumbMap = Arguments.createMap().apply {
1766
+ putDouble("timeMs", (targetUs / 1000L).toDouble())
1767
+ putString("uri", dataUrl)
1768
+ putInt("index", i)
1769
+ }
1770
+ thumbnailsArray.pushMap(thumbMap)
1771
+ }
1772
+ }
1773
+
1774
+ retriever.release()
1775
+ retriever = null
1776
+
1777
+ val resultMap = Arguments.createMap().apply {
1778
+ putArray("thumbnails", thumbnailsArray)
1779
+ putDouble("durationMs", durationMs.toDouble())
1780
+ }
1781
+ promise.resolve(resultMap)
1782
+ } catch (e: Exception) {
1783
+ try { retriever?.release() } catch (_: Exception) {}
1784
+ promise.reject("FILMSTRIP_ERROR", e.message ?: "Filmstrip generation failed", e)
1785
+ }
1786
+ }
1787
+ }
1788
+
1789
+
1269
1790
 
1270
1791
  @ReactMethod
1271
1792
  fun removeListeners(count: Double) {
@@ -5,6 +5,7 @@ export type AudioSource = 'none' | 'app' | 'mic' | 'mixed';
5
5
  export type RecordingFormat = 'mp4' | 'gif';
6
6
  export declare const ScreenCapture: {
7
7
  takeScreenshot: (options?: ScreenshotOptions) => Promise<ScreenshotResult | null>;
8
+ captureScreenshot: (options?: ScreenshotOptions) => Promise<ScreenshotResult | null>;
8
9
  startRecording: (options?: RecordingOptions) => Promise<boolean>;
9
10
  stopRecording: () => Promise<RecordingResult | null>;
10
11
  isRecording: () => Promise<boolean>;
@@ -16,6 +17,7 @@ export declare const ScreenCapture: {
16
17
  };
17
18
  export declare const ScreenRecorder: {
18
19
  takeScreenshot: (options?: ScreenshotOptions) => Promise<ScreenshotResult | null>;
20
+ captureScreenshot: (options?: ScreenshotOptions) => Promise<ScreenshotResult | null>;
19
21
  startRecording: (options?: RecordingOptions) => Promise<boolean>;
20
22
  stopRecording: () => Promise<RecordingResult | null>;
21
23
  isRecording: () => Promise<boolean>;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ScreenRecorder=exports.ScreenCapture=void 0;const NativeInspector_1=require("./native/NativeInspector");exports.ScreenCapture={takeScreenshot:async e=>(0,NativeInspector_1.takeNativeScreenshot)(e),startRecording:async e=>(0,NativeInspector_1.startNativeVideoRecording)(e),stopRecording:async()=>(0,NativeInspector_1.stopNativeVideoRecording)(),isRecording:async()=>(0,NativeInspector_1.isNativeRecordingActive)(),playVideo:async e=>(0,NativeInspector_1.playNativeVideo)(e),convertToGif:async(e,r)=>(0,NativeInspector_1.convertNativeVideoToGif)(e,r),getMediaList:async()=>(0,NativeInspector_1.fetchCapturedMediaList)(),deleteMedia:async e=>(0,NativeInspector_1.deleteCapturedMediaFile)(e),clearAllMedia:async()=>(0,NativeInspector_1.clearAllCapturedMediaFiles)()},exports.ScreenRecorder=exports.ScreenCapture,exports.default=exports.ScreenCapture;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ScreenRecorder=exports.ScreenCapture=void 0;const NativeInspector_1=require("./native/NativeInspector");exports.ScreenCapture={takeScreenshot:async e=>(0,NativeInspector_1.takeNativeScreenshot)(e),captureScreenshot:async e=>(0,NativeInspector_1.takeNativeScreenshot)(e),startRecording:async e=>(0,NativeInspector_1.startNativeVideoRecording)(e),stopRecording:async()=>(0,NativeInspector_1.stopNativeVideoRecording)(),isRecording:async()=>(0,NativeInspector_1.isNativeRecordingActive)(),playVideo:async e=>(0,NativeInspector_1.playNativeVideo)(e),convertToGif:async(e,r)=>(0,NativeInspector_1.convertNativeVideoToGif)(e,r),getMediaList:async()=>(0,NativeInspector_1.fetchCapturedMediaList)(),deleteMedia:async e=>(0,NativeInspector_1.deleteCapturedMediaFile)(e),clearAllMedia:async()=>(0,NativeInspector_1.clearAllCapturedMediaFiles)()},exports.ScreenRecorder=exports.ScreenCapture,exports.default=exports.ScreenCapture;