From 7b8412d66dbe84920b769fdede302e31c86cd468 Mon Sep 17 00:00:00 2001 From: David Vacca Date: Fri, 20 Dec 2024 00:18:50 -0800 Subject: [PATCH] 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 --- .../ReactAndroid/api/ReactAndroid.api | 6 +- .../react/views/view/ReactDrawableHelper.java | 114 ------------------ .../react/views/view/ReactDrawableHelper.kt | 111 +++++++++++++++++ 3 files changed, 114 insertions(+), 117 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 714498b12fc..45e56e949b3 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 ()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 { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java deleted file mode 100644 index af3b25867aa..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java +++ /dev/null @@ -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; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt new file mode 100644 index 00000000000..735d69bd290 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt @@ -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 + } +}