diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 138b80791dddc8..d85fa5c259c8e3 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -649,6 +649,7 @@ public class com/facebook/react/bridge/ColorPropConverter { public fun ()V public static fun getColor (Ljava/lang/Object;Landroid/content/Context;)Ljava/lang/Integer; public static fun getColor (Ljava/lang/Object;Landroid/content/Context;I)Ljava/lang/Integer; + public static fun getColorInstance (Ljava/lang/Object;Landroid/content/Context;)Landroid/graphics/Color; public static fun resolveResourcePath (Landroid/content/Context;Ljava/lang/String;)Ljava/lang/Integer; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java index afc690dedaabbf..ab6462898fd564 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ColorPropConverter.java @@ -9,13 +9,22 @@ import android.content.Context; import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.ColorSpace; +import android.os.Build; import android.util.TypedValue; +import androidx.annotation.ColorLong; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; import com.facebook.common.logging.FLog; import com.facebook.react.common.ReactConstants; public class ColorPropConverter { + + private static Boolean supportWideGamut() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + private static final String JSON_KEY = "resource_paths"; private static final String PREFIX_RESOURCE = "@"; private static final String PREFIX_ATTR = "?"; @@ -24,7 +33,7 @@ public class ColorPropConverter { private static final String ATTR = "attr"; private static final String ATTR_SEGMENT = "attr/"; - public static Integer getColor(Object value, Context context) { + private static Integer getColorInteger(Object value, Context context) { if (value == null) { return null; } @@ -33,18 +42,13 @@ public static Integer getColor(Object value, Context context) { return ((Double) value).intValue(); } - if (context == null) { - throw new RuntimeException("Context may not be null."); - } + throwIfNullContext(context); if (value instanceof ReadableMap) { ReadableMap map = (ReadableMap) value; ReadableArray resourcePaths = map.getArray(JSON_KEY); - if (resourcePaths == null) { - throw new JSApplicationCausedNativeException( - "ColorValue: The `" + JSON_KEY + "` must be an array of color resource path strings."); - } + throwIfNullResourcePaths(resourcePaths); for (int i = 0; i < resourcePaths.size(); i++) { Integer result = resolveResourcePath(context, resourcePaths.getString(i)); @@ -53,16 +57,62 @@ public static Integer getColor(Object value, Context context) { } } - throw new JSApplicationCausedNativeException( - "ColorValue: None of the paths in the `" - + JSON_KEY - + "` array resolved to a color resource."); + throwColorResourceNotFound(); + } + + throw new JSApplicationCausedNativeException( + "ColorValue: the value must be a number or Object."); + } + + public static Color getColorInstance(Object value, Context context) { + if (value == null) { + return null; } + if (supportWideGamut() && value instanceof Double) { + return Color.valueOf(((Double) value).intValue()); + } + + throwIfNullContext(context); + + if (value instanceof ReadableMap) { + ReadableMap map = (ReadableMap) value; + + Color wideGamutColor = extractWideGamutColorIfPossible(map); + if (wideGamutColor != null) { + return wideGamutColor; + } + + ReadableArray resourcePaths = map.getArray(JSON_KEY); + throwIfNullResourcePaths(resourcePaths); + + for (int i = 0; i < resourcePaths.size(); i++) { + Integer result = resolveResourcePath(context, resourcePaths.getString(i)); + if (supportWideGamut() && result != null) { + return Color.valueOf(result); + } + } + + throwColorResourceNotFound(); + } throw new JSApplicationCausedNativeException( "ColorValue: the value must be a number or Object."); } + public static Integer getColor(Object value, Context context) { + try { + if (supportWideGamut()) { + Color color = getColorInstance(value, context); + if (color != null) { + return color.toArgb(); + } + } + } catch (JSApplicationCausedNativeException ex) { + FLog.w(ReactConstants.TAG, ex, "Error extracting color from WideGamut"); + } + return getColorInteger(value, context); + } + public static Integer getColor(Object value, Context context, int defaultInt) { try { return getColor(value, context); @@ -139,4 +189,41 @@ private static int resolveThemeAttribute(Context context, String resourcePath) { throw new Resources.NotFoundException(); } + + private static void throwIfNullContext(Context context) { + if (context == null) { + throw new RuntimeException("Context may not be null."); + } + } + + private static void throwIfNullResourcePaths(ReadableArray resourcePaths) { + if (resourcePaths == null) { + throw new JSApplicationCausedNativeException( + "ColorValue: The `" + JSON_KEY + "` must be an array of color resource path strings."); + } + } + + private static void throwColorResourceNotFound() { + throw new JSApplicationCausedNativeException( + "ColorValue: None of the paths in the `" + + JSON_KEY + + "` array resolved to a color resource."); + } + + private static Color extractWideGamutColorIfPossible(ReadableMap map) { + if (supportWideGamut() && map.hasKey("space")) { + String rawColorSpace = map.getString("space"); + boolean isDisplayP3 = rawColorSpace.equals("display-p3"); + ColorSpace space = + ColorSpace.get(isDisplayP3 ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB); + float r = (float) map.getDouble("r"); + float g = (float) map.getDouble("g"); + float b = (float) map.getDouble("b"); + float a = (float) map.getDouble("a"); + + @ColorLong long color = Color.pack(r, g, b, a, space); + return Color.valueOf(color); + } + return null; + } }