Skip to content

Commit a1de9cc

Browse files
authored
Merge 0e2a446 into 62b579c
2 parents 62b579c + 0e2a446 commit a1de9cc

8 files changed

Lines changed: 175 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Android: Remove the dependency on protobuf-lite for tombstones ([#5157](https://github.com/getsentry/sentry-java/pull/5157))
8+
- Support masking/unmasking and click/scroll detection for Jetpack Compose 1.10+ ([#5189](https://github.com/getsentry/sentry-java/pull/5189))
89

910
### Features
1011

sentry-android-replay/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ kotlin { explicitApi() }
7070

7171
dependencies {
7272
api(projects.sentry)
73+
api(projects.sentryCompose)
7374

7475
compileOnly(libs.androidx.compose.ui.replay)
7576
implementation(kotlin(Config.kotlinStdLib, Config.kotlinStdLibVersionAndroid))

sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import io.sentry.android.replay.util.toOpaque
3030
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.GenericViewHierarchyNode
3131
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.ImageViewHierarchyNode
3232
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.TextViewHierarchyNode
33+
import io.sentry.compose.SentryLayoutNodeHelper
3334
import java.lang.ref.WeakReference
3435
import java.lang.reflect.Method
3536

@@ -157,15 +158,15 @@ internal object ComposeViewHierarchyNode {
157158
shouldMask = true,
158159
isImportantForContentCapture = false, // will be set by children
159160
isVisible =
160-
!node.outerCoordinator.isTransparent() &&
161+
!SentryLayoutNodeHelper.isTransparent(node) &&
161162
visibleRect.height() > 0 &&
162163
visibleRect.width() > 0,
163164
visibleRect = visibleRect,
164165
)
165166
}
166167

167168
val isVisible =
168-
!node.outerCoordinator.isTransparent() &&
169+
!SentryLayoutNodeHelper.isTransparent(node) &&
169170
(semantics == null || !semantics.contains(SemanticsProperties.InvisibleToUser)) &&
170171
visibleRect.height() > 0 &&
171172
visibleRect.width() > 0
@@ -301,7 +302,7 @@ internal object ComposeViewHierarchyNode {
301302
options: SentryMaskingOptions,
302303
logger: ILogger,
303304
) {
304-
val children = this.children
305+
val children = SentryLayoutNodeHelper.getChildren(this)
305306
if (children.isEmpty()) {
306307
return
307308
}

sentry-compose/api/android/sentry-compose.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ public final class io/sentry/compose/SentryComposeTracingKt {
1414
public static final fun SentryTraced (Ljava/lang/String;Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
1515
}
1616

17+
public final class io/sentry/compose/SentryLayoutNodeHelper {
18+
public static final field $stable I
19+
public static final field INSTANCE Lio/sentry/compose/SentryLayoutNodeHelper;
20+
public final fun getChildren (Landroidx/compose/ui/node/LayoutNode;)Ljava/util/List;
21+
public final fun isTransparent (Landroidx/compose/ui/node/LayoutNode;)Z
22+
}
23+
1724
public final class io/sentry/compose/SentryModifier {
1825
public static final field $stable I
1926
public static final field INSTANCE Lio/sentry/compose/SentryModifier;

sentry-compose/build.gradle.kts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import io.gitlab.arturbosch.detekt.Detekt
22
import org.jetbrains.dokka.gradle.DokkaTask
33
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
44
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
5+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
56

67
plugins {
78
alias(libs.plugins.kotlin.multiplatform)
@@ -118,6 +119,80 @@ android {
118119
}
119120
}
120121

122+
// Compile Compose110Helper.kt against Compose 1.10 where internal LayoutNode accessors
123+
// are mangled with module name "ui" (e.g. getChildren$ui()) instead of "ui_release"
124+
val compose110Classpath by
125+
configurations.creating {
126+
isCanBeConsumed = false
127+
isCanBeResolved = true
128+
attributes {
129+
attribute(Attribute.of("artifactType", String::class.java), "android-classes-jar")
130+
}
131+
}
132+
133+
val compose110KotlinCompiler by
134+
configurations.creating {
135+
isCanBeConsumed = false
136+
isCanBeResolved = true
137+
}
138+
139+
dependencies {
140+
//noinspection UseTomlInstead
141+
compose110Classpath("androidx.compose.ui:ui-android:1.10.0")
142+
//noinspection UseTomlInstead
143+
compose110KotlinCompiler("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.0")
144+
}
145+
146+
val compileCompose110 by
147+
tasks.registering(JavaExec::class) {
148+
val sourceDir = file("src/compose110/kotlin")
149+
val outputDir = layout.buildDirectory.dir("classes/kotlin/compose110")
150+
val compileClasspathFiles = compose110Classpath.incoming.files
151+
152+
inputs.dir(sourceDir)
153+
inputs.files(compileClasspathFiles)
154+
outputs.dir(outputDir)
155+
156+
classpath = compose110KotlinCompiler
157+
mainClass.set("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler")
158+
159+
argumentProviders.add(
160+
CommandLineArgumentProvider {
161+
val cp = compileClasspathFiles.files.joinToString(File.pathSeparator)
162+
outputDir.get().asFile.mkdirs()
163+
listOf(
164+
sourceDir.absolutePath,
165+
"-classpath",
166+
cp,
167+
"-d",
168+
outputDir.get().asFile.absolutePath,
169+
"-jvm-target",
170+
"1.8",
171+
"-language-version",
172+
"1.9",
173+
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
174+
"-Xsuppress-version-warnings",
175+
"-no-stdlib",
176+
)
177+
}
178+
)
179+
}
180+
181+
// Make compose110 output available to the Android Kotlin compilation
182+
val compose110Output = files(compileCompose110.map { it.outputs.files })
183+
184+
tasks.withType<KotlinCompile>().configureEach {
185+
if (name == "compileReleaseKotlinAndroid" || name == "compileDebugKotlinAndroid") {
186+
dependsOn(compileCompose110)
187+
libraries.from(compose110Output)
188+
}
189+
}
190+
191+
// Include compose110 classes in the AAR
192+
android.libraryVariants.all {
193+
registerPreJavacGeneratedBytecode(project.files(compileCompose110.map { it.outputs.files }))
194+
}
195+
121196
tasks.withType<Detekt>().configureEach {
122197
// Target version of the generated JVM bytecode. It is used for type resolution.
123198
jvmTarget = JavaVersion.VERSION_1_8.toString()
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
@file:Suppress(
2+
"INVISIBLE_MEMBER",
3+
"INVISIBLE_REFERENCE",
4+
"EXPOSED_PARAMETER_TYPE",
5+
"EXPOSED_RETURN_TYPE",
6+
"EXPOSED_FUNCTION_RETURN_TYPE",
7+
)
8+
9+
package io.sentry.compose
10+
11+
import androidx.compose.ui.node.LayoutNode
12+
import org.jetbrains.annotations.ApiStatus
13+
14+
/**
15+
* Provides access to internal LayoutNode members that are subject to Kotlin name-mangling.
16+
*
17+
* LayoutNode.children and LayoutNode.outerCoordinator are Kotlin `internal`, so their getters are
18+
* mangled with the module name: getChildren$ui_release() in Compose < 1.10 vs getChildren$ui() in
19+
* Compose >= 1.10. This class detects the version on first use and delegates to the correct
20+
* accessor.
21+
*/
22+
@ApiStatus.Internal
23+
public object SentryLayoutNodeHelper {
24+
@Volatile private var compose110Helper: Compose110Helper? = null
25+
@Volatile private var useCompose110: Boolean? = null
26+
27+
private fun getHelper(): Compose110Helper {
28+
compose110Helper?.let {
29+
return it
30+
}
31+
val helper = Compose110Helper()
32+
compose110Helper = helper
33+
return helper
34+
}
35+
36+
public fun getChildren(node: LayoutNode): List<LayoutNode> {
37+
return if (useCompose110 == false) {
38+
node.children
39+
} else {
40+
try {
41+
getHelper().getChildren(node).also { useCompose110 = true }
42+
} catch (_: NoSuchMethodError) {
43+
useCompose110 = false
44+
node.children
45+
}
46+
}
47+
}
48+
49+
public fun isTransparent(node: LayoutNode): Boolean {
50+
return if (useCompose110 == false) {
51+
node.outerCoordinator.isTransparent()
52+
} else {
53+
try {
54+
getHelper().getOuterCoordinator(node).isTransparent().also { useCompose110 = true }
55+
} catch (_: NoSuchMethodError) {
56+
useCompose110 = false
57+
node.outerCoordinator.isTransparent()
58+
}
59+
}
60+
}
61+
}

sentry-compose/src/androidMain/kotlin/io/sentry/compose/gestures/ComposeGestureTargetLocator.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@ public class ComposeGestureTargetLocator(private val logger: ILogger) : GestureT
5252

5353
// the last known tag when iterating the node tree
5454
var lastKnownTag: String? = null
55+
var isClickable = false
56+
var isScrollable = false
57+
5558
while (!queue.isEmpty()) {
5659
val node = queue.poll() ?: continue
5760
if (node.isPlaced && layoutNodeBoundsContain(rootLayoutNode, node, x, y)) {
58-
var isClickable = false
59-
var isScrollable = false
6061

6162
val modifiers = node.getModifierInfo()
6263
for (index in modifiers.indices) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@file:Suppress(
2+
"INVISIBLE_MEMBER",
3+
"INVISIBLE_REFERENCE",
4+
"EXPOSED_PARAMETER_TYPE",
5+
"EXPOSED_RETURN_TYPE",
6+
"EXPOSED_FUNCTION_RETURN_TYPE",
7+
)
8+
9+
package io.sentry.compose
10+
11+
import androidx.compose.ui.node.LayoutNode
12+
import androidx.compose.ui.node.NodeCoordinator
13+
14+
/**
15+
* Compiled against Compose >= 1.10 where internal LayoutNode accessors are mangled with the module
16+
* name "ui" (e.g. getChildren$ui(), getOuterCoordinator$ui()) instead of "ui_release" used in
17+
* earlier versions.
18+
*/
19+
public class Compose110Helper {
20+
public fun getChildren(node: LayoutNode): List<LayoutNode> = node.children
21+
22+
public fun getOuterCoordinator(node: LayoutNode): NodeCoordinator = node.outerCoordinator
23+
}

0 commit comments

Comments
 (0)