From d8a110384b8a529c5b9401e0a7e51a97e349c9f6 Mon Sep 17 00:00:00 2001 From: Miguel Juarez Lopez Date: Mon, 18 Nov 2024 11:01:49 -0500 Subject: [PATCH 1/2] Handle hybrid AndroidViews inside Compose --- .../capture/replay/internal/ScannableView.kt | 2 +- .../replay/internal/compose/ComposeTreeParser.kt | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/ScannableView.kt b/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/ScannableView.kt index d67ec7b1..f891da8c 100644 --- a/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/ScannableView.kt +++ b/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/ScannableView.kt @@ -29,7 +29,7 @@ internal sealed class ScannableView { /** The children of this view. */ abstract val children: Sequence - class AndroidView(val view: View, private val skipReplayComposeViews: Boolean) : ScannableView() { + class AndroidView(val view: View, skipReplayComposeViews: Boolean) : ScannableView() { override val displayName: String get() = view::class.java.simpleName override val children: Sequence = view.scannableChildren(skipReplayComposeViews) diff --git a/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/compose/ComposeTreeParser.kt b/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/compose/ComposeTreeParser.kt index c0ac77ee..ad1c1d6d 100644 --- a/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/compose/ComposeTreeParser.kt +++ b/platform/jvm/replay/src/main/kotlin/io/bitdrift/capture/replay/internal/compose/ComposeTreeParser.kt @@ -11,6 +11,7 @@ package io.bitdrift.capture.replay.internal.compose import android.view.View import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.InternalComposeUiApi import androidx.compose.ui.geometry.Rect import androidx.compose.ui.platform.AndroidComposeView import androidx.compose.ui.semantics.Role @@ -25,6 +26,7 @@ import io.bitdrift.capture.replay.ReplayType import io.bitdrift.capture.replay.SessionReplayController import io.bitdrift.capture.replay.internal.ReplayRect import io.bitdrift.capture.replay.internal.ScannableView +import io.bitdrift.capture.replay.internal.compose.ComposeTreeParser.unclippedGlobalBounds internal object ComposeTreeParser { internal val View.mightBeComposeView: Boolean @@ -45,8 +47,8 @@ internal object ComposeTreeParser { return rootNode.toScannableView() } - @OptIn(ExperimentalComposeUiApi::class) - private fun SemanticsNode.toScannableView(): ScannableView.ComposeView { + @OptIn(ExperimentalComposeUiApi::class, InternalComposeUiApi::class) + private fun SemanticsNode.toScannableView(): ScannableView { val notAttachedOrPlaced = !this.layoutNode.isPlaced || !this.layoutNode.isAttached val isVisible = !this.isTransparent && !unmergedConfig.contains(SemanticsProperties.InvisibleToUser) val type = if (notAttachedOrPlaced) { @@ -56,6 +58,16 @@ internal object ComposeTreeParser { } else { this.unmergedConfig.toReplayType() } + + // Handle hybrid interop AndroidViews inside Compose elements + val interopAndroidView = this.layoutNode.getInteropView() + if (type == ReplayType.View && interopAndroidView != null) { + return ScannableView.AndroidView( + view = interopAndroidView, + skipReplayComposeViews = false, + ) + } + return ScannableView.ComposeView( replayRect = ReplayRect( type = type, From 931fb1c7ce008e292dc1d865377da6bab80dcfaf Mon Sep 17 00:00:00 2001 From: Miguel Juarez Lopez Date: Mon, 18 Nov 2024 11:46:34 -0500 Subject: [PATCH 2/2] Add regression test --- .../bitdrift/gradletestapp/ComposeReplayTest.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/platform/jvm/gradle-test-app/src/androidTest/java/io/bitdrift/gradletestapp/ComposeReplayTest.kt b/platform/jvm/gradle-test-app/src/androidTest/java/io/bitdrift/gradletestapp/ComposeReplayTest.kt index ce4359e9..e8df57cc 100644 --- a/platform/jvm/gradle-test-app/src/androidTest/java/io/bitdrift/gradletestapp/ComposeReplayTest.kt +++ b/platform/jvm/gradle-test-app/src/androidTest/java/io/bitdrift/gradletestapp/ComposeReplayTest.kt @@ -8,6 +8,7 @@ package io.bitdrift.gradletestapp import android.view.ViewGroup +import android.widget.Button import android.widget.TextView import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box @@ -324,9 +325,9 @@ class ComposeReplayTest { Column { AndroidView(::TextView) { it.layoutParams = ViewGroup.LayoutParams(200, 80) - it.text = "Baguette Avec Fromage" + it.text = "hi" } - AndroidView(::TextView) { + AndroidView(::Button) { it.layoutParams = ViewGroup.LayoutParams(200, 80) it.text = "short" } @@ -337,12 +338,10 @@ class ComposeReplayTest { val capture = verifyReplayScreen(viewCount = 10) // Column assertThat(capture).contains(ReplayRect(ReplayType.View, 0, 88, 200, 160)) - // AndroidView Top - // TODO(murki): The ReplayRect should be a Label - assertThat(capture).contains(ReplayRect(ReplayType.View, 0, 88, 200, 80)) - // AndroidView Bottom - // TODO(murki): The ReplayRect should be a Label - assertThat(capture).contains(ReplayRect(ReplayType.View, 0, 168, 200, 80)) + // AndroidView w/TextView Top (width reflects the text length + assertThat(capture).contains(ReplayRect(ReplayType.Label, 3, 88, 33, 37)) + // AndroidView w/Button Bottom + assertThat(capture).contains(ReplayRect(ReplayType.Button, 0, 168, 200, 80)) } @Test