Make litho/sections a separate plugin
Summary: Breaking Litho/Sections off. Doesn't really make sense to have two deps for this especially since they're pretty tightly coupled. This makes our core already quite slim. Reviewed By: jknoxville Differential Revision: D17420118 fbshipit-source-id: 9a03911f4af6410745b9aefd0e6a75bdf106660f
This commit is contained in:
committed by
Facebook Github Bot
parent
c4a9b603e2
commit
2baadf9867
30
android/plugins/litho/build.gradle
Normal file
30
android/plugins/litho/build.gradle
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.compileSdkVersion
|
||||
buildToolsVersion rootProject.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.minSdkVersion
|
||||
targetSdkVersion rootProject.targetSdkVersion
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly deps.lithoAnnotations
|
||||
implementation project(':android')
|
||||
implementation deps.lithoCore
|
||||
implementation deps.lithoSectionsDebug
|
||||
implementation deps.lithoSectionsCore
|
||||
implementation deps.lithoWidget
|
||||
implementation deps.supportAppCompat
|
||||
compileOnly deps.jsr305
|
||||
}
|
||||
}
|
||||
11
android/plugins/litho/src/main/AndroidManifest.xml
Normal file
11
android/plugins/litho/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Facebook, Inc. and its affiliates.
|
||||
~
|
||||
~ This source code is licensed under the MIT license found in the LICENSE
|
||||
~ file in the root directory of this source tree.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.facebook.flipper.plugins.litho">
|
||||
</manifest>
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import static com.facebook.flipper.plugins.inspector.InspectorValue.Type.Color;
|
||||
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorValue;
|
||||
import com.facebook.flipper.plugins.inspector.Named;
|
||||
import com.facebook.litho.StateContainer;
|
||||
import com.facebook.litho.annotations.Prop;
|
||||
import com.facebook.litho.annotations.State;
|
||||
import com.facebook.litho.drawable.ComparableColorDrawable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class DataUtils {
|
||||
|
||||
static List<com.facebook.flipper.plugins.inspector.Named<com.facebook.flipper.core.FlipperObject>>
|
||||
getPropData(Object node) throws Exception {
|
||||
final FlipperObject.Builder props = new FlipperObject.Builder();
|
||||
List<Named<FlipperObject>> data = new ArrayList<>();
|
||||
|
||||
boolean hasProps = false;
|
||||
|
||||
for (Field f : node.getClass().getDeclaredFields()) {
|
||||
f.setAccessible(true);
|
||||
|
||||
final Prop annotation = f.getAnnotation(Prop.class);
|
||||
if (annotation != null) {
|
||||
if (f.get(node) != null
|
||||
&& PropWithInspectorSection.class.isAssignableFrom(f.get(node).getClass())) {
|
||||
final AbstractMap.SimpleEntry<String, String> datum =
|
||||
((PropWithInspectorSection) f.get(node)).getFlipperLayoutInspectorSection();
|
||||
if (datum != null) {
|
||||
data.add(new Named<>(datum.getKey(), new FlipperObject(datum.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
switch (annotation.resType()) {
|
||||
case COLOR:
|
||||
props.put(f.getName(), f.get(node) == null ? "null" : fromColor((Integer) f.get(node)));
|
||||
break;
|
||||
case DRAWABLE:
|
||||
props.put(
|
||||
f.getName(), f.get(node) == null ? "null" : fromDrawable((Drawable) f.get(node)));
|
||||
break;
|
||||
default:
|
||||
if (f.get(node) != null
|
||||
&& PropWithDescription.class.isAssignableFrom(f.get(node).getClass())) {
|
||||
final Object description =
|
||||
((PropWithDescription) f.get(node)).getFlipperLayoutInspectorPropDescription();
|
||||
// Treat the description as immutable for now, because it's a "translation" of the
|
||||
// actual prop,
|
||||
// mutating them is not going to change the original prop.
|
||||
if (description instanceof Map<?, ?>) {
|
||||
final Map<?, ?> descriptionMap = (Map<?, ?>) description;
|
||||
for (Map.Entry<?, ?> entry : descriptionMap.entrySet()) {
|
||||
props.put(entry.getKey().toString(), InspectorValue.immutable(entry.getValue()));
|
||||
}
|
||||
} else {
|
||||
props.put(f.getName(), InspectorValue.immutable(description));
|
||||
}
|
||||
} else {
|
||||
if (isTypeMutable(f.getType())) {
|
||||
props.put(f.getName(), InspectorValue.mutable(f.get(node)));
|
||||
} else {
|
||||
props.put(f.getName(), InspectorValue.immutable(f.get(node)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
hasProps = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasProps) {
|
||||
data.add(new Named<>("Props", props.build()));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static FlipperObject getStateData(Object node, StateContainer stateContainer) throws Exception {
|
||||
if (stateContainer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final FlipperObject.Builder state = new FlipperObject.Builder();
|
||||
|
||||
boolean hasState = false;
|
||||
for (Field f : stateContainer.getClass().getDeclaredFields()) {
|
||||
f.setAccessible(true);
|
||||
|
||||
final State annotation = f.getAnnotation(State.class);
|
||||
if (annotation != null) {
|
||||
if (DataUtils.isTypeMutable(f.getType())) {
|
||||
state.put(f.getName(), InspectorValue.mutable(f.get(stateContainer)));
|
||||
} else {
|
||||
state.put(f.getName(), InspectorValue.immutable(f.get(stateContainer)));
|
||||
}
|
||||
hasState = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasState ? state.build() : null;
|
||||
}
|
||||
|
||||
static boolean isTypeMutable(Class<?> type) {
|
||||
if (type == int.class || type == Integer.class) {
|
||||
return true;
|
||||
} else if (type == long.class || type == Long.class) {
|
||||
return true;
|
||||
} else if (type == float.class || type == Float.class) {
|
||||
return true;
|
||||
} else if (type == double.class || type == Double.class) {
|
||||
return true;
|
||||
} else if (type == boolean.class || type == Boolean.class) {
|
||||
return true;
|
||||
} else if (type.isAssignableFrom(String.class)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static com.facebook.flipper.plugins.inspector.InspectorValue fromDrawable(Drawable d) {
|
||||
int color = 0;
|
||||
if (d instanceof ColorDrawable) {
|
||||
color = ((ColorDrawable) d).getColor();
|
||||
} else if (d instanceof ComparableColorDrawable) {
|
||||
color = ((ComparableColorDrawable) d).getColor();
|
||||
}
|
||||
return com.facebook.flipper.plugins.inspector.InspectorValue.mutable(Color, color);
|
||||
}
|
||||
|
||||
static com.facebook.flipper.plugins.inspector.InspectorValue fromColor(int color) {
|
||||
return com.facebook.flipper.plugins.inspector.InspectorValue.mutable(Color, color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import static com.facebook.flipper.plugins.inspector.InspectorValue.Type.Enum;
|
||||
import static com.facebook.flipper.plugins.inspector.InspectorValue.Type.Number;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import androidx.core.util.Pair;
|
||||
import com.facebook.flipper.core.FlipperDynamic;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.flipper.plugins.inspector.HighlightedOverlay;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorValue;
|
||||
import com.facebook.flipper.plugins.inspector.Named;
|
||||
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
|
||||
import com.facebook.flipper.plugins.inspector.Touch;
|
||||
import com.facebook.flipper.plugins.inspector.descriptors.ObjectDescriptor;
|
||||
import com.facebook.litho.Component;
|
||||
import com.facebook.litho.DebugComponent;
|
||||
import com.facebook.litho.DebugLayoutNode;
|
||||
import com.facebook.litho.LithoView;
|
||||
import com.facebook.litho.StateContainer;
|
||||
import com.facebook.yoga.YogaAlign;
|
||||
import com.facebook.yoga.YogaDirection;
|
||||
import com.facebook.yoga.YogaEdge;
|
||||
import com.facebook.yoga.YogaFlexDirection;
|
||||
import com.facebook.yoga.YogaJustify;
|
||||
import com.facebook.yoga.YogaPositionType;
|
||||
import com.facebook.yoga.YogaValue;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class DebugComponentDescriptor extends NodeDescriptor<DebugComponent> {
|
||||
|
||||
private Map<String, List<Pair<String[], FlipperDynamic>>> mOverrides = new HashMap<>();
|
||||
private DebugComponent.Overrider mOverrider =
|
||||
new DebugComponent.Overrider() {
|
||||
@Override
|
||||
public void applyComponentOverrides(String key, Component component) {
|
||||
final List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(key);
|
||||
if (overrides == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Pair<String[], FlipperDynamic> override : overrides) {
|
||||
if (override.first[0].equals("Props")) {
|
||||
applyReflectiveOverride(component, override.first[1], override.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStateOverrides(String key, StateContainer stateContainer) {
|
||||
final List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(key);
|
||||
if (overrides == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Pair<String[], FlipperDynamic> override : overrides) {
|
||||
if (override.first[0].equals("State")) {
|
||||
applyReflectiveOverride(stateContainer, override.first[1], override.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyLayoutOverrides(String key, DebugLayoutNode node) {
|
||||
final List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(key);
|
||||
if (overrides == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Pair<String[], FlipperDynamic> override : overrides) {
|
||||
if (override.first[0].equals("Layout")) {
|
||||
try {
|
||||
applyLayoutOverride(
|
||||
node,
|
||||
Arrays.copyOfRange(override.first, 1, override.first.length),
|
||||
override.second);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void init(DebugComponent node) {
|
||||
// We rely on the LithoView being invalidated when a component hierarchy changes.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(DebugComponent node) {
|
||||
return node.getGlobalKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(DebugComponent node) throws Exception {
|
||||
NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass());
|
||||
if (componentDescriptor.getClass() != ObjectDescriptor.class) {
|
||||
return componentDescriptor.getName(node.getComponent());
|
||||
}
|
||||
return node.getComponent().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount(DebugComponent node) {
|
||||
if (node.getMountedView() != null || node.getMountedDrawable() != null) {
|
||||
return 1;
|
||||
} else {
|
||||
return node.getChildComponents().size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getChildAt(DebugComponent node, int index) {
|
||||
final View mountedView = node.getMountedView();
|
||||
final Drawable mountedDrawable = node.getMountedDrawable();
|
||||
|
||||
if (mountedView != null) {
|
||||
return mountedView;
|
||||
} else if (mountedDrawable != null) {
|
||||
return mountedDrawable;
|
||||
} else {
|
||||
return node.getChildComponents().get(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<FlipperObject>> getData(DebugComponent node) throws Exception {
|
||||
NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass());
|
||||
if (componentDescriptor.getClass() != ObjectDescriptor.class) {
|
||||
return componentDescriptor.getData(node.getComponent());
|
||||
}
|
||||
|
||||
final List<Named<FlipperObject>> data = new ArrayList<>();
|
||||
|
||||
final FlipperObject layoutData = getLayoutData(node);
|
||||
if (layoutData != null) {
|
||||
data.add(new Named<>("Layout", layoutData));
|
||||
}
|
||||
|
||||
final List<Named<FlipperObject>> propData = getPropData(node);
|
||||
if (propData != null) {
|
||||
data.addAll(propData);
|
||||
}
|
||||
|
||||
final FlipperObject stateData = getStateData(node);
|
||||
if (stateData != null) {
|
||||
data.add(new Named<>("State", stateData));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FlipperObject getLayoutData(DebugComponent node) {
|
||||
final DebugLayoutNode layout = node.getLayoutNode();
|
||||
if (layout == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final FlipperObject.Builder data = new FlipperObject.Builder();
|
||||
data.put("background", DataUtils.fromDrawable(layout.getBackground()));
|
||||
data.put("foreground", DataUtils.fromDrawable(layout.getForeground()));
|
||||
|
||||
data.put("direction", InspectorValue.mutable(Enum, layout.getLayoutDirection().toString()));
|
||||
data.put("flex-direction", InspectorValue.mutable(Enum, layout.getFlexDirection().toString()));
|
||||
data.put(
|
||||
"justify-content", InspectorValue.mutable(Enum, layout.getJustifyContent().toString()));
|
||||
data.put("align-items", InspectorValue.mutable(Enum, layout.getAlignItems().toString()));
|
||||
data.put("align-self", InspectorValue.mutable(Enum, layout.getAlignSelf().toString()));
|
||||
data.put("align-content", InspectorValue.mutable(Enum, layout.getAlignContent().toString()));
|
||||
data.put("position-type", InspectorValue.mutable(Enum, layout.getPositionType().toString()));
|
||||
|
||||
data.put("flex-grow", fromFloat(layout.getFlexGrow()));
|
||||
data.put("flex-shrink", fromFloat(layout.getFlexShrink()));
|
||||
data.put("flex-basis", fromYogaValue(layout.getFlexBasis()));
|
||||
|
||||
data.put("width", fromYogaValue(layout.getWidth()));
|
||||
data.put("min-width", fromYogaValue(layout.getMinWidth()));
|
||||
data.put("max-width", fromYogaValue(layout.getMaxWidth()));
|
||||
|
||||
data.put("height", fromYogaValue(layout.getHeight()));
|
||||
data.put("min-height", fromYogaValue(layout.getMinHeight()));
|
||||
data.put("max-height", fromYogaValue(layout.getMaxHeight()));
|
||||
|
||||
data.put("aspect-ratio", fromFloat(layout.getAspectRatio()));
|
||||
|
||||
data.put(
|
||||
"margin",
|
||||
new FlipperObject.Builder()
|
||||
.put("left", fromYogaValue(layout.getMargin(YogaEdge.LEFT)))
|
||||
.put("top", fromYogaValue(layout.getMargin(YogaEdge.TOP)))
|
||||
.put("right", fromYogaValue(layout.getMargin(YogaEdge.RIGHT)))
|
||||
.put("bottom", fromYogaValue(layout.getMargin(YogaEdge.BOTTOM)))
|
||||
.put("start", fromYogaValue(layout.getMargin(YogaEdge.START)))
|
||||
.put("end", fromYogaValue(layout.getMargin(YogaEdge.END)))
|
||||
.put("horizontal", fromYogaValue(layout.getMargin(YogaEdge.HORIZONTAL)))
|
||||
.put("vertical", fromYogaValue(layout.getMargin(YogaEdge.VERTICAL)))
|
||||
.put("all", fromYogaValue(layout.getMargin(YogaEdge.ALL))));
|
||||
|
||||
data.put(
|
||||
"padding",
|
||||
new FlipperObject.Builder()
|
||||
.put("left", fromYogaValue(layout.getPadding(YogaEdge.LEFT)))
|
||||
.put("top", fromYogaValue(layout.getPadding(YogaEdge.TOP)))
|
||||
.put("right", fromYogaValue(layout.getPadding(YogaEdge.RIGHT)))
|
||||
.put("bottom", fromYogaValue(layout.getPadding(YogaEdge.BOTTOM)))
|
||||
.put("start", fromYogaValue(layout.getPadding(YogaEdge.START)))
|
||||
.put("end", fromYogaValue(layout.getPadding(YogaEdge.END)))
|
||||
.put("horizontal", fromYogaValue(layout.getPadding(YogaEdge.HORIZONTAL)))
|
||||
.put("vertical", fromYogaValue(layout.getPadding(YogaEdge.VERTICAL)))
|
||||
.put("all", fromYogaValue(layout.getPadding(YogaEdge.ALL))));
|
||||
|
||||
data.put(
|
||||
"border",
|
||||
new FlipperObject.Builder()
|
||||
.put("left", fromFloat(layout.getBorderWidth(YogaEdge.LEFT)))
|
||||
.put("top", fromFloat(layout.getBorderWidth(YogaEdge.TOP)))
|
||||
.put("right", fromFloat(layout.getBorderWidth(YogaEdge.RIGHT)))
|
||||
.put("bottom", fromFloat(layout.getBorderWidth(YogaEdge.BOTTOM)))
|
||||
.put("start", fromFloat(layout.getBorderWidth(YogaEdge.START)))
|
||||
.put("end", fromFloat(layout.getBorderWidth(YogaEdge.END)))
|
||||
.put("horizontal", fromFloat(layout.getBorderWidth(YogaEdge.HORIZONTAL)))
|
||||
.put("vertical", fromFloat(layout.getBorderWidth(YogaEdge.VERTICAL)))
|
||||
.put("all", fromFloat(layout.getBorderWidth(YogaEdge.ALL))));
|
||||
|
||||
data.put(
|
||||
"position",
|
||||
new FlipperObject.Builder()
|
||||
.put("left", fromYogaValue(layout.getPosition(YogaEdge.LEFT)))
|
||||
.put("top", fromYogaValue(layout.getPosition(YogaEdge.TOP)))
|
||||
.put("right", fromYogaValue(layout.getPosition(YogaEdge.RIGHT)))
|
||||
.put("bottom", fromYogaValue(layout.getPosition(YogaEdge.BOTTOM)))
|
||||
.put("start", fromYogaValue(layout.getPosition(YogaEdge.START)))
|
||||
.put("end", fromYogaValue(layout.getPosition(YogaEdge.END)))
|
||||
.put("horizontal", fromYogaValue(layout.getPosition(YogaEdge.HORIZONTAL)))
|
||||
.put("vertical", fromYogaValue(layout.getPosition(YogaEdge.VERTICAL)))
|
||||
.put("all", fromYogaValue(layout.getPosition(YogaEdge.ALL))));
|
||||
|
||||
return data.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static List<Named<FlipperObject>> getPropData(DebugComponent node) throws Exception {
|
||||
if (node.canResolve()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Component component = node.getComponent();
|
||||
return DataUtils.getPropData(component);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FlipperObject getStateData(DebugComponent node) throws Exception {
|
||||
return DataUtils.getStateData(node, node.getStateContainer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(DebugComponent node, String[] path, FlipperDynamic value) {
|
||||
List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(node.getGlobalKey());
|
||||
if (overrides == null) {
|
||||
overrides = new ArrayList<>();
|
||||
mOverrides.put(node.getGlobalKey(), overrides);
|
||||
}
|
||||
overrides.add(new Pair<>(path, value));
|
||||
|
||||
node.setOverrider(mOverrider);
|
||||
node.rerender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<String>> getAttributes(DebugComponent node) {
|
||||
final List<Named<String>> attributes = new ArrayList<>();
|
||||
final String key = node.getKey();
|
||||
final String testKey = node.getTestKey();
|
||||
|
||||
if (key != null && key.trim().length() > 0) {
|
||||
attributes.add(new Named<>("key", key));
|
||||
}
|
||||
|
||||
if (testKey != null && testKey.trim().length() > 0) {
|
||||
attributes.add(new Named<>("testKey", testKey));
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlipperObject getExtraInfo(DebugComponent node) {
|
||||
FlipperObject.Builder extraInfo = new FlipperObject.Builder();
|
||||
final NodeDescriptor descriptor = descriptorForClass(View.class);
|
||||
final View hostView = node.getComponentHost();
|
||||
final View lithoView = node.getLithoView();
|
||||
|
||||
if (hostView != null) {
|
||||
try {
|
||||
extraInfo.put("linkedNode", descriptor.getId(hostView));
|
||||
} catch (Exception ignored) {
|
||||
// doesn't have linked node descriptor
|
||||
}
|
||||
} else if (lithoView != null) {
|
||||
try {
|
||||
extraInfo.put("linkedNode", descriptor.getId(lithoView)).put("expandWithParent", true);
|
||||
} catch (Exception ignored) {
|
||||
// doesn't add linked node descriptor
|
||||
}
|
||||
}
|
||||
return extraInfo.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHighlighted(DebugComponent node, boolean selected, boolean isAlignmentMode) {
|
||||
final LithoView lithoView = node.getLithoView();
|
||||
if (lithoView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selected) {
|
||||
HighlightedOverlay.removeHighlight(lithoView);
|
||||
return;
|
||||
}
|
||||
|
||||
final DebugLayoutNode layout = node.getLayoutNode();
|
||||
final boolean hasNode = layout != null;
|
||||
final Rect margin;
|
||||
if (!node.isRoot()) {
|
||||
margin =
|
||||
new Rect(
|
||||
hasNode ? (int) layout.getResultMargin(YogaEdge.START) : 0,
|
||||
hasNode ? (int) layout.getResultMargin(YogaEdge.TOP) : 0,
|
||||
hasNode ? (int) layout.getResultMargin(YogaEdge.END) : 0,
|
||||
hasNode ? (int) layout.getResultMargin(YogaEdge.BOTTOM) : 0);
|
||||
} else {
|
||||
// Margin not applied if you're at the root
|
||||
margin = new Rect();
|
||||
}
|
||||
|
||||
final Rect padding =
|
||||
new Rect(
|
||||
hasNode ? (int) layout.getResultPadding(YogaEdge.START) : 0,
|
||||
hasNode ? (int) layout.getResultPadding(YogaEdge.TOP) : 0,
|
||||
hasNode ? (int) layout.getResultPadding(YogaEdge.END) : 0,
|
||||
hasNode ? (int) layout.getResultPadding(YogaEdge.BOTTOM) : 0);
|
||||
|
||||
final Rect contentBounds = node.getBoundsInLithoView();
|
||||
HighlightedOverlay.setHighlighted(lithoView, margin, padding, contentBounds, isAlignmentMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitTest(DebugComponent node, Touch touch) {
|
||||
for (int i = getChildCount(node) - 1; i >= 0; i--) {
|
||||
final Object child = getChildAt(node, i);
|
||||
if (child instanceof DebugComponent) {
|
||||
final DebugComponent componentChild = (DebugComponent) child;
|
||||
final Rect bounds = componentChild.getBounds();
|
||||
|
||||
if (touch.containedIn(bounds.left, bounds.top, bounds.right, bounds.bottom)) {
|
||||
touch.continueWithOffset(i, bounds.left, bounds.top);
|
||||
return;
|
||||
}
|
||||
} else if (child instanceof View || child instanceof Drawable) {
|
||||
// Components can only mount one view or drawable and its bounds are the same as the
|
||||
// hosting component.
|
||||
touch.continueWithOffset(i, 0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
touch.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDecoration(DebugComponent node) throws Exception {
|
||||
if (node.getComponent() != null) {
|
||||
NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass());
|
||||
if (componentDescriptor.getClass() != ObjectDescriptor.class) {
|
||||
return componentDescriptor.getDecoration(node.getComponent());
|
||||
}
|
||||
}
|
||||
return "litho";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String query, DebugComponent node) throws Exception {
|
||||
NodeDescriptor descriptor = descriptorForClass(Object.class);
|
||||
return descriptor.matches(query, node) || getId(node).equals(query);
|
||||
}
|
||||
|
||||
private static void applyLayoutOverride(
|
||||
DebugLayoutNode node, String[] path, FlipperDynamic value) {
|
||||
switch (path[0]) {
|
||||
case "background":
|
||||
node.setBackgroundColor(value.asInt());
|
||||
break;
|
||||
case "foreground":
|
||||
node.setForegroundColor(value.asInt());
|
||||
break;
|
||||
case "direction":
|
||||
node.setLayoutDirection(YogaDirection.valueOf(value.asString().toUpperCase()));
|
||||
break;
|
||||
case "flex-direction":
|
||||
node.setFlexDirection(YogaFlexDirection.valueOf(value.asString().toUpperCase()));
|
||||
break;
|
||||
case "justify-content":
|
||||
node.setJustifyContent(YogaJustify.valueOf(value.asString().toUpperCase()));
|
||||
break;
|
||||
case "align-items":
|
||||
node.setAlignItems(YogaAlign.valueOf(value.asString().toUpperCase()));
|
||||
break;
|
||||
case "align-self":
|
||||
node.setAlignSelf(YogaAlign.valueOf(value.asString().toUpperCase()));
|
||||
break;
|
||||
case "align-content":
|
||||
node.setAlignContent(YogaAlign.valueOf(value.asString().toUpperCase()));
|
||||
break;
|
||||
case "position-type":
|
||||
node.setPositionType(YogaPositionType.valueOf(value.asString().toUpperCase()));
|
||||
break;
|
||||
case "flex-grow":
|
||||
node.setFlexGrow(value.asFloat());
|
||||
break;
|
||||
case "flex-shrink":
|
||||
node.setFlexShrink(value.asFloat());
|
||||
break;
|
||||
case "flex-basis":
|
||||
node.setFlexBasis(YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "width":
|
||||
node.setWidth(YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "min-width":
|
||||
node.setMinWidth(YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "max-width":
|
||||
node.setMaxWidth(YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "height":
|
||||
node.setHeight(YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "min-height":
|
||||
node.setMinHeight(YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "max-height":
|
||||
node.setMaxHeight(YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "aspect-ratio":
|
||||
node.setAspectRatio(value.asFloat());
|
||||
break;
|
||||
case "margin":
|
||||
node.setMargin(edgeFromString(path[1]), YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "padding":
|
||||
node.setPadding(edgeFromString(path[1]), YogaValue.parse(value.asString()));
|
||||
break;
|
||||
case "border":
|
||||
node.setBorderWidth(edgeFromString(path[1]), value.asFloat());
|
||||
break;
|
||||
case "position":
|
||||
node.setPosition(edgeFromString(path[1]), YogaValue.parse(value.asString()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static YogaEdge edgeFromString(String s) {
|
||||
return YogaEdge.valueOf(s.toUpperCase());
|
||||
}
|
||||
|
||||
private static void applyReflectiveOverride(Object o, String key, FlipperDynamic dynamic) {
|
||||
try {
|
||||
final Field field = o.getClass().getDeclaredField(key);
|
||||
field.setAccessible(true);
|
||||
|
||||
final Class type = field.getType();
|
||||
|
||||
Object value = null;
|
||||
if (type == int.class || type == Integer.class) {
|
||||
value = dynamic.asInt();
|
||||
} else if (type == long.class || type == Long.class) {
|
||||
value = dynamic.asLong();
|
||||
} else if (type == float.class || type == Float.class) {
|
||||
value = dynamic.asFloat();
|
||||
} else if (type == double.class || type == Double.class) {
|
||||
value = dynamic.asDouble();
|
||||
} else if (type == boolean.class || type == Boolean.class) {
|
||||
value = dynamic.asBoolean();
|
||||
} else if (type.isAssignableFrom(String.class)) {
|
||||
value = dynamic.asString();
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
field.set(o, value);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static InspectorValue fromFloat(float f) {
|
||||
if (Float.isNaN(f)) {
|
||||
return InspectorValue.mutable(Enum, "undefined");
|
||||
}
|
||||
return InspectorValue.mutable(Number, f);
|
||||
}
|
||||
|
||||
static InspectorValue fromYogaValue(YogaValue v) {
|
||||
// TODO add support for Type.Dimension or similar
|
||||
return InspectorValue.mutable(Enum, v.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.core.view.MarginLayoutParamsCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import com.facebook.flipper.core.ErrorReportingRunnable;
|
||||
import com.facebook.flipper.core.FlipperDynamic;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.flipper.plugins.inspector.HighlightedOverlay;
|
||||
import com.facebook.flipper.plugins.inspector.Named;
|
||||
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
|
||||
import com.facebook.flipper.plugins.inspector.Touch;
|
||||
import com.facebook.litho.sections.Section;
|
||||
import com.facebook.litho.sections.debug.DebugSection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class DebugSectionDescriptor extends NodeDescriptor<DebugSection> {
|
||||
|
||||
@Override
|
||||
public void invalidate(final DebugSection debugSection) {
|
||||
super.invalidate(debugSection);
|
||||
|
||||
new ErrorReportingRunnable(mConnection) {
|
||||
@Override
|
||||
protected void runOrThrow() throws Exception {
|
||||
for (int i = 0; i < getChildCount(debugSection); i++) {
|
||||
Object child = getChildAt(debugSection, i);
|
||||
if (child instanceof DebugSection) {
|
||||
invalidate((DebugSection) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(DebugSection node) throws Exception {}
|
||||
|
||||
@Override
|
||||
public String getId(DebugSection node) throws Exception {
|
||||
return node.getGlobalKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(DebugSection node) throws Exception {
|
||||
return node.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount(DebugSection node) throws Exception {
|
||||
return node.getSectionChildren().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getChildAt(DebugSection node, int index) throws Exception {
|
||||
return node.getSectionChildren().get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<FlipperObject>> getData(DebugSection node) throws Exception {
|
||||
// TODO T39526148 add changeset info
|
||||
final List<Named<FlipperObject>> data = new ArrayList<>();
|
||||
|
||||
final List<Named<FlipperObject>> propData = getPropData(node);
|
||||
if (propData != null) {
|
||||
data.addAll(propData);
|
||||
}
|
||||
|
||||
final FlipperObject stateData = getStateData(node);
|
||||
if (stateData != null) {
|
||||
data.add(new Named<>("State", stateData));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static @Nullable List<Named<FlipperObject>> getPropData(DebugSection node)
|
||||
throws Exception {
|
||||
final Section section = node.getSection();
|
||||
return DataUtils.getPropData(section);
|
||||
}
|
||||
|
||||
private static @Nullable FlipperObject getStateData(DebugSection node) throws Exception {
|
||||
return DataUtils.getStateData(node, node.getStateContainer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(DebugSection node, String[] path, FlipperDynamic value) throws Exception {
|
||||
// TODO T39526148
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<String>> getAttributes(DebugSection node) throws Exception {
|
||||
// TODO T39526148
|
||||
final List<Named<String>> attrs = new ArrayList<>();
|
||||
return attrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHighlighted(DebugSection node, boolean selected, boolean isAlignmentMode)
|
||||
throws Exception {
|
||||
final int childCount = getChildCount(node);
|
||||
|
||||
if (node.isDiffSectionSpec()) {
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View view = (View) getChildAt(node, i);
|
||||
highlightChildView(view, selected, isAlignmentMode);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final Object child = getChildAt(node, i);
|
||||
final NodeDescriptor descriptor = descriptorForClass(child.getClass());
|
||||
descriptor.setHighlighted(child, selected, isAlignmentMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is similar to the implementation in ViewDescriptor but doesn't
|
||||
// target the parent view.
|
||||
private void highlightChildView(View node, boolean selected, boolean isAlignmentMode) {
|
||||
if (!selected) {
|
||||
HighlightedOverlay.removeHighlight(node);
|
||||
return;
|
||||
}
|
||||
|
||||
final Rect padding =
|
||||
new Rect(
|
||||
ViewCompat.getPaddingStart(node),
|
||||
node.getPaddingTop(),
|
||||
ViewCompat.getPaddingEnd(node),
|
||||
node.getPaddingBottom());
|
||||
|
||||
final Rect margin;
|
||||
final ViewGroup.LayoutParams params = node.getLayoutParams();
|
||||
if (params instanceof ViewGroup.MarginLayoutParams) {
|
||||
final ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) params;
|
||||
margin =
|
||||
new Rect(
|
||||
MarginLayoutParamsCompat.getMarginStart(marginParams),
|
||||
marginParams.topMargin,
|
||||
MarginLayoutParamsCompat.getMarginEnd(marginParams),
|
||||
marginParams.bottomMargin);
|
||||
} else {
|
||||
margin = new Rect();
|
||||
}
|
||||
|
||||
final int left = node.getLeft();
|
||||
final int top = node.getTop();
|
||||
|
||||
final Rect contentBounds = new Rect(left, top, left + node.getWidth(), top + node.getHeight());
|
||||
|
||||
contentBounds.offset(-left, -top);
|
||||
|
||||
HighlightedOverlay.setHighlighted(node, margin, padding, contentBounds, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitTest(DebugSection node, Touch touch) throws Exception {
|
||||
final int childCount = getChildCount(node);
|
||||
|
||||
// For a DiffSectionSpec, check if child view to see if the touch is in its bounds.
|
||||
// For a GroupSectionSpec, check the bounds of the entire section.
|
||||
|
||||
if (node.isDiffSectionSpec()) {
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = (View) getChildAt(node, i);
|
||||
int left = child.getLeft() + (int) child.getTranslationX();
|
||||
int top = (child.getTop() + (int) child.getTranslationY());
|
||||
int right = (child.getRight() + (int) child.getTranslationX());
|
||||
int bottom = (child.getBottom() + (int) child.getTranslationY());
|
||||
|
||||
final boolean hit = touch.containedIn(left, top, right, bottom);
|
||||
if (hit) {
|
||||
touch.continueWithOffset(i, left, top);
|
||||
return;
|
||||
}
|
||||
}
|
||||
touch.finish();
|
||||
} else {
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
DebugSection child = (DebugSection) getChildAt(node, i);
|
||||
Rect bounds = child.getBounds();
|
||||
final boolean hit = touch.containedIn(bounds.left, bounds.top, bounds.right, bounds.bottom);
|
||||
if (hit) {
|
||||
touch.continueWithOffset(i, 0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
touch.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDecoration(DebugSection node) throws Exception {
|
||||
// TODO T39526148
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String query, DebugSection node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(Object.class);
|
||||
return descriptor.matches(query, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAXChildCount(DebugSection node) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import com.facebook.flipper.core.FlipperConnection;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.flipper.core.FlipperReceiver;
|
||||
import com.facebook.flipper.core.FlipperResponder;
|
||||
import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver;
|
||||
import com.facebook.flipper.plugins.inspector.ApplicationWrapper;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.inspector.ObjectTracker;
|
||||
import com.facebook.litho.LithoView;
|
||||
import java.util.Stack;
|
||||
|
||||
public final class GenerateLithoAccessibilityRenderExtensionCommand
|
||||
implements InspectorFlipperPlugin.ExtensionCommand {
|
||||
|
||||
@Override
|
||||
public String command() {
|
||||
return "forceLithoAXRender";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlipperReceiver receiver(final ObjectTracker tracker, final FlipperConnection connection) {
|
||||
return new MainThreadFlipperReceiver() {
|
||||
@Override
|
||||
public void onReceiveOnMainThread(
|
||||
final FlipperObject params, final FlipperResponder responder) throws Exception {
|
||||
final String applicationId = params.getString("applicationId");
|
||||
|
||||
// check that the application is valid
|
||||
if (applicationId == null) {
|
||||
return;
|
||||
}
|
||||
final Object obj = tracker.get(applicationId);
|
||||
if (obj != null && !(obj instanceof ApplicationWrapper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ApplicationWrapper applicationWrapper = ((ApplicationWrapper) obj);
|
||||
final boolean forceLithoAXRender = params.getBoolean("forceLithoAXRender");
|
||||
final boolean prevForceLithoAXRender = Boolean.getBoolean("is_accessibility_enabled");
|
||||
|
||||
// nothing has changed, so return
|
||||
if (forceLithoAXRender == prevForceLithoAXRender) {
|
||||
return;
|
||||
}
|
||||
|
||||
// change property and rerender
|
||||
System.setProperty("is_accessibility_enabled", forceLithoAXRender + "");
|
||||
forceRerenderAllLithoViews(forceLithoAXRender, applicationWrapper);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void forceRerenderAllLithoViews(
|
||||
boolean forceLithoAXRender, ApplicationWrapper applicationWrapper) {
|
||||
|
||||
// iterate through tree and rerender all litho views
|
||||
Stack<ViewGroup> lithoViewSearchStack = new Stack<>();
|
||||
for (View root : applicationWrapper.getViewRoots()) {
|
||||
if (root instanceof ViewGroup) {
|
||||
lithoViewSearchStack.push((ViewGroup) root);
|
||||
}
|
||||
}
|
||||
|
||||
while (!lithoViewSearchStack.isEmpty()) {
|
||||
ViewGroup v = lithoViewSearchStack.pop();
|
||||
if (v instanceof LithoView) {
|
||||
((LithoView) v).rerenderForAccessibility(forceLithoAXRender);
|
||||
} else {
|
||||
for (int i = 0; i < v.getChildCount(); i++) {
|
||||
View child = v.getChildAt(i);
|
||||
if (child instanceof ViewGroup) {
|
||||
lithoViewSearchStack.push((ViewGroup) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||
import com.facebook.litho.DebugComponent;
|
||||
import com.facebook.litho.LithoView;
|
||||
import com.facebook.litho.sections.debug.DebugSection;
|
||||
import com.facebook.litho.widget.LithoRecylerView;
|
||||
|
||||
public final class LithoFlipperDescriptors {
|
||||
|
||||
public static void add(DescriptorMapping descriptorMapping) {
|
||||
descriptorMapping.register(LithoView.class, new LithoViewDescriptor());
|
||||
descriptorMapping.register(DebugComponent.class, new DebugComponentDescriptor());
|
||||
}
|
||||
|
||||
public static void addWithSections(DescriptorMapping descriptorMapping) {
|
||||
add(descriptorMapping);
|
||||
descriptorMapping.register(LithoRecylerView.class, new LithoRecyclerViewDescriptor());
|
||||
descriptorMapping.register(DebugSection.class, new DebugSectionDescriptor());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import com.facebook.flipper.core.FlipperDynamic;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.flipper.plugins.inspector.Named;
|
||||
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
|
||||
import com.facebook.flipper.plugins.inspector.Touch;
|
||||
import com.facebook.litho.sections.debug.DebugSection;
|
||||
import com.facebook.litho.widget.LithoRecylerView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LithoRecyclerViewDescriptor extends NodeDescriptor<LithoRecylerView> {
|
||||
|
||||
@Override
|
||||
public void invalidate(final LithoRecylerView node) {
|
||||
super.invalidate(node);
|
||||
|
||||
new com.facebook.flipper.core.ErrorReportingRunnable(mConnection) {
|
||||
@Override
|
||||
protected void runOrThrow() throws Exception {
|
||||
final Object child;
|
||||
child = getChildAt(node, 0);
|
||||
if (child instanceof DebugSection) {
|
||||
DebugSection childSection = (DebugSection) child;
|
||||
final NodeDescriptor descriptor = descriptorForClass(DebugSection.class);
|
||||
descriptor.invalidate(childSection);
|
||||
}
|
||||
}
|
||||
}.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(final LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
descriptor.init(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getId(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getName(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount(LithoRecylerView node) throws Exception {
|
||||
// TODO T39526148 this might not always be true when using the RecyclerBinder manually.
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getChildAt(LithoRecylerView node, int index) throws Exception {
|
||||
// TODO T39526148 account for the case above
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
int count = descriptor.getChildCount(node);
|
||||
|
||||
final List<View> childrenViews = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
childrenViews.add((View) descriptor.getChildAt(node, i));
|
||||
}
|
||||
|
||||
return DebugSection.getRootInstance(childrenViews);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<FlipperObject>> getData(LithoRecylerView node) throws Exception {
|
||||
final List<Named<FlipperObject>> props = new ArrayList<>();
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
props.addAll(descriptor.getData(node));
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(LithoRecylerView node, String[] path, FlipperDynamic value)
|
||||
throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
descriptor.setValue(node, path, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<String>> getAttributes(LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAttributes(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlipperObject getExtraInfo(LithoRecylerView node) {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getExtraInfo(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitTest(LithoRecylerView node, Touch touch) throws Exception {
|
||||
touch.continueWithOffset(0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void axHitTest(LithoRecylerView node, Touch touch) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
descriptor.axHitTest(node, touch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAXName(LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAXName(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<String>> getAXAttributes(LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAXAttributes(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHighlighted(LithoRecylerView node, boolean selected, boolean isAlignmentMode)
|
||||
throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
descriptor.setHighlighted(node, selected, isAlignmentMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDecoration(LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getDecoration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAXDecoration(LithoRecylerView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAXDecoration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String query, LithoRecylerView node) throws Exception {
|
||||
NodeDescriptor descriptor = descriptorForClass(Object.class);
|
||||
return descriptor.matches(query, node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.ViewGroup;
|
||||
import com.facebook.flipper.core.FlipperDynamic;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.flipper.plugins.inspector.Named;
|
||||
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
|
||||
import com.facebook.flipper.plugins.inspector.Touch;
|
||||
import com.facebook.litho.DebugComponent;
|
||||
import com.facebook.litho.LithoView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class LithoViewDescriptor extends NodeDescriptor<LithoView> {
|
||||
|
||||
@Override
|
||||
public void init(LithoView node) throws Exception {
|
||||
node.setOnDirtyMountListener(
|
||||
new LithoView.OnDirtyMountListener() {
|
||||
@Override
|
||||
public void onDirtyMount(LithoView view) {
|
||||
invalidate(view);
|
||||
invalidateAX(view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(LithoView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getId(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(LithoView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getName(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAXName(LithoView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAXName(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount(LithoView node) {
|
||||
return DebugComponent.getRootInstance(node) == null ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAXChildCount(LithoView node) {
|
||||
return node.getChildCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getChildAt(LithoView node, int index) {
|
||||
return DebugComponent.getRootInstance(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getAXChildAt(LithoView node, int index) {
|
||||
return node.getChildAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<FlipperObject>> getData(LithoView node) throws Exception {
|
||||
final List<Named<FlipperObject>> props = new ArrayList<>();
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
final Rect mountedBounds = node.getPreviousMountBounds();
|
||||
|
||||
props.add(
|
||||
0,
|
||||
new Named<>(
|
||||
"LithoView",
|
||||
new FlipperObject.Builder()
|
||||
.put(
|
||||
"mountbounds",
|
||||
new FlipperObject.Builder()
|
||||
.put("left", mountedBounds.left)
|
||||
.put("top", mountedBounds.top)
|
||||
.put("right", mountedBounds.right)
|
||||
.put("bottom", mountedBounds.bottom))
|
||||
.build()));
|
||||
|
||||
props.addAll(descriptor.getData(node));
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<FlipperObject>> getAXData(LithoView node) throws Exception {
|
||||
final List<Named<FlipperObject>> props = new ArrayList<>();
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
props.addAll(descriptor.getAXData(node));
|
||||
return props;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(LithoView node, String[] path, FlipperDynamic value) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
descriptor.setValue(node, path, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<String>> getAttributes(LithoView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAttributes(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Named<String>> getAXAttributes(LithoView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAXAttributes(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlipperObject getExtraInfo(LithoView node) {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getExtraInfo(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHighlighted(LithoView node, boolean selected, boolean isAlignmentMode)
|
||||
throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
descriptor.setHighlighted(node, selected, isAlignmentMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitTest(LithoView node, Touch touch) {
|
||||
touch.continueWithOffset(0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void axHitTest(LithoView node, Touch touch) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
descriptor.axHitTest(node, touch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDecoration(LithoView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getDecoration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAXDecoration(LithoView node) throws Exception {
|
||||
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
|
||||
return descriptor.getAXDecoration(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String query, LithoView node) throws Exception {
|
||||
NodeDescriptor descriptor = descriptorForClass(Object.class);
|
||||
return descriptor.matches(query, node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
public interface PropWithDescription {
|
||||
|
||||
Object getFlipperLayoutInspectorPropDescription();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.litho;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import javax.annotation.CheckForNull;
|
||||
|
||||
public interface PropWithInspectorSection {
|
||||
@CheckForNull
|
||||
AbstractMap.SimpleEntry<String, String> getFlipperLayoutInspectorSection();
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.sections;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import com.facebook.flipper.core.FlipperArray;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.litho.sections.Change;
|
||||
import com.facebook.litho.sections.ChangesInfo;
|
||||
import com.facebook.litho.sections.ChangesetDebugConfiguration;
|
||||
import com.facebook.litho.sections.ChangesetDebugConfiguration.ChangesetDebugInfo;
|
||||
import com.facebook.litho.sections.ChangesetDebugConfiguration.ChangesetDebugListener;
|
||||
import com.facebook.litho.sections.Section;
|
||||
import com.facebook.litho.sections.SectionsLogEventUtils;
|
||||
import com.facebook.litho.sections.SectionsLogEventUtils.ApplyNewChangeSet;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ChangesetDebug implements ChangesetDebugListener {
|
||||
|
||||
private static ChangesetListener sSectionsFlipperPlugin;
|
||||
private static ChangesetDebug sInstance;
|
||||
private static AtomicInteger sChangesetIdGenerator = new AtomicInteger();
|
||||
|
||||
public interface ChangesetListener {
|
||||
void onChangesetApplied(
|
||||
String eventName,
|
||||
String eventSource,
|
||||
String updateStateMethodName,
|
||||
boolean isAsync,
|
||||
String surfaceId,
|
||||
String id,
|
||||
FlipperArray tree,
|
||||
FlipperObject changesetData);
|
||||
}
|
||||
|
||||
public static void setListener(ChangesetListener listener) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new ChangesetDebug(listener);
|
||||
ChangesetDebugConfiguration.setListener(sInstance);
|
||||
}
|
||||
}
|
||||
|
||||
private ChangesetDebug(ChangesetListener listener) {
|
||||
sSectionsFlipperPlugin = listener;
|
||||
}
|
||||
|
||||
public void onChangesetApplied(
|
||||
Section rootSection,
|
||||
Section oldSection,
|
||||
ChangesInfo changesInfo,
|
||||
String surfaceId,
|
||||
@ApplyNewChangeSet int source,
|
||||
String attribution) {}
|
||||
|
||||
public void onChangesetApplied(
|
||||
Section rootSection,
|
||||
ChangesInfo changesInfo,
|
||||
String surfaceId,
|
||||
ChangesetDebugInfo changesetDebugInfo) {
|
||||
final FlipperArray.Builder tree = new FlipperArray.Builder();
|
||||
final FlipperObject.Builder changesetData = new FlipperObject.Builder();
|
||||
final Section oldRootSection = changesetDebugInfo.getOldSection();
|
||||
final int eventSource = changesetDebugInfo.getSource();
|
||||
final String attribution = changesetDebugInfo.getAttribution();
|
||||
final String stateUpdateAttribution = changesetDebugInfo.getUpdateStateAttribution();
|
||||
|
||||
final String eventSourceName =
|
||||
SectionsLogEventUtils.applyNewChangeSetSourceToString(eventSource);
|
||||
|
||||
extractSidePanelChangesetData(changesInfo, changesetData, rootSection.getGlobalKey());
|
||||
|
||||
createSectionTree(rootSection, tree, oldRootSection, stateUpdateAttribution);
|
||||
|
||||
List<DataModelChangeInfo> prevData = getDataFromPreviousTree(oldRootSection);
|
||||
applyChangesInfoOnPreviousData(prevData, changesInfo, tree);
|
||||
|
||||
String eventSourceSection;
|
||||
if (stateUpdateAttribution == null) {
|
||||
eventSourceSection = rootSection == null ? "" : rootSection.getSimpleName();
|
||||
} else {
|
||||
final Section updatedStateSection =
|
||||
findSectionInPreviousTree(oldRootSection, stateUpdateAttribution);
|
||||
eventSourceSection = updatedStateSection == null ? "" : updatedStateSection.getSimpleName();
|
||||
}
|
||||
|
||||
final String stateUpdateMethodName = stateUpdateAttribution == null ? null : attribution;
|
||||
|
||||
sSectionsFlipperPlugin.onChangesetApplied(
|
||||
eventSourceName,
|
||||
eventSourceSection,
|
||||
stateUpdateMethodName,
|
||||
isEventAsync(eventSource),
|
||||
surfaceId,
|
||||
sChangesetIdGenerator.incrementAndGet() + "-" + surfaceId,
|
||||
tree.build(),
|
||||
changesetData.build());
|
||||
}
|
||||
|
||||
private static boolean isEventAsync(@ApplyNewChangeSet int source) {
|
||||
switch (source) {
|
||||
case ApplyNewChangeSet.SET_ROOT_ASYNC:
|
||||
case ApplyNewChangeSet.UPDATE_STATE_ASYNC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Extract the changesets for this section tree. */
|
||||
private static void extractSidePanelChangesetData(
|
||||
ChangesInfo changesInfo, FlipperObject.Builder changesetData, String globalKey) {
|
||||
final FlipperObject.Builder sectionChangesetInfo = new FlipperObject.Builder();
|
||||
|
||||
final List<Change> changeList = changesInfo.getAllChanges();
|
||||
|
||||
final FlipperObject.Builder changesets = new FlipperObject.Builder();
|
||||
for (int i = 0; i < changeList.size(); i++) {
|
||||
final Change change = changeList.get(i);
|
||||
final FlipperObject.Builder changeData = new FlipperObject.Builder();
|
||||
changeData.put("type", Change.changeTypeToString(change.getType()));
|
||||
changeData.put("index", change.getIndex());
|
||||
if (change.getToIndex() >= 0) {
|
||||
changeData.put("toIndex", change.getToIndex());
|
||||
}
|
||||
|
||||
changeData.put("count", change.getCount());
|
||||
|
||||
changeData.put("render_infos", ChangesetDebugConfiguration.getRenderInfoNames(change));
|
||||
|
||||
changeData.put("prev_data", getPrevDataFromChange(change));
|
||||
|
||||
changeData.put("next_data", getNextDataFromChange(change));
|
||||
|
||||
changesets.put(i + "", changeData.build());
|
||||
}
|
||||
sectionChangesetInfo.put("changesets", changesets.build());
|
||||
|
||||
changesetData.put(globalKey, sectionChangesetInfo.build());
|
||||
}
|
||||
|
||||
private static List getPrevDataFromChange(Change change) {
|
||||
if (change.getPrevData() != null) {
|
||||
return getDataNamesFromChange(change.getPrevData());
|
||||
}
|
||||
|
||||
List data = new ArrayList<>();
|
||||
if (change.getRenderInfo() != null) {
|
||||
data.add(change.getRenderInfo().getDebugInfo("SCS_DATA_INFO_PREV"));
|
||||
} else if (change.getRenderInfos() != null) {
|
||||
for (int i = 0; i < change.getRenderInfos().size(); i++) {
|
||||
data.add(change.getRenderInfos().get(i).getDebugInfo("SCS_DATA_INFO_PREV"));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static List getNextDataFromChange(Change change) {
|
||||
if (change.getNextData() != null) {
|
||||
return getDataNamesFromChange(change.getNextData());
|
||||
}
|
||||
|
||||
List data = new ArrayList<>();
|
||||
if (change.getRenderInfo() != null) {
|
||||
data.add(change.getRenderInfo().getDebugInfo("SCS_DATA_INFO_NEXT"));
|
||||
} else if (change.getRenderInfos() != null) {
|
||||
for (int i = 0; i < change.getRenderInfos().size(); i++) {
|
||||
data.add(change.getRenderInfos().get(i).getDebugInfo("SCS_DATA_INFO_NEXT"));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static List<String> getDataNamesFromChange(List<?> data) {
|
||||
final List<String> names = new ArrayList<>();
|
||||
|
||||
if (data == null) {
|
||||
return names;
|
||||
}
|
||||
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
names.add(data.get(i).toString());
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/** Finds the section with the same global key in the previous tree, if it existed. */
|
||||
private static Section findSectionInPreviousTree(Section previousRoot, String globalKey) {
|
||||
if (previousRoot == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (previousRoot.getGlobalKey().equals(globalKey)) {
|
||||
return previousRoot;
|
||||
}
|
||||
|
||||
if (previousRoot.getChildren() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int count = previousRoot.getChildren().size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Section child = previousRoot.getChildren().get(i);
|
||||
final Section previousSection = findSectionInPreviousTree(child, globalKey);
|
||||
|
||||
if (previousSection != null) {
|
||||
return previousSection;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Represents a data model node in a DiffSection. */
|
||||
private static class DataModelChangeInfo {
|
||||
static final int UNCHANGED = -100;
|
||||
Object model;
|
||||
int operation = UNCHANGED;
|
||||
String sectionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips all nodes which have been removed and finds the item on which the Change operation with
|
||||
* the given index is applied on.
|
||||
*/
|
||||
private static int getPositionWithChangesApplied(
|
||||
List<DataModelChangeInfo> dataInfos, int operationIndex) {
|
||||
int count = -1;
|
||||
int i = 0;
|
||||
int size = dataInfos.size();
|
||||
for (i = 0; i < size; i++) {
|
||||
if (dataInfos.get(i).operation != Change.DELETE
|
||||
&& dataInfos.get(i).operation != Change.DELETE_RANGE) {
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == operationIndex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/** For a given DiffSectionSpec section, returns the list of data on which it performs diffing. */
|
||||
private static List getDataFromPreviousSection(Section previousSection) {
|
||||
List data = new ArrayList();
|
||||
|
||||
if (previousSection == null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
Class clasz = previousSection.getClass();
|
||||
final String diffSectionType = clasz.getSimpleName();
|
||||
|
||||
try {
|
||||
if (diffSectionType.equals("DataDiffSection")) {
|
||||
Field field = clasz.getDeclaredField("data");
|
||||
field.setAccessible(true);
|
||||
data = (List) field.get(previousSection);
|
||||
} else if (diffSectionType.equals("SingleComponentSection")) {
|
||||
Field field = clasz.getDeclaredField("component");
|
||||
field.setAccessible(true);
|
||||
data.add(field.get(previousSection));
|
||||
}
|
||||
} catch (@SuppressLint("NewApi") NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void addRemovedSectionNodes(
|
||||
Section previousRoot,
|
||||
Section currentRoot,
|
||||
final FlipperArray.Builder tree,
|
||||
String stateUpdateAttribution) {
|
||||
final Map<String, Section> prevSections = serializeChildren(previousRoot);
|
||||
final Map<String, Section> currSections = serializeChildren(currentRoot);
|
||||
final boolean checkStateUpdateTrigger = stateUpdateAttribution != null;
|
||||
|
||||
for (String prevSectionKey : prevSections.keySet()) {
|
||||
if (!currSections.containsKey(prevSectionKey)) {
|
||||
final FlipperObject.Builder nodeBuilder = new FlipperObject.Builder();
|
||||
final Section section = prevSections.get(prevSectionKey);
|
||||
nodeBuilder.put("identifier", prevSectionKey);
|
||||
nodeBuilder.put("name", section.getSimpleName());
|
||||
nodeBuilder.put(
|
||||
"parent", section.getParent() == null ? null : section.getParent().getGlobalKey());
|
||||
nodeBuilder.put("removed", true);
|
||||
nodeBuilder.put("isSection", true);
|
||||
if (checkStateUpdateTrigger && stateUpdateAttribution.equals(prevSectionKey)) {
|
||||
nodeBuilder.put("didTriggerStateUpdate", true);
|
||||
}
|
||||
tree.put(nodeBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Section> serializeChildren(Section root) {
|
||||
final Map<String, Section> children = new HashMap<>();
|
||||
serializeChildrenRecursive(root, children);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
private static void serializeChildrenRecursive(Section root, Map<String, Section> children) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
children.put(root.getGlobalKey(), root);
|
||||
|
||||
if (root.getChildren() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Section child : root.getChildren()) {
|
||||
serializeChildrenRecursive(child, children);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<DataModelChangeInfo> getDataFromPreviousTree(Section previousRoot) {
|
||||
final List<DataModelChangeInfo> dataModelChangeInfos = new ArrayList<>();
|
||||
|
||||
getDataFromPreviousTreeRecursive(previousRoot, dataModelChangeInfos);
|
||||
|
||||
return dataModelChangeInfos;
|
||||
}
|
||||
|
||||
private static void getDataFromPreviousTreeRecursive(
|
||||
Section previousRoot, List<DataModelChangeInfo> data) {
|
||||
if (previousRoot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousRoot.isDiffSectionSpec()) {
|
||||
List models = getDataFromPreviousSection(previousRoot);
|
||||
for (int i = 0; i < models.size(); i++) {
|
||||
DataModelChangeInfo dataModelChangeInfo = new DataModelChangeInfo();
|
||||
dataModelChangeInfo.model = models.get(i);
|
||||
dataModelChangeInfo.sectionKey = previousRoot.getGlobalKey();
|
||||
data.add(dataModelChangeInfo);
|
||||
}
|
||||
} else if (previousRoot.getChildren() != null) {
|
||||
for (Section child : previousRoot.getChildren()) {
|
||||
getDataFromPreviousTreeRecursive(child, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyChangesInfoOnPreviousData(
|
||||
List<DataModelChangeInfo> dataInfos, ChangesInfo changesInfo, FlipperArray.Builder tree) {
|
||||
final List<Change> changes = changesInfo.getAllChanges();
|
||||
|
||||
for (int i = 0; i < changes.size(); i++) {
|
||||
final Change change = changes.get(i);
|
||||
int index = change.getIndex();
|
||||
|
||||
switch (change.getType()) {
|
||||
case Change.INSERT:
|
||||
{
|
||||
DataModelChangeInfo dataInfo = new DataModelChangeInfo();
|
||||
if (change.getNextData() != null) {
|
||||
dataInfo.model = change.getNextData().get(0);
|
||||
} else {
|
||||
dataInfo.model = getNextDataFromChange(change);
|
||||
}
|
||||
dataInfo.sectionKey =
|
||||
(String) change.getRenderInfo().getDebugInfo("section_global_key");
|
||||
|
||||
dataInfo.operation = Change.INSERT;
|
||||
|
||||
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
|
||||
dataInfos.add(addToPosition, dataInfo);
|
||||
break;
|
||||
}
|
||||
case Change.INSERT_RANGE:
|
||||
{
|
||||
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
|
||||
for (int item = 0; item < change.getCount(); item++) {
|
||||
DataModelChangeInfo dataInfo = new DataModelChangeInfo();
|
||||
if (change.getNextData() != null) {
|
||||
dataInfo.model = change.getNextData().get(item);
|
||||
} else {
|
||||
dataInfo.model = getNextDataFromChange(change);
|
||||
}
|
||||
dataInfo.operation = Change.INSERT_RANGE;
|
||||
dataInfo.sectionKey =
|
||||
(String) change.getRenderInfos().get(item).getDebugInfo("section_global_key");
|
||||
dataInfos.add(addToPosition + item, dataInfo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Change.DELETE:
|
||||
{
|
||||
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
|
||||
DataModelChangeInfo dataInfo = dataInfos.get(addToPosition);
|
||||
dataInfo.operation = Change.DELETE;
|
||||
break;
|
||||
}
|
||||
case Change.DELETE_RANGE:
|
||||
{
|
||||
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
|
||||
for (int del = addToPosition; del < addToPosition + change.getCount(); del++) {
|
||||
dataInfos.get(del).operation = Change.DELETE_RANGE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Change.UPDATE:
|
||||
{
|
||||
int getPosition = getPositionWithChangesApplied(dataInfos, index);
|
||||
DataModelChangeInfo dataInfo = dataInfos.get(getPosition);
|
||||
dataInfo.operation = Change.UPDATE;
|
||||
dataInfo.model = getNextDataFromChange(change);
|
||||
break;
|
||||
}
|
||||
case Change.UPDATE_RANGE:
|
||||
{
|
||||
for (int updateIndex = index; updateIndex < index + change.getCount(); updateIndex++) {
|
||||
int getPosition = getPositionWithChangesApplied(dataInfos, updateIndex);
|
||||
DataModelChangeInfo dataInfo = dataInfos.get(getPosition);
|
||||
dataInfo.operation = Change.UPDATE_RANGE;
|
||||
dataInfo.model = getNextDataFromChange(change);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < dataInfos.size(); i++) {
|
||||
|
||||
DataModelChangeInfo dataInfo = dataInfos.get(i);
|
||||
final FlipperObject.Builder dataObject = new FlipperObject.Builder();
|
||||
|
||||
String name = dataInfo.model == null ? "N/A" : dataInfo.model.toString();
|
||||
int operation = dataInfo.operation;
|
||||
|
||||
dataObject.put("identifier", name);
|
||||
dataObject.put("name", name);
|
||||
dataObject.put("parent", dataInfo.sectionKey);
|
||||
dataObject.put("unchanged", operation == DataModelChangeInfo.UNCHANGED);
|
||||
dataObject.put("inserted", operation == Change.INSERT || operation == Change.INSERT_RANGE);
|
||||
dataObject.put("removed", operation == Change.DELETE || operation == Change.DELETE_RANGE);
|
||||
dataObject.put("updated", operation == Change.UPDATE || operation == Change.UPDATE_RANGE);
|
||||
dataObject.put("isDataModel", true);
|
||||
tree.put(dataObject.build());
|
||||
}
|
||||
}
|
||||
|
||||
private static void createSectionTree(
|
||||
Section rootSection,
|
||||
FlipperArray.Builder tree,
|
||||
Section oldRootSection,
|
||||
String stateUpdateAttribution) {
|
||||
createSectionTreeRecursive(rootSection, "", tree, oldRootSection, 0, stateUpdateAttribution);
|
||||
addRemovedSectionNodes(oldRootSection, rootSection, tree, stateUpdateAttribution);
|
||||
}
|
||||
|
||||
private static void createSectionTreeRecursive(
|
||||
Section rootSection,
|
||||
String parentKey,
|
||||
FlipperArray.Builder tree,
|
||||
Section oldRootSection,
|
||||
int startIndex,
|
||||
String stateUpdateAttribution) {
|
||||
if (rootSection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int endIndex = startIndex + ChangesetDebugConfiguration.getSectionCount(rootSection) - 1;
|
||||
final String name = "[" + startIndex + ", " + endIndex + "] " + rootSection.getSimpleName();
|
||||
|
||||
final String globalKey = rootSection.getGlobalKey();
|
||||
final FlipperObject.Builder nodeBuilder = new FlipperObject.Builder();
|
||||
|
||||
final Section oldSection = findSectionInPreviousTree(oldRootSection, globalKey);
|
||||
final boolean isDirty = ChangesetDebugConfiguration.isSectionDirty(oldSection, rootSection);
|
||||
final boolean triggeredStateUpdate =
|
||||
stateUpdateAttribution != null && stateUpdateAttribution.equals(globalKey);
|
||||
|
||||
nodeBuilder.put("identifier", globalKey);
|
||||
nodeBuilder.put("name", name);
|
||||
nodeBuilder.put("parent", parentKey);
|
||||
nodeBuilder.put("isDirty", isDirty);
|
||||
nodeBuilder.put("isReused", !isDirty);
|
||||
nodeBuilder.put("didTriggerStateUpdate", triggeredStateUpdate);
|
||||
nodeBuilder.put("isSection", true);
|
||||
tree.put(nodeBuilder.build());
|
||||
|
||||
if (rootSection.getChildren() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rootSection.getChildren().size(); i++) {
|
||||
if (i > 0) {
|
||||
startIndex +=
|
||||
ChangesetDebugConfiguration.getSectionCount(rootSection.getChildren().get(i - 1));
|
||||
}
|
||||
createSectionTreeRecursive(
|
||||
rootSection.getChildren().get(i),
|
||||
globalKey,
|
||||
tree,
|
||||
oldRootSection,
|
||||
startIndex,
|
||||
stateUpdateAttribution);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
package com.facebook.flipper.plugins.sections;
|
||||
|
||||
import com.facebook.flipper.core.FlipperArray;
|
||||
import com.facebook.flipper.core.FlipperConnection;
|
||||
import com.facebook.flipper.core.FlipperObject;
|
||||
import com.facebook.flipper.core.FlipperPlugin;
|
||||
import com.facebook.flipper.plugins.sections.ChangesetDebug.ChangesetListener;
|
||||
|
||||
public class SectionsFlipperPlugin implements FlipperPlugin, ChangesetListener {
|
||||
|
||||
private final boolean mEnableDebugging;
|
||||
private FlipperConnection mConnection;
|
||||
|
||||
public SectionsFlipperPlugin(boolean enableDebugging) {
|
||||
mEnableDebugging = enableDebugging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "Sections";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnect(FlipperConnection connection) throws Exception {
|
||||
if (!mEnableDebugging) {
|
||||
return;
|
||||
}
|
||||
|
||||
mConnection = connection;
|
||||
ChangesetDebug.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() throws Exception {}
|
||||
|
||||
@Override
|
||||
public boolean runInBackground() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param eventName Name of event
|
||||
* @param isAsync Whether the event was sync or async
|
||||
* @param surfaceId SectionTree tag
|
||||
* @param id Changeset generation unique id
|
||||
* @param tree Representation of the SectionTree hierarchy
|
||||
* @param changesetData Changeset information
|
||||
*/
|
||||
@Override
|
||||
public void onChangesetApplied(
|
||||
String eventName,
|
||||
String eventSource,
|
||||
String updateStateMethodName,
|
||||
boolean isAsync,
|
||||
String surfaceId,
|
||||
String id,
|
||||
FlipperArray tree,
|
||||
FlipperObject changesetData) {
|
||||
if (mConnection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String reason = eventName + " " + eventSource;
|
||||
final FlipperObject.Builder eventPayloadBuilder = new FlipperObject.Builder();
|
||||
eventPayloadBuilder.put("Event", eventName);
|
||||
eventPayloadBuilder.put("Async", isAsync);
|
||||
eventPayloadBuilder.put("Section", eventSource);
|
||||
if (updateStateMethodName != null) {
|
||||
eventPayloadBuilder.put("Update state method", updateStateMethodName);
|
||||
}
|
||||
|
||||
mConnection.send(
|
||||
"addEvent",
|
||||
new FlipperObject.Builder()
|
||||
.put("id", id)
|
||||
.put("update_mode", isAsync ? 0 : 1)
|
||||
.put("reason", reason)
|
||||
.put("surface_key", surfaceId)
|
||||
.put("tree_generation_timestamp", 10000) // TODO
|
||||
.put("stack_trace", new FlipperArray.Builder().build())
|
||||
.put("payload", eventPayloadBuilder.build())
|
||||
.build());
|
||||
|
||||
mConnection.send(
|
||||
"updateTreeGenerationHierarchyGeneration",
|
||||
new FlipperObject.Builder()
|
||||
.put("id", id)
|
||||
.put("hierarchy_generation_timestamp", 10000) // TODO
|
||||
.put("hierarchy_generation_duration", 0) // TODO
|
||||
.put("tree", tree)
|
||||
.put("reason", eventName)
|
||||
.build());
|
||||
|
||||
// Not sure both CHANGESET_GENERATED and CHANGESET_APPLIED need to sent here, need
|
||||
// to investigate a bit more.
|
||||
mConnection.send(
|
||||
"updateTreeGenerationChangesetGeneration",
|
||||
new FlipperObject.Builder()
|
||||
.put("type", "CHANGESET_GENERATED")
|
||||
.put("identifier", id)
|
||||
.put("tree_generation_id", "" + id)
|
||||
.put("timestamp", 10000) // TODO
|
||||
.put("duration", 0) // TODO
|
||||
.put("changeset", changesetData)
|
||||
.build());
|
||||
|
||||
mConnection.send(
|
||||
"updateTreeGenerationChangesetApplication",
|
||||
new FlipperObject.Builder()
|
||||
.put("type", "CHANGESET_APPLIED")
|
||||
.put("identifier", id)
|
||||
.put("tree_generation_id", id)
|
||||
.put("timestamp", 10000) // TODO
|
||||
.put("duration", 0) // TODO
|
||||
.put("changeset", changesetData)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user