diff --git a/packages/react-native-session-replay/android/build.gradle b/packages/react-native-session-replay/android/build.gradle index 89a0bcaf0..133522fe1 100644 --- a/packages/react-native-session-replay/android/build.gradle +++ b/packages/react-native-session-replay/android/build.gradle @@ -135,7 +135,9 @@ android { java.srcDirs += ['src/oldarch/kotlin'] } - if (reactNativeMinorVersion >= 76) { + if (reactNativeMinorVersion >= 79) { + java.srcDirs += ['src/rn79/kotlin'] + } else if (reactNativeMinorVersion >= 76) { java.srcDirs += ['src/rn76/kotlin'] } else if (reactNativeMinorVersion >= 75) { java.srcDirs += ['src/rn75/kotlin'] diff --git a/packages/react-native-session-replay/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt b/packages/react-native-session-replay/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt new file mode 100644 index 000000000..8ba1e1c86 --- /dev/null +++ b/packages/react-native-session-replay/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt @@ -0,0 +1,13 @@ +/* + * 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.extensions + +import com.facebook.react.uimanager.LengthPercentage + +internal fun LengthPercentage?.getRadius(width: Float, height: Float) = this + ?.resolve(width, height) + ?.let { (it.horizontal + it.vertical) / 2f } + ?: 0f diff --git a/packages/react-native-session-replay/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt b/packages/react-native-session-replay/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt new file mode 100644 index 000000000..9eaf46360 --- /dev/null +++ b/packages/react-native-session-replay/android/src/rn79/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt @@ -0,0 +1,111 @@ +/* + * 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. + */ +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.getRadius +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 { + 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? { + return when(drawable) { + is CSSBackgroundDrawable -> drawable + is InsetDrawable -> getReactBackgroundFromDrawable(drawable.drawable) + is LayerDrawable -> getDrawableFromLayerDrawable(drawable) + else -> null + } + } + + @OptIn(UnstableReactNativeAPI::class) + private fun getDrawableFromLayerDrawable(layerDrawable: LayerDrawable): Drawable? { + for (layerNumber in 0 until layerDrawable.numberOfLayers) { + val layer = layerDrawable.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() + val uniform = getBorderRadiusUniform(drawable) + return uniform?.getRadius(width, height) ?: 0f + } + + @OptIn(UnstableReactNativeAPI::class) + private fun getBorderRadiusUniform( + drawable: CSSBackgroundDrawable + ): LengthPercentage? { + return reflectionUtils.getDeclaredField( + drawable.borderRadius, + UNIFORM_FIELD_NAME + ) as? LengthPercentage + } + + @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 / pixelDensity).toLong() + val borderColor = formatAsRgba(backgroundDrawable.getBorderColor(Spacing.ALL)) + + return MobileSegment.ShapeBorder( + color = borderColor, + width = borderWidth + ) + } + + private companion object { + private const val COLOR_FIELD_NAME = "mColor" + private const val UNIFORM_FIELD_NAME = "uniform" + } +}