diff --git a/RNTester/js/examples/PlatformColor/PlatformColorExample.js b/RNTester/js/examples/PlatformColor/PlatformColorExample.js index a0bd80ecbf4498..462fdaa5212a68 100644 --- a/RNTester/js/examples/PlatformColor/PlatformColorExample.js +++ b/RNTester/js/examples/PlatformColor/PlatformColorExample.js @@ -86,8 +86,8 @@ class PlatformColorsExample extends React.Component<{}, State> { '?attr/colorControlActivated', '?attr/colorControlHighlight', '?attr/colorControlNormal', + '?attr/colorControlNormal', // '?attr/colorEdgeEffect', - '?attr/colorError', // '?attr/colorFocusedHighlight', // '?attr/colorForeground', // '?attr/colorForegroundInverse', @@ -95,9 +95,15 @@ class PlatformColorsExample extends React.Component<{}, State> { // '?attr/colorMode', // '?attr/colorMultiSelectHighlight', // '?attr/colorPressedHighlight', + '?android:colorError', + '?android:attr/colorError', '?attr/colorPrimary', - '?attr/colorPrimaryDark', - // '?attr/colorSecondary', + '?colorPrimaryDark', + '?attr/colorSecondary', + '@android:color/holo_purple', + '@android:color/holo_green_light', + '@color/catalyst_redbox_background', + '@color/catalyst_logbox_background', ]; } @@ -125,17 +131,28 @@ class PlatformColorsExample extends React.Component<{}, State> { class FallbackColorsExample extends React.Component<{}, State> { state: State; + + getFallbackColor() { + if (Platform.OS === 'ios') { + return 'systemGreenColor'; + } else if (Platform.OS === 'android') { + return '@color/catalyst_redbox_background'; + } + + throw "Unexpected Platform.OS: " + Platform.OS; + } + render() { return ( - First choice is 'bogus' so falls back to 'systemGreenColor' + First choice is 'bogus' so falls back to {this.getFallbackColor()} @@ -202,17 +219,17 @@ exports.examples = [ render: function(): React.Element { return ; }, + }, + { + title: 'Fallback Colors', + render: function(): React.Element { + return ; + }, } ]; if (Platform.OS !== 'android') { exports.examples = [ ...exports.examples, - { - title: 'Fallback Colors', - render: function(): React.Element { - return ; - }, - }, { title: 'Dynamic Colors', render: function(): React.Element { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index e0acefb9fb2d00..f2762c39969aaf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -9,13 +9,13 @@ import android.content.Context; import android.content.res.Resources; -import android.view.View; import android.util.TypedValue; +import android.view.View; + import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; import com.facebook.common.logging.FLog; -import com.facebook.react.R; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.DynamicFromObject; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; @@ -25,7 +25,6 @@ import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; @@ -199,6 +198,13 @@ protected Object getValueOrDefault(Object value, Context context) { } private static class ColorPropSetter extends PropSetter { + private static final String PREFIX_RESOURCE = "@"; + private static final String PREFIX_ATTR = "?"; + private static final String PACKAGE_DELIMITER = ":"; + private static final String PATH_DELIMITER = "/"; + private static final String ATTR = "attr"; + private static final String ATTR_SEGMENT = "attr/"; + private final int mDefaultValue; public ColorPropSetter(ReactProp prop, Method setter) { @@ -213,91 +219,97 @@ public ColorPropSetter(ReactProp prop, Method setter, int defaultValue) { @Override protected Object getValueOrDefault(Object value, Context context) { - if (value == null) { - return new Integer(mDefaultValue); + if (context == null || value == null) { + return mDefaultValue; } + + if (value instanceof Double) { + return ((Double) value).intValue(); + } + if (value.getClass() == ReadableMap.class || value.getClass() == ReadableNativeMap.class) { - // handle custom map - if (context != null) { - ReadableMap map = (ReadableMap)value; - ReadableArray resourcePaths = map.getArray("resource_paths"); - for (int i = 0; i < resourcePaths.size(); i++) { - String resourcePath = resourcePaths.getString(i); - // parse out: - // @[:]/ - // ?[:][/] - - // parse '@' or '?' - boolean themed; - if (resourcePath.startsWith("@")) { - themed = false; - } else if (resourcePath.startsWith("?")) { - themed = true; - } else { - continue; - } - resourcePath = resourcePath.substring(1); - - // parse [:] - String packageName; - String packageNameTokens[] = resourcePath.split(":"); - if (packageNameTokens.length == 2) { - packageName = packageNameTokens[0]; - resourcePath = packageNameTokens[1]; - } + ReadableMap map = (ReadableMap) value; + ReadableArray resourcePaths = map.getArray("resource_paths"); - // parse [/] - String resourceType = "attr"; // default to R.attr - String resourceTypeTokens[] = resourcePath.split("/"); - if (resourceTypeTokens.length == 2) { - resourceType = resourceTypeTokens[0]; - resourcePath = resourceTypeTokens[1]; - } + if (resourcePaths == null) { + return mDefaultValue; + } - // parse - String resourceName = resourcePath; - - int resourceTypeId = 0; - int resourceId = 0; - try { - for (Class resourceTypeClass : R.class.getClasses()) { - if (resourceTypeClass.getName().endsWith(resourceType)) { - - Field resourceIdField = resourceTypeClass.getField(resourceName); - resourceId = resourceIdField.getInt(null); - - break; - } - } - if (resourceId == 0) { - continue; - } - } catch (Exception e) { - continue; - } + for (int i = 0; i < resourcePaths.size(); i++) { + String resourcePath = resourcePaths.getString(i); - if (themed) { - Resources.Theme themes = context.getTheme(); - TypedValue storedValueInTheme = new TypedValue(); - if (themes.resolveAttribute(resourceId, storedValueInTheme, true)) { - return storedValueInTheme.data; - } else { - continue; - } - } else { - int color = ResourcesCompat.getColor(context.getResources(), resourceId, null); - return color; - } + if (resourcePath == null || resourcePath.isEmpty()) { + continue; } - return new Integer(0xFF000000); + boolean isResource = resourcePath.startsWith(PREFIX_RESOURCE); + boolean isThemeAttribute = resourcePath.startsWith(PREFIX_ATTR); + + resourcePath = resourcePath.substring(1); + + try { + if (isResource) { + return resolveResource(context, resourcePath); + } else if (isThemeAttribute) { + return resolveThemeAttribute(context, resourcePath); + } + } catch (Resources.NotFoundException exception) { + exception.printStackTrace(); + } } + + return mDefaultValue; } - if (value instanceof Double) { - return ((Double) value).intValue(); - } + return null; } + + private int resolveResource(Context context, String resourcePath) { + String[] pathTokens = resourcePath.split(PACKAGE_DELIMITER); + + String packageName = context.getPackageName(); + String resource = resourcePath; + + if (pathTokens.length > 1) { + packageName = pathTokens[0]; + resource = pathTokens[1]; + } + + String[] resourceTokens = resource.split(PATH_DELIMITER); + String resourceType = resourceTokens[0]; + String resourceName = resourceTokens[1]; + + int resourceId = context.getResources().getIdentifier( + resourceName, resourceType, packageName); + + return ResourcesCompat.getColor( + context.getResources(), resourceId, context.getTheme()); + } + + private int resolveThemeAttribute(Context context, String resourcePath) { + String path = resourcePath.replaceAll(ATTR_SEGMENT, ""); + String[] pathTokens = path.split(PACKAGE_DELIMITER); + + String packageName = context.getPackageName(); + String resourceName = path; + + if (pathTokens.length > 1) { + packageName = pathTokens[0]; + resourceName = pathTokens[1]; + } + + int resourceId = context.getResources().getIdentifier( + resourceName, ATTR, packageName); + + TypedValue outValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + + if (theme.resolveAttribute(resourceId, outValue, true)) { + return outValue.data; + } + + throw new Resources.NotFoundException(); + } } private static class BooleanPropSetter extends PropSetter {