Migrate ReactDrawableHelper to Kotlin (#48346)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48346

Migrate ReactDrawableHelper to Kotlin

changelog: [internal] internal

Reviewed By: rshest

Differential Revision: D67420410

fbshipit-source-id: 524062d0c0a0d3440bf5ac4c61e8cae53b32a0d2
This commit is contained in:
David Vacca
2024-12-20 00:18:50 -08:00
committed by Facebook GitHub Bot
parent e9faea2f3e
commit 7b8412d66d
3 changed files with 114 additions and 117 deletions
@@ -7739,9 +7739,9 @@ public abstract class com/facebook/react/views/view/ReactClippingViewManager : c
public fun setRemoveClippedSubviews (Lcom/facebook/react/views/view/ReactViewGroup;Z)V
}
public class com/facebook/react/views/view/ReactDrawableHelper {
public fun <init> ()V
public static fun createDrawableFromJSDescription (Landroid/content/Context;Lcom/facebook/react/bridge/ReadableMap;)Landroid/graphics/drawable/Drawable;
public final class com/facebook/react/views/view/ReactDrawableHelper {
public static final field INSTANCE Lcom/facebook/react/views/view/ReactDrawableHelper;
public static final fun createDrawableFromJSDescription (Landroid/content/Context;Lcom/facebook/react/bridge/ReadableMap;)Landroid/graphics/drawable/Drawable;
}
public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGroup, com/facebook/react/touch/ReactHitSlopView, com/facebook/react/touch/ReactInterceptingViewGroup, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/uimanager/ReactPointerEventsView, com/facebook/react/uimanager/ReactZIndexedViewGroup {
@@ -1,114 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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.views.view;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.util.TypedValue;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ViewProps;
/**
* Utility class that helps with converting android drawable description used in JS to an actual
* instance of {@link Drawable}.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactDrawableHelper {
private static final TypedValue sResolveOutValue = new TypedValue();
public static @Nullable Drawable createDrawableFromJSDescription(
Context context, ReadableMap drawableDescriptionDict) {
String type = drawableDescriptionDict.getString("type");
if ("ThemeAttrAndroid".equals(type)) {
String attr = drawableDescriptionDict.getString("attribute");
if (attr == null) {
throw new JSApplicationIllegalArgumentException("JS description missing 'attribute' field");
}
int attrId = getAttrId(context, attr);
if (!context.getTheme().resolveAttribute(attrId, sResolveOutValue, true)) {
throw new JSApplicationIllegalArgumentException(
"Attribute " + attr + " with id " + attrId + " couldn't be resolved into a drawable");
}
Drawable drawable = getDefaultThemeDrawable(context);
return setRadius(drawableDescriptionDict, drawable);
} else if ("RippleAndroid".equals(type)) {
RippleDrawable rd = getRippleDrawable(context, drawableDescriptionDict);
return setRadius(drawableDescriptionDict, rd);
} else {
throw new JSApplicationIllegalArgumentException("Invalid type for android drawable: " + type);
}
}
private static int getAttrId(Context context, String attr) {
if ("selectableItemBackground".equals(attr)) {
return android.R.attr.selectableItemBackground;
} else if ("selectableItemBackgroundBorderless".equals(attr)) {
return android.R.attr.selectableItemBackgroundBorderless;
} else {
return context.getResources().getIdentifier(attr, "attr", "android");
}
}
private static @Nullable Drawable getDefaultThemeDrawable(Context context) {
return context.getResources().getDrawable(sResolveOutValue.resourceId, context.getTheme());
}
private static RippleDrawable getRippleDrawable(
Context context, ReadableMap drawableDescriptionDict) {
int color = getColor(context, drawableDescriptionDict);
Drawable mask = getMask(drawableDescriptionDict);
ColorStateList colorStateList =
new ColorStateList(new int[][] {new int[] {}}, new int[] {color});
return new RippleDrawable(colorStateList, null, mask);
}
private static @Nullable Drawable setRadius(
ReadableMap drawableDescriptionDict, @Nullable Drawable drawable) {
if (drawableDescriptionDict.hasKey("rippleRadius") && drawable instanceof RippleDrawable) {
RippleDrawable rippleDrawable = (RippleDrawable) drawable;
double rippleRadius = drawableDescriptionDict.getDouble("rippleRadius");
rippleDrawable.setRadius((int) PixelUtil.toPixelFromDIP(rippleRadius));
}
return drawable;
}
private static int getColor(Context context, ReadableMap drawableDescriptionDict) {
if (drawableDescriptionDict.hasKey(ViewProps.COLOR)
&& !drawableDescriptionDict.isNull(ViewProps.COLOR)) {
return drawableDescriptionDict.getInt(ViewProps.COLOR);
} else {
if (context
.getTheme()
.resolveAttribute(android.R.attr.colorControlHighlight, sResolveOutValue, true)) {
return context.getResources().getColor(sResolveOutValue.resourceId);
} else {
throw new JSApplicationIllegalArgumentException(
"Attribute colorControlHighlight couldn't be resolved into a drawable");
}
}
}
private static @Nullable Drawable getMask(ReadableMap drawableDescriptionDict) {
if (!drawableDescriptionDict.hasKey("borderless")
|| drawableDescriptionDict.isNull("borderless")
|| !drawableDescriptionDict.getBoolean("borderless")) {
return new ColorDrawable(Color.WHITE);
}
return null;
}
}
@@ -0,0 +1,111 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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.views.view
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.RippleDrawable
import android.util.TypedValue
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.ViewProps
/**
* Utility class that helps with converting android drawable description used in JS to an actual
* instance of [Drawable].
*/
public object ReactDrawableHelper {
private val resolveOutValue = TypedValue()
@JvmStatic
public fun createDrawableFromJSDescription(
context: Context,
drawableDescriptionDict: ReadableMap
): Drawable? {
val type = drawableDescriptionDict.getString("type")
if ("ThemeAttrAndroid" == type) {
val attr =
drawableDescriptionDict.getString("attribute")
?: throw JSApplicationIllegalArgumentException(
"JS description missing 'attribute' field")
val attrId = getAttrId(context, attr)
if (!context.theme.resolveAttribute(attrId, resolveOutValue, true)) {
throw JSApplicationIllegalArgumentException(
"Attribute $attr with id $attrId couldn't be resolved into a drawable")
}
val drawable = getDefaultThemeDrawable(context)
return setRadius(drawableDescriptionDict, drawable)
} else if ("RippleAndroid" == type) {
val rd = getRippleDrawable(context, drawableDescriptionDict)
return setRadius(drawableDescriptionDict, rd)
} else {
throw JSApplicationIllegalArgumentException("Invalid type for android drawable: $type")
}
}
@SuppressLint("DiscouragedApi", "InternalInsetResource")
private fun getAttrId(context: Context, attr: String): Int =
if ("selectableItemBackground" == attr) {
android.R.attr.selectableItemBackground
} else if ("selectableItemBackgroundBorderless" == attr) {
android.R.attr.selectableItemBackgroundBorderless
} else {
context.resources.getIdentifier(attr, "attr", "android")
}
private fun getDefaultThemeDrawable(context: Context): Drawable? =
context.resources.getDrawable(resolveOutValue.resourceId, context.theme)
private fun getRippleDrawable(
context: Context,
drawableDescriptionDict: ReadableMap
): RippleDrawable {
val color = getColor(context, drawableDescriptionDict)
val mask = getMask(drawableDescriptionDict)
val colorStateList = ColorStateList(arrayOf(intArrayOf()), intArrayOf(color))
return RippleDrawable(colorStateList, null, mask)
}
private fun setRadius(drawableDescriptionDict: ReadableMap, drawable: Drawable?): Drawable? {
if (drawableDescriptionDict.hasKey("rippleRadius") && drawable is RippleDrawable) {
val rippleRadius = drawableDescriptionDict.getDouble("rippleRadius")
drawable.radius = PixelUtil.toPixelFromDIP(rippleRadius).toInt()
}
return drawable
}
private fun getColor(context: Context, drawableDescriptionDict: ReadableMap): Int =
if (drawableDescriptionDict.hasKey(ViewProps.COLOR) &&
!drawableDescriptionDict.isNull(ViewProps.COLOR)) {
drawableDescriptionDict.getInt(ViewProps.COLOR)
} else {
if (context.theme.resolveAttribute(
android.R.attr.colorControlHighlight, resolveOutValue, true)) {
context.resources.getColor(resolveOutValue.resourceId, context.theme)
} else {
throw JSApplicationIllegalArgumentException(
"Attribute colorControlHighlight couldn't be resolved into a drawable")
}
}
private fun getMask(drawableDescriptionDict: ReadableMap): Drawable? {
if (!drawableDescriptionDict.hasKey("borderless") ||
drawableDescriptionDict.isNull("borderless") ||
!drawableDescriptionDict.getBoolean("borderless")) {
return ColorDrawable(Color.WHITE)
}
return null
}
}