-
-
Notifications
You must be signed in to change notification settings - Fork 444
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
345 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
sentry-compose-helper/src/jvmMain/java/io/sentry/compose/SentryComposeUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package io.sentry.compose; | ||
|
||
import androidx.compose.ui.layout.LayoutCoordinatesKt; | ||
import androidx.compose.ui.node.LayoutNode; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
public class SentryComposeUtil { | ||
public static final int[] getLayoutNodeXY(@NotNull final LayoutNode node) { | ||
// Offset is a Kotlin value class, packing x/y into a long | ||
// TODO find a way to use the existing APIs | ||
final long nodePosition = LayoutCoordinatesKt.positionInWindow(node.getCoordinates()); | ||
final int nodeX = (int) Float.intBitsToFloat((int) (nodePosition >> 32)); | ||
final int nodeY = (int) Float.intBitsToFloat((int) (nodePosition)); | ||
return new int[] {nodeX, nodeY}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
...helper/src/jvmMain/java/io/sentry/compose/viewhierarchy/ComposeViewHierarchyExporter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package io.sentry.compose.viewhierarchy; | ||
|
||
import androidx.compose.runtime.collection.MutableVector; | ||
import androidx.compose.ui.layout.ModifierInfo; | ||
import androidx.compose.ui.node.LayoutNode; | ||
import androidx.compose.ui.node.Owner; | ||
import androidx.compose.ui.semantics.SemanticsConfiguration; | ||
import androidx.compose.ui.semantics.SemanticsModifier; | ||
import androidx.compose.ui.semantics.SemanticsPropertyKey; | ||
import io.sentry.compose.SentryComposeUtil; | ||
import io.sentry.internal.viewhierarchy.ViewHierarchyExporter; | ||
import io.sentry.protocol.ViewHierarchyNode; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
@SuppressWarnings("KotlinInternalInJava") | ||
public final class ComposeViewHierarchyExporter implements ViewHierarchyExporter { | ||
|
||
@Override | ||
public boolean export(@NotNull final ViewHierarchyNode parent, @NotNull final Object element) { | ||
|
||
if (!(element instanceof Owner)) { | ||
return false; | ||
} | ||
|
||
final @NotNull LayoutNode rootNode = ((Owner) element).getRoot(); | ||
addChild(parent, rootNode); | ||
return true; | ||
} | ||
|
||
private static void addChild( | ||
@NotNull final ViewHierarchyNode parent, @NotNull final LayoutNode node) { | ||
if (node.isPlaced()) { | ||
final ViewHierarchyNode vhNode = new ViewHierarchyNode(); | ||
setBounds(node, vhNode); | ||
setTag(node, vhNode); | ||
if (parent.getChildren() == null) { | ||
parent.setChildren(new ArrayList<>()); | ||
} | ||
parent.getChildren().add(vhNode); | ||
|
||
final MutableVector<LayoutNode> children = node.getZSortedChildren(); | ||
final int childrenCount = children.getSize(); | ||
for (int i = 0; i < childrenCount; i++) { | ||
final LayoutNode child = children.get(i); | ||
addChild(vhNode, child); | ||
} | ||
} | ||
} | ||
|
||
private static void setTag( | ||
@NotNull final LayoutNode node, @NotNull final ViewHierarchyNode vhNode) { | ||
final List<ModifierInfo> modifiers = node.getModifierInfo(); | ||
for (ModifierInfo modifierInfo : modifiers) { | ||
if (modifierInfo.getModifier() instanceof SemanticsModifier) { | ||
final SemanticsModifier semanticsModifierCore = | ||
(SemanticsModifier) modifierInfo.getModifier(); | ||
final SemanticsConfiguration semanticsConfiguration = | ||
semanticsModifierCore.getSemanticsConfiguration(); | ||
for (Map.Entry<? extends SemanticsPropertyKey<?>, ?> entry : semanticsConfiguration) { | ||
final @Nullable String key = entry.getKey().getName(); | ||
if ("SentryTag".equals(key) || "TestTag".equals(key)) { | ||
if (entry.getValue() instanceof String) { | ||
vhNode.setTag((String) entry.getValue()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private static void setBounds(@NotNull LayoutNode node, @NotNull ViewHierarchyNode vhNode) { | ||
final int nodeHeight = node.getHeight(); | ||
final int nodeWidth = node.getWidth(); | ||
|
||
final int[] xy = SentryComposeUtil.getLayoutNodeXY(node); | ||
|
||
vhNode.setHeight(Double.valueOf(nodeHeight)); | ||
vhNode.setWidth(Double.valueOf(nodeWidth)); | ||
vhNode.setX(Double.valueOf(xy[0])); | ||
vhNode.setY(Double.valueOf(xy[1])); | ||
} | ||
} |
104 changes: 104 additions & 0 deletions
104
...er/src/jvmTest/java/io/sentry/compose/viewhierarchy/ComposeViewHierarchyExporterTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package io.sentry.compose.viewhierarchy; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertNull; | ||
|
||
import androidx.compose.runtime.collection.MutableVector; | ||
import androidx.compose.ui.layout.LayoutCoordinates; | ||
import androidx.compose.ui.layout.ModifierInfo; | ||
import androidx.compose.ui.node.LayoutNode; | ||
import androidx.compose.ui.node.Owner; | ||
import androidx.compose.ui.semantics.SemanticsConfiguration; | ||
import androidx.compose.ui.semantics.SemanticsModifier; | ||
import androidx.compose.ui.semantics.SemanticsPropertyKey; | ||
import io.sentry.internal.viewhierarchy.ViewHierarchyExporter; | ||
import io.sentry.protocol.ViewHierarchyNode; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import kotlin.jvm.functions.Function2; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.junit.Test; | ||
import org.mockito.Mockito; | ||
|
||
public class ComposeViewHierarchyExporterTest { | ||
|
||
@Test | ||
public void testComposeViewHierarchyExport() { | ||
final ViewHierarchyNode rootVhNode = new ViewHierarchyNode(); | ||
|
||
final LayoutNode childA = mockLayoutNode(true, "childA", 10, 20); | ||
final LayoutNode childB = mockLayoutNode(true, null, 10, 20); | ||
final LayoutNode childC = mockLayoutNode(false, null, 10, 20); | ||
final LayoutNode parent = mockLayoutNode(true, "root", 30, 40, childA, childB, childC); | ||
|
||
final Owner node = Mockito.mock(Owner.class); | ||
Mockito.when(node.getRoot()).thenReturn(parent); | ||
|
||
final ViewHierarchyExporter exporter = new ComposeViewHierarchyExporter(); | ||
exporter.export(rootVhNode, node); | ||
|
||
assertEquals(1, rootVhNode.getChildren().size()); | ||
final ViewHierarchyNode parentVhNode = rootVhNode.getChildren().get(0); | ||
|
||
assertEquals("root", parentVhNode.getTag()); | ||
assertEquals(30.0, parentVhNode.getWidth().doubleValue(), 0.001); | ||
assertEquals(40.0, parentVhNode.getHeight().doubleValue(), 0.001); | ||
|
||
// ensure not placed elements (childC) are not part of the view hierarchy | ||
assertEquals(2, parentVhNode.getChildren().size()); | ||
|
||
final ViewHierarchyNode childAVhNode = parentVhNode.getChildren().get(0); | ||
assertEquals("childA", childAVhNode.getTag()); | ||
assertEquals(10.0, childAVhNode.getWidth().doubleValue(), 0.001); | ||
assertEquals(20.0, childAVhNode.getHeight().doubleValue(), 0.001); | ||
assertNull(childAVhNode.getChildren()); | ||
|
||
final ViewHierarchyNode childBVhNode = parentVhNode.getChildren().get(1); | ||
assertNull(childBVhNode.getTag()); | ||
} | ||
|
||
private static LayoutNode mockLayoutNode( | ||
final boolean isPlaced, | ||
final @Nullable String tag, | ||
final int width, | ||
final int height, | ||
LayoutNode... children) { | ||
final LayoutNode nodeA = Mockito.mock(LayoutNode.class); | ||
Mockito.when(nodeA.isPlaced()).thenReturn(isPlaced); | ||
Mockito.when((nodeA.getWidth())).thenReturn(width); | ||
Mockito.when((nodeA.getHeight())).thenReturn(height); | ||
|
||
final ModifierInfo modifierInfo = Mockito.mock(ModifierInfo.class); | ||
Mockito.when(modifierInfo.getModifier()) | ||
.thenReturn( | ||
new SemanticsModifier() { | ||
@NotNull | ||
@Override | ||
public SemanticsConfiguration getSemanticsConfiguration() { | ||
final SemanticsConfiguration config = new SemanticsConfiguration(); | ||
config.set( | ||
new SemanticsPropertyKey<>( | ||
"SentryTag", | ||
new Function2<String, String, String>() { | ||
@Override | ||
public String invoke(String s, String s2) { | ||
return s; | ||
} | ||
}), | ||
tag); | ||
return config; | ||
} | ||
}); | ||
final List<ModifierInfo> modifierInfoList = new ArrayList<>(); | ||
modifierInfoList.add(modifierInfo); | ||
Mockito.when((nodeA.getModifierInfo())).thenReturn(modifierInfoList); | ||
|
||
Mockito.when((nodeA.getZSortedChildren())) | ||
.thenReturn(new MutableVector<>(children, children.length)); | ||
|
||
final LayoutCoordinates coordinates = Mockito.mock(LayoutCoordinates.class); | ||
Mockito.when(nodeA.getCoordinates()).thenReturn(coordinates); | ||
return nodeA; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.