@datadog/mobile-react-native-session-replay 2.4.4-alpha.0 → 2.6.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/DatadogSDKReactNativeSessionReplay.podspec +1 -1
- package/android/build.gradle +11 -1
- package/android/consumer-proguard-rules.pro +3 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +32 -27
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +15 -4
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +8 -10
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayPrivacySettings.kt +90 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +14 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +10 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/ReactDrawablesExt.kt +190 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactEditTextMapper.kt +84 -10
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactNativeImageViewMapper.kt +106 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +4 -5
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/resources/ReactDrawableCopier.kt +34 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +10 -23
- package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +28 -4
- package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +35 -3
- package/android/src/{main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt → rn75/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt} +6 -8
- package/android/src/rn75/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt +13 -0
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
- package/android/src/rnlegacy/kotlin/com.datadog.reactnative.sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +88 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +59 -50
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +7 -3
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +2 -7
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +2 -7
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +2 -1
- package/ios/Sources/DdSessionReplay.mm +46 -4
- package/ios/Sources/DdSessionReplayImplementation.swift +83 -11
- package/ios/Sources/RCTTextViewRecorder.swift +1 -1
- package/lib/commonjs/SessionReplay.js +78 -10
- package/lib/commonjs/SessionReplay.js.map +1 -1
- package/lib/commonjs/index.js +18 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -1
- package/lib/module/SessionReplay.js +77 -9
- package/lib/module/SessionReplay.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/NativeDdSessionReplay.js.map +1 -1
- package/lib/typescript/SessionReplay.d.ts +73 -4
- package/lib/typescript/SessionReplay.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/nativeModulesTypes.d.ts +18 -3
- package/lib/typescript/nativeModulesTypes.d.ts.map +1 -1
- package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -2
- package/lib/typescript/specs/NativeDdSessionReplay.d.ts.map +1 -1
- package/package.json +85 -84
- package/src/SessionReplay.ts +170 -23
- package/src/__tests__/SessionReplay.test.ts +94 -8
- package/src/index.ts +14 -2
- package/src/nativeModulesTypes.ts +27 -4
- package/src/specs/NativeDdSessionReplay.ts +21 -3
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +0 -66
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactEditTextMapper.kt
CHANGED
|
@@ -6,41 +6,51 @@
|
|
|
6
6
|
|
|
7
7
|
package com.datadog.reactnative.sessionreplay.mappers
|
|
8
8
|
|
|
9
|
+
import ReactViewBackgroundDrawableUtils
|
|
10
|
+
import android.view.View
|
|
9
11
|
import com.datadog.android.api.InternalLogger
|
|
10
12
|
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
11
13
|
import com.datadog.android.sessionreplay.recorder.MappingContext
|
|
14
|
+
import com.datadog.android.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapper
|
|
12
15
|
import com.datadog.android.sessionreplay.recorder.mapper.EditTextMapper
|
|
13
|
-
import com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper
|
|
14
16
|
import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback
|
|
15
17
|
import com.datadog.android.sessionreplay.utils.DefaultColorStringFormatter
|
|
16
18
|
import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver
|
|
17
19
|
import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver
|
|
18
20
|
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
|
|
21
|
+
import com.datadog.android.sessionreplay.utils.GlobalBounds
|
|
19
22
|
import com.datadog.reactnative.sessionreplay.NoopTextPropertiesResolver
|
|
20
23
|
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver
|
|
21
24
|
import com.datadog.reactnative.sessionreplay.TextPropertiesResolver
|
|
22
25
|
import com.datadog.reactnative.sessionreplay.utils.TextViewUtils
|
|
23
26
|
import com.facebook.react.bridge.ReactContext
|
|
24
27
|
import com.facebook.react.uimanager.UIManagerModule
|
|
28
|
+
import com.facebook.react.views.image.ReactImageView
|
|
25
29
|
import com.facebook.react.views.textinput.ReactEditText
|
|
26
30
|
|
|
27
31
|
internal class ReactEditTextMapper(
|
|
28
32
|
private val reactTextPropertiesResolver: TextPropertiesResolver =
|
|
29
33
|
NoopTextPropertiesResolver(),
|
|
30
34
|
private val textViewUtils: TextViewUtils = TextViewUtils(),
|
|
31
|
-
):
|
|
35
|
+
) : BaseAsyncBackgroundWireframeMapper<ReactEditText>(
|
|
36
|
+
viewIdentifierResolver = DefaultViewIdentifierResolver,
|
|
37
|
+
colorStringFormatter = DefaultColorStringFormatter,
|
|
38
|
+
viewBoundsResolver = DefaultViewBoundsResolver,
|
|
39
|
+
drawableToColorMapper = DrawableToColorMapper.getDefault(),
|
|
40
|
+
) {
|
|
41
|
+
private val drawableUtils = ReactViewBackgroundDrawableUtils()
|
|
32
42
|
|
|
33
43
|
private val editTextMapper = EditTextMapper(
|
|
34
|
-
viewIdentifierResolver =
|
|
35
|
-
colorStringFormatter =
|
|
36
|
-
viewBoundsResolver =
|
|
37
|
-
drawableToColorMapper =
|
|
44
|
+
viewIdentifierResolver = viewIdentifierResolver,
|
|
45
|
+
colorStringFormatter = colorStringFormatter,
|
|
46
|
+
viewBoundsResolver = viewBoundsResolver,
|
|
47
|
+
drawableToColorMapper = drawableToColorMapper,
|
|
38
48
|
)
|
|
39
49
|
|
|
40
50
|
internal constructor(
|
|
41
51
|
reactContext: ReactContext,
|
|
42
52
|
uiManagerModule: UIManagerModule?
|
|
43
|
-
): this(
|
|
53
|
+
) : this(
|
|
44
54
|
reactTextPropertiesResolver = if (uiManagerModule == null) {
|
|
45
55
|
NoopTextPropertiesResolver()
|
|
46
56
|
} else {
|
|
@@ -50,24 +60,88 @@ internal class ReactEditTextMapper(
|
|
|
50
60
|
)
|
|
51
61
|
}
|
|
52
62
|
)
|
|
63
|
+
|
|
53
64
|
override fun map(
|
|
54
65
|
view: ReactEditText,
|
|
55
66
|
mappingContext: MappingContext,
|
|
56
67
|
asyncJobStatusCallback: AsyncJobStatusCallback,
|
|
57
68
|
internalLogger: InternalLogger
|
|
58
69
|
): List<MobileSegment.Wireframe> {
|
|
59
|
-
val
|
|
70
|
+
val backgroundWireframes = mutableListOf<MobileSegment.Wireframe>().apply {
|
|
71
|
+
addAll(
|
|
72
|
+
super.map(
|
|
73
|
+
view,
|
|
74
|
+
mappingContext,
|
|
75
|
+
asyncJobStatusCallback,
|
|
76
|
+
internalLogger
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
backgroundWireframes += editTextMapper.map(
|
|
60
82
|
view = view,
|
|
61
83
|
mappingContext = mappingContext,
|
|
62
84
|
asyncJobStatusCallback = asyncJobStatusCallback,
|
|
63
85
|
internalLogger = internalLogger
|
|
64
|
-
)
|
|
86
|
+
).filterNot { it is MobileSegment.Wireframe.ImageWireframe }
|
|
65
87
|
|
|
66
88
|
return textViewUtils.mapTextViewToWireframes(
|
|
67
|
-
wireframes =
|
|
89
|
+
wireframes = backgroundWireframes,
|
|
68
90
|
view = view,
|
|
69
91
|
mappingContext = mappingContext,
|
|
70
92
|
reactTextPropertiesResolver = reactTextPropertiesResolver
|
|
71
93
|
)
|
|
72
94
|
}
|
|
95
|
+
|
|
96
|
+
@Suppress("FunctionMaxLength")
|
|
97
|
+
override fun resolveBackgroundAsImageWireframe(
|
|
98
|
+
view: View,
|
|
99
|
+
bounds: GlobalBounds,
|
|
100
|
+
width: Int,
|
|
101
|
+
height: Int,
|
|
102
|
+
mappingContext: MappingContext,
|
|
103
|
+
asyncJobStatusCallback: AsyncJobStatusCallback
|
|
104
|
+
): MobileSegment.Wireframe? {
|
|
105
|
+
if (view !is ReactImageView) {
|
|
106
|
+
return super.resolveBackgroundAsImageWireframe(
|
|
107
|
+
view,
|
|
108
|
+
bounds,
|
|
109
|
+
width,
|
|
110
|
+
height,
|
|
111
|
+
mappingContext,
|
|
112
|
+
asyncJobStatusCallback
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
val backgroundDrawable = drawableUtils.getReactBackgroundFromDrawable(view.background)
|
|
117
|
+
?: return null
|
|
118
|
+
|
|
119
|
+
val density = mappingContext.systemInformation.screenDensity
|
|
120
|
+
|
|
121
|
+
val identifier = viewIdentifierResolver.resolveChildUniqueIdentifier(
|
|
122
|
+
view,
|
|
123
|
+
"drawable0"
|
|
124
|
+
) ?: return null
|
|
125
|
+
|
|
126
|
+
val globalBounds = viewBoundsResolver.resolveViewGlobalBounds(
|
|
127
|
+
view,
|
|
128
|
+
density
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
val (shape, border) = drawableUtils.resolveShapeAndBorder(
|
|
132
|
+
backgroundDrawable,
|
|
133
|
+
view.alpha,
|
|
134
|
+
mappingContext.systemInformation.screenDensity
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return MobileSegment.Wireframe.ShapeWireframe(
|
|
138
|
+
identifier,
|
|
139
|
+
globalBounds.x,
|
|
140
|
+
globalBounds.y,
|
|
141
|
+
globalBounds.width,
|
|
142
|
+
globalBounds.height,
|
|
143
|
+
border = border,
|
|
144
|
+
shapeStyle = shape
|
|
145
|
+
)
|
|
146
|
+
}
|
|
73
147
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
|
|
3
|
+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
|
|
4
|
+
* Copyright 2016-Present Datadog, Inc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
package com.datadog.reactnative.sessionreplay.mappers
|
|
8
|
+
|
|
9
|
+
import android.graphics.Rect
|
|
10
|
+
import com.datadog.android.api.InternalLogger
|
|
11
|
+
import com.datadog.android.internal.utils.ImageViewUtils
|
|
12
|
+
import com.datadog.android.internal.utils.densityNormalized
|
|
13
|
+
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
14
|
+
import com.datadog.android.sessionreplay.recorder.MappingContext
|
|
15
|
+
import com.datadog.android.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapper
|
|
16
|
+
import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback
|
|
17
|
+
import com.datadog.android.sessionreplay.utils.DefaultColorStringFormatter
|
|
18
|
+
import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver
|
|
19
|
+
import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver
|
|
20
|
+
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
|
|
21
|
+
import com.datadog.reactnative.sessionreplay.extensions.getScaleTypeDrawable
|
|
22
|
+
import com.datadog.reactnative.sessionreplay.extensions.imageViewScaleType
|
|
23
|
+
import com.datadog.reactnative.sessionreplay.resources.ReactDrawableCopier
|
|
24
|
+
import com.facebook.drawee.drawable.FadeDrawable
|
|
25
|
+
import com.facebook.react.views.image.ReactImageView
|
|
26
|
+
|
|
27
|
+
internal class ReactNativeImageViewMapper: BaseAsyncBackgroundWireframeMapper<ReactImageView>(
|
|
28
|
+
viewIdentifierResolver = DefaultViewIdentifierResolver,
|
|
29
|
+
colorStringFormatter = DefaultColorStringFormatter,
|
|
30
|
+
viewBoundsResolver = DefaultViewBoundsResolver,
|
|
31
|
+
drawableToColorMapper = DrawableToColorMapper.getDefault()
|
|
32
|
+
) {
|
|
33
|
+
private val drawableCopier = ReactDrawableCopier()
|
|
34
|
+
|
|
35
|
+
override fun map(
|
|
36
|
+
view: ReactImageView,
|
|
37
|
+
mappingContext: MappingContext,
|
|
38
|
+
asyncJobStatusCallback: AsyncJobStatusCallback,
|
|
39
|
+
internalLogger: InternalLogger
|
|
40
|
+
): List<MobileSegment.Wireframe> {
|
|
41
|
+
val wireframes = mutableListOf<MobileSegment.Wireframe>()
|
|
42
|
+
wireframes.addAll(super.map(view, mappingContext, asyncJobStatusCallback, internalLogger))
|
|
43
|
+
|
|
44
|
+
val drawable = view.drawable?.current ?: return wireframes
|
|
45
|
+
|
|
46
|
+
val parentRect = ImageViewUtils.resolveParentRectAbsPosition(view)
|
|
47
|
+
val scaleType = (drawable as? FadeDrawable)
|
|
48
|
+
?.getScaleTypeDrawable()
|
|
49
|
+
?.imageViewScaleType() ?: view.scaleType
|
|
50
|
+
val contentRect = ImageViewUtils.resolveContentRectWithScaling(view, drawable, scaleType)
|
|
51
|
+
|
|
52
|
+
val resources = view.resources
|
|
53
|
+
val density = resources.displayMetrics.density
|
|
54
|
+
|
|
55
|
+
val clipping = if (view.cropToPadding) {
|
|
56
|
+
ImageViewUtils.calculateClipping(parentRect, contentRect, density)
|
|
57
|
+
} else {
|
|
58
|
+
null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
val contentXPosInDp = contentRect.left.densityNormalized(density).toLong()
|
|
62
|
+
val contentYPosInDp = contentRect.top.densityNormalized(density).toLong()
|
|
63
|
+
val contentWidthPx = contentRect.width()
|
|
64
|
+
val contentHeightPx = contentRect.height()
|
|
65
|
+
|
|
66
|
+
// resolve foreground
|
|
67
|
+
mappingContext.imageWireframeHelper.createImageWireframeByDrawable(
|
|
68
|
+
view = view,
|
|
69
|
+
imagePrivacy = mappingContext.imagePrivacy,
|
|
70
|
+
currentWireframeIndex = wireframes.size,
|
|
71
|
+
x = contentXPosInDp,
|
|
72
|
+
y = contentYPosInDp,
|
|
73
|
+
width = contentWidthPx,
|
|
74
|
+
height = contentHeightPx,
|
|
75
|
+
usePIIPlaceholder = true,
|
|
76
|
+
drawable = drawable,
|
|
77
|
+
drawableCopier = drawableCopier,
|
|
78
|
+
asyncJobStatusCallback = asyncJobStatusCallback,
|
|
79
|
+
clipping = clipping?.toWireframeClip(),
|
|
80
|
+
shapeStyle = null,
|
|
81
|
+
border = null,
|
|
82
|
+
prefix = "drawable",
|
|
83
|
+
customResourceIdCacheKey = generateUUID(view)
|
|
84
|
+
)?.let {
|
|
85
|
+
wireframes.add(it)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return wireframes
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun generateUUID(reactImageView: ReactImageView): String {
|
|
92
|
+
val hashCode = System.identityHashCode(reactImageView).toString()
|
|
93
|
+
val drawableType = reactImageView.drawable.current::class.java.name
|
|
94
|
+
return "${drawableType}-${hashCode}"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private fun Rect.toWireframeClip(): MobileSegment.WireframeClip {
|
|
98
|
+
return MobileSegment.WireframeClip(
|
|
99
|
+
top = top.toLong(),
|
|
100
|
+
bottom = bottom.toLong(),
|
|
101
|
+
left = left.toLong(),
|
|
102
|
+
right = right.toLong()
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
package com.datadog.reactnative.sessionreplay.mappers
|
|
8
8
|
|
|
9
|
+
import ReactViewBackgroundDrawableUtils
|
|
9
10
|
import com.datadog.android.api.InternalLogger
|
|
10
11
|
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
11
12
|
import com.datadog.android.sessionreplay.recorder.MappingContext
|
|
@@ -18,13 +19,11 @@ import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver.resolve
|
|
|
18
19
|
import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver
|
|
19
20
|
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
|
|
20
21
|
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
21
|
-
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
|
|
22
22
|
import com.facebook.react.views.view.ReactViewGroup
|
|
23
23
|
|
|
24
24
|
internal class ReactViewGroupMapper(
|
|
25
|
-
private val
|
|
26
|
-
ReactViewBackgroundDrawableUtils()
|
|
27
|
-
private val drawableUtils: DrawableUtils = DrawableUtils()
|
|
25
|
+
private val drawableUtils: DrawableUtils =
|
|
26
|
+
ReactViewBackgroundDrawableUtils()
|
|
28
27
|
) :
|
|
29
28
|
BaseWireframeMapper<ReactViewGroup>(
|
|
30
29
|
viewIdentifierResolver = DefaultViewIdentifierResolver,
|
|
@@ -49,7 +48,7 @@ internal class ReactViewGroupMapper(
|
|
|
49
48
|
|
|
50
49
|
val (shapeStyle, border) =
|
|
51
50
|
if (backgroundDrawable != null) {
|
|
52
|
-
|
|
51
|
+
drawableUtils
|
|
53
52
|
.resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
|
|
54
53
|
} else {
|
|
55
54
|
null to null
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
|
|
3
|
+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
|
|
4
|
+
* Copyright 2016-Present Datadog, Inc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
package com.datadog.reactnative.sessionreplay.resources
|
|
8
|
+
|
|
9
|
+
import android.content.res.Resources
|
|
10
|
+
import android.graphics.drawable.BitmapDrawable
|
|
11
|
+
import android.graphics.drawable.Drawable
|
|
12
|
+
import com.datadog.android.sessionreplay.recorder.resources.DefaultDrawableCopier
|
|
13
|
+
import com.datadog.android.sessionreplay.recorder.resources.DrawableCopier
|
|
14
|
+
import com.datadog.reactnative.sessionreplay.extensions.tryToExtractBitmap
|
|
15
|
+
|
|
16
|
+
internal class ReactDrawableCopier : DrawableCopier {
|
|
17
|
+
private val defaultCopier = DefaultDrawableCopier()
|
|
18
|
+
|
|
19
|
+
override fun copy(
|
|
20
|
+
originalDrawable: Drawable,
|
|
21
|
+
resources: Resources
|
|
22
|
+
): Drawable? {
|
|
23
|
+
return if (originalDrawable.constantState != null) {
|
|
24
|
+
defaultCopier.copy(originalDrawable, resources)
|
|
25
|
+
} else {
|
|
26
|
+
originalDrawable.tryToExtractBitmap(resources)?.let { bitmap ->
|
|
27
|
+
BitmapDrawable(resources, bitmap).apply {
|
|
28
|
+
bounds = originalDrawable.bounds
|
|
29
|
+
alpha = originalDrawable.alpha
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt
CHANGED
|
@@ -7,29 +7,16 @@
|
|
|
7
7
|
package com.datadog.reactnative.sessionreplay.utils
|
|
8
8
|
|
|
9
9
|
import android.graphics.drawable.Drawable
|
|
10
|
-
import android.
|
|
11
|
-
import android.graphics.drawable.LayerDrawable
|
|
12
|
-
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
10
|
+
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
13
11
|
|
|
14
|
-
internal class DrawableUtils
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
internal abstract class DrawableUtils(
|
|
13
|
+
protected val reflectionUtils: ReflectionUtils = ReflectionUtils()
|
|
14
|
+
) {
|
|
15
|
+
internal abstract fun resolveShapeAndBorder(
|
|
16
|
+
drawable: Drawable,
|
|
17
|
+
opacity: Float,
|
|
18
|
+
pixelDensity: Float
|
|
19
|
+
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
return getReactBackgroundFromDrawable(drawable.drawable)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (drawable is LayerDrawable) {
|
|
25
|
-
for (layerNumber in 0 until drawable.numberOfLayers) {
|
|
26
|
-
val layer = drawable.getDrawable(layerNumber)
|
|
27
|
-
if (layer is ReactViewBackgroundDrawable) {
|
|
28
|
-
return layer
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return null
|
|
34
|
-
}
|
|
21
|
+
internal abstract fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable?
|
|
35
22
|
}
|
|
@@ -20,20 +20,44 @@ class DdSessionReplay(
|
|
|
20
20
|
private val implementation = DdSessionReplayImplementation(reactContext)
|
|
21
21
|
|
|
22
22
|
override fun getName(): String = DdSessionReplayImplementation.NAME
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
/**
|
|
25
25
|
* Enable session replay and start recording session.
|
|
26
26
|
* @param replaySampleRate The sample rate applied for session replay.
|
|
27
|
-
* @param defaultPrivacyLevel The privacy level used for replay.
|
|
28
27
|
* @param customEndpoint Custom server url for sending replay data.
|
|
28
|
+
* @param imagePrivacyLevel Defines the way images should be masked.
|
|
29
|
+
* @param touchPrivacyLevel Defines the way user touches should be masked.
|
|
30
|
+
* @param textAndInputPrivacyLevel Defines the way text and input should be masked.
|
|
31
|
+
* @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
|
|
29
32
|
*/
|
|
30
33
|
@ReactMethod
|
|
31
34
|
override fun enable(
|
|
32
35
|
replaySampleRate: Double,
|
|
33
|
-
defaultPrivacyLevel: String,
|
|
34
36
|
customEndpoint: String,
|
|
37
|
+
imagePrivacyLevel: String,
|
|
38
|
+
touchPrivacyLevel: String,
|
|
39
|
+
textAndInputPrivacyLevel: String,
|
|
40
|
+
startRecordingImmediately: Boolean,
|
|
35
41
|
promise: Promise
|
|
36
42
|
) {
|
|
37
|
-
implementation.enable(
|
|
43
|
+
implementation.enable(
|
|
44
|
+
replaySampleRate,
|
|
45
|
+
customEndpoint,
|
|
46
|
+
SessionReplayPrivacySettings(
|
|
47
|
+
imagePrivacyLevel = imagePrivacyLevel,
|
|
48
|
+
touchPrivacyLevel = touchPrivacyLevel,
|
|
49
|
+
textAndInputPrivacyLevel = textAndInputPrivacyLevel
|
|
50
|
+
),
|
|
51
|
+
startRecordingImmediately,
|
|
52
|
+
promise
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun startRecording(promise: Promise) {
|
|
57
|
+
implementation.startRecording(promise)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override fun stopRecording(promise: Promise) {
|
|
61
|
+
implementation.stopRecording(promise)
|
|
38
62
|
}
|
|
39
63
|
}
|
|
@@ -25,16 +25,48 @@ class DdSessionReplay(
|
|
|
25
25
|
/**
|
|
26
26
|
* Enable session replay and start recording session.
|
|
27
27
|
* @param replaySampleRate The sample rate applied for session replay.
|
|
28
|
-
* @param defaultPrivacyLevel The privacy level used for replay.
|
|
29
28
|
* @param customEndpoint Custom server url for sending replay data.
|
|
29
|
+
* @param imagePrivacyLevel Defines the way images should be masked.
|
|
30
|
+
* @param touchPrivacyLevel Defines the way user touches should be masked.
|
|
31
|
+
* @param textAndInputPrivacyLevel Defines the way text and input should be masked.
|
|
32
|
+
* @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
|
|
30
33
|
*/
|
|
31
34
|
@ReactMethod
|
|
32
35
|
fun enable(
|
|
33
36
|
replaySampleRate: Double,
|
|
34
|
-
defaultPrivacyLevel: String,
|
|
35
37
|
customEndpoint: String,
|
|
38
|
+
imagePrivacyLevel: String,
|
|
39
|
+
touchPrivacyLevel: String,
|
|
40
|
+
textAndInputPrivacyLevel: String,
|
|
41
|
+
startRecordingImmediately: Boolean,
|
|
36
42
|
promise: Promise
|
|
37
43
|
) {
|
|
38
|
-
implementation.enable(
|
|
44
|
+
implementation.enable(
|
|
45
|
+
replaySampleRate,
|
|
46
|
+
customEndpoint,
|
|
47
|
+
SessionReplayPrivacySettings(
|
|
48
|
+
imagePrivacyLevel = imagePrivacyLevel,
|
|
49
|
+
touchPrivacyLevel = touchPrivacyLevel,
|
|
50
|
+
textAndInputPrivacyLevel = textAndInputPrivacyLevel
|
|
51
|
+
),
|
|
52
|
+
startRecordingImmediately,
|
|
53
|
+
promise
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Manually start recording the current session.
|
|
59
|
+
*/
|
|
60
|
+
@ReactMethod
|
|
61
|
+
fun startRecording(promise: Promise) {
|
|
62
|
+
implementation.startRecording(promise)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Manually stop recording the current session.
|
|
67
|
+
*/
|
|
68
|
+
@ReactMethod
|
|
69
|
+
fun stopRecording(promise: Promise) {
|
|
70
|
+
implementation.stopRecording(promise)
|
|
39
71
|
}
|
|
40
72
|
}
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
* This product includes software developed at Datadog (https://www.datadoghq.com/).
|
|
4
4
|
* Copyright 2016-Present Datadog, Inc.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
6
|
package com.datadog.reactnative.sessionreplay.extensions
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
8
|
+
import com.facebook.react.uimanager.LengthPercentage
|
|
9
|
+
|
|
10
|
+
internal fun LengthPercentage?.getRadius(width: Float, height: Float) = this
|
|
11
|
+
?.resolve(width, height)
|
|
12
|
+
?.let { (it.horizontal + it.vertical) / 2f }
|
|
13
|
+
?: 0f
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
|
|
3
|
+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
|
|
4
|
+
* Copyright 2016-Present Datadog, Inc.
|
|
5
|
+
*/
|
|
6
|
+
import android.graphics.drawable.Drawable
|
|
7
|
+
import android.graphics.drawable.InsetDrawable
|
|
8
|
+
import android.graphics.drawable.LayerDrawable
|
|
9
|
+
import com.datadog.android.internal.utils.densityNormalized
|
|
10
|
+
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
11
|
+
import com.datadog.reactnative.sessionreplay.extensions.getRadius
|
|
12
|
+
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
13
|
+
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
|
|
14
|
+
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
15
|
+
import com.facebook.react.uimanager.Spacing
|
|
16
|
+
import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable
|
|
17
|
+
|
|
18
|
+
internal class ReactViewBackgroundDrawableUtils : DrawableUtils() {
|
|
19
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
20
|
+
override fun resolveShapeAndBorder(
|
|
21
|
+
drawable: Drawable,
|
|
22
|
+
opacity: Float,
|
|
23
|
+
pixelDensity: Float
|
|
24
|
+
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?> {
|
|
25
|
+
if (drawable !is CSSBackgroundDrawable) {
|
|
26
|
+
return null to null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
val borderProps = resolveBorder(drawable, pixelDensity)
|
|
30
|
+
val backgroundColor = getBackgroundColor(drawable)
|
|
31
|
+
val colorHexString = if (backgroundColor != null) {
|
|
32
|
+
formatAsRgba(backgroundColor)
|
|
33
|
+
} else {
|
|
34
|
+
return null to borderProps
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return MobileSegment.ShapeStyle(
|
|
38
|
+
colorHexString,
|
|
39
|
+
opacity,
|
|
40
|
+
getBorderRadius(drawable)
|
|
41
|
+
) to borderProps
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
45
|
+
override fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable? {
|
|
46
|
+
if (drawable is CSSBackgroundDrawable) {
|
|
47
|
+
return drawable
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (drawable is InsetDrawable) {
|
|
51
|
+
return getReactBackgroundFromDrawable(drawable.drawable)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (drawable is LayerDrawable) {
|
|
55
|
+
for (layerNumber in 0 until drawable.numberOfLayers) {
|
|
56
|
+
val layer = drawable.getDrawable(layerNumber)
|
|
57
|
+
if (layer is CSSBackgroundDrawable) {
|
|
58
|
+
return layer
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
67
|
+
private fun getBorderRadius(drawable: CSSBackgroundDrawable): Float {
|
|
68
|
+
val width = drawable.intrinsicWidth.toFloat()
|
|
69
|
+
val height = drawable.intrinsicHeight.toFloat()
|
|
70
|
+
return drawable.borderRadius.uniform?.getRadius(width, height) ?: 0f
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
74
|
+
private fun getBackgroundColor(
|
|
75
|
+
backgroundDrawable: CSSBackgroundDrawable
|
|
76
|
+
): Int? {
|
|
77
|
+
return reflectionUtils.getDeclaredField(
|
|
78
|
+
backgroundDrawable,
|
|
79
|
+
COLOR_FIELD_NAME
|
|
80
|
+
) as Int?
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
84
|
+
private fun resolveBorder(
|
|
85
|
+
backgroundDrawable: CSSBackgroundDrawable,
|
|
86
|
+
pixelDensity: Float
|
|
87
|
+
): MobileSegment.ShapeBorder {
|
|
88
|
+
val borderWidth =
|
|
89
|
+
backgroundDrawable.fullBorderWidth.toLong().densityNormalized(pixelDensity)
|
|
90
|
+
val borderColor = formatAsRgba(backgroundDrawable.getBorderColor(Spacing.ALL))
|
|
91
|
+
|
|
92
|
+
return MobileSegment.ShapeBorder(
|
|
93
|
+
color = borderColor,
|
|
94
|
+
width = borderWidth
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private companion object {
|
|
99
|
+
private const val COLOR_FIELD_NAME = "mColor"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
|
|
3
|
+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
|
|
4
|
+
* Copyright 2016-Present Datadog, Inc.
|
|
5
|
+
*/
|
|
6
|
+
package com.datadog.reactnative.sessionreplay.extensions
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.uimanager.LengthPercentage
|
|
9
|
+
|
|
10
|
+
internal fun LengthPercentage?.getRadius(width: Float, height: Float) = this
|
|
11
|
+
?.resolve(width, height)
|
|
12
|
+
?.let { (it.horizontal + it.vertical) / 2f }
|
|
13
|
+
?: 0f
|