Skip to content

Commit

Permalink
Conditional DrawableUtils based on RN version
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-saia-datadog committed Dec 17, 2024
1 parent dd9c158 commit caf4235
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 76 deletions.
6 changes: 6 additions & 0 deletions packages/react-native-session-replay/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ android {
} else {
java.srcDirs += ['src/oldarch/kotlin']
}

if (reactNativeMinorVersion >= 75) {
java.srcDirs += ['src/rn75/kotlin']
} else {
java.srcDirs += ['src/rnlegacy/kotlin']
}
}
test {
java.srcDir("src/test/kotlin")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,26 @@

package com.datadog.reactnative.sessionreplay

import ReactViewBackgroundDrawableUtils
import android.view.Gravity
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerModule
import com.facebook.react.views.text.TextAttributes
import com.facebook.react.views.view.ReactViewBackgroundDrawable
import java.util.Locale

internal class ReactTextPropertiesResolver(
private val reactContext: ReactContext,
private val uiManagerModule: UIManagerModule,
private val reflectionUtils: ReflectionUtils = ReflectionUtils(),
private val reactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils =
ReactViewBackgroundDrawableUtils(),
private val drawableUtils: DrawableUtils = DrawableUtils()
private val drawableUtils: DrawableUtils =
ReactViewBackgroundDrawableUtils()
): TextPropertiesResolver {
override fun addReactNativeProperties(
originalWireframe: MobileSegment.Wireframe.TextWireframe,
Expand Down Expand Up @@ -94,14 +92,14 @@ internal class ReactTextPropertiesResolver(
view: TextView,
pixelDensity: Float,
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>? {
val backgroundDrawable: ReactViewBackgroundDrawable =
drawableUtils.getReactBackgroundFromDrawable(view.background) ?: return null
val backgroundDrawable = drawableUtils
.getReactBackgroundFromDrawable(view.background) ?: return null

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

val (shapeStyle, border) =
reactViewBackgroundDrawableUtils
drawableUtils
.resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)

return shapeStyle to border
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.datadog.reactnative.sessionreplay.mappers

import ReactViewBackgroundDrawableUtils
import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.android.sessionreplay.recorder.MappingContext
Expand All @@ -18,13 +19,11 @@ import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver.resolve
import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
import com.facebook.react.views.view.ReactViewGroup

internal class ReactViewGroupMapper(
private val reactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils =
ReactViewBackgroundDrawableUtils(),
private val drawableUtils: DrawableUtils = DrawableUtils()
private val drawableUtils: DrawableUtils =
ReactViewBackgroundDrawableUtils()
) :
BaseWireframeMapper<ReactViewGroup>(
viewIdentifierResolver = DefaultViewIdentifierResolver,
Expand All @@ -49,7 +48,7 @@ internal class ReactViewGroupMapper(

val (shapeStyle, border) =
if (backgroundDrawable != null) {
reactViewBackgroundDrawableUtils
drawableUtils
.resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
} else {
null to null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,16 @@
package com.datadog.reactnative.sessionreplay.utils

import android.graphics.drawable.Drawable
import android.graphics.drawable.InsetDrawable
import android.graphics.drawable.LayerDrawable
import com.facebook.react.views.view.ReactViewBackgroundDrawable
import com.datadog.android.sessionreplay.model.MobileSegment

internal class DrawableUtils {
internal fun getReactBackgroundFromDrawable(drawable: Drawable?): ReactViewBackgroundDrawable? {
if (drawable is ReactViewBackgroundDrawable) {
return drawable
}
internal abstract class DrawableUtils(
protected val reflectionUtils: ReflectionUtils = ReflectionUtils()
) {
internal abstract fun resolveShapeAndBorder(
drawable: Drawable,
opacity: Float,
pixelDensity: Float
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>

if (drawable is InsetDrawable) {
return getReactBackgroundFromDrawable(drawable.drawable)
}

if (drawable is LayerDrawable) {
for (layerNumber in 0 until drawable.numberOfLayers) {
val layer = drawable.getDrawable(layerNumber)
if (layer is ReactViewBackgroundDrawable) {
return layer
}
}
}

return null
}
}
internal abstract fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import android.graphics.drawable.Drawable
import android.graphics.drawable.InsetDrawable
import android.graphics.drawable.LayerDrawable
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.Spacing
import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable

internal class ReactViewBackgroundDrawableUtils : DrawableUtils() {
@OptIn(UnstableReactNativeAPI::class)
override fun resolveShapeAndBorder(
drawable: Drawable,
opacity: Float,
pixelDensity: Float
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?> {
if (drawable !is CSSBackgroundDrawable) {
return null to null
}

val borderProps = resolveBorder(drawable, pixelDensity)
val backgroundColor = getBackgroundColor(drawable)
val colorHexString = if (backgroundColor != null) {
formatAsRgba(backgroundColor)
} else {
return null to borderProps
}

return MobileSegment.ShapeStyle(
colorHexString,
opacity,
getBorderRadius(drawable)
) to borderProps
}

@OptIn(UnstableReactNativeAPI::class)
override fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable? {
if (drawable is CSSBackgroundDrawable) {
return drawable
}

if (drawable is InsetDrawable) {
return getReactBackgroundFromDrawable(drawable.drawable)
}

if (drawable is LayerDrawable) {
for (layerNumber in 0 until drawable.numberOfLayers) {
val layer = drawable.getDrawable(layerNumber)
if (layer is CSSBackgroundDrawable) {
return layer
}
}
}

return null
}

@OptIn(UnstableReactNativeAPI::class)
private fun getBorderRadius(drawable: CSSBackgroundDrawable): Float {
val width = drawable.intrinsicWidth.toFloat()
val height = drawable.intrinsicHeight.toFloat()
return drawable.borderRadius.uniform?.getRadius(width, height) ?: 0f
}

@OptIn(UnstableReactNativeAPI::class)
private fun getBackgroundColor(
backgroundDrawable: CSSBackgroundDrawable
): Int? {
return reflectionUtils.getDeclaredField(
backgroundDrawable,
COLOR_FIELD_NAME
) as Int?
}

@OptIn(UnstableReactNativeAPI::class)
private fun resolveBorder(
backgroundDrawable: CSSBackgroundDrawable,
pixelDensity: Float
): MobileSegment.ShapeBorder {
val borderWidth =
backgroundDrawable.fullBorderWidth.toLong().convertToDensityNormalized(pixelDensity)
val borderColor = formatAsRgba(backgroundDrawable.getBorderColor(Spacing.ALL))

return MobileSegment.ShapeBorder(
color = borderColor,
width = borderWidth
)
}

private fun LengthPercentage?.getRadius(width: Float, height: Float) = this
?.resolve(width, height)
?.let { (it.horizontal + it.vertical) / 2f }
?: 0f

private companion object {
private const val COLOR_FIELD_NAME = "mColor"
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,62 @@
/*
* 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.utils

import android.graphics.drawable.Drawable
import android.graphics.drawable.InsetDrawable
import android.graphics.drawable.LayerDrawable
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
import com.facebook.react.uimanager.Spacing
import com.facebook.react.views.view.ReactViewBackgroundDrawable

internal class ReactViewBackgroundDrawableUtils(
private val reflectionUtils: ReflectionUtils = ReflectionUtils()
) {
internal fun resolveShapeAndBorder(
drawable: ReactViewBackgroundDrawable,
internal class ReactViewBackgroundDrawableUtils() : DrawableUtils() {
override fun resolveShapeAndBorder(
drawable: Drawable,
opacity: Float,
pixelDensity: Float
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?> {
if (drawable !is ReactViewBackgroundDrawable) {
return null to null
}

val borderProps = resolveBorder(drawable, pixelDensity)
val cornerRadius = drawable
.fullBorderRadius
.toLong()
.convertToDensityNormalized(pixelDensity)

val backgroundColor = getBackgroundColor(drawable)
val colorHexString = if (backgroundColor != null) {
formatAsRgba(backgroundColor)
} else {
return null to borderProps
}

val cornerRadius =
drawable.fullBorderRadius.toLong().convertToDensityNormalized(pixelDensity)

return MobileSegment.ShapeStyle(
colorHexString,
opacity,
cornerRadius
) to borderProps
}

private fun getBackgroundColor(
backgroundDrawable: ReactViewBackgroundDrawable,
): Int? {
return reflectionUtils.getDeclaredField(
backgroundDrawable,
COLOR_FIELD_NAME
) as Int?
override fun getReactBackgroundFromDrawable(drawable: Drawable?): Drawable? {
if (drawable is ReactViewBackgroundDrawable) {
return drawable
}

if (drawable is InsetDrawable) {
return getReactBackgroundFromDrawable(drawable.drawable)
}

if (drawable is LayerDrawable) {
for (layerNumber in 0 until drawable.numberOfLayers) {
val layer = drawable.getDrawable(layerNumber)
if (layer is ReactViewBackgroundDrawable) {
return layer
}
}
}

return null
}

private fun resolveBorder(
Expand All @@ -60,7 +73,16 @@ internal class ReactViewBackgroundDrawableUtils(
)
}

private fun getBackgroundColor(
backgroundDrawable: ReactViewBackgroundDrawable
): Int? {
return reflectionUtils.getDeclaredField(
backgroundDrawable,
COLOR_FIELD_NAME
) as Int?
}

private companion object {
private const val COLOR_FIELD_NAME = "mColor"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Compani
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.TEXT_ATTRIBUTES_FIELD_NAME
import com.datadog.reactnative.sessionreplay.ShadowNodeWrapper.Companion.UI_IMPLEMENTATION_FIELD_NAME
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
import com.datadog.reactnative.tools.unit.forge.ForgeConfigurator
Expand Down Expand Up @@ -64,14 +63,11 @@ internal class ReactTextPropertiesResolverTest {
@Mock
lateinit var mockTextView: TextView

@Mock
lateinit var mockDrawableUtils: DrawableUtils

@Mock
lateinit var mockReactViewBackgroundDrawable: ReactViewBackgroundDrawable

@Mock
lateinit var mockReactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils
lateinit var mockDrawableUtils: DrawableUtils

@Mock
lateinit var mockShadowNodeWrapper: ShadowNodeWrapper
Expand Down Expand Up @@ -108,7 +104,6 @@ internal class ReactTextPropertiesResolverTest {
testedResolver = ReactTextPropertiesResolver(
reactContext = mockReactContext,
uiManagerModule = mockUiManagerModule,
reactViewBackgroundDrawableUtils = mockReactViewBackgroundDrawableUtils,
drawableUtils = mockDrawableUtils,
reflectionUtils = mockReflectionUtils
)
Expand Down Expand Up @@ -145,7 +140,7 @@ internal class ReactTextPropertiesResolverTest {
)
).thenReturn(mockReactViewBackgroundDrawable)
whenever(
mockReactViewBackgroundDrawableUtils.resolveShapeAndBorder(
mockDrawableUtils.resolveShapeAndBorder(
drawable = eq(mockReactViewBackgroundDrawable),
opacity = eq(0f),
pixelDensity = eq(0f)
Expand Down
Loading

0 comments on commit caf4235

Please sign in to comment.