Skip to content

Commit

Permalink
Merge pull request #1 from ArazAbishov/fb-platformcolor-android
Browse files Browse the repository at this point in the history
Resolving semantic colors from arbitrary packages on android
  • Loading branch information
tom-un authored Jan 28, 2020
2 parents 2b71a4e + 8d1312f commit d9ec0ca
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 88 deletions.
39 changes: 28 additions & 11 deletions RNTester/js/examples/PlatformColor/PlatformColorExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,24 @@ class PlatformColorsExample extends React.Component<{}, State> {
'?attr/colorControlActivated',
'?attr/colorControlHighlight',
'?attr/colorControlNormal',
'?attr/colorControlNormal',
// '?attr/colorEdgeEffect',
'?attr/colorError',
// '?attr/colorFocusedHighlight',
// '?attr/colorForeground',
// '?attr/colorForegroundInverse',
// '?attr/colorLongPressedHighlight',
// '?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',
];
}

Expand Down Expand Up @@ -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 (
<View style={styles.column}>
<View style={styles.row}>
<Text style={styles.labelCell}>
First choice is 'bogus' so falls back to 'systemGreenColor'
First choice is 'bogus' so falls back to {this.getFallbackColor()}
</Text>
<View
style={{
...styles.colorCell,
backgroundColor: PlatformColor('bogus', 'systemGreenColor'),
backgroundColor: PlatformColor('bogus', this.getFallbackColor()),
}}
/>
</View>
Expand Down Expand Up @@ -202,17 +219,17 @@ exports.examples = [
render: function(): React.Element<any> {
return <PlatformColorsExample />;
},
},
{
title: 'Fallback Colors',
render: function(): React.Element<any> {
return <FallbackColorsExample />;
},
}
];
if (Platform.OS !== 'android') {
exports.examples = [
...exports.examples,
{
title: 'Fallback Colors',
render: function(): React.Element<any> {
return <FallbackColorsExample />;
},
},
{
title: 'Dynamic Colors',
render: function(): React.Element<any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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:
// @[<package_name>:]<resource_type>/<resource_name>
// ?[<package_name>:][<resource_type>/]<resource_name>

// parse '@' or '?'
boolean themed;
if (resourcePath.startsWith("@")) {
themed = false;
} else if (resourcePath.startsWith("?")) {
themed = true;
} else {
continue;
}
resourcePath = resourcePath.substring(1);

// parse [<package_name>:]
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 [<resource_type>/]
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 <resource_name>
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 {
Expand Down

0 comments on commit d9ec0ca

Please sign in to comment.