@rejourneyco/react-native 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.
- package/README.md +29 -0
- package/android/build.gradle.kts +135 -0
- package/android/consumer-rules.pro +10 -0
- package/android/proguard-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +860 -0
- package/android/src/main/java/com/rejourney/engine/DeviceRegistrar.kt +290 -0
- package/android/src/main/java/com/rejourney/engine/DiagnosticLog.kt +385 -0
- package/android/src/main/java/com/rejourney/engine/RejourneyImpl.kt +512 -0
- package/android/src/main/java/com/rejourney/platform/OEMDetector.kt +173 -0
- package/android/src/main/java/com/rejourney/platform/PerfTiming.kt +384 -0
- package/android/src/main/java/com/rejourney/platform/SessionLifecycleService.kt +160 -0
- package/android/src/main/java/com/rejourney/platform/Telemetry.kt +301 -0
- package/android/src/main/java/com/rejourney/platform/WindowUtils.kt +100 -0
- package/android/src/main/java/com/rejourney/recording/AnrSentinel.kt +129 -0
- package/android/src/main/java/com/rejourney/recording/EventBuffer.kt +330 -0
- package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +519 -0
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +740 -0
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +559 -0
- package/android/src/main/java/com/rejourney/recording/StabilityMonitor.kt +238 -0
- package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +633 -0
- package/android/src/main/java/com/rejourney/recording/ViewHierarchyScanner.kt +232 -0
- package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +474 -0
- package/android/src/main/java/com/rejourney/utility/DataCompression.kt +63 -0
- package/android/src/main/java/com/rejourney/utility/ImageBlur.kt +412 -0
- package/android/src/main/java/com/rejourney/utility/ViewIdentifier.kt +169 -0
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +232 -0
- package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +268 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
- package/ios/Engine/DeviceRegistrar.swift +288 -0
- package/ios/Engine/DiagnosticLog.swift +387 -0
- package/ios/Engine/RejourneyImpl.swift +719 -0
- package/ios/Recording/AnrSentinel.swift +142 -0
- package/ios/Recording/EventBuffer.swift +326 -0
- package/ios/Recording/InteractionRecorder.swift +428 -0
- package/ios/Recording/ReplayOrchestrator.swift +624 -0
- package/ios/Recording/SegmentDispatcher.swift +492 -0
- package/ios/Recording/StabilityMonitor.swift +223 -0
- package/ios/Recording/TelemetryPipeline.swift +547 -0
- package/ios/Recording/ViewHierarchyScanner.swift +156 -0
- package/ios/Recording/VisualCapture.swift +675 -0
- package/ios/Rejourney.h +38 -0
- package/ios/Rejourney.mm +375 -0
- package/ios/Utility/DataCompression.swift +55 -0
- package/ios/Utility/ImageBlur.swift +89 -0
- package/ios/Utility/RuntimeMethodSwap.swift +41 -0
- package/ios/Utility/ViewIdentifier.swift +37 -0
- package/lib/commonjs/NativeRejourney.js +40 -0
- package/lib/commonjs/components/Mask.js +88 -0
- package/lib/commonjs/index.js +1443 -0
- package/lib/commonjs/sdk/autoTracking.js +1087 -0
- package/lib/commonjs/sdk/constants.js +166 -0
- package/lib/commonjs/sdk/errorTracking.js +187 -0
- package/lib/commonjs/sdk/index.js +50 -0
- package/lib/commonjs/sdk/metricsTracking.js +205 -0
- package/lib/commonjs/sdk/navigation.js +128 -0
- package/lib/commonjs/sdk/networkInterceptor.js +375 -0
- package/lib/commonjs/sdk/utils.js +433 -0
- package/lib/commonjs/sdk/version.js +13 -0
- package/lib/commonjs/types/expo-router.d.js +2 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/module/NativeRejourney.js +38 -0
- package/lib/module/components/Mask.js +83 -0
- package/lib/module/index.js +1341 -0
- package/lib/module/sdk/autoTracking.js +1059 -0
- package/lib/module/sdk/constants.js +154 -0
- package/lib/module/sdk/errorTracking.js +177 -0
- package/lib/module/sdk/index.js +26 -0
- package/lib/module/sdk/metricsTracking.js +187 -0
- package/lib/module/sdk/navigation.js +120 -0
- package/lib/module/sdk/networkInterceptor.js +364 -0
- package/lib/module/sdk/utils.js +412 -0
- package/lib/module/sdk/version.js +7 -0
- package/lib/module/types/expo-router.d.js +2 -0
- package/lib/module/types/index.js +2 -0
- package/lib/typescript/NativeRejourney.d.ts +160 -0
- package/lib/typescript/components/Mask.d.ts +54 -0
- package/lib/typescript/index.d.ts +117 -0
- package/lib/typescript/sdk/autoTracking.d.ts +226 -0
- package/lib/typescript/sdk/constants.d.ts +138 -0
- package/lib/typescript/sdk/errorTracking.d.ts +47 -0
- package/lib/typescript/sdk/index.d.ts +24 -0
- package/lib/typescript/sdk/metricsTracking.d.ts +75 -0
- package/lib/typescript/sdk/navigation.d.ts +48 -0
- package/lib/typescript/sdk/networkInterceptor.d.ts +62 -0
- package/lib/typescript/sdk/utils.d.ts +193 -0
- package/lib/typescript/sdk/version.d.ts +6 -0
- package/lib/typescript/types/index.d.ts +618 -0
- package/package.json +122 -0
- package/rejourney.podspec +23 -0
- package/src/NativeRejourney.ts +185 -0
- package/src/components/Mask.tsx +93 -0
- package/src/index.ts +1555 -0
- package/src/sdk/autoTracking.ts +1245 -0
- package/src/sdk/constants.ts +155 -0
- package/src/sdk/errorTracking.ts +231 -0
- package/src/sdk/index.ts +25 -0
- package/src/sdk/metricsTracking.ts +227 -0
- package/src/sdk/navigation.ts +152 -0
- package/src/sdk/networkInterceptor.ts +423 -0
- package/src/sdk/utils.ts +442 -0
- package/src/sdk/version.ts +6 -0
- package/src/types/expo-router.d.ts +7 -0
- package/src/types/index.ts +709 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Rejourney
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
package com.rejourney.utility
|
|
18
|
+
|
|
19
|
+
import java.io.ByteArrayOutputStream
|
|
20
|
+
import java.util.zip.GZIPOutputStream
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Data compression utilities
|
|
24
|
+
* Android implementation aligned with iOS DataCompression.swift
|
|
25
|
+
*/
|
|
26
|
+
object DataCompression {
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compress data using gzip
|
|
30
|
+
*/
|
|
31
|
+
fun gzipCompress(data: ByteArray): ByteArray? {
|
|
32
|
+
return try {
|
|
33
|
+
val bos = ByteArrayOutputStream()
|
|
34
|
+
GZIPOutputStream(bos).use { gzip ->
|
|
35
|
+
gzip.write(data)
|
|
36
|
+
}
|
|
37
|
+
bos.toByteArray()
|
|
38
|
+
} catch (e: Exception) {
|
|
39
|
+
null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compress string using gzip
|
|
45
|
+
*/
|
|
46
|
+
fun gzipCompress(text: String): ByteArray? {
|
|
47
|
+
return gzipCompress(text.toByteArray(Charsets.UTF_8))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extension function for ByteArray gzip compression
|
|
53
|
+
*/
|
|
54
|
+
fun ByteArray.gzipCompress(): ByteArray? {
|
|
55
|
+
return DataCompression.gzipCompress(this)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extension function for String gzip compression
|
|
60
|
+
*/
|
|
61
|
+
fun String.gzipCompress(): ByteArray? {
|
|
62
|
+
return DataCompression.gzipCompress(this)
|
|
63
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Rejourney
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
package com.rejourney.utility
|
|
18
|
+
|
|
19
|
+
import android.graphics.Bitmap
|
|
20
|
+
import android.graphics.Canvas
|
|
21
|
+
import android.graphics.ColorMatrix
|
|
22
|
+
import android.graphics.ColorMatrixColorFilter
|
|
23
|
+
import android.graphics.Paint
|
|
24
|
+
import android.renderscript.Allocation
|
|
25
|
+
import android.renderscript.Element
|
|
26
|
+
import android.renderscript.RenderScript
|
|
27
|
+
import android.renderscript.ScriptIntrinsicBlur
|
|
28
|
+
import android.content.Context
|
|
29
|
+
import kotlin.math.min
|
|
30
|
+
import kotlin.math.sqrt
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Image blur utilities for privacy masking
|
|
34
|
+
* Android implementation aligned with iOS ImageBlur.swift
|
|
35
|
+
*/
|
|
36
|
+
object ImageBlur {
|
|
37
|
+
|
|
38
|
+
private const val MAX_BLUR_RADIUS = 25f // RenderScript limit
|
|
39
|
+
private const val DEFAULT_BLUR_RADIUS = 15f
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Apply Gaussian blur using RenderScript (fast, GPU-accelerated)
|
|
43
|
+
* Falls back to box blur if RenderScript unavailable
|
|
44
|
+
*/
|
|
45
|
+
@Suppress("DEPRECATION")
|
|
46
|
+
fun applyGaussianBlur(
|
|
47
|
+
context: Context,
|
|
48
|
+
bitmap: Bitmap,
|
|
49
|
+
radius: Float = DEFAULT_BLUR_RADIUS
|
|
50
|
+
): Bitmap {
|
|
51
|
+
val safeRadius = min(radius, MAX_BLUR_RADIUS).coerceAtLeast(1f)
|
|
52
|
+
|
|
53
|
+
return try {
|
|
54
|
+
applyRenderScriptBlur(context, bitmap, safeRadius)
|
|
55
|
+
} catch (e: Exception) {
|
|
56
|
+
// Fallback to stack blur
|
|
57
|
+
applyStackBlur(bitmap, safeRadius.toInt())
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Apply RenderScript Gaussian blur (deprecated but still works)
|
|
63
|
+
*/
|
|
64
|
+
@Suppress("DEPRECATION")
|
|
65
|
+
private fun applyRenderScriptBlur(
|
|
66
|
+
context: Context,
|
|
67
|
+
bitmap: Bitmap,
|
|
68
|
+
radius: Float
|
|
69
|
+
): Bitmap {
|
|
70
|
+
val outputBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
|
71
|
+
|
|
72
|
+
val rs = RenderScript.create(context)
|
|
73
|
+
val input = Allocation.createFromBitmap(rs, bitmap)
|
|
74
|
+
val output = Allocation.createFromBitmap(rs, outputBitmap)
|
|
75
|
+
|
|
76
|
+
val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
|
|
77
|
+
script.setRadius(radius)
|
|
78
|
+
script.setInput(input)
|
|
79
|
+
script.forEach(output)
|
|
80
|
+
|
|
81
|
+
output.copyTo(outputBitmap)
|
|
82
|
+
|
|
83
|
+
input.destroy()
|
|
84
|
+
output.destroy()
|
|
85
|
+
script.destroy()
|
|
86
|
+
rs.destroy()
|
|
87
|
+
|
|
88
|
+
return outputBitmap
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Stack blur algorithm (fallback for non-RenderScript devices)
|
|
93
|
+
* Based on Mario Klingemann's algorithm
|
|
94
|
+
*/
|
|
95
|
+
private fun applyStackBlur(bitmap: Bitmap, radius: Int): Bitmap {
|
|
96
|
+
val w = bitmap.width
|
|
97
|
+
val h = bitmap.height
|
|
98
|
+
|
|
99
|
+
val pixels = IntArray(w * h)
|
|
100
|
+
bitmap.getPixels(pixels, 0, w, 0, 0, w, h)
|
|
101
|
+
|
|
102
|
+
val wm = w - 1
|
|
103
|
+
val hm = h - 1
|
|
104
|
+
val wh = w * h
|
|
105
|
+
val div = radius + radius + 1
|
|
106
|
+
|
|
107
|
+
val r = IntArray(wh)
|
|
108
|
+
val g = IntArray(wh)
|
|
109
|
+
val b = IntArray(wh)
|
|
110
|
+
|
|
111
|
+
var rsum: Int
|
|
112
|
+
var gsum: Int
|
|
113
|
+
var bsum: Int
|
|
114
|
+
var x: Int
|
|
115
|
+
var y: Int
|
|
116
|
+
var i: Int
|
|
117
|
+
var p: Int
|
|
118
|
+
var yp: Int
|
|
119
|
+
var yi: Int
|
|
120
|
+
var yw: Int
|
|
121
|
+
|
|
122
|
+
val vmin = IntArray(maxOf(w, h))
|
|
123
|
+
|
|
124
|
+
var divsum = (div + 1) shr 1
|
|
125
|
+
divsum *= divsum
|
|
126
|
+
val dv = IntArray(256 * divsum)
|
|
127
|
+
for (i in 0 until 256 * divsum) {
|
|
128
|
+
dv[i] = i / divsum
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
yw = 0
|
|
132
|
+
yi = 0
|
|
133
|
+
|
|
134
|
+
val stack = Array(div) { IntArray(3) }
|
|
135
|
+
var stackpointer: Int
|
|
136
|
+
var stackstart: Int
|
|
137
|
+
var sir: IntArray
|
|
138
|
+
var rbs: Int
|
|
139
|
+
val r1 = radius + 1
|
|
140
|
+
var routsum: Int
|
|
141
|
+
var goutsum: Int
|
|
142
|
+
var boutsum: Int
|
|
143
|
+
var rinsum: Int
|
|
144
|
+
var ginsum: Int
|
|
145
|
+
var binsum: Int
|
|
146
|
+
|
|
147
|
+
y = 0
|
|
148
|
+
while (y < h) {
|
|
149
|
+
bsum = 0
|
|
150
|
+
gsum = 0
|
|
151
|
+
rsum = 0
|
|
152
|
+
boutsum = 0
|
|
153
|
+
goutsum = 0
|
|
154
|
+
routsum = 0
|
|
155
|
+
binsum = 0
|
|
156
|
+
ginsum = 0
|
|
157
|
+
rinsum = 0
|
|
158
|
+
|
|
159
|
+
i = -radius
|
|
160
|
+
while (i <= radius) {
|
|
161
|
+
p = pixels[yi + minOf(wm, maxOf(i, 0))]
|
|
162
|
+
sir = stack[i + radius]
|
|
163
|
+
sir[0] = (p and 0xff0000) shr 16
|
|
164
|
+
sir[1] = (p and 0x00ff00) shr 8
|
|
165
|
+
sir[2] = p and 0x0000ff
|
|
166
|
+
rbs = r1 - kotlin.math.abs(i)
|
|
167
|
+
rsum += sir[0] * rbs
|
|
168
|
+
gsum += sir[1] * rbs
|
|
169
|
+
bsum += sir[2] * rbs
|
|
170
|
+
if (i > 0) {
|
|
171
|
+
rinsum += sir[0]
|
|
172
|
+
ginsum += sir[1]
|
|
173
|
+
binsum += sir[2]
|
|
174
|
+
} else {
|
|
175
|
+
routsum += sir[0]
|
|
176
|
+
goutsum += sir[1]
|
|
177
|
+
boutsum += sir[2]
|
|
178
|
+
}
|
|
179
|
+
i++
|
|
180
|
+
}
|
|
181
|
+
stackpointer = radius
|
|
182
|
+
|
|
183
|
+
x = 0
|
|
184
|
+
while (x < w) {
|
|
185
|
+
r[yi] = dv[rsum]
|
|
186
|
+
g[yi] = dv[gsum]
|
|
187
|
+
b[yi] = dv[bsum]
|
|
188
|
+
|
|
189
|
+
rsum -= routsum
|
|
190
|
+
gsum -= goutsum
|
|
191
|
+
bsum -= boutsum
|
|
192
|
+
|
|
193
|
+
stackstart = stackpointer - radius + div
|
|
194
|
+
sir = stack[stackstart % div]
|
|
195
|
+
|
|
196
|
+
routsum -= sir[0]
|
|
197
|
+
goutsum -= sir[1]
|
|
198
|
+
boutsum -= sir[2]
|
|
199
|
+
|
|
200
|
+
if (y == 0) {
|
|
201
|
+
vmin[x] = minOf(x + radius + 1, wm)
|
|
202
|
+
}
|
|
203
|
+
p = pixels[yw + vmin[x]]
|
|
204
|
+
|
|
205
|
+
sir[0] = (p and 0xff0000) shr 16
|
|
206
|
+
sir[1] = (p and 0x00ff00) shr 8
|
|
207
|
+
sir[2] = p and 0x0000ff
|
|
208
|
+
|
|
209
|
+
rinsum += sir[0]
|
|
210
|
+
ginsum += sir[1]
|
|
211
|
+
binsum += sir[2]
|
|
212
|
+
|
|
213
|
+
rsum += rinsum
|
|
214
|
+
gsum += ginsum
|
|
215
|
+
bsum += binsum
|
|
216
|
+
|
|
217
|
+
stackpointer = (stackpointer + 1) % div
|
|
218
|
+
sir = stack[stackpointer % div]
|
|
219
|
+
|
|
220
|
+
routsum += sir[0]
|
|
221
|
+
goutsum += sir[1]
|
|
222
|
+
boutsum += sir[2]
|
|
223
|
+
|
|
224
|
+
rinsum -= sir[0]
|
|
225
|
+
ginsum -= sir[1]
|
|
226
|
+
binsum -= sir[2]
|
|
227
|
+
|
|
228
|
+
yi++
|
|
229
|
+
x++
|
|
230
|
+
}
|
|
231
|
+
yw += w
|
|
232
|
+
y++
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
x = 0
|
|
236
|
+
while (x < w) {
|
|
237
|
+
bsum = 0
|
|
238
|
+
gsum = 0
|
|
239
|
+
rsum = 0
|
|
240
|
+
boutsum = 0
|
|
241
|
+
goutsum = 0
|
|
242
|
+
routsum = 0
|
|
243
|
+
binsum = 0
|
|
244
|
+
ginsum = 0
|
|
245
|
+
rinsum = 0
|
|
246
|
+
|
|
247
|
+
yp = -radius * w
|
|
248
|
+
|
|
249
|
+
i = -radius
|
|
250
|
+
while (i <= radius) {
|
|
251
|
+
yi = maxOf(0, yp) + x
|
|
252
|
+
|
|
253
|
+
sir = stack[i + radius]
|
|
254
|
+
|
|
255
|
+
sir[0] = r[yi]
|
|
256
|
+
sir[1] = g[yi]
|
|
257
|
+
sir[2] = b[yi]
|
|
258
|
+
|
|
259
|
+
rbs = r1 - kotlin.math.abs(i)
|
|
260
|
+
|
|
261
|
+
rsum += r[yi] * rbs
|
|
262
|
+
gsum += g[yi] * rbs
|
|
263
|
+
bsum += b[yi] * rbs
|
|
264
|
+
|
|
265
|
+
if (i > 0) {
|
|
266
|
+
rinsum += sir[0]
|
|
267
|
+
ginsum += sir[1]
|
|
268
|
+
binsum += sir[2]
|
|
269
|
+
} else {
|
|
270
|
+
routsum += sir[0]
|
|
271
|
+
goutsum += sir[1]
|
|
272
|
+
boutsum += sir[2]
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (i < hm) {
|
|
276
|
+
yp += w
|
|
277
|
+
}
|
|
278
|
+
i++
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
yi = x
|
|
282
|
+
stackpointer = radius
|
|
283
|
+
|
|
284
|
+
y = 0
|
|
285
|
+
while (y < h) {
|
|
286
|
+
pixels[yi] = (0xff000000.toInt() and pixels[yi]) or (dv[rsum] shl 16) or (dv[gsum] shl 8) or dv[bsum]
|
|
287
|
+
|
|
288
|
+
rsum -= routsum
|
|
289
|
+
gsum -= goutsum
|
|
290
|
+
bsum -= boutsum
|
|
291
|
+
|
|
292
|
+
stackstart = stackpointer - radius + div
|
|
293
|
+
sir = stack[stackstart % div]
|
|
294
|
+
|
|
295
|
+
routsum -= sir[0]
|
|
296
|
+
goutsum -= sir[1]
|
|
297
|
+
boutsum -= sir[2]
|
|
298
|
+
|
|
299
|
+
if (x == 0) {
|
|
300
|
+
vmin[y] = minOf(y + r1, hm) * w
|
|
301
|
+
}
|
|
302
|
+
p = x + vmin[y]
|
|
303
|
+
|
|
304
|
+
sir[0] = r[p]
|
|
305
|
+
sir[1] = g[p]
|
|
306
|
+
sir[2] = b[p]
|
|
307
|
+
|
|
308
|
+
rinsum += sir[0]
|
|
309
|
+
ginsum += sir[1]
|
|
310
|
+
binsum += sir[2]
|
|
311
|
+
|
|
312
|
+
rsum += rinsum
|
|
313
|
+
gsum += ginsum
|
|
314
|
+
bsum += binsum
|
|
315
|
+
|
|
316
|
+
stackpointer = (stackpointer + 1) % div
|
|
317
|
+
sir = stack[stackpointer]
|
|
318
|
+
|
|
319
|
+
routsum += sir[0]
|
|
320
|
+
goutsum += sir[1]
|
|
321
|
+
boutsum += sir[2]
|
|
322
|
+
|
|
323
|
+
rinsum -= sir[0]
|
|
324
|
+
ginsum -= sir[1]
|
|
325
|
+
binsum -= sir[2]
|
|
326
|
+
|
|
327
|
+
yi += w
|
|
328
|
+
y++
|
|
329
|
+
}
|
|
330
|
+
x++
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
val result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
|
334
|
+
result.setPixels(pixels, 0, w, 0, 0, w, h)
|
|
335
|
+
return result
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Apply pixelation blur (faster, for heavy privacy masking)
|
|
340
|
+
*/
|
|
341
|
+
fun applyPixelation(bitmap: Bitmap, blockSize: Int = 10): Bitmap {
|
|
342
|
+
val w = bitmap.width
|
|
343
|
+
val h = bitmap.height
|
|
344
|
+
|
|
345
|
+
val smallW = w / blockSize
|
|
346
|
+
val smallH = h / blockSize
|
|
347
|
+
|
|
348
|
+
// Scale down
|
|
349
|
+
val small = Bitmap.createScaledBitmap(bitmap, smallW, smallH, false)
|
|
350
|
+
|
|
351
|
+
// Scale back up with nearest neighbor
|
|
352
|
+
val result = Bitmap.createScaledBitmap(small, w, h, false)
|
|
353
|
+
|
|
354
|
+
small.recycle()
|
|
355
|
+
|
|
356
|
+
return result
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Apply blur to a specific region of the bitmap
|
|
361
|
+
*/
|
|
362
|
+
fun blurRegion(
|
|
363
|
+
context: Context,
|
|
364
|
+
bitmap: Bitmap,
|
|
365
|
+
left: Int,
|
|
366
|
+
top: Int,
|
|
367
|
+
right: Int,
|
|
368
|
+
bottom: Int,
|
|
369
|
+
radius: Float = DEFAULT_BLUR_RADIUS
|
|
370
|
+
): Bitmap {
|
|
371
|
+
val mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
|
372
|
+
|
|
373
|
+
val safeLeft = left.coerceIn(0, bitmap.width)
|
|
374
|
+
val safeTop = top.coerceIn(0, bitmap.height)
|
|
375
|
+
val safeRight = right.coerceIn(safeLeft, bitmap.width)
|
|
376
|
+
val safeBottom = bottom.coerceIn(safeTop, bitmap.height)
|
|
377
|
+
|
|
378
|
+
val regionWidth = safeRight - safeLeft
|
|
379
|
+
val regionHeight = safeBottom - safeTop
|
|
380
|
+
|
|
381
|
+
if (regionWidth <= 0 || regionHeight <= 0) return mutableBitmap
|
|
382
|
+
|
|
383
|
+
// Extract region
|
|
384
|
+
val region = Bitmap.createBitmap(bitmap, safeLeft, safeTop, regionWidth, regionHeight)
|
|
385
|
+
|
|
386
|
+
// Blur region
|
|
387
|
+
val blurredRegion = applyGaussianBlur(context, region, radius)
|
|
388
|
+
|
|
389
|
+
// Draw blurred region back
|
|
390
|
+
val canvas = Canvas(mutableBitmap)
|
|
391
|
+
canvas.drawBitmap(blurredRegion, safeLeft.toFloat(), safeTop.toFloat(), null)
|
|
392
|
+
|
|
393
|
+
region.recycle()
|
|
394
|
+
blurredRegion.recycle()
|
|
395
|
+
|
|
396
|
+
return mutableBitmap
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Extension function to blur bitmap
|
|
402
|
+
*/
|
|
403
|
+
fun Bitmap.blur(context: Context, radius: Float = 15f): Bitmap {
|
|
404
|
+
return ImageBlur.applyGaussianBlur(context, this, radius)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Extension function to pixelate bitmap
|
|
409
|
+
*/
|
|
410
|
+
fun Bitmap.pixelate(blockSize: Int = 10): Bitmap {
|
|
411
|
+
return ImageBlur.applyPixelation(this, blockSize)
|
|
412
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Rejourney
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
package com.rejourney.utility
|
|
18
|
+
|
|
19
|
+
import android.view.View
|
|
20
|
+
import android.view.ViewGroup
|
|
21
|
+
import android.widget.EditText
|
|
22
|
+
import android.widget.TextView
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* View identification utilities
|
|
26
|
+
* Android implementation aligned with iOS ViewIdentifier.swift
|
|
27
|
+
*/
|
|
28
|
+
object ViewIdentifier {
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate a stable identifier for a view based on its position in hierarchy
|
|
32
|
+
*/
|
|
33
|
+
fun generateStableId(view: View): String {
|
|
34
|
+
val path = mutableListOf<String>()
|
|
35
|
+
var current: View? = view
|
|
36
|
+
|
|
37
|
+
while (current != null) {
|
|
38
|
+
val segment = buildSegment(current)
|
|
39
|
+
path.add(0, segment)
|
|
40
|
+
|
|
41
|
+
current = current.parent as? View
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return path.joinToString("/")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate a short hash identifier
|
|
49
|
+
*/
|
|
50
|
+
fun generateShortId(view: View): String {
|
|
51
|
+
val stableId = generateStableId(view)
|
|
52
|
+
return stableId.hashCode().toUInt().toString(16).take(8)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun buildSegment(view: View): String {
|
|
56
|
+
val className = view.javaClass.simpleName
|
|
57
|
+
val index = getIndexInParent(view)
|
|
58
|
+
|
|
59
|
+
// Use resource ID if available
|
|
60
|
+
val resourceId = view.id
|
|
61
|
+
if (resourceId != View.NO_ID) {
|
|
62
|
+
try {
|
|
63
|
+
val resourceName = view.resources.getResourceEntryName(resourceId)
|
|
64
|
+
return "$className[$resourceName]"
|
|
65
|
+
} catch (_: Exception) {
|
|
66
|
+
// Resource name not available
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Use content description if available
|
|
71
|
+
val contentDesc = view.contentDescription?.toString()
|
|
72
|
+
if (!contentDesc.isNullOrBlank() && contentDesc.length < 32) {
|
|
73
|
+
return "$className[\"$contentDesc\"]"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Use accessibility text for text views
|
|
77
|
+
if (view is TextView) {
|
|
78
|
+
val text = view.text?.toString()?.take(16)
|
|
79
|
+
if (!text.isNullOrBlank()) {
|
|
80
|
+
val sanitized = text.replace(Regex("[^a-zA-Z0-9]"), "_")
|
|
81
|
+
return "$className[\"$sanitized\"]"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return "$className[$index]"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private fun getIndexInParent(view: View): Int {
|
|
89
|
+
val parent = view.parent as? ViewGroup ?: return 0
|
|
90
|
+
|
|
91
|
+
var index = 0
|
|
92
|
+
val viewClass = view.javaClass
|
|
93
|
+
|
|
94
|
+
for (i in 0 until parent.childCount) {
|
|
95
|
+
val child = parent.getChildAt(i)
|
|
96
|
+
if (child === view) {
|
|
97
|
+
return index
|
|
98
|
+
}
|
|
99
|
+
if (child.javaClass == viewClass) {
|
|
100
|
+
index++
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return 0
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Find view by stable identifier
|
|
109
|
+
*/
|
|
110
|
+
fun findViewByStableId(root: View, stableId: String): View? {
|
|
111
|
+
val segments = stableId.split("/")
|
|
112
|
+
if (segments.isEmpty()) return null
|
|
113
|
+
|
|
114
|
+
var current: View? = root
|
|
115
|
+
|
|
116
|
+
for (segment in segments.drop(1)) {
|
|
117
|
+
current = findChildBySegment(current as? ViewGroup ?: return null, segment)
|
|
118
|
+
if (current == null) return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return current
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private fun findChildBySegment(parent: ViewGroup, segment: String): View? {
|
|
125
|
+
// Parse segment: ClassName[identifier]
|
|
126
|
+
val match = Regex("(\\w+)\\[(.+)]").find(segment) ?: return null
|
|
127
|
+
val className = match.groupValues[1]
|
|
128
|
+
val identifier = match.groupValues[2]
|
|
129
|
+
|
|
130
|
+
// Try to find by resource ID first
|
|
131
|
+
if (!identifier.startsWith("\"") && !identifier.all { it.isDigit() }) {
|
|
132
|
+
val resourceId = parent.resources.getIdentifier(
|
|
133
|
+
identifier,
|
|
134
|
+
"id",
|
|
135
|
+
parent.context.packageName
|
|
136
|
+
)
|
|
137
|
+
if (resourceId != 0) {
|
|
138
|
+
return parent.findViewById(resourceId)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Find by index
|
|
143
|
+
if (identifier.all { it.isDigit() }) {
|
|
144
|
+
val index = identifier.toIntOrNull() ?: return null
|
|
145
|
+
var count = 0
|
|
146
|
+
|
|
147
|
+
for (i in 0 until parent.childCount) {
|
|
148
|
+
val child = parent.getChildAt(i)
|
|
149
|
+
if (child.javaClass.simpleName == className) {
|
|
150
|
+
if (count == index) {
|
|
151
|
+
return child
|
|
152
|
+
}
|
|
153
|
+
count++
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Extension functions for View identification
|
|
164
|
+
*/
|
|
165
|
+
val View.rjStableId: String
|
|
166
|
+
get() = ViewIdentifier.generateStableId(this)
|
|
167
|
+
|
|
168
|
+
val View.rjShortId: String
|
|
169
|
+
get() = ViewIdentifier.generateShortId(this)
|