react-native-inapp-inspector 2.4.1 → 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.
- package/README.ar.md +163 -0
- package/README.de.md +163 -0
- package/README.es.md +164 -0
- package/README.fr.md +163 -0
- package/README.hi.md +163 -0
- package/README.ja.md +180 -0
- package/README.ko.md +163 -0
- package/README.md +340 -243
- package/README.pt-BR.md +163 -0
- package/README.ru.md +163 -0
- package/README.zh-CN.md +387 -0
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +605 -23
- package/assets/banner_dark.svg +186 -0
- package/assets/banner_light.svg +186 -0
- package/assets/icon_circle.svg +148 -0
- package/assets/icon_square.svg +369 -0
- package/assets/inapp_inspector_icon.gif +0 -0
- package/assets/inapp_inspector_icon_original.gif +0 -0
- package/assets/social_preview.png +0 -0
- package/assets/walkthrough.gif +0 -0
- package/dist/commonjs/capture.d.ts +2 -0
- package/dist/commonjs/capture.js +1 -1
- package/dist/commonjs/components/AnalyticsDetail.js +23 -23
- package/dist/commonjs/components/AnalyticsEventCard.js +1 -1
- package/dist/commonjs/components/AnalyticsGraph.js +44 -44
- package/dist/commonjs/components/AnimatedBrandOwl.d.ts +7 -0
- package/dist/commonjs/components/AnimatedBrandOwl.js +175 -0
- package/dist/commonjs/components/AppHeaderLogo.d.ts +1 -0
- package/dist/commonjs/components/AppHeaderLogo.js +5 -3
- package/dist/commonjs/components/BrandCircleIcon.js +28 -28
- package/dist/commonjs/components/BrandSquareIcon.d.ts +5 -0
- package/dist/commonjs/components/BrandSquareIcon.js +139 -0
- package/dist/commonjs/components/ConsoleLogCard.d.ts +2 -1
- package/dist/commonjs/components/ConsoleLogCard.js +23 -39
- package/dist/commonjs/components/Inspector/AboutModal.js +44 -54
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +6 -6
- package/dist/commonjs/components/Inspector/ConfirmationModal.d.ts +13 -0
- package/dist/commonjs/components/Inspector/ConfirmationModal.js +30 -0
- package/dist/commonjs/components/Inspector/ConsoleTab.js +12 -12
- package/dist/commonjs/components/Inspector/CrashTab.js +44 -9
- package/dist/commonjs/components/Inspector/DebuggingTab.js +17 -17
- package/dist/commonjs/components/Inspector/DeviceInfoTab.js +1 -1
- package/dist/commonjs/components/Inspector/FabLauncher.js +30 -33
- package/dist/commonjs/components/Inspector/FeatureUnderDevNotice.js +2 -2
- package/dist/commonjs/components/Inspector/FloatingCaptureWidget.d.ts +3 -0
- package/dist/commonjs/components/Inspector/FloatingCaptureWidget.js +54 -0
- package/dist/commonjs/components/Inspector/GlobalCrashScreenModal.d.ts +11 -0
- package/dist/commonjs/components/Inspector/GlobalCrashScreenModal.js +234 -0
- package/dist/commonjs/components/Inspector/InspectorHeader.js +137 -127
- package/dist/commonjs/components/Inspector/LanguageSelectorModal.d.ts +15 -0
- package/dist/commonjs/components/Inspector/LanguageSelectorModal.js +172 -0
- package/dist/commonjs/components/Inspector/MainScreen.js +55 -35
- package/dist/commonjs/components/Inspector/MediaGalleryTab.js +40 -35
- package/dist/commonjs/components/Inspector/MediaPreviewModal.d.ts +36 -0
- package/dist/commonjs/components/Inspector/MediaPreviewModal.js +341 -135
- package/dist/commonjs/components/Inspector/NetworkTab.js +50 -50
- package/dist/commonjs/components/Inspector/NpmUpdateToast.js +1 -1
- package/dist/commonjs/components/Inspector/PushCard.d.ts +3 -2
- package/dist/commonjs/components/Inspector/PushCard.js +5 -4
- package/dist/commonjs/components/Inspector/PushDetail.js +41 -32
- package/dist/commonjs/components/Inspector/PushTab.js +12 -12
- package/dist/commonjs/components/Inspector/ReduxDetail.js +32 -32
- package/dist/commonjs/components/Inspector/ReduxTab.js +10 -8
- package/dist/commonjs/components/Inspector/SecondaryTelemetryStrip.d.ts +2 -0
- package/dist/commonjs/components/Inspector/SecondaryTelemetryStrip.js +43 -0
- package/dist/commonjs/components/Inspector/SettingsPanel.js +287 -139
- package/dist/commonjs/components/Inspector/SocketCard.d.ts +5 -0
- package/dist/commonjs/components/Inspector/SocketCard.js +116 -0
- package/dist/commonjs/components/Inspector/SocketDetail.d.ts +4 -0
- package/dist/commonjs/components/Inspector/SocketDetail.js +327 -0
- package/dist/commonjs/components/Inspector/SocketFilterModal.d.ts +13 -0
- package/dist/commonjs/components/Inspector/SocketFilterModal.js +69 -0
- package/dist/commonjs/components/Inspector/SocketTab.d.ts +3 -0
- package/dist/commonjs/components/Inspector/SocketTab.js +112 -0
- package/dist/commonjs/components/Inspector/StorageTab.js +39 -37
- package/dist/commonjs/components/Inspector/SupportModal.d.ts +7 -0
- package/dist/commonjs/components/Inspector/SupportModal.js +125 -0
- package/dist/commonjs/components/Inspector/SupportPage.d.ts +5 -0
- package/dist/commonjs/components/Inspector/SupportPage.js +227 -0
- package/dist/commonjs/components/Inspector/TabBar.js +24 -23
- package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +24 -24
- package/dist/commonjs/components/JsonViewer.js +50 -48
- package/dist/commonjs/components/LogCard.d.ts +3 -2
- package/dist/commonjs/components/LogCard.js +27 -25
- package/dist/commonjs/components/LogSyntaxHighlighter.js +5 -5
- package/dist/commonjs/components/ModuleErrorBoundary.js +5 -5
- package/dist/commonjs/components/NetworkIcons.d.ts +42 -0
- package/dist/commonjs/components/NetworkIcons.js +139 -6
- package/dist/commonjs/components/SegmentedTabs.js +1 -1
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/crash.d.ts +1 -1
- package/dist/commonjs/crash.js +1 -1
- package/dist/commonjs/customHooks/crashHandler.d.ts +2 -1
- package/dist/commonjs/customHooks/crashHandler.js +7 -7
- package/dist/commonjs/customHooks/socketLogger.d.ts +26 -0
- package/dist/commonjs/customHooks/socketLogger.js +2 -0
- package/dist/commonjs/editor.d.ts +13 -0
- package/dist/commonjs/editor.js +1 -0
- package/dist/commonjs/helpers/index.d.ts +1 -0
- package/dist/commonjs/helpers/index.js +3 -3
- package/dist/commonjs/helpers/memoryManager.d.ts +1 -0
- package/dist/commonjs/helpers/memoryManager.js +1 -1
- package/dist/commonjs/helpers/searchQueryParser.d.ts +1 -1
- package/dist/commonjs/helpers/searchQueryParser.js +5 -1
- package/dist/commonjs/helpers/settingsStore.d.ts +1 -0
- package/dist/commonjs/helpers/settingsStore.js +1 -1
- package/dist/commonjs/helpers/shareFormatter.d.ts +3 -1
- package/dist/commonjs/helpers/shareFormatter.js +8 -7
- package/dist/commonjs/helpers/telemetry.js +1 -1
- package/dist/commonjs/i18n/index.d.ts +99 -1
- package/dist/commonjs/i18n/index.js +1 -1
- package/dist/commonjs/i18n/locales/ar.json +1 -1
- package/dist/commonjs/i18n/locales/bn.json +1 -0
- package/dist/commonjs/i18n/locales/de.json +1 -1
- package/dist/commonjs/i18n/locales/en.json +1 -1
- package/dist/commonjs/i18n/locales/es.json +1 -1
- package/dist/commonjs/i18n/locales/fr.json +1 -1
- package/dist/commonjs/i18n/locales/gu.json +1 -0
- package/dist/commonjs/i18n/locales/hi.json +1 -1
- package/dist/commonjs/i18n/locales/id.json +1 -0
- package/dist/commonjs/i18n/locales/it.json +1 -0
- package/dist/commonjs/i18n/locales/ja.json +1 -1
- package/dist/commonjs/i18n/locales/kn.json +1 -0
- package/dist/commonjs/i18n/locales/ko.json +1 -1
- package/dist/commonjs/i18n/locales/ml.json +1 -0
- package/dist/commonjs/i18n/locales/mr.json +1 -0
- package/dist/commonjs/i18n/locales/nl.json +1 -0
- package/dist/commonjs/i18n/locales/pa.json +1 -0
- package/dist/commonjs/i18n/locales/pl.json +1 -0
- package/dist/commonjs/i18n/locales/pt.json +1 -1
- package/dist/commonjs/i18n/locales/ru.json +1 -1
- package/dist/commonjs/i18n/locales/ta.json +1 -0
- package/dist/commonjs/i18n/locales/te.json +1 -0
- package/dist/commonjs/i18n/locales/tr.json +1 -0
- package/dist/commonjs/i18n/locales/vi.json +1 -0
- package/dist/commonjs/i18n/locales/zh.json +1 -1
- package/dist/commonjs/index.d.ts +16 -13
- package/dist/commonjs/index.js +5 -4
- package/dist/commonjs/native/NativeInspector.d.ts +4 -0
- package/dist/commonjs/native/NativeInspector.js +1 -1
- package/dist/commonjs/native/NativeNetworkInspector.d.ts +4 -0
- package/dist/commonjs/socket.d.ts +4 -0
- package/dist/commonjs/socket.js +1 -0
- package/dist/commonjs/styles/AppColors.d.ts +42 -2
- package/dist/commonjs/styles/AppColors.js +1 -1
- package/dist/commonjs/styles/common.js +1 -1
- package/dist/commonjs/styles/index.d.ts +30 -0
- package/dist/commonjs/styles/index.js +1 -1
- package/dist/commonjs/types/editor.d.ts +96 -0
- package/dist/commonjs/types/editor.js +1 -0
- package/dist/commonjs/types/enums.d.ts +40 -0
- package/dist/commonjs/types/enums.js +1 -1
- package/dist/commonjs/types/interfaces.d.ts +143 -1
- package/dist/esm/capture.d.ts +2 -0
- package/dist/esm/capture.js +1 -1
- package/dist/esm/components/AnalyticsDetail.js +31 -31
- package/dist/esm/components/AnalyticsEventCard.js +1 -1
- package/dist/esm/components/AnalyticsGraph.js +74 -74
- package/dist/esm/components/AnimatedBrandOwl.d.ts +7 -0
- package/dist/esm/components/AnimatedBrandOwl.js +175 -0
- package/dist/esm/components/AppHeaderLogo.d.ts +1 -0
- package/dist/esm/components/AppHeaderLogo.js +7 -5
- package/dist/esm/components/BrandCircleIcon.js +70 -70
- package/dist/esm/components/BrandSquareIcon.d.ts +5 -0
- package/dist/esm/components/BrandSquareIcon.js +139 -0
- package/dist/esm/components/ConsoleLogCard.d.ts +2 -1
- package/dist/esm/components/ConsoleLogCard.js +61 -77
- package/dist/esm/components/Inspector/AboutModal.js +85 -95
- package/dist/esm/components/Inspector/AnalyticsTab.js +24 -24
- package/dist/esm/components/Inspector/ConfirmationModal.d.ts +13 -0
- package/dist/esm/components/Inspector/ConfirmationModal.js +30 -0
- package/dist/esm/components/Inspector/ConsoleTab.js +48 -48
- package/dist/esm/components/Inspector/CrashTab.js +203 -168
- package/dist/esm/components/Inspector/DebuggingTab.js +29 -29
- package/dist/esm/components/Inspector/DeviceInfoTab.js +75 -75
- package/dist/esm/components/Inspector/FabLauncher.js +57 -60
- package/dist/esm/components/Inspector/FeatureUnderDevNotice.js +7 -7
- package/dist/esm/components/Inspector/FloatingCaptureWidget.d.ts +3 -0
- package/dist/esm/components/Inspector/FloatingCaptureWidget.js +54 -0
- package/dist/esm/components/Inspector/GlobalCrashScreenModal.d.ts +11 -0
- package/dist/esm/components/Inspector/GlobalCrashScreenModal.js +234 -0
- package/dist/esm/components/Inspector/InspectorHeader.js +162 -152
- package/dist/esm/components/Inspector/LanguageSelectorModal.d.ts +15 -0
- package/dist/esm/components/Inspector/LanguageSelectorModal.js +172 -0
- package/dist/esm/components/Inspector/MainScreen.js +130 -110
- package/dist/esm/components/Inspector/MediaGalleryTab.js +83 -78
- package/dist/esm/components/Inspector/MediaPreviewModal.d.ts +36 -0
- package/dist/esm/components/Inspector/MediaPreviewModal.js +345 -139
- package/dist/esm/components/Inspector/NetworkTab.js +130 -130
- package/dist/esm/components/Inspector/NpmUpdateToast.js +1 -1
- package/dist/esm/components/Inspector/PushCard.d.ts +3 -2
- package/dist/esm/components/Inspector/PushCard.js +23 -22
- package/dist/esm/components/Inspector/PushDetail.js +45 -36
- package/dist/esm/components/Inspector/PushTab.js +23 -23
- package/dist/esm/components/Inspector/ReduxDetail.js +42 -42
- package/dist/esm/components/Inspector/ReduxTab.js +20 -18
- package/dist/esm/components/Inspector/SecondaryTelemetryStrip.d.ts +2 -0
- package/dist/esm/components/Inspector/SecondaryTelemetryStrip.js +43 -0
- package/dist/esm/components/Inspector/SettingsPanel.js +526 -378
- package/dist/esm/components/Inspector/SocketCard.d.ts +5 -0
- package/dist/esm/components/Inspector/SocketCard.js +116 -0
- package/dist/esm/components/Inspector/SocketDetail.d.ts +4 -0
- package/dist/esm/components/Inspector/SocketDetail.js +327 -0
- package/dist/esm/components/Inspector/SocketFilterModal.d.ts +13 -0
- package/dist/esm/components/Inspector/SocketFilterModal.js +69 -0
- package/dist/esm/components/Inspector/SocketTab.d.ts +3 -0
- package/dist/esm/components/Inspector/SocketTab.js +112 -0
- package/dist/esm/components/Inspector/StorageTab.js +139 -137
- package/dist/esm/components/Inspector/SupportModal.d.ts +7 -0
- package/dist/esm/components/Inspector/SupportModal.js +125 -0
- package/dist/esm/components/Inspector/SupportPage.d.ts +5 -0
- package/dist/esm/components/Inspector/SupportPage.js +227 -0
- package/dist/esm/components/Inspector/TabBar.js +36 -35
- package/dist/esm/components/Inspector/UpdateAvailableModal.js +22 -22
- package/dist/esm/components/JsonViewer.js +66 -64
- package/dist/esm/components/LogCard.d.ts +3 -2
- package/dist/esm/components/LogCard.js +57 -55
- package/dist/esm/components/LogSyntaxHighlighter.js +5 -5
- package/dist/esm/components/ModuleErrorBoundary.js +23 -23
- package/dist/esm/components/NetworkIcons.d.ts +42 -0
- package/dist/esm/components/NetworkIcons.js +139 -6
- package/dist/esm/components/SegmentedTabs.js +7 -7
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/crash.d.ts +1 -1
- package/dist/esm/crash.js +1 -1
- package/dist/esm/customHooks/crashHandler.d.ts +2 -1
- package/dist/esm/customHooks/crashHandler.js +7 -7
- package/dist/esm/customHooks/socketLogger.d.ts +26 -0
- package/dist/esm/customHooks/socketLogger.js +2 -0
- package/dist/esm/editor.d.ts +13 -0
- package/dist/esm/editor.js +1 -0
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/index.js +4 -4
- package/dist/esm/helpers/memoryManager.d.ts +1 -0
- package/dist/esm/helpers/memoryManager.js +1 -1
- package/dist/esm/helpers/searchQueryParser.d.ts +1 -1
- package/dist/esm/helpers/searchQueryParser.js +5 -1
- package/dist/esm/helpers/settingsStore.d.ts +1 -0
- package/dist/esm/helpers/settingsStore.js +1 -1
- package/dist/esm/helpers/shareFormatter.d.ts +3 -1
- package/dist/esm/helpers/shareFormatter.js +8 -7
- package/dist/esm/helpers/telemetry.js +1 -1
- package/dist/esm/i18n/index.d.ts +99 -1
- package/dist/esm/i18n/index.js +1 -1
- package/dist/esm/i18n/locales/ar.json +1 -1
- package/dist/esm/i18n/locales/bn.json +1 -0
- package/dist/esm/i18n/locales/de.json +1 -1
- package/dist/esm/i18n/locales/en.json +1 -1
- package/dist/esm/i18n/locales/es.json +1 -1
- package/dist/esm/i18n/locales/fr.json +1 -1
- package/dist/esm/i18n/locales/gu.json +1 -0
- package/dist/esm/i18n/locales/hi.json +1 -1
- package/dist/esm/i18n/locales/id.json +1 -0
- package/dist/esm/i18n/locales/it.json +1 -0
- package/dist/esm/i18n/locales/ja.json +1 -1
- package/dist/esm/i18n/locales/kn.json +1 -0
- package/dist/esm/i18n/locales/ko.json +1 -1
- package/dist/esm/i18n/locales/ml.json +1 -0
- package/dist/esm/i18n/locales/mr.json +1 -0
- package/dist/esm/i18n/locales/nl.json +1 -0
- package/dist/esm/i18n/locales/pa.json +1 -0
- package/dist/esm/i18n/locales/pl.json +1 -0
- package/dist/esm/i18n/locales/pt.json +1 -1
- package/dist/esm/i18n/locales/ru.json +1 -1
- package/dist/esm/i18n/locales/ta.json +1 -0
- package/dist/esm/i18n/locales/te.json +1 -0
- package/dist/esm/i18n/locales/tr.json +1 -0
- package/dist/esm/i18n/locales/vi.json +1 -0
- package/dist/esm/i18n/locales/zh.json +1 -1
- package/dist/esm/index.d.ts +16 -13
- package/dist/esm/index.js +8 -7
- package/dist/esm/native/NativeInspector.d.ts +4 -0
- package/dist/esm/native/NativeInspector.js +1 -1
- package/dist/esm/native/NativeNetworkInspector.d.ts +4 -0
- package/dist/esm/socket.d.ts +4 -0
- package/dist/esm/socket.js +1 -0
- package/dist/esm/styles/AppColors.d.ts +42 -2
- package/dist/esm/styles/AppColors.js +1 -1
- package/dist/esm/styles/common.js +1 -1
- package/dist/esm/styles/index.d.ts +30 -0
- package/dist/esm/styles/index.js +1 -1
- package/dist/esm/types/editor.d.ts +96 -0
- package/dist/esm/types/editor.js +1 -0
- package/dist/esm/types/enums.d.ts +40 -0
- package/dist/esm/types/enums.js +1 -1
- package/dist/esm/types/interfaces.d.ts +143 -1
- package/editor.d.ts +1 -0
- package/editor.js +1 -0
- package/ios/NetworkInspectorModule.mm +531 -12
- package/package.json +213 -2
- package/react-native-inapp-inspector.podspec +1 -1
- package/src/native/NativeInspector.ts +65 -4
- package/src/native/NativeNetworkInspector.ts +4 -0
|
@@ -14,6 +14,7 @@ import android.os.Build
|
|
|
14
14
|
import android.os.Debug
|
|
15
15
|
import android.os.Environment
|
|
16
16
|
import android.os.Handler
|
|
17
|
+
import android.os.HandlerThread
|
|
17
18
|
import android.os.Looper
|
|
18
19
|
import android.os.StatFs
|
|
19
20
|
import android.util.Base64
|
|
@@ -37,6 +38,7 @@ import java.io.StringWriter
|
|
|
37
38
|
import java.util.Timer
|
|
38
39
|
import java.util.TimerTask
|
|
39
40
|
import java.util.concurrent.Executors
|
|
41
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
class NetworkInspectorModule(private val reactContext: ReactApplicationContext) :
|
|
@@ -60,6 +62,9 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
60
62
|
private var recordingTimer: Timer? = null
|
|
61
63
|
private val recordedFrames = mutableListOf<Bitmap>()
|
|
62
64
|
private var recordingFps = 15
|
|
65
|
+
private val isFrameCapturing = AtomicBoolean(false)
|
|
66
|
+
private var captureHandlerThread: HandlerThread? = null
|
|
67
|
+
private var captureBackgroundHandler: Handler? = null
|
|
63
68
|
|
|
64
69
|
private var defaultHandler: Thread.UncaughtExceptionHandler? = null
|
|
65
70
|
private var isProtectionEnabled = false
|
|
@@ -69,6 +74,34 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
69
74
|
return MODULE_NAME
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
override fun getConstants(): MutableMap<String, Any> {
|
|
78
|
+
val constants = HashMap<String, Any>()
|
|
79
|
+
try {
|
|
80
|
+
val packageManager = reactContext.packageManager
|
|
81
|
+
val packageName = reactContext.packageName
|
|
82
|
+
val packageInfo = packageManager.getPackageInfo(packageName, 0)
|
|
83
|
+
val appInfo = packageManager.getApplicationInfo(packageName, 0)
|
|
84
|
+
val appName = packageManager.getApplicationLabel(appInfo).toString()
|
|
85
|
+
val appVersion = packageInfo.versionName ?: "1.0"
|
|
86
|
+
val appBuild = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
87
|
+
packageInfo.longVersionCode.toString()
|
|
88
|
+
} else {
|
|
89
|
+
@Suppress("DEPRECATION")
|
|
90
|
+
packageInfo.versionCode.toString()
|
|
91
|
+
}
|
|
92
|
+
constants["appName"] = appName
|
|
93
|
+
constants["appVersion"] = appVersion
|
|
94
|
+
constants["appBuild"] = appBuild
|
|
95
|
+
constants["appPackageName"] = packageName
|
|
96
|
+
constants["appBundleId"] = packageName
|
|
97
|
+
} catch (e: Exception) {
|
|
98
|
+
constants["appName"] = "App"
|
|
99
|
+
constants["appVersion"] = "1.0"
|
|
100
|
+
constants["appBuild"] = "1"
|
|
101
|
+
}
|
|
102
|
+
return constants
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
private fun setupNativeCrashProtection() {
|
|
73
106
|
if (isProtectionEnabled) return
|
|
74
107
|
|
|
@@ -715,7 +748,8 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
715
748
|
return
|
|
716
749
|
}
|
|
717
750
|
|
|
718
|
-
Handler(Looper.getMainLooper())
|
|
751
|
+
val mainHandler = Handler(Looper.getMainLooper())
|
|
752
|
+
mainHandler.post {
|
|
719
753
|
try {
|
|
720
754
|
val decorView = targetWindow.decorView
|
|
721
755
|
val metrics = reactContext.resources.displayMetrics
|
|
@@ -725,22 +759,19 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
725
759
|
val targetHeight = (origHeight * scale).toInt().coerceAtLeast(1)
|
|
726
760
|
|
|
727
761
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
728
|
-
val
|
|
762
|
+
val destBitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888)
|
|
763
|
+
val copyHandler = captureBackgroundHandler ?: mainHandler
|
|
729
764
|
try {
|
|
730
|
-
PixelCopy.request(targetWindow, null,
|
|
765
|
+
PixelCopy.request(targetWindow, null, destBitmap, { copyResult ->
|
|
731
766
|
if (copyResult == PixelCopy.SUCCESS) {
|
|
732
|
-
|
|
733
|
-
val scaled = Bitmap.createScaledBitmap(rawBitmap, targetWidth, targetHeight, true)
|
|
734
|
-
if (scaled != rawBitmap) rawBitmap.recycle()
|
|
735
|
-
callback(scaled)
|
|
736
|
-
} else {
|
|
737
|
-
callback(rawBitmap)
|
|
738
|
-
}
|
|
767
|
+
callback(destBitmap)
|
|
739
768
|
} else {
|
|
769
|
+
destBitmap.recycle()
|
|
740
770
|
fallbackCanvasCapture(decorView, targetWidth, targetHeight, scale, callback)
|
|
741
771
|
}
|
|
742
|
-
},
|
|
772
|
+
}, copyHandler)
|
|
743
773
|
} catch (e: Exception) {
|
|
774
|
+
destBitmap.recycle()
|
|
744
775
|
fallbackCanvasCapture(decorView, targetWidth, targetHeight, scale, callback)
|
|
745
776
|
}
|
|
746
777
|
} else {
|
|
@@ -864,12 +895,23 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
864
895
|
recordedFrames.clear()
|
|
865
896
|
}
|
|
866
897
|
|
|
867
|
-
val fps = if (options.hasKey("fps")) options.getInt("fps").coerceIn(5,
|
|
898
|
+
val fps = if (options.hasKey("fps")) options.getInt("fps").coerceIn(5, 60) else 30
|
|
899
|
+
val scale = if (options.hasKey("scale")) options.getDouble("scale").toFloat().coerceIn(0.2f, 1.0f) else 0.5f
|
|
900
|
+
val maxDuration = if (options.hasKey("maxDurationSeconds")) options.getInt("maxDurationSeconds").coerceIn(5, 300) else 120
|
|
868
901
|
recordingFps = fps
|
|
869
|
-
val intervalMs = (1000L / fps).coerceAtLeast(
|
|
902
|
+
val intervalMs = (1000L / fps).coerceAtLeast(16L)
|
|
903
|
+
val maxFrames = (fps * maxDuration).coerceAtMost(3600)
|
|
870
904
|
isRecordingVideo = true
|
|
905
|
+
isFrameCapturing.set(false)
|
|
871
906
|
recordingStartTime = System.currentTimeMillis()
|
|
872
907
|
|
|
908
|
+
if (captureHandlerThread == null) {
|
|
909
|
+
val ht = HandlerThread("InAppInspector-PixelCopy")
|
|
910
|
+
ht.start()
|
|
911
|
+
captureHandlerThread = ht
|
|
912
|
+
captureBackgroundHandler = Handler(ht.looper)
|
|
913
|
+
}
|
|
914
|
+
|
|
873
915
|
recordingTimer = Timer("InAppInspector-Recorder", true).apply {
|
|
874
916
|
scheduleAtFixedRate(object : TimerTask() {
|
|
875
917
|
override fun run() {
|
|
@@ -877,16 +919,34 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
877
919
|
cancel()
|
|
878
920
|
return
|
|
879
921
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
922
|
+
if (!isFrameCapturing.compareAndSet(false, true)) {
|
|
923
|
+
// Previous frame still copying or processing; drop tick to guarantee smooth scrolling
|
|
924
|
+
return
|
|
925
|
+
}
|
|
926
|
+
// Auto-stop if max duration exceeded
|
|
927
|
+
val elapsed = System.currentTimeMillis() - recordingStartTime
|
|
928
|
+
if (elapsed > maxDuration * 1000L) {
|
|
929
|
+
isFrameCapturing.set(false)
|
|
930
|
+
return
|
|
931
|
+
}
|
|
932
|
+
val curWindow = getActiveWindow()
|
|
933
|
+
if (curWindow == null) {
|
|
934
|
+
isFrameCapturing.set(false)
|
|
935
|
+
return
|
|
936
|
+
}
|
|
937
|
+
captureWindowBitmap(curWindow, scale) { frame ->
|
|
938
|
+
try {
|
|
939
|
+
if (frame != null && isRecordingVideo) {
|
|
940
|
+
synchronized(recordedFrames) {
|
|
941
|
+
if (recordedFrames.size < maxFrames) {
|
|
942
|
+
recordedFrames.add(frame)
|
|
943
|
+
} else {
|
|
944
|
+
frame.recycle()
|
|
945
|
+
}
|
|
888
946
|
}
|
|
889
947
|
}
|
|
948
|
+
} finally {
|
|
949
|
+
isFrameCapturing.set(false)
|
|
890
950
|
}
|
|
891
951
|
}
|
|
892
952
|
}
|
|
@@ -906,6 +966,7 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
906
966
|
isRecordingVideo = false
|
|
907
967
|
recordingTimer?.cancel()
|
|
908
968
|
recordingTimer = null
|
|
969
|
+
isFrameCapturing.set(false)
|
|
909
970
|
|
|
910
971
|
val durationMs = (System.currentTimeMillis() - recordingStartTime).coerceAtLeast(500L)
|
|
911
972
|
val timestamp = System.currentTimeMillis()
|
|
@@ -1198,14 +1259,535 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
|
|
|
1198
1259
|
promise.resolve(map)
|
|
1199
1260
|
}
|
|
1200
1261
|
}
|
|
1262
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1263
|
+
// 100% Native Media Editor: Photo Editing (Bitmap + ColorMatrix + Canvas)
|
|
1264
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
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
|
+
}
|
|
1201
1561
|
|
|
1562
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1563
|
+
// 100% Native Media Editor: Video Trimming (MediaExtractor + MediaMuxer)
|
|
1564
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1202
1565
|
|
|
1203
1566
|
@ReactMethod
|
|
1204
|
-
fun
|
|
1205
|
-
|
|
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
|
+
}
|
|
1700
|
+
}
|
|
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
|
+
}
|
|
1206
1787
|
}
|
|
1207
1788
|
|
|
1208
1789
|
|
|
1790
|
+
|
|
1209
1791
|
@ReactMethod
|
|
1210
1792
|
fun removeListeners(count: Double) {
|
|
1211
1793
|
// Required for React Native NativeEventEmitter
|