mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
f4de45800f
Summary: This Pull Request implements the PlatformColor proposal discussed at https://github.com/react-native-community/discussions-and-proposals/issues/126. The changes include implementations for iOS and Android as well as a PlatformColorExample page in RNTester. Every native platform has the concept of system defined colors. Instead of specifying a concrete color value the app developer can choose a system color that varies in appearance depending on a system theme settings such Light or Dark mode, accessibility settings such as a High Contrast mode, and even its context within the app such as the traits of a containing view or window. The proposal is to add true platform color support to react-native by extending the Flow type `ColorValue` with platform specific color type information for each platform and to provide a convenience function, `PlatformColor()`, for instantiating platform specific ColorValue objects. `PlatformColor(name [, name ...])` where `name` is a system color name on a given platform. If `name` does not resolve to a color for any reason, the next `name` in the argument list will be resolved and so on. If none of the names resolve, a RedBox error occurs. This allows a latest platform color to be used, but if running on an older platform it will fallback to a previous version. The function returns a `ColorValue`. On iOS the values of `name` is one of the iOS [UI Element](https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors) or [Standard Color](https://developer.apple.com/documentation/uikit/uicolor/standard_colors) names such as `labelColor` or `systemFillColor`. On Android the `name` values are the same [app resource](https://developer.android.com/guide/topics/resources/providing-resources) path strings that can be expressed in XML: XML Resource: `@ [<package_name>:]<resource_type>/<resource_name>` Style reference from current theme: `?[<package_name>:][<resource_type>/]<resource_name>` For example: - `?android:colorError` - `?android:attr/colorError` - `?attr/colorPrimary` - `?colorPrimaryDark` - `android:color/holo_purple` - `color/catalyst_redbox_background` On iOS another type of system dynamic color can be created using the `IOSDynamicColor({dark: <color>, light:<color>})` method. The arguments are a tuple containing custom colors for light and dark themes. Such dynamic colors are useful for branding colors or other app specific colors that still respond automatically to system setting changes. Example: `<View style={{ backgroundColor: IOSDynamicColor({light: 'black', dark: 'white'}) }}/>` Other platforms could create platform specific functions similar to `IOSDynamicColor` per the needs of those platforms. For example, macOS has a similar dynamic color type that could be implemented via a `MacDynamicColor`. On Windows custom brushes that tint or otherwise modify a system brush could be created using a platform specific method. ## Changelog [General] [Added] - Added PlatformColor implementations for iOS and Android Pull Request resolved: https://github.com/facebook/react-native/pull/27908 Test Plan: The changes have been tested using the RNTester test app for iOS and Android. On iOS a set of XCTestCase's were added to the Unit Tests. <img width="924" alt="PlatformColor-ios-android" src="https://user-images.githubusercontent.com/30053638/73472497-ff183a80-433f-11ea-90d8-2b04338bbe79.png"> In addition `PlatformColor` support has been added to other out-of-tree platforms such as macOS and Windows has been implemented using these changes: react-native for macOS branch: https://github.com/microsoft/react-native/compare/master...tom-un:tomun/platformcolors react-native for Windows branch: https://github.com/microsoft/react-native-windows/compare/master...tom-un:tomun/platformcolors iOS |Light|Dark| |{F229354502}|{F229354515}| Android |Light|Dark| |{F230114392}|{F230114490}| {F230122700} Reviewed By: hramos Differential Revision: D19837753 Pulled By: TheSavior fbshipit-source-id: 82ca70d40802f3b24591bfd4b94b61f3c38ba829
563 lines
20 KiB
Java
563 lines
20 KiB
Java
/*
|
|
* 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.react.uimanager;
|
|
|
|
import android.content.Context;
|
|
import android.view.View;
|
|
import androidx.annotation.Nullable;
|
|
import com.facebook.common.logging.FLog;
|
|
import com.facebook.react.bridge.ColorPropConverter;
|
|
import com.facebook.react.bridge.Dynamic;
|
|
import com.facebook.react.bridge.DynamicFromObject;
|
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
|
import com.facebook.react.bridge.ReadableArray;
|
|
import com.facebook.react.bridge.ReadableMap;
|
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
|
import com.facebook.react.uimanager.annotations.ReactPropGroup;
|
|
import java.lang.reflect.Method;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* This class is responsible for holding view manager property setters and is used in a process of
|
|
* updating views with the new properties set in JS.
|
|
*/
|
|
/*package*/ class ViewManagersPropertyCache {
|
|
|
|
private static final Map<Class, Map<String, PropSetter>> CLASS_PROPS_CACHE = new HashMap<>();
|
|
private static final Map<String, PropSetter> EMPTY_PROPS_MAP = new HashMap<>();
|
|
|
|
public static void clear() {
|
|
CLASS_PROPS_CACHE.clear();
|
|
EMPTY_PROPS_MAP.clear();
|
|
}
|
|
|
|
/*package*/ abstract static class PropSetter {
|
|
|
|
protected final String mPropName;
|
|
protected final String mPropType;
|
|
protected final Method mSetter;
|
|
protected final @Nullable Integer mIndex; /* non-null only for group setters */
|
|
|
|
// The following Object arrays are used to prevent extra allocations from varargs when we call
|
|
// Method.invoke. It's safe for those objects to be static as we update properties in a single
|
|
// thread sequentially
|
|
private static final Object[] VIEW_MGR_ARGS = new Object[2];
|
|
private static final Object[] VIEW_MGR_GROUP_ARGS = new Object[3];
|
|
private static final Object[] SHADOW_ARGS = new Object[1];
|
|
private static final Object[] SHADOW_GROUP_ARGS = new Object[2];
|
|
|
|
private PropSetter(ReactProp prop, String defaultType, Method setter) {
|
|
mPropName = prop.name();
|
|
mPropType =
|
|
ReactProp.USE_DEFAULT_TYPE.equals(prop.customType()) ? defaultType : prop.customType();
|
|
mSetter = setter;
|
|
mIndex = null;
|
|
}
|
|
|
|
private PropSetter(ReactPropGroup prop, String defaultType, Method setter, int index) {
|
|
mPropName = prop.names()[index];
|
|
mPropType =
|
|
ReactPropGroup.USE_DEFAULT_TYPE.equals(prop.customType())
|
|
? defaultType
|
|
: prop.customType();
|
|
mSetter = setter;
|
|
mIndex = index;
|
|
}
|
|
|
|
public String getPropName() {
|
|
return mPropName;
|
|
}
|
|
|
|
public String getPropType() {
|
|
return mPropType;
|
|
}
|
|
|
|
public void updateViewProp(ViewManager viewManager, View viewToUpdate, Object value) {
|
|
try {
|
|
if (mIndex == null) {
|
|
VIEW_MGR_ARGS[0] = viewToUpdate;
|
|
VIEW_MGR_ARGS[1] = getValueOrDefault(value, viewToUpdate.getContext());
|
|
mSetter.invoke(viewManager, VIEW_MGR_ARGS);
|
|
Arrays.fill(VIEW_MGR_ARGS, null);
|
|
} else {
|
|
VIEW_MGR_GROUP_ARGS[0] = viewToUpdate;
|
|
VIEW_MGR_GROUP_ARGS[1] = mIndex;
|
|
VIEW_MGR_GROUP_ARGS[2] = getValueOrDefault(value, viewToUpdate.getContext());
|
|
mSetter.invoke(viewManager, VIEW_MGR_GROUP_ARGS);
|
|
Arrays.fill(VIEW_MGR_GROUP_ARGS, null);
|
|
}
|
|
} catch (Throwable t) {
|
|
FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t);
|
|
throw new JSApplicationIllegalArgumentException(
|
|
"Error while updating property '"
|
|
+ mPropName
|
|
+ "' of a view managed by: "
|
|
+ viewManager.getName(),
|
|
t);
|
|
}
|
|
}
|
|
|
|
public void updateShadowNodeProp(ReactShadowNode nodeToUpdate, Object value) {
|
|
try {
|
|
if (mIndex == null) {
|
|
SHADOW_ARGS[0] = getValueOrDefault(value, nodeToUpdate.getThemedContext());
|
|
mSetter.invoke(nodeToUpdate, SHADOW_ARGS);
|
|
Arrays.fill(SHADOW_ARGS, null);
|
|
} else {
|
|
SHADOW_GROUP_ARGS[0] = mIndex;
|
|
SHADOW_GROUP_ARGS[1] = getValueOrDefault(value, nodeToUpdate.getThemedContext());
|
|
mSetter.invoke(nodeToUpdate, SHADOW_GROUP_ARGS);
|
|
Arrays.fill(SHADOW_GROUP_ARGS, null);
|
|
}
|
|
} catch (Throwable t) {
|
|
FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t);
|
|
throw new JSApplicationIllegalArgumentException(
|
|
"Error while updating property '"
|
|
+ mPropName
|
|
+ "' in shadow node of type: "
|
|
+ nodeToUpdate.getViewClass(),
|
|
t);
|
|
}
|
|
}
|
|
|
|
protected abstract @Nullable Object getValueOrDefault(Object value, Context context);
|
|
}
|
|
|
|
private static class DynamicPropSetter extends PropSetter {
|
|
|
|
public DynamicPropSetter(ReactProp prop, Method setter) {
|
|
super(prop, "mixed", setter);
|
|
}
|
|
|
|
public DynamicPropSetter(ReactPropGroup prop, Method setter, int index) {
|
|
super(prop, "mixed", setter, index);
|
|
}
|
|
|
|
@Override
|
|
protected Object getValueOrDefault(Object value, Context context) {
|
|
if (value instanceof Dynamic) {
|
|
return value;
|
|
} else {
|
|
return new DynamicFromObject(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class IntPropSetter extends PropSetter {
|
|
|
|
private final int mDefaultValue;
|
|
|
|
public IntPropSetter(ReactProp prop, Method setter, int defaultValue) {
|
|
super(prop, "number", setter);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
public IntPropSetter(ReactPropGroup prop, Method setter, int index, int defaultValue) {
|
|
super(prop, "number", setter, index);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
@Override
|
|
protected Object getValueOrDefault(Object value, Context context) {
|
|
// All numbers from JS are Doubles which can't be simply cast to Integer
|
|
return value == null ? mDefaultValue : (Integer) ((Double) value).intValue();
|
|
}
|
|
}
|
|
|
|
private static class DoublePropSetter extends PropSetter {
|
|
|
|
private final double mDefaultValue;
|
|
|
|
public DoublePropSetter(ReactProp prop, Method setter, double defaultValue) {
|
|
super(prop, "number", setter);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
public DoublePropSetter(ReactPropGroup prop, Method setter, int index, double defaultValue) {
|
|
super(prop, "number", setter, index);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
@Override
|
|
protected Object getValueOrDefault(Object value, Context context) {
|
|
return value == null ? mDefaultValue : (Double) value;
|
|
}
|
|
}
|
|
|
|
private static class ColorPropSetter extends PropSetter {
|
|
|
|
private final int mDefaultValue;
|
|
|
|
public ColorPropSetter(ReactProp prop, Method setter) {
|
|
this(prop, setter, 0);
|
|
}
|
|
|
|
public ColorPropSetter(ReactProp prop, Method setter, int defaultValue) {
|
|
super(prop, "mixed", setter);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
@Override
|
|
protected Object getValueOrDefault(Object value, Context context) {
|
|
if (value == null) {
|
|
return mDefaultValue;
|
|
}
|
|
|
|
return ColorPropConverter.getColor(value, context);
|
|
}
|
|
}
|
|
|
|
private static class BooleanPropSetter extends PropSetter {
|
|
|
|
private final boolean mDefaultValue;
|
|
|
|
public BooleanPropSetter(ReactProp prop, Method setter, boolean defaultValue) {
|
|
super(prop, "boolean", setter);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
@Override
|
|
protected Object getValueOrDefault(Object value, Context context) {
|
|
boolean val = value == null ? mDefaultValue : (boolean) value;
|
|
return val ? Boolean.TRUE : Boolean.FALSE;
|
|
}
|
|
}
|
|
|
|
private static class FloatPropSetter extends PropSetter {
|
|
|
|
private final float mDefaultValue;
|
|
|
|
public FloatPropSetter(ReactProp prop, Method setter, float defaultValue) {
|
|
super(prop, "number", setter);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
public FloatPropSetter(ReactPropGroup prop, Method setter, int index, float defaultValue) {
|
|
super(prop, "number", setter, index);
|
|
mDefaultValue = defaultValue;
|
|
}
|
|
|
|
@Override
|
|
protected Object getValueOrDefault(Object value, Context context) {
|
|
// All numbers from JS are Doubles which can't be simply cast to Float
|
|
return value == null ? mDefaultValue : (Float) ((Double) value).floatValue();
|
|
}
|
|
}
|
|
|
|
private static class ArrayPropSetter extends PropSetter {
|
|
|
|
public ArrayPropSetter(ReactProp prop, Method setter) {
|
|
super(prop, "Array", setter);
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
|
return (ReadableArray) value;
|
|
}
|
|
}
|
|
|
|
private static class MapPropSetter extends PropSetter {
|
|
|
|
public MapPropSetter(ReactProp prop, Method setter) {
|
|
super(prop, "Map", setter);
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
|
return (ReadableMap) value;
|
|
}
|
|
}
|
|
|
|
private static class StringPropSetter extends PropSetter {
|
|
|
|
public StringPropSetter(ReactProp prop, Method setter) {
|
|
super(prop, "String", setter);
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
|
return (String) value;
|
|
}
|
|
}
|
|
|
|
private static class BoxedBooleanPropSetter extends PropSetter {
|
|
|
|
public BoxedBooleanPropSetter(ReactProp prop, Method setter) {
|
|
super(prop, "boolean", setter);
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
|
if (value != null) {
|
|
return (boolean) value ? Boolean.TRUE : Boolean.FALSE;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static class BoxedIntPropSetter extends PropSetter {
|
|
|
|
public BoxedIntPropSetter(ReactProp prop, Method setter) {
|
|
super(prop, "number", setter);
|
|
}
|
|
|
|
public BoxedIntPropSetter(ReactPropGroup prop, Method setter, int index) {
|
|
super(prop, "number", setter, index);
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
|
if (value != null) {
|
|
if (value instanceof Double) {
|
|
return ((Double) value).intValue();
|
|
} else {
|
|
return (Integer) value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/*package*/ static Map<String, String> getNativePropsForView(
|
|
Class<? extends ViewManager> viewManagerTopClass,
|
|
Class<? extends ReactShadowNode> shadowNodeTopClass) {
|
|
Map<String, String> nativeProps = new HashMap<>();
|
|
|
|
Map<String, PropSetter> viewManagerProps =
|
|
getNativePropSettersForViewManagerClass(viewManagerTopClass);
|
|
for (PropSetter setter : viewManagerProps.values()) {
|
|
nativeProps.put(setter.getPropName(), setter.getPropType());
|
|
}
|
|
|
|
Map<String, PropSetter> shadowNodeProps =
|
|
getNativePropSettersForShadowNodeClass(shadowNodeTopClass);
|
|
for (PropSetter setter : shadowNodeProps.values()) {
|
|
nativeProps.put(setter.getPropName(), setter.getPropType());
|
|
}
|
|
|
|
return nativeProps;
|
|
}
|
|
|
|
/**
|
|
* Returns map from property name to setter instances for all the property setters annotated with
|
|
* {@link ReactProp} in the given {@link ViewManager} class plus all the setter declared by its
|
|
* parent classes.
|
|
*/
|
|
/*package*/ static Map<String, PropSetter> getNativePropSettersForViewManagerClass(
|
|
Class<? extends ViewManager> cls) {
|
|
if (cls == ViewManager.class) {
|
|
return EMPTY_PROPS_MAP;
|
|
}
|
|
Map<String, PropSetter> props = CLASS_PROPS_CACHE.get(cls);
|
|
if (props != null) {
|
|
return props;
|
|
}
|
|
// This is to include all the setters from parent classes. Once calculated the result will be
|
|
// stored in CLASS_PROPS_CACHE so that we only scan for @ReactProp annotations once per class.
|
|
props =
|
|
new HashMap<>(
|
|
getNativePropSettersForViewManagerClass(
|
|
(Class<? extends ViewManager>) cls.getSuperclass()));
|
|
extractPropSettersFromViewManagerClassDefinition(cls, props);
|
|
CLASS_PROPS_CACHE.put(cls, props);
|
|
return props;
|
|
}
|
|
|
|
/**
|
|
* Returns map from property name to setter instances for all the property setters annotated with
|
|
* {@link ReactProp} (or {@link ReactPropGroup} in the given {@link ReactShadowNode} subclass plus
|
|
* all the setters declared by its parent classes up to {@link ReactShadowNode} which is treated
|
|
* as a base class.
|
|
*/
|
|
/*package*/ static Map<String, PropSetter> getNativePropSettersForShadowNodeClass(
|
|
Class<? extends ReactShadowNode> cls) {
|
|
for (Class iface : cls.getInterfaces()) {
|
|
if (iface == ReactShadowNode.class) {
|
|
return EMPTY_PROPS_MAP;
|
|
}
|
|
}
|
|
Map<String, PropSetter> props = CLASS_PROPS_CACHE.get(cls);
|
|
if (props != null) {
|
|
return props;
|
|
}
|
|
// This is to include all the setters from parent classes up to ReactShadowNode class
|
|
props =
|
|
new HashMap<>(
|
|
getNativePropSettersForShadowNodeClass(
|
|
(Class<? extends ReactShadowNode>) cls.getSuperclass()));
|
|
extractPropSettersFromShadowNodeClassDefinition(cls, props);
|
|
CLASS_PROPS_CACHE.put(cls, props);
|
|
return props;
|
|
}
|
|
|
|
private static PropSetter createPropSetter(
|
|
ReactProp annotation, Method method, Class<?> propTypeClass) {
|
|
if (propTypeClass == Dynamic.class) {
|
|
return new DynamicPropSetter(annotation, method);
|
|
} else if (propTypeClass == boolean.class) {
|
|
return new BooleanPropSetter(annotation, method, annotation.defaultBoolean());
|
|
} else if (propTypeClass == int.class) {
|
|
if ("Color".equals(annotation.customType())) {
|
|
return new ColorPropSetter(annotation, method, annotation.defaultInt());
|
|
}
|
|
return new IntPropSetter(annotation, method, annotation.defaultInt());
|
|
} else if (propTypeClass == float.class) {
|
|
return new FloatPropSetter(annotation, method, annotation.defaultFloat());
|
|
} else if (propTypeClass == double.class) {
|
|
return new DoublePropSetter(annotation, method, annotation.defaultDouble());
|
|
} else if (propTypeClass == String.class) {
|
|
return new StringPropSetter(annotation, method);
|
|
} else if (propTypeClass == Boolean.class) {
|
|
return new BoxedBooleanPropSetter(annotation, method);
|
|
} else if (propTypeClass == Integer.class) {
|
|
if ("Color".equals(annotation.customType())) {
|
|
return new ColorPropSetter(annotation, method);
|
|
}
|
|
return new BoxedIntPropSetter(annotation, method);
|
|
} else if (propTypeClass == ReadableArray.class) {
|
|
return new ArrayPropSetter(annotation, method);
|
|
} else if (propTypeClass == ReadableMap.class) {
|
|
return new MapPropSetter(annotation, method);
|
|
} else {
|
|
throw new RuntimeException(
|
|
"Unrecognized type: "
|
|
+ propTypeClass
|
|
+ " for method: "
|
|
+ method.getDeclaringClass().getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
}
|
|
|
|
private static void createPropSetters(
|
|
ReactPropGroup annotation,
|
|
Method method,
|
|
Class<?> propTypeClass,
|
|
Map<String, PropSetter> props) {
|
|
String[] names = annotation.names();
|
|
if (propTypeClass == Dynamic.class) {
|
|
for (int i = 0; i < names.length; i++) {
|
|
props.put(names[i], new DynamicPropSetter(annotation, method, i));
|
|
}
|
|
} else if (propTypeClass == int.class) {
|
|
for (int i = 0; i < names.length; i++) {
|
|
props.put(names[i], new IntPropSetter(annotation, method, i, annotation.defaultInt()));
|
|
}
|
|
} else if (propTypeClass == float.class) {
|
|
for (int i = 0; i < names.length; i++) {
|
|
props.put(names[i], new FloatPropSetter(annotation, method, i, annotation.defaultFloat()));
|
|
}
|
|
} else if (propTypeClass == double.class) {
|
|
for (int i = 0; i < names.length; i++) {
|
|
props.put(
|
|
names[i], new DoublePropSetter(annotation, method, i, annotation.defaultDouble()));
|
|
}
|
|
} else if (propTypeClass == Integer.class) {
|
|
for (int i = 0; i < names.length; i++) {
|
|
props.put(names[i], new BoxedIntPropSetter(annotation, method, i));
|
|
}
|
|
} else {
|
|
throw new RuntimeException(
|
|
"Unrecognized type: "
|
|
+ propTypeClass
|
|
+ " for method: "
|
|
+ method.getDeclaringClass().getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
}
|
|
|
|
private static void extractPropSettersFromViewManagerClassDefinition(
|
|
Class<? extends ViewManager> cls, Map<String, PropSetter> props) {
|
|
Method[] declaredMethods = cls.getDeclaredMethods();
|
|
for (int i = 0; i < declaredMethods.length; i++) {
|
|
Method method = declaredMethods[i];
|
|
ReactProp annotation = method.getAnnotation(ReactProp.class);
|
|
if (annotation != null) {
|
|
Class<?>[] paramTypes = method.getParameterTypes();
|
|
if (paramTypes.length != 2) {
|
|
throw new RuntimeException(
|
|
"Wrong number of args for prop setter: " + cls.getName() + "#" + method.getName());
|
|
}
|
|
if (!View.class.isAssignableFrom(paramTypes[0])) {
|
|
throw new RuntimeException(
|
|
"First param should be a view subclass to be updated: "
|
|
+ cls.getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
props.put(annotation.name(), createPropSetter(annotation, method, paramTypes[1]));
|
|
}
|
|
|
|
ReactPropGroup groupAnnotation = method.getAnnotation(ReactPropGroup.class);
|
|
if (groupAnnotation != null) {
|
|
Class<?>[] paramTypes = method.getParameterTypes();
|
|
if (paramTypes.length != 3) {
|
|
throw new RuntimeException(
|
|
"Wrong number of args for group prop setter: "
|
|
+ cls.getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
if (!View.class.isAssignableFrom(paramTypes[0])) {
|
|
throw new RuntimeException(
|
|
"First param should be a view subclass to be updated: "
|
|
+ cls.getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
if (paramTypes[1] != int.class) {
|
|
throw new RuntimeException(
|
|
"Second argument should be property index: "
|
|
+ cls.getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
createPropSetters(groupAnnotation, method, paramTypes[2], props);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void extractPropSettersFromShadowNodeClassDefinition(
|
|
Class<? extends ReactShadowNode> cls, Map<String, PropSetter> props) {
|
|
for (Method method : cls.getDeclaredMethods()) {
|
|
ReactProp annotation = method.getAnnotation(ReactProp.class);
|
|
if (annotation != null) {
|
|
Class<?>[] paramTypes = method.getParameterTypes();
|
|
if (paramTypes.length != 1) {
|
|
throw new RuntimeException(
|
|
"Wrong number of args for prop setter: " + cls.getName() + "#" + method.getName());
|
|
}
|
|
props.put(annotation.name(), createPropSetter(annotation, method, paramTypes[0]));
|
|
}
|
|
|
|
ReactPropGroup groupAnnotation = method.getAnnotation(ReactPropGroup.class);
|
|
if (groupAnnotation != null) {
|
|
Class<?>[] paramTypes = method.getParameterTypes();
|
|
if (paramTypes.length != 2) {
|
|
throw new RuntimeException(
|
|
"Wrong number of args for group prop setter: "
|
|
+ cls.getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
if (paramTypes[0] != int.class) {
|
|
throw new RuntimeException(
|
|
"Second argument should be property index: "
|
|
+ cls.getName()
|
|
+ "#"
|
|
+ method.getName());
|
|
}
|
|
createPropSetters(groupAnnotation, method, paramTypes[1], props);
|
|
}
|
|
}
|
|
}
|
|
}
|