From b35cbaae5578addb67490d44223f8ea94578faaf Mon Sep 17 00:00:00 2001 From: Pascal Hartig Date: Fri, 23 Jun 2023 14:42:14 -0700 Subject: [PATCH] Export jetpack-compose code Summary: Move the jetpack plugin to a location that will get exported to GitHub. It won't get built as part of Gradle just yet. Reviewed By: lblasa Differential Revision: D46932691 fbshipit-source-id: 5837bbb2f31aad4221ee745fd145b91b2783d7fe --- .../UIDebuggerComposeSupport.kt | 24 ++++ .../descriptors/ComposeInnerViewDescriptor.kt | 72 ++++++++++ .../descriptors/ComposeNodeDescriptor.kt | 132 ++++++++++++++++++ .../descriptors/ComposeViewDescriptor.kt | 43 ++++++ .../model/ComposeInnerViewNode.kt | 12 ++ .../jetpackcompose/model/ComposeNode.kt | 59 ++++++++ 6 files changed, 342 insertions(+) create mode 100644 android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/UIDebuggerComposeSupport.kt create mode 100644 android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt create mode 100644 android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt create mode 100644 android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeViewDescriptor.kt create mode 100644 android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeInnerViewNode.kt create mode 100644 android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeNode.kt diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/UIDebuggerComposeSupport.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/UIDebuggerComposeSupport.kt new file mode 100644 index 000000000..7853d988b --- /dev/null +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/UIDebuggerComposeSupport.kt @@ -0,0 +1,24 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +package com.facebook.flipper.plugins.jetpackcompose + +import androidx.compose.ui.platform.ComposeView +import com.facebook.flipper.plugins.jetpackcompose.descriptors.* +import com.facebook.flipper.plugins.jetpackcompose.model.* +import com.facebook.flipper.plugins.uidebugger.core.UIDContext +import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister + +const val JetpackComposeTag = "JetpackCompose" + +object UIDebuggerComposeSupport { + + fun enable(context: UIDContext) { + addDescriptors(context.descriptorRegister) + } + + private fun addDescriptors(register: DescriptorRegister) { + register.register(ComposeView::class.java, ComposeViewDescriptor) + register.register(ComposeNode::class.java, ComposeNodeDescriptor) + register.register(ComposeInnerViewNode::class.java, ComposeInnerViewDescriptor) + } +} diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt new file mode 100644 index 000000000..4befe7df3 --- /dev/null +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeInnerViewDescriptor.kt @@ -0,0 +1,72 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +package com.facebook.flipper.plugins.jetpackcompose.descriptors + +import android.graphics.Bitmap +import android.view.ViewGroup +import com.facebook.flipper.plugins.jetpackcompose.model.ComposeInnerViewNode +import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor +import com.facebook.flipper.plugins.uidebugger.descriptors.ViewDescriptor +import com.facebook.flipper.plugins.uidebugger.descriptors.ViewGroupDescriptor +import com.facebook.flipper.plugins.uidebugger.model.Bounds +import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred + +object ComposeInnerViewDescriptor : NodeDescriptor { + + override fun getBounds(node: ComposeInnerViewNode): Bounds { + return node.bounds + } + + override fun getName(node: ComposeInnerViewNode): String { + if (node.view is ViewGroup) { + return ViewGroupDescriptor.getName(node.view) + } + return ViewDescriptor.getName(node.view) + } + + override fun getQualifiedName(node: ComposeInnerViewNode): String { + if (node.view is ViewGroup) { + return ViewGroupDescriptor.getQualifiedName(node.view) + } + return ViewDescriptor.getQualifiedName(node.view) + } + + override fun getChildren(node: ComposeInnerViewNode): List { + if (node.view is ViewGroup) { + return ViewGroupDescriptor.getChildren(node.view) + } + return ViewDescriptor.getChildren(node.view) + } + + override fun getSnapshot(node: ComposeInnerViewNode, bitmap: Bitmap?): Bitmap? { + if (node.view is ViewGroup) { + return ViewGroupDescriptor.getSnapshot(node.view, bitmap) + } + return ViewDescriptor.getSnapshot(node.view, bitmap) + } + + override fun getActiveChild(node: ComposeInnerViewNode): Any? { + if (node.view is ViewGroup) { + return ViewGroupDescriptor.getActiveChild(node.view) + } + return ViewDescriptor.getActiveChild(node.view) + } + + override fun getAttributes( + node: ComposeInnerViewNode + ): MaybeDeferred> { + if (node.view is ViewGroup) { + return ViewGroupDescriptor.getAttributes(node.view) + } + return ViewDescriptor.getAttributes(node.view) + } + + override fun getTags(node: ComposeInnerViewNode): Set { + if (node.view is ViewGroup) { + return ViewGroupDescriptor.getTags(node.view) + } + return ViewDescriptor.getTags(node.view) + } +} diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt new file mode 100644 index 000000000..c14fc1ffd --- /dev/null +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeNodeDescriptor.kt @@ -0,0 +1,132 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +package com.facebook.flipper.plugins.jetpackcompose.descriptors + +import android.graphics.Bitmap +import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode +import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags +import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister +import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor +import com.facebook.flipper.plugins.uidebugger.model.Bounds +import com.facebook.flipper.plugins.uidebugger.model.Coordinate +import com.facebook.flipper.plugins.uidebugger.model.Inspectable +import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.InspectableValue +import com.facebook.flipper.plugins.uidebugger.model.MetadataId +import com.facebook.flipper.plugins.uidebugger.util.Immediate +import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred + +object ComposeNodeDescriptor : NodeDescriptor { + + private const val NAMESPACE = "ComposeNode" + + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + private val IdAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "id") + private val KeyAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "key") + private val NameAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "name") + private val FilenameAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "filename") + private val PackageHashAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "packageHash") + private val LineNumberAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "lineNumber") + private val OffsetAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "offset") + private val LengthAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "length") + private val BoxAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "box") + private val BoundsAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "bounds") + private val Bounds0AttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x0, y0)") + private val Bounds1AttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x1, y1)") + private val Bounds2AttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x2, y2)") + private val Bounds3AttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x3, y3)") + private val ViewIdAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "viewId") + private val MergedSemanticsAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "mergedSemantics") + private val UnmergedSemanticsAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "unmergedSemantics") + + override fun getName(node: ComposeNode): String = node.inspectorNode.name + + override fun getChildren(node: ComposeNode): List { + return node.children + } + + override fun getAttributes(node: ComposeNode): MaybeDeferred> { + + val builder = mutableMapOf() + val props = mutableMapOf() + + props[IdAttributeId] = InspectableValue.Number(node.inspectorNode.id) + props[ViewIdAttributeId] = InspectableValue.Number(node.inspectorNode.viewId) + props[KeyAttributeId] = InspectableValue.Number(node.inspectorNode.key) + props[NameAttributeId] = InspectableValue.Text(node.inspectorNode.name) + props[FilenameAttributeId] = InspectableValue.Text(node.inspectorNode.fileName) + props[PackageHashAttributeId] = InspectableValue.Number(node.inspectorNode.packageHash) + props[LineNumberAttributeId] = InspectableValue.Number(node.inspectorNode.lineNumber) + props[OffsetAttributeId] = InspectableValue.Number(node.inspectorNode.offset) + props[LengthAttributeId] = InspectableValue.Number(node.inspectorNode.length) + + props[BoxAttributeId] = + InspectableValue.Bounds( + Bounds( + node.inspectorNode.left, + node.inspectorNode.top, + node.inspectorNode.width, + node.inspectorNode.height)) + + node.inspectorNode.bounds?.let { bounds -> + val quadBounds = mutableMapOf() + quadBounds[Bounds0AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x0, bounds.y0)) + quadBounds[Bounds1AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x1, bounds.y1)) + quadBounds[Bounds2AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x2, bounds.y2)) + quadBounds[Bounds3AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x3, bounds.y3)) + props[BoundsAttributeId] = InspectableObject(quadBounds.toMap()) + } + + val mergedSemantics = mutableMapOf() + node.inspectorNode.mergedSemantics.forEach { + val keyAttributeId = + MetadataRegister.register( + MetadataRegister.TYPE_ATTRIBUTE, node.inspectorNode.name, it.name) + mergedSemantics[keyAttributeId] = InspectableValue.Text(it.value.toString()) + } + props[MergedSemanticsAttributeId] = InspectableObject(mergedSemantics.toMap()) + + val unmergedSemantics = mutableMapOf() + node.inspectorNode.unmergedSemantics.forEach { + val keyAttributeId = + MetadataRegister.register( + MetadataRegister.TYPE_ATTRIBUTE, node.inspectorNode.name, it.name) + mergedSemantics[keyAttributeId] = InspectableValue.Text(it.value.toString()) + } + props[UnmergedSemanticsAttributeId] = InspectableObject(unmergedSemantics.toMap()) + + builder[SectionId] = InspectableObject(props.toMap()) + + return Immediate(builder) + } + + override fun getBounds(node: ComposeNode): Bounds { + return node.bounds + } + + override fun getQualifiedName(node: ComposeNode): String = node.inspectorNode.name + + override fun getSnapshot(node: ComposeNode, bitmap: Bitmap?): Bitmap? = null + + override fun getActiveChild(node: ComposeNode): Any? = null + + override fun getTags(node: ComposeNode): Set = setOf(BaseTags.Android, "Compose") +} diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeViewDescriptor.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeViewDescriptor.kt new file mode 100644 index 000000000..e6a21ba6d --- /dev/null +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/descriptors/ComposeViewDescriptor.kt @@ -0,0 +1,43 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +package com.facebook.flipper.plugins.jetpackcompose.descriptors + +import android.os.Build +import android.view.View +import androidx.compose.ui.platform.ComposeView +import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode +import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor +import facebook.internal.androidx.compose.ui.inspection.inspector.InspectorNode +import facebook.internal.androidx.compose.ui.inspection.inspector.LayoutInspectorTree + +object ComposeViewDescriptor : ChainedDescriptor() { + override fun onGetName(node: ComposeView): String = node.javaClass.simpleName + + private fun transform(view: View, nodes: List): List { + val positionOnScreen = IntArray(2) + view.getLocationOnScreen(positionOnScreen) + + val xOffset = positionOnScreen[0] + val yOffset = positionOnScreen[1] + + return nodes.map { node -> ComposeNode(view, node, xOffset, yOffset) } + } + + override fun onGetChildren(node: ComposeView): List { + val children = mutableListOf() + val count = node.childCount - 1 + for (i in 0..count) { + val child: View = node.getChildAt(i) + children.add(child) + + if (child.javaClass.simpleName.contains("AndroidComposeView") && + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)) { + val layoutInspector = LayoutInspectorTree() + layoutInspector.hideSystemNodes = false + return transform(child, layoutInspector.convert(child)) + } + } + + return children + } +} diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeInnerViewNode.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeInnerViewNode.kt new file mode 100644 index 000000000..726ee558b --- /dev/null +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeInnerViewNode.kt @@ -0,0 +1,12 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +package com.facebook.flipper.plugins.jetpackcompose.model + +import android.view.View +import com.facebook.flipper.plugins.uidebugger.model.Bounds + +class ComposeInnerViewNode( + val view: View, +) { + val bounds: Bounds = Bounds(0, 0, view.width, view.height) +} diff --git a/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeNode.kt b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeNode.kt new file mode 100644 index 000000000..a7211d91d --- /dev/null +++ b/android/plugins/jetpack-compose/src/main/java/com/facebook/flipper/plugins/jetpackcompose/model/ComposeNode.kt @@ -0,0 +1,59 @@ +// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. + +package com.facebook.flipper.plugins.jetpackcompose.model + +import android.os.Build +import android.view.View +import android.view.ViewGroup +import androidx.annotation.RequiresApi +import com.facebook.flipper.plugins.uidebugger.model.* +import facebook.internal.androidx.compose.ui.inspection.inspector.InspectorNode + +class ComposeNode( + private val parentComposeView: View, + val inspectorNode: InspectorNode, + xOffset: Int, + yOffset: Int +) { + val bounds: Bounds = + Bounds( + inspectorNode.left - xOffset, + inspectorNode.top - yOffset, + inspectorNode.width, + inspectorNode.height) + + val children: List = collectChildren() + + private fun collectChildren(): List { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val viewId = inspectorNode.viewId + if (viewId != 0L) { + val view = parentComposeView.findViewByDrawingId(viewId) + if (view != null) { + return listOf(ComposeInnerViewNode(view)) + } + } + } + + return inspectorNode.children.map { child -> + ComposeNode(parentComposeView, child, inspectorNode.left, inspectorNode.top) + } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun View.findViewByDrawingId(drawingId: Long): View? { + if (this.uniqueDrawingId == drawingId) { + return this + } + if (this is ViewGroup) { + for (i in 0 until this.childCount) { + val child = this.getChildAt(i) + val foundView = child.findViewByDrawingId(drawingId) + if (foundView != null) { + return foundView + } + } + } + return null + } +}