@datadog/mobile-react-native-session-replay 2.4.4-alpha.0 → 2.5.0-alpha.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/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/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 +2 -1
- 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/build.gradle
CHANGED
|
@@ -134,6 +134,14 @@ android {
|
|
|
134
134
|
} else {
|
|
135
135
|
java.srcDirs += ['src/oldarch/kotlin']
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
if (reactNativeMinorVersion >= 76) {
|
|
139
|
+
java.srcDirs += ['src/rn76/kotlin']
|
|
140
|
+
} else if (reactNativeMinorVersion >= 75) {
|
|
141
|
+
java.srcDirs += ['src/rn75/kotlin']
|
|
142
|
+
} else {
|
|
143
|
+
java.srcDirs += ['src/rnlegacy/kotlin']
|
|
144
|
+
}
|
|
137
145
|
}
|
|
138
146
|
test {
|
|
139
147
|
java.srcDir("src/test/kotlin")
|
|
@@ -149,6 +157,7 @@ android {
|
|
|
149
157
|
buildTypes {
|
|
150
158
|
release {
|
|
151
159
|
minifyEnabled false
|
|
160
|
+
consumerProguardFiles 'consumer-proguard-rules.pro'
|
|
152
161
|
}
|
|
153
162
|
}
|
|
154
163
|
lintOptions {
|
|
@@ -188,7 +197,8 @@ dependencies {
|
|
|
188
197
|
api "com.facebook.react:react-android:$reactNativeVersion"
|
|
189
198
|
}
|
|
190
199
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
191
|
-
implementation "com.datadoghq:dd-sdk-android-session-replay:2.
|
|
200
|
+
implementation "com.datadoghq:dd-sdk-android-session-replay:2.16.1"
|
|
201
|
+
implementation "com.datadoghq:dd-sdk-android-internal:2.16.1"
|
|
192
202
|
implementation project(path: ':datadog_mobile-react-native')
|
|
193
203
|
|
|
194
204
|
testImplementation "org.junit.platform:junit-platform-launcher:1.6.2"
|
|
@@ -28,14 +28,25 @@ class DdSessionReplayImplementation(
|
|
|
28
28
|
/**
|
|
29
29
|
* Enable session replay and start recording session.
|
|
30
30
|
* @param replaySampleRate The sample rate applied for session replay.
|
|
31
|
-
* @param defaultPrivacyLevel The privacy level used for replay.
|
|
32
31
|
* @param customEndpoint Custom server url for sending replay data.
|
|
32
|
+
* @param privacySettings Defines the way visual elements should be masked.
|
|
33
|
+
* @param customEndpoint Custom server url for sending replay data.
|
|
34
|
+
* @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
|
|
33
35
|
*/
|
|
34
|
-
fun enable(
|
|
36
|
+
fun enable(
|
|
37
|
+
replaySampleRate: Double,
|
|
38
|
+
customEndpoint: String,
|
|
39
|
+
privacySettings: SessionReplayPrivacySettings,
|
|
40
|
+
startRecordingImmediately: Boolean,
|
|
41
|
+
promise: Promise
|
|
42
|
+
) {
|
|
35
43
|
val sdkCore = DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
|
|
36
44
|
val logger = sdkCore.internalLogger
|
|
37
45
|
val configuration = SessionReplayConfiguration.Builder(replaySampleRate.toFloat())
|
|
38
|
-
.
|
|
46
|
+
.startRecordingImmediately(startRecordingImmediately)
|
|
47
|
+
.setImagePrivacy(privacySettings.imagePrivacyLevel)
|
|
48
|
+
.setTouchPrivacy(privacySettings.touchPrivacyLevel)
|
|
49
|
+
.setTextAndInputPrivacy(privacySettings.textAndInputPrivacyLevel)
|
|
39
50
|
.addExtensionSupport(ReactNativeSessionReplayExtensionSupport(reactContext, logger))
|
|
40
51
|
|
|
41
52
|
if (customEndpoint != "") {
|
|
@@ -46,30 +57,24 @@ class DdSessionReplayImplementation(
|
|
|
46
57
|
promise.resolve(null)
|
|
47
58
|
}
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS)
|
|
68
|
-
this.setImagePrivacy(ImagePrivacy.MASK_NONE)
|
|
69
|
-
this.setTouchPrivacy(TouchPrivacy.SHOW)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return this
|
|
60
|
+
/**
|
|
61
|
+
* Manually start recording the current session.
|
|
62
|
+
*/
|
|
63
|
+
fun startRecording(promise: Promise) {
|
|
64
|
+
sessionReplayProvider().startRecording(
|
|
65
|
+
DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
|
|
66
|
+
)
|
|
67
|
+
promise.resolve(null)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Manually stop recording the current session.
|
|
72
|
+
*/
|
|
73
|
+
fun stopRecording(promise: Promise) {
|
|
74
|
+
sessionReplayProvider().stopRecording(
|
|
75
|
+
DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
|
|
76
|
+
)
|
|
77
|
+
promise.resolve(null)
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
companion object {
|
|
@@ -11,11 +11,14 @@ import com.datadog.android.api.InternalLogger
|
|
|
11
11
|
import com.datadog.android.sessionreplay.ExtensionSupport
|
|
12
12
|
import com.datadog.android.sessionreplay.MapperTypeWrapper
|
|
13
13
|
import com.datadog.android.sessionreplay.recorder.OptionSelectorDetector
|
|
14
|
+
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
|
|
14
15
|
import com.datadog.reactnative.sessionreplay.mappers.ReactEditTextMapper
|
|
16
|
+
import com.datadog.reactnative.sessionreplay.mappers.ReactNativeImageViewMapper
|
|
15
17
|
import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper
|
|
16
18
|
import com.datadog.reactnative.sessionreplay.mappers.ReactViewGroupMapper
|
|
17
19
|
import com.facebook.react.bridge.ReactContext
|
|
18
20
|
import com.facebook.react.uimanager.UIManagerModule
|
|
21
|
+
import com.facebook.react.views.image.ReactImageView
|
|
19
22
|
import com.facebook.react.views.text.ReactTextView
|
|
20
23
|
import com.facebook.react.views.textinput.ReactEditText
|
|
21
24
|
import com.facebook.react.views.view.ReactViewGroup
|
|
@@ -24,21 +27,21 @@ internal class ReactNativeSessionReplayExtensionSupport(
|
|
|
24
27
|
private val reactContext: ReactContext,
|
|
25
28
|
private val logger: InternalLogger
|
|
26
29
|
) : ExtensionSupport {
|
|
30
|
+
override fun name(): String {
|
|
31
|
+
return ReactNativeSessionReplayExtensionSupport::class.java.simpleName
|
|
32
|
+
}
|
|
27
33
|
|
|
28
34
|
override fun getCustomViewMappers(): List<MapperTypeWrapper<*>> {
|
|
29
35
|
val uiManagerModule = getUiManagerModule()
|
|
30
36
|
|
|
31
37
|
return listOf(
|
|
38
|
+
MapperTypeWrapper(ReactImageView::class.java, ReactNativeImageViewMapper()),
|
|
32
39
|
MapperTypeWrapper(ReactViewGroup::class.java, ReactViewGroupMapper()),
|
|
33
40
|
MapperTypeWrapper(ReactTextView::class.java, ReactTextMapper(reactContext, uiManagerModule)),
|
|
34
41
|
MapperTypeWrapper(ReactEditText::class.java, ReactEditTextMapper(reactContext, uiManagerModule)),
|
|
35
42
|
)
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
|
|
39
|
-
return listOf()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
45
|
@VisibleForTesting
|
|
43
46
|
internal fun getUiManagerModule(): UIManagerModule? {
|
|
44
47
|
return try {
|
|
@@ -54,6 +57,14 @@ internal class ReactNativeSessionReplayExtensionSupport(
|
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
|
|
61
|
+
return listOf()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override fun getCustomDrawableMapper(): List<DrawableToColorMapper> {
|
|
65
|
+
return emptyList()
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
internal companion object {
|
|
58
69
|
internal const val RESOLVE_UIMANAGERMODULE_ERROR = "Unable to resolve UIManagerModule"
|
|
59
70
|
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt
CHANGED
|
@@ -6,28 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
package com.datadog.reactnative.sessionreplay
|
|
8
8
|
|
|
9
|
+
import ReactViewBackgroundDrawableUtils
|
|
9
10
|
import android.view.Gravity
|
|
10
11
|
import android.widget.TextView
|
|
11
12
|
import androidx.annotation.VisibleForTesting
|
|
13
|
+
import com.datadog.android.internal.utils.densityNormalized
|
|
12
14
|
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
13
|
-
import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
|
|
14
15
|
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
15
|
-
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
|
|
16
16
|
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
|
|
17
17
|
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
|
|
18
18
|
import com.facebook.react.bridge.ReactContext
|
|
19
19
|
import com.facebook.react.uimanager.UIManagerModule
|
|
20
20
|
import com.facebook.react.views.text.TextAttributes
|
|
21
|
-
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
22
21
|
import java.util.Locale
|
|
23
22
|
|
|
24
23
|
internal class ReactTextPropertiesResolver(
|
|
25
24
|
private val reactContext: ReactContext,
|
|
26
25
|
private val uiManagerModule: UIManagerModule,
|
|
27
26
|
private val reflectionUtils: ReflectionUtils = ReflectionUtils(),
|
|
28
|
-
private val
|
|
29
|
-
ReactViewBackgroundDrawableUtils()
|
|
30
|
-
private val drawableUtils: DrawableUtils = DrawableUtils()
|
|
27
|
+
private val drawableUtils: DrawableUtils =
|
|
28
|
+
ReactViewBackgroundDrawableUtils()
|
|
31
29
|
): TextPropertiesResolver {
|
|
32
30
|
override fun addReactNativeProperties(
|
|
33
31
|
originalWireframe: MobileSegment.Wireframe.TextWireframe,
|
|
@@ -94,14 +92,14 @@ internal class ReactTextPropertiesResolver(
|
|
|
94
92
|
view: TextView,
|
|
95
93
|
pixelDensity: Float,
|
|
96
94
|
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>? {
|
|
97
|
-
val backgroundDrawable
|
|
98
|
-
|
|
95
|
+
val backgroundDrawable = drawableUtils
|
|
96
|
+
.getReactBackgroundFromDrawable(view.background) ?: return null
|
|
99
97
|
|
|
100
98
|
// view.alpha is the value of the opacity prop on the js side
|
|
101
99
|
val opacity = view.alpha
|
|
102
100
|
|
|
103
101
|
val (shapeStyle, border) =
|
|
104
|
-
|
|
102
|
+
drawableUtils
|
|
105
103
|
.resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
|
|
106
104
|
|
|
107
105
|
return shapeStyle to border
|
|
@@ -136,7 +134,7 @@ internal class ReactTextPropertiesResolver(
|
|
|
136
134
|
val fontFamily = getFontFamily(shadowNodeWrapper)
|
|
137
135
|
?: textWireframe.textStyle.family
|
|
138
136
|
val fontSize = getFontSize(shadowNodeWrapper)
|
|
139
|
-
?.
|
|
137
|
+
?.densityNormalized(pixelsDensity)
|
|
140
138
|
?: textWireframe.textStyle.size
|
|
141
139
|
val fontColor = getTextColor(shadowNodeWrapper)
|
|
142
140
|
?: textWireframe.textStyle.color
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
|
8
|
+
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import com.datadog.android.sessionreplay.ImagePrivacy
|
|
11
|
+
import com.datadog.android.sessionreplay.TextAndInputPrivacy
|
|
12
|
+
import com.datadog.android.sessionreplay.TouchPrivacy
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A utility class to store Session Replay privacy settings, and convert them from string to
|
|
16
|
+
* enum values.
|
|
17
|
+
*
|
|
18
|
+
* @param imagePrivacyLevel Defines the way images should be masked.
|
|
19
|
+
* @param touchPrivacyLevel Defines the way user touches should be masked.
|
|
20
|
+
* @param textAndInputPrivacyLevel Defines the way text and input should be masked.
|
|
21
|
+
*/
|
|
22
|
+
class SessionReplayPrivacySettings(
|
|
23
|
+
imagePrivacyLevel: String,
|
|
24
|
+
touchPrivacyLevel: String,
|
|
25
|
+
textAndInputPrivacyLevel: String
|
|
26
|
+
){
|
|
27
|
+
/**
|
|
28
|
+
* Defines the way images should be masked.
|
|
29
|
+
*/
|
|
30
|
+
val imagePrivacyLevel = getImagePrivacy(imagePrivacyLevel)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Defines the way user touches should be masked.
|
|
34
|
+
*/
|
|
35
|
+
val touchPrivacyLevel = getTouchPrivacy(touchPrivacyLevel)
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Defines the way text and input should be masked.
|
|
39
|
+
*/
|
|
40
|
+
val textAndInputPrivacyLevel = getTextAndInputPrivacy(textAndInputPrivacyLevel)
|
|
41
|
+
|
|
42
|
+
companion object {
|
|
43
|
+
internal fun getImagePrivacy(imagePrivacyLevel: String): ImagePrivacy {
|
|
44
|
+
return when (imagePrivacyLevel) {
|
|
45
|
+
"MASK_NON_BUNDLED_ONLY" -> ImagePrivacy.MASK_LARGE_ONLY
|
|
46
|
+
"MASK_ALL" -> ImagePrivacy.MASK_ALL
|
|
47
|
+
"MASK_NONE" -> ImagePrivacy.MASK_NONE
|
|
48
|
+
else -> {
|
|
49
|
+
Log.w(
|
|
50
|
+
SessionReplayPrivacySettings::class.java.canonicalName,
|
|
51
|
+
"Unknown Session Replay Image Privacy Level given: $imagePrivacyLevel, " +
|
|
52
|
+
"using ${ImagePrivacy.MASK_ALL} as default"
|
|
53
|
+
)
|
|
54
|
+
ImagePrivacy.MASK_ALL
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
internal fun getTouchPrivacy(touchPrivacyLevel: String): TouchPrivacy {
|
|
60
|
+
return when (touchPrivacyLevel) {
|
|
61
|
+
"SHOW" -> TouchPrivacy.SHOW
|
|
62
|
+
"HIDE" -> TouchPrivacy.HIDE
|
|
63
|
+
else -> {
|
|
64
|
+
Log.w(
|
|
65
|
+
SessionReplayPrivacySettings::class.java.canonicalName,
|
|
66
|
+
"Unknown Session Replay Touch Privacy Level given: $touchPrivacyLevel, " +
|
|
67
|
+
"using ${TouchPrivacy.HIDE} as default"
|
|
68
|
+
)
|
|
69
|
+
TouchPrivacy.HIDE
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
internal fun getTextAndInputPrivacy(textAndInputPrivacyLevel: String): TextAndInputPrivacy {
|
|
75
|
+
return when (textAndInputPrivacyLevel) {
|
|
76
|
+
"MASK_SENSITIVE_INPUTS" -> TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
|
|
77
|
+
"MASK_ALL_INPUTS" -> TextAndInputPrivacy.MASK_ALL_INPUTS
|
|
78
|
+
"MASK_ALL" -> TextAndInputPrivacy.MASK_ALL
|
|
79
|
+
else -> {
|
|
80
|
+
Log.w(
|
|
81
|
+
SessionReplayPrivacySettings::class.java.canonicalName,
|
|
82
|
+
"Unknown Session Replay Text And Input Privacy Level given: $textAndInputPrivacyLevel, " +
|
|
83
|
+
"using ${TextAndInputPrivacy.MASK_ALL} as default"
|
|
84
|
+
)
|
|
85
|
+
TextAndInputPrivacy.MASK_ALL
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt
CHANGED
|
@@ -24,4 +24,18 @@ internal class SessionReplaySDKWrapper : SessionReplayWrapper {
|
|
|
24
24
|
sdkCore,
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Manually start recording the current session.
|
|
30
|
+
*/
|
|
31
|
+
override fun startRecording(sdkCore: SdkCore) {
|
|
32
|
+
SessionReplay.startRecording(sdkCore)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Manually stop recording the current session.
|
|
37
|
+
*/
|
|
38
|
+
override fun stopRecording(sdkCore: SdkCore) {
|
|
39
|
+
SessionReplay.stopRecording(sdkCore)
|
|
40
|
+
}
|
|
27
41
|
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt
CHANGED
|
@@ -21,4 +21,14 @@ interface SessionReplayWrapper {
|
|
|
21
21
|
sessionReplayConfiguration: SessionReplayConfiguration,
|
|
22
22
|
sdkCore: SdkCore
|
|
23
23
|
)
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Manually start recording the current session.
|
|
27
|
+
*/
|
|
28
|
+
fun startRecording(sdkCore: SdkCore)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Manually stop recording the current session.
|
|
32
|
+
*/
|
|
33
|
+
fun stopRecording(sdkCore: SdkCore)
|
|
24
34
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
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.extensions
|
|
8
|
+
|
|
9
|
+
import android.content.res.Resources
|
|
10
|
+
import android.graphics.Bitmap
|
|
11
|
+
import android.graphics.Bitmap.Config
|
|
12
|
+
import android.graphics.Canvas
|
|
13
|
+
import android.graphics.drawable.BitmapDrawable
|
|
14
|
+
import android.graphics.drawable.Drawable
|
|
15
|
+
import android.graphics.drawable.ShapeDrawable
|
|
16
|
+
import android.graphics.drawable.VectorDrawable
|
|
17
|
+
import android.widget.ImageView
|
|
18
|
+
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
|
|
19
|
+
import com.facebook.drawee.drawable.ArrayDrawable
|
|
20
|
+
import com.facebook.drawee.drawable.ForwardingDrawable
|
|
21
|
+
import com.facebook.drawee.drawable.RoundedBitmapDrawable
|
|
22
|
+
import com.facebook.drawee.drawable.ScaleTypeDrawable
|
|
23
|
+
import com.facebook.drawee.drawable.ScalingUtils
|
|
24
|
+
|
|
25
|
+
internal fun ScaleTypeDrawable.imageViewScaleType(): ImageView.ScaleType? {
|
|
26
|
+
return when (scaleType) {
|
|
27
|
+
ScalingUtils.ScaleType.CENTER -> ImageView.ScaleType.CENTER
|
|
28
|
+
ScalingUtils.ScaleType.CENTER_CROP -> ImageView.ScaleType.CENTER_CROP
|
|
29
|
+
ScalingUtils.ScaleType.CENTER_INSIDE -> ImageView.ScaleType.CENTER_INSIDE
|
|
30
|
+
ScalingUtils.ScaleType.FIT_CENTER -> ImageView.ScaleType.FIT_CENTER
|
|
31
|
+
ScalingUtils.ScaleType.FIT_START -> ImageView.ScaleType.FIT_START
|
|
32
|
+
ScalingUtils.ScaleType.FIT_END -> ImageView.ScaleType.FIT_END
|
|
33
|
+
ScalingUtils.ScaleType.FIT_XY -> ImageView.ScaleType.FIT_XY
|
|
34
|
+
else -> null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
internal fun ArrayDrawable.getScaleTypeDrawable(): ScaleTypeDrawable? {
|
|
39
|
+
for (i in 0 until numberOfLayers) {
|
|
40
|
+
val drawable = getDrawableOrNull(i)
|
|
41
|
+
if (drawable is ScaleTypeDrawable) return drawable
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
internal fun ArrayDrawable.getDrawableOrNull(index: Int): Drawable? {
|
|
48
|
+
return try {
|
|
49
|
+
getDrawable(index)
|
|
50
|
+
} catch (_: IllegalArgumentException) {
|
|
51
|
+
null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
internal fun ForwardingDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
|
|
56
|
+
val forwardedDrawable = drawable
|
|
57
|
+
return if (forwardedDrawable != null) {
|
|
58
|
+
forwardedDrawable.tryToExtractBitmap(resources)
|
|
59
|
+
} else {
|
|
60
|
+
toBitmapOrNull(
|
|
61
|
+
intrinsicWidth,
|
|
62
|
+
intrinsicHeight,
|
|
63
|
+
Config.ARGB_8888
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
internal fun RoundedBitmapDrawable.tryToExtractBitmap(): Bitmap? {
|
|
69
|
+
val privateBitmap = try {
|
|
70
|
+
val field = RoundedBitmapDrawable::class.java.getDeclaredField("mBitmap")
|
|
71
|
+
field.isAccessible = true
|
|
72
|
+
field.get(this) as? Bitmap
|
|
73
|
+
} catch (_: NoSuchFieldException) {
|
|
74
|
+
null
|
|
75
|
+
} catch (_: IllegalAccessException) {
|
|
76
|
+
null
|
|
77
|
+
} catch (_: Exception) {
|
|
78
|
+
null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return privateBitmap ?: toBitmapOrNull(
|
|
82
|
+
intrinsicWidth,
|
|
83
|
+
intrinsicHeight,
|
|
84
|
+
Config.ARGB_8888
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
internal fun BitmapDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
|
|
89
|
+
if (bitmap != null) {
|
|
90
|
+
return bitmap
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (constantState != null) {
|
|
94
|
+
val copy = constantState?.newDrawable(resources)
|
|
95
|
+
return (copy as? BitmapDrawable)?.bitmap ?: copy?.toBitmapOrNull(
|
|
96
|
+
intrinsicWidth,
|
|
97
|
+
intrinsicHeight,
|
|
98
|
+
Config.ARGB_8888
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
internal fun ArrayDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
|
|
106
|
+
var width = 0
|
|
107
|
+
var height = 0
|
|
108
|
+
for (index in 0 until numberOfLayers) {
|
|
109
|
+
val drawable = getDrawableOrNull(index) ?: continue
|
|
110
|
+
|
|
111
|
+
if (drawable is ScaleTypeDrawable) {
|
|
112
|
+
return drawable.tryToExtractBitmap(resources)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (drawable.intrinsicWidth * drawable.intrinsicHeight > width * height) {
|
|
116
|
+
width = drawable.intrinsicWidth
|
|
117
|
+
height = drawable.intrinsicHeight
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return if (width > 0 && height > 0)
|
|
122
|
+
toBitmapOrNull(width, height, Config.ARGB_8888)
|
|
123
|
+
else
|
|
124
|
+
null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
internal fun Drawable.tryToExtractBitmap(
|
|
128
|
+
resources: Resources
|
|
129
|
+
): Bitmap? {
|
|
130
|
+
when (this) {
|
|
131
|
+
is ArrayDrawable -> {
|
|
132
|
+
return tryToExtractBitmap(resources)
|
|
133
|
+
}
|
|
134
|
+
is ForwardingDrawable -> {
|
|
135
|
+
return tryToExtractBitmap(resources)
|
|
136
|
+
}
|
|
137
|
+
is RoundedBitmapDrawable -> {
|
|
138
|
+
return tryToExtractBitmap()
|
|
139
|
+
}
|
|
140
|
+
is BitmapDrawable -> {
|
|
141
|
+
return tryToExtractBitmap(resources)
|
|
142
|
+
}
|
|
143
|
+
is VectorDrawable, is ShapeDrawable, is DrawerArrowDrawable -> {
|
|
144
|
+
return toBitmapOrNull(
|
|
145
|
+
intrinsicWidth,
|
|
146
|
+
intrinsicHeight,
|
|
147
|
+
Config.ARGB_8888
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
else -> return null
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
internal fun Drawable.toBitmapOrNull(
|
|
155
|
+
width: Int = intrinsicWidth,
|
|
156
|
+
height: Int = intrinsicHeight,
|
|
157
|
+
config: Config? = null
|
|
158
|
+
): Bitmap? {
|
|
159
|
+
if (this is BitmapDrawable && bitmap == null) {
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
return toBitmap(width, height, config)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
internal fun Drawable.toBitmap(
|
|
166
|
+
width: Int = intrinsicWidth,
|
|
167
|
+
height: Int = intrinsicHeight,
|
|
168
|
+
config: Config? = null
|
|
169
|
+
): Bitmap {
|
|
170
|
+
if (this is BitmapDrawable) {
|
|
171
|
+
if (bitmap == null) {
|
|
172
|
+
return Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
|
|
173
|
+
}
|
|
174
|
+
if (config == null || bitmap.config == config) {
|
|
175
|
+
// Fast-path to return original. Bitmap.createScaledBitmap will do this check, but it
|
|
176
|
+
// involves allocation and two jumps into native code so we perform the check ourselves.
|
|
177
|
+
if (width == bitmap.width && height == bitmap.height) {
|
|
178
|
+
return bitmap
|
|
179
|
+
}
|
|
180
|
+
return Bitmap.createScaledBitmap(bitmap, width, height, true)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
val bitmap = Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
|
|
185
|
+
setBounds(0, 0, width, height)
|
|
186
|
+
draw(Canvas(bitmap))
|
|
187
|
+
|
|
188
|
+
setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom)
|
|
189
|
+
return bitmap
|
|
190
|
+
}
|