From b470622a24d01f41bc9c74bd5d27461f5cf8128a Mon Sep 17 00:00:00 2001 From: Jonathan Moskovich <48201295+jonathanmos@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:49:17 +0200 Subject: [PATCH] Make parent opacity override child opacity --- .../reactnative/sessionreplay/ColorUtils.kt | 30 ++++++++++++++----- .../sessionreplay/ReactViewGroupMapper.kt | 30 ++++++++++++------- .../sessionreplay/ColorUtilsTest.kt | 23 ++++++++++---- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ColorUtils.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ColorUtils.kt index 1ca1b5f1b..f8c588ffc 100644 --- a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ColorUtils.kt +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ColorUtils.kt @@ -6,25 +6,29 @@ package com.datadog.reactnative.sessionreplay +import androidx.annotation.VisibleForTesting +import com.facebook.react.views.view.ReactViewGroup + private const val ALPHA_CODE_LENGTH: Int = 2 private const val HEX_RADIX: Int = 16 private const val OPAQUE_ALPHA_VALUE: Int = 255 private const val HEX_COLOR_INCLUDING_ALPHA_LENGTH: Int = 8 -internal fun resolveAlphaValue(viewAlpha: Float, backgroundColor: Int): Int { +internal fun resolveAlpha(opacity: Float, backgroundColor: Int?): Int { // must not be toHexString because we'll lose the sign bit - val bgColorAsHexCode = backgroundColor.toString(HEX_RADIX) - var pct = viewAlpha + val bgColorAsHexCode = backgroundColor?.toString(HEX_RADIX) + var opacityPercentage = opacity - if (bgColorAsHexCode.length == HEX_COLOR_INCLUDING_ALPHA_LENGTH) { + if (bgColorAsHexCode?.length == HEX_COLOR_INCLUDING_ALPHA_LENGTH) { val opacityCode = bgColorAsHexCode.take(ALPHA_CODE_LENGTH) - pct = alphaCodeToPct(opacityCode) + opacityPercentage = alphaCodeToPct(opacityCode) } - return (pct * OPAQUE_ALPHA_VALUE).toInt() + return (opacityPercentage * OPAQUE_ALPHA_VALUE).toInt() } -private fun alphaCodeToPct(hexAlpha: String): Float { +@VisibleForTesting +internal fun alphaCodeToPct(hexAlpha: String): Float { return try { val alphaDecimal = hexAlpha.toInt(HEX_RADIX) (alphaDecimal / 255.0).toFloat() @@ -33,3 +37,15 @@ private fun alphaCodeToPct(hexAlpha: String): Float { 1f } } + +internal fun resolveOpacity(view: ReactViewGroup, currentOpacity: Float): Float { + return if (view.alpha == 0f) { + 0f + } else { + if (view.parent != null && view.parent is ReactViewGroup) { + resolveOpacity(view.parent as ReactViewGroup, Math.min(view.alpha, currentOpacity)) + } else { + Math.min(view.alpha, currentOpacity) + } + } +} diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactViewGroupMapper.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactViewGroupMapper.kt index 4c9718941..26ec132b2 100644 --- a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactViewGroupMapper.kt +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactViewGroupMapper.kt @@ -7,6 +7,7 @@ package com.datadog.reactnative.sessionreplay import android.annotation.SuppressLint +import android.graphics.Color import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback import com.datadog.android.sessionreplay.internal.recorder.MappingContext import com.datadog.android.sessionreplay.internal.recorder.mapper.BaseWireframeMapper @@ -32,16 +33,23 @@ internal class ReactViewGroupMapper : ) val pixelDensity = mappingContext.systemInformation.screenDensity - val backgroundDrawable = view.background as? ReactViewBackgroundDrawable + val backgroundDrawable = view.background as? ReactViewBackgroundDrawable ?: return emptyList() - val backgroundColor = backgroundDrawable?.color ?: 0 - val viewAlpha = resolveAlphaValue(view.alpha, backgroundColor) + val backgroundColor = view.backgroundColor + if (backgroundColor == Color.TRANSPARENT) { + return emptyList() + } + val opacity = resolveOpacity(view, view.alpha) - if (viewAlpha == 0) { + // view.alpha is the value of the opacity prop on the js side + // this should override the alpha value of the background color if it is explicitly 0 + if (opacity == 0f) { return emptyList() } - val color = colorAndAlphaAsStringHexa( + val viewAlpha = resolveAlpha(opacity, backgroundColor) + + val backgroundColorAndAlpha = colorAndAlphaAsStringHexa( color = backgroundColor, alphaAsHexa = viewAlpha ) @@ -49,7 +57,8 @@ internal class ReactViewGroupMapper : val (shapeStyle, border) = resolveRNShapeStyleAndBorder( backgroundDrawable = backgroundDrawable, viewAlpha = viewAlpha, - viewColor = color, + viewColor = backgroundColorAndAlpha, + opacity = opacity, pixelDensity = pixelDensity ) @@ -66,12 +75,11 @@ internal class ReactViewGroupMapper : ) } - - private fun resolveRNShapeStyleAndBorder( backgroundDrawable: ReactViewBackgroundDrawable?, viewAlpha: Int, viewColor: String, + opacity: Float, pixelDensity: Float ): Pair { @@ -82,7 +90,7 @@ internal class ReactViewGroupMapper : ) to null } else { val cornerRadius = backgroundDrawable.fullBorderRadius - val borderColor = resolveBorderColor(backgroundDrawable) + val borderColor = resolveBorderColor(backgroundDrawable, opacity) val borderWidth = backgroundDrawable.fullBorderWidth val cornerRadiusDp = cornerRadius.toLong().convertToDensityNormalized(pixelDensity) @@ -97,9 +105,9 @@ internal class ReactViewGroupMapper : } } - private fun resolveBorderColor(backgroundDrawable: ReactViewBackgroundDrawable): String { + private fun resolveBorderColor(backgroundDrawable: ReactViewBackgroundDrawable, opacity: Float): String { val borderColor = backgroundDrawable.getBorderColor(Spacing.ALL) - val alpha = resolveAlphaValue(1f, borderColor) + val alpha = resolveAlpha(opacity, borderColor) return colorAndAlphaAsStringHexa(borderColor, alpha) } } \ No newline at end of file diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ColorUtilsTest.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ColorUtilsTest.kt index b97d37f50..39c988709 100644 --- a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ColorUtilsTest.kt +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ColorUtilsTest.kt @@ -6,8 +6,6 @@ package com.datadog.reactnative.sessionreplay -import android.graphics.Color -import androidx.annotation.Size import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -27,8 +25,9 @@ internal class ColorUtilsTest { @Test fun `M use default alpha W resolveAlphaValue { color without opacity }`() { // When - val alphaValue = resolveAlphaValue(0.1f, 0x000000) + val alphaValue = resolveAlpha(0.1f, 0x000000) + // 0.1f = 10% opacity -> 10% of 255 is 25 // Then assertThat(alphaValue) .isEqualTo(25) @@ -40,10 +39,24 @@ internal class ColorUtilsTest { val color = 1717960806 // When - val alphaValue = resolveAlphaValue(0.1f, color) + val alphaValue = resolveAlpha(0.1f, color) + + // decimal color 1717960806 translates to hex 66660066 + // 66 being the hex alpha, which is 40% opacity -> 40% of 255 is 102 + // Then + assertThat(alphaValue).isEqualTo(102) + } + + @Test + fun `M return alpha of 1f W alphaCodeToPct { NumberFormatException }`() { + // Given + val color = "not a number" + + // When + val alphaValue = alphaCodeToPct(color) // Then assertThat(alphaValue) - .isEqualTo(102) + .isEqualTo(1f) } } \ No newline at end of file