Skip to content

Commit

Permalink
Implement Android SR for ReactViewGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Nov 30, 2023
1 parent 2c52133 commit 7bed2b7
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.reactnative.sessionreplay

private const val HEX_COLOR_INCLUDING_ALPHA_LENGTH: Int = 8

internal fun formatAsRgba(backgroundColor: Int): String {
val colorHexString = Integer.toHexString(backgroundColor)
return "#${convertArgbToRgba(colorHexString)}"
}

private fun convertArgbToRgba(hexString: String): String {
return if (hexString.length == HEX_COLOR_INCLUDING_ALPHA_LENGTH) {
hexString.substring(2, 8) + hexString.substring(0, 2)
} else {
hexString
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class DdSessionReplayImplementation(
fun enable(replaySampleRate: Double, defaultPrivacyLevel: String, promise: Promise) {
val configuration = SessionReplayConfiguration.Builder(replaySampleRate.toFloat())
.setPrivacy(buildPrivacy(defaultPrivacyLevel))
.addExtensionSupport(ReactNativeSessionReplayExtensionSupport())
.build()
sessionReplayProvider().enable(configuration)
promise.resolve(null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.reactnative.sessionreplay

internal fun Long.convertToDensityNormalized(density: Float): Long {
return if (density == 0f) {
this
} else {
(this / density).toLong()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.reactnative.sessionreplay

import android.view.View
import com.datadog.android.sessionreplay.ExtensionSupport
import com.datadog.android.sessionreplay.SessionReplayPrivacy
import com.datadog.android.sessionreplay.internal.recorder.OptionSelectorDetector
import com.datadog.android.sessionreplay.internal.recorder.mapper.WireframeMapper
import com.facebook.react.views.view.ReactViewGroup

internal class ReactNativeSessionReplayExtensionSupport : ExtensionSupport {

override fun getCustomViewMappers(): Map<SessionReplayPrivacy, Map<Class<*>, WireframeMapper<View, *>>> {
return mapOf(SessionReplayPrivacy.ALLOW to mapOf(
ReactViewGroup::class.java to ReactViewGroupMapper() as WireframeMapper<View, *>
))
}

override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
return listOf()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.reactnative.sessionreplay

import android.annotation.SuppressLint
import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback
import com.datadog.android.sessionreplay.internal.recorder.MappingContext
import com.datadog.android.sessionreplay.internal.recorder.mapper.BaseWireframeMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.TraverseAllChildrenMapper
import com.datadog.android.sessionreplay.model.MobileSegment
import com.facebook.react.uimanager.Spacing
import com.facebook.react.views.view.ReactViewBackgroundDrawable
import com.facebook.react.views.view.ReactViewGroup

internal class ReactViewGroupMapper :
BaseWireframeMapper<ReactViewGroup, MobileSegment.Wireframe>(),
TraverseAllChildrenMapper<ReactViewGroup, MobileSegment.Wireframe> {

override fun map(
view: ReactViewGroup,
mappingContext: MappingContext,
asyncJobStatusCallback: AsyncJobStatusCallback
): List<MobileSegment.Wireframe> {
val pixelDensity = mappingContext.systemInformation.screenDensity

val viewGlobalBounds = resolveViewGlobalBounds(
view,
pixelDensity
)

val backgroundDrawable = view.background

// view.alpha is the value of the opacity prop on the js side
val opacity = view.alpha

val (shapeStyle, border) =
if (backgroundDrawable is ReactViewBackgroundDrawable) {
resolveRNShapeStyleAndBorder(
view = view,
backgroundDrawable = backgroundDrawable,
opacity = opacity,
pixelDensity = pixelDensity
)
} else {
backgroundDrawable?.resolveShapeStyleAndBorder(opacity) ?: (null to null)
}

return listOf(
MobileSegment.Wireframe.ShapeWireframe(
resolveViewId(view),
viewGlobalBounds.x,
viewGlobalBounds.y,
viewGlobalBounds.width,
viewGlobalBounds.height,
shapeStyle = shapeStyle,
border = border
)
)
}

@SuppressLint("VisibleForTests")
private fun resolveRNShapeStyleAndBorder(
view: ReactViewGroup,
backgroundDrawable: ReactViewBackgroundDrawable,
opacity: Float,
pixelDensity: Float
): Pair<MobileSegment.ShapeStyle, MobileSegment.ShapeBorder> {
val backgroundColor = view.backgroundColor
val colorHexString = formatAsRgba(backgroundColor)
val cornerRadius = backgroundDrawable.fullBorderRadius.toLong().convertToDensityNormalized(pixelDensity)
val borderWidth = backgroundDrawable.fullBorderWidth.toLong().convertToDensityNormalized(pixelDensity)
val borderColor = formatAsRgba(backgroundDrawable.getBorderColor(Spacing.ALL))

return MobileSegment.ShapeStyle(
backgroundColor = colorHexString,
opacity = opacity,
cornerRadius = cornerRadius
) to MobileSegment.ShapeBorder(
color = borderColor,
width = borderWidth
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.reactnative.sessionreplay

import fr.xgouchet.elmyr.junit5.ForgeExtension
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.Extensions
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.junit.jupiter.MockitoSettings
import org.mockito.quality.Strictness

@Extensions(
ExtendWith(MockitoExtension::class),
ExtendWith(ForgeExtension::class)
)
@MockitoSettings(strictness = Strictness.LENIENT)
internal class ColorUtilsTest {

@Test
fun `M return without alpha W formatAsRgba { color without alpha }`() {
// When
val hexColor = formatAsRgba(16711680)

// Then
assertThat(hexColor).isEqualTo("#ff0000")
}

@Test
fun `M resolve with alpha W formatAsRgba { color with alpha }`() {
// When
val hexColor = formatAsRgba(1717960806)

// Then
assertThat(hexColor).isEqualTo("#66006666")
}
}

0 comments on commit 7bed2b7

Please sign in to comment.