Convert ReactViewManager to Kotlin (#46965)

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

Converting this class as close to 1:1 as possible.

Changelog: [Internal]

Reviewed By: NickGerleman, rshest

Differential Revision: D64124890

fbshipit-source-id: 2034ed8ecb877bb267a953b183b77daaf883a80a
This commit is contained in:
Thomas Nardone
2024-10-11 09:14:55 -07:00
committed by Facebook GitHub Bot
parent 483b928224
commit fc199bb5f3
3 changed files with 379 additions and 420 deletions
@@ -8295,6 +8295,8 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
}
public class com/facebook/react/views/view/ReactViewManager : com/facebook/react/views/view/ReactClippingViewManager {
public static final field Companion Lcom/facebook/react/views/view/ReactViewManager$Companion;
public static final field REACT_CLASS Ljava/lang/String;
public fun <init> ()V
public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
public fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/view/ReactViewGroup;
@@ -8305,7 +8307,7 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
public fun nextFocusLeft (Lcom/facebook/react/views/view/ReactViewGroup;I)V
public fun nextFocusRight (Lcom/facebook/react/views/view/ReactViewGroup;I)V
public fun nextFocusUp (Lcom/facebook/react/views/view/ReactViewGroup;I)V
protected synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
public synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/view/ReactViewGroup;)Lcom/facebook/react/views/view/ReactViewGroup;
public synthetic fun receiveCommand (Landroid/view/View;ILcom/facebook/react/bridge/ReadableArray;)V
public synthetic fun receiveCommand (Landroid/view/View;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
@@ -8331,7 +8333,7 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
public fun setOverflow (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;)V
public fun setPointerEvents (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;)V
public fun setTVPreferredFocus (Lcom/facebook/react/views/view/ReactViewGroup;Z)V
protected synthetic fun setTransformProperty (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
public synthetic fun setTransformProperty (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
protected fun setTransformProperty (Lcom/facebook/react/views/view/ReactViewGroup;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
}
@@ -8342,6 +8344,9 @@ public class com/facebook/react/views/view/ReactViewManager$$PropsSetter : com/f
public fun setProperty (Lcom/facebook/react/views/view/ReactViewManager;Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;Ljava/lang/Object;)V
}
public final class com/facebook/react/views/view/ReactViewManager$Companion {
}
public final class com/facebook/react/views/view/ViewGroupClickEvent : com/facebook/react/uimanager/events/Event {
public fun <init> (I)V
public fun <init> (II)V
@@ -1,418 +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.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.DynamicFromObject;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.BackgroundStyleApplicator;
import com.facebook.react.uimanager.LengthPercentage;
import com.facebook.react.uimanager.LengthPercentageType;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.style.BackgroundImageLayer;
import com.facebook.react.uimanager.style.BorderRadiusProp;
import com.facebook.react.uimanager.style.BorderStyle;
import com.facebook.react.uimanager.style.LogicalEdge;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** View manager for AndroidViews (plain React Views). */
@ReactModule(name = ReactViewManager.REACT_CLASS)
@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
@VisibleForTesting public static final String REACT_CLASS = ViewProps.VIEW_CLASS_NAME;
private static final int[] SPACING_TYPES = {
Spacing.ALL,
Spacing.LEFT,
Spacing.RIGHT,
Spacing.TOP,
Spacing.BOTTOM,
Spacing.START,
Spacing.END,
Spacing.BLOCK,
Spacing.BLOCK_END,
Spacing.BLOCK_START
};
private static final int CMD_HOTSPOT_UPDATE = 1;
private static final int CMD_SET_PRESSED = 2;
private static final String HOTSPOT_UPDATE_KEY = "hotspotUpdate";
public ReactViewManager() {
super();
setupViewRecycling();
}
@Override
protected @Nullable ReactViewGroup prepareToRecycleView(
ThemedReactContext reactContext, ReactViewGroup view) {
// BaseViewManager
ReactViewGroup preparedView = super.prepareToRecycleView(reactContext, view);
if (preparedView != null) {
preparedView.recycleView();
}
return view;
}
@ReactProp(name = "accessible")
public void setAccessible(ReactViewGroup view, boolean accessible) {
view.setFocusable(accessible);
}
@ReactProp(name = "hasTVPreferredFocus")
public void setTVPreferredFocus(ReactViewGroup view, boolean hasTVPreferredFocus) {
if (hasTVPreferredFocus) {
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
}
}
@ReactProp(name = ViewProps.BACKGROUND_IMAGE, customType = "BackgroundImage")
public void setBackgroundImage(ReactViewGroup view, @Nullable ReadableArray backgroundImage) {
if (ViewUtil.getUIManagerType(view) == UIManagerType.FABRIC) {
if (backgroundImage != null && backgroundImage.size() > 0) {
List<BackgroundImageLayer> backgroundImageLayers = new ArrayList<>(backgroundImage.size());
for (int i = 0; i < backgroundImage.size(); i++) {
ReadableMap backgroundImageMap = backgroundImage.getMap(i);
BackgroundImageLayer layer =
new BackgroundImageLayer(backgroundImageMap, view.getContext());
backgroundImageLayers.add(layer);
}
BackgroundStyleApplicator.setBackgroundImage(view, backgroundImageLayers);
} else {
BackgroundStyleApplicator.setBackgroundImage(view, null);
}
}
}
@ReactProp(name = "nextFocusDown", defaultInt = View.NO_ID)
public void nextFocusDown(ReactViewGroup view, int viewId) {
view.setNextFocusDownId(viewId);
}
@ReactProp(name = "nextFocusForward", defaultInt = View.NO_ID)
public void nextFocusForward(ReactViewGroup view, int viewId) {
view.setNextFocusForwardId(viewId);
}
@ReactProp(name = "nextFocusLeft", defaultInt = View.NO_ID)
public void nextFocusLeft(ReactViewGroup view, int viewId) {
view.setNextFocusLeftId(viewId);
}
@ReactProp(name = "nextFocusRight", defaultInt = View.NO_ID)
public void nextFocusRight(ReactViewGroup view, int viewId) {
view.setNextFocusRightId(viewId);
}
@ReactProp(name = "nextFocusUp", defaultInt = View.NO_ID)
public void nextFocusUp(ReactViewGroup view, int viewId) {
view.setNextFocusUpId(viewId);
}
@ReactPropGroup(
names = {
ViewProps.BORDER_RADIUS,
ViewProps.BORDER_TOP_LEFT_RADIUS,
ViewProps.BORDER_TOP_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_LEFT_RADIUS,
ViewProps.BORDER_TOP_START_RADIUS,
ViewProps.BORDER_TOP_END_RADIUS,
ViewProps.BORDER_BOTTOM_START_RADIUS,
ViewProps.BORDER_BOTTOM_END_RADIUS,
ViewProps.BORDER_END_END_RADIUS,
ViewProps.BORDER_END_START_RADIUS,
ViewProps.BORDER_START_END_RADIUS,
ViewProps.BORDER_START_START_RADIUS,
})
public void setBorderRadius(ReactViewGroup view, int index, Dynamic rawBorderRadius) {
@Nullable LengthPercentage borderRadius = LengthPercentage.setFromDynamic(rawBorderRadius);
// We do not support percentage border radii on Paper in order to be consistent with iOS (to
// avoid developer surprise if it works on one platform but not another).
if (ViewUtil.getUIManagerType(view) != UIManagerType.FABRIC
&& borderRadius != null
&& borderRadius.getType() == LengthPercentageType.PERCENT) {
borderRadius = null;
}
BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], borderRadius);
}
/**
* @deprecated Use {@link #setBorderRadius(ReactViewGroup, int, Dynamic)} instead.
*/
@Deprecated(since = "0.75.0", forRemoval = true)
public void setBorderRadius(ReactViewGroup view, int index, float borderRadius) {
setBorderRadius(view, index, new DynamicFromObject(borderRadius));
}
@ReactProp(name = "borderStyle")
public void setBorderStyle(ReactViewGroup view, @Nullable String borderStyle) {
@Nullable
BorderStyle parsedBorderStyle =
borderStyle == null ? null : BorderStyle.fromString(borderStyle);
BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle);
}
@ReactProp(name = "hitSlop")
public void setHitSlop(final ReactViewGroup view, Dynamic hitSlop) {
switch (hitSlop.getType()) {
case Map:
ReadableMap hitSlopMap = hitSlop.asMap();
view.setHitSlopRect(
new Rect(
hitSlopMap.hasKey("left")
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("left"))
: 0,
hitSlopMap.hasKey("top")
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("top"))
: 0,
hitSlopMap.hasKey("right")
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("right"))
: 0,
hitSlopMap.hasKey("bottom")
? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("bottom"))
: 0));
break;
case Number:
int hitSlopValue = (int) PixelUtil.toPixelFromDIP(hitSlop.asDouble());
view.setHitSlopRect(new Rect(hitSlopValue, hitSlopValue, hitSlopValue, hitSlopValue));
break;
default:
FLog.w(ReactConstants.TAG, "Invalid type for 'hitSlop' value " + hitSlop.getType());
/* falls through */
case Null:
view.setHitSlopRect(null);
break;
}
}
@ReactProp(name = ViewProps.POINTER_EVENTS)
public void setPointerEvents(ReactViewGroup view, @Nullable String pointerEventsStr) {
view.setPointerEvents(PointerEvents.parsePointerEvents(pointerEventsStr));
}
@ReactProp(name = "nativeBackgroundAndroid")
public void setNativeBackground(ReactViewGroup view, @Nullable ReadableMap bg) {
Drawable background;
if (bg != null) {
background = ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg);
} else {
background = null;
}
BackgroundStyleApplicator.setFeedbackUnderlay(view, background);
}
@ReactProp(name = "nativeForegroundAndroid")
public void setNativeForeground(ReactViewGroup view, @Nullable ReadableMap fg) {
view.setForeground(
fg == null
? null
: ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), fg));
}
@ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
public void setNeedsOffscreenAlphaCompositing(
ReactViewGroup view, boolean needsOffscreenAlphaCompositing) {
view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing);
}
@ReactPropGroup(
names = {
ViewProps.BORDER_WIDTH,
ViewProps.BORDER_LEFT_WIDTH,
ViewProps.BORDER_RIGHT_WIDTH,
ViewProps.BORDER_TOP_WIDTH,
ViewProps.BORDER_BOTTOM_WIDTH,
ViewProps.BORDER_START_WIDTH,
ViewProps.BORDER_END_WIDTH,
},
defaultFloat = Float.NaN)
public void setBorderWidth(ReactViewGroup view, int index, float width) {
BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width);
}
@ReactPropGroup(
names = {
ViewProps.BORDER_COLOR,
ViewProps.BORDER_LEFT_COLOR,
ViewProps.BORDER_RIGHT_COLOR,
ViewProps.BORDER_TOP_COLOR,
ViewProps.BORDER_BOTTOM_COLOR,
ViewProps.BORDER_START_COLOR,
ViewProps.BORDER_END_COLOR,
ViewProps.BORDER_BLOCK_COLOR,
ViewProps.BORDER_BLOCK_END_COLOR,
ViewProps.BORDER_BLOCK_START_COLOR
},
customType = "Color")
public void setBorderColor(ReactViewGroup view, int index, @Nullable Integer color) {
BackgroundStyleApplicator.setBorderColor(
view, LogicalEdge.fromSpacingType(SPACING_TYPES[index]), color);
}
@ReactProp(name = ViewProps.COLLAPSABLE)
public void setCollapsable(ReactViewGroup view, boolean collapsable) {
// no-op: it's here only so that "collapsable" property is exported to JS. The value is actually
// handled in NativeViewHierarchyOptimizer
}
@ReactProp(name = ViewProps.COLLAPSABLE_CHILDREN)
public void setCollapsableChildren(ReactViewGroup view, boolean collapsableChildren) {
// no-op: it's here only so that "collapsableChildren" property is exported to JS.
}
@ReactProp(name = "focusable")
public void setFocusable(final ReactViewGroup view, boolean focusable) {
if (focusable) {
view.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
final EventDispatcher mEventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(
(ReactContext) view.getContext(), view.getId());
if (mEventDispatcher == null) {
return;
}
mEventDispatcher.dispatchEvent(
new ViewGroupClickEvent(
UIManagerHelper.getSurfaceId(view.getContext()), view.getId()));
}
});
// Clickable elements are focusable. On API 26, this is taken care by setClickable.
// Explicitly calling setFocusable here for backward compatibility.
view.setFocusable(true /*isFocusable*/);
} else {
view.setOnClickListener(null);
view.setClickable(false);
// Don't set view.setFocusable(false) because we might still want it to be focusable for
// accessibility reasons
}
}
@ReactProp(name = ViewProps.OVERFLOW)
public void setOverflow(ReactViewGroup view, String overflow) {
view.setOverflow(overflow);
}
@ReactProp(name = "backfaceVisibility")
public void setBackfaceVisibility(ReactViewGroup view, String backfaceVisibility) {
view.setBackfaceVisibility(backfaceVisibility);
}
@Override
public void setOpacity(ReactViewGroup view, float opacity) {
view.setOpacityIfPossible(opacity);
}
@Override
protected void setTransformProperty(
ReactViewGroup view,
@Nullable ReadableArray transforms,
@Nullable ReadableArray transformOrigin) {
super.setTransformProperty(view, transforms, transformOrigin);
view.setBackfaceVisibilityDependantOpacity();
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public ReactViewGroup createViewInstance(ThemedReactContext context) {
return new ReactViewGroup(context);
}
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of(HOTSPOT_UPDATE_KEY, CMD_HOTSPOT_UPDATE, "setPressed", CMD_SET_PRESSED);
}
@Override
public void receiveCommand(ReactViewGroup root, int commandId, @Nullable ReadableArray args) {
switch (commandId) {
case CMD_HOTSPOT_UPDATE:
{
handleHotspotUpdate(root, args);
break;
}
case CMD_SET_PRESSED:
{
handleSetPressed(root, args);
break;
}
}
}
@Override
public void receiveCommand(ReactViewGroup root, String commandId, @Nullable ReadableArray args) {
switch (commandId) {
case HOTSPOT_UPDATE_KEY:
{
handleHotspotUpdate(root, args);
break;
}
case "setPressed":
{
handleSetPressed(root, args);
break;
}
}
}
private void handleSetPressed(ReactViewGroup root, @Nullable ReadableArray args) {
if (args == null || args.size() != 1) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'setPressed' command");
}
root.setPressed(args.getBoolean(0));
}
private void handleHotspotUpdate(ReactViewGroup root, @Nullable ReadableArray args) {
if (args == null || args.size() != 2) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'updateHotspot' command");
}
float x = PixelUtil.toPixelFromDIP(args.getDouble(0));
float y = PixelUtil.toPixelFromDIP(args.getDouble(1));
root.drawableHotspotChanged(x, y);
}
}
@@ -0,0 +1,372 @@
/*
* 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.graphics.Rect
import android.view.View
import com.facebook.common.logging.FLog
import com.facebook.react.bridge.Dynamic
import com.facebook.react.bridge.DynamicFromObject
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.common.ReactConstants
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.BackgroundStyleApplicator
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.facebook.react.uimanager.PointerEvents
import com.facebook.react.uimanager.Spacing
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.ViewProps
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.uimanager.annotations.ReactPropGroup
import com.facebook.react.uimanager.common.UIManagerType
import com.facebook.react.uimanager.common.ViewUtil
import com.facebook.react.uimanager.style.BackgroundImageLayer
import com.facebook.react.uimanager.style.BorderRadiusProp
import com.facebook.react.uimanager.style.BorderStyle
import com.facebook.react.uimanager.style.LogicalEdge
/** View manager for AndroidViews (plain React Views). */
@ReactModule(name = ReactViewManager.REACT_CLASS)
public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>() {
public companion object {
public const val REACT_CLASS: String = ViewProps.VIEW_CLASS_NAME
private val SPACING_TYPES =
intArrayOf(
Spacing.ALL,
Spacing.LEFT,
Spacing.RIGHT,
Spacing.TOP,
Spacing.BOTTOM,
Spacing.START,
Spacing.END,
Spacing.BLOCK,
Spacing.BLOCK_END,
Spacing.BLOCK_START,
)
private const val CMD_HOTSPOT_UPDATE = 1
private const val CMD_SET_PRESSED = 2
private const val HOTSPOT_UPDATE_KEY = "hotspotUpdate"
}
init {
setupViewRecycling()
}
override fun prepareToRecycleView(
reactContext: ThemedReactContext,
view: ReactViewGroup
): ReactViewGroup {
// BaseViewManager
val preparedView = super.prepareToRecycleView(reactContext, view)
preparedView?.recycleView()
return view
}
@ReactProp(name = "accessible")
public open fun setAccessible(view: ReactViewGroup, accessible: Boolean) {
view.isFocusable = accessible
}
@ReactProp(name = "hasTVPreferredFocus")
public open fun setTVPreferredFocus(view: ReactViewGroup, hasTVPreferredFocus: Boolean) {
if (hasTVPreferredFocus) {
view.isFocusable = true
view.isFocusableInTouchMode = true
view.requestFocus()
}
}
@ReactProp(name = ViewProps.BACKGROUND_IMAGE, customType = "BackgroundImage")
public open fun setBackgroundImage(view: ReactViewGroup, backgroundImage: ReadableArray?) {
if (ViewUtil.getUIManagerType(view) == UIManagerType.FABRIC) {
if (backgroundImage != null && backgroundImage.size() > 0) {
val backgroundImageLayers = ArrayList<BackgroundImageLayer>(backgroundImage.size())
for (i in 0 until backgroundImage.size()) {
val backgroundImageMap = backgroundImage.getMap(i)
val layer = BackgroundImageLayer(backgroundImageMap, view.context)
backgroundImageLayers.add(layer)
}
BackgroundStyleApplicator.setBackgroundImage(view, backgroundImageLayers)
} else {
BackgroundStyleApplicator.setBackgroundImage(view, null)
}
}
}
@ReactProp(name = "nextFocusDown", defaultInt = View.NO_ID)
public open fun nextFocusDown(view: ReactViewGroup, viewId: Int) {
view.nextFocusDownId = viewId
}
@ReactProp(name = "nextFocusForward", defaultInt = View.NO_ID)
public open fun nextFocusForward(view: ReactViewGroup, viewId: Int) {
view.nextFocusForwardId = viewId
}
@ReactProp(name = "nextFocusLeft", defaultInt = View.NO_ID)
public open fun nextFocusLeft(view: ReactViewGroup, viewId: Int) {
view.nextFocusLeftId = viewId
}
@ReactProp(name = "nextFocusRight", defaultInt = View.NO_ID)
public open fun nextFocusRight(view: ReactViewGroup, viewId: Int) {
view.nextFocusRightId = viewId
}
@ReactProp(name = "nextFocusUp", defaultInt = View.NO_ID)
public open fun nextFocusUp(view: ReactViewGroup, viewId: Int) {
view.nextFocusUpId = viewId
}
@ReactPropGroup(
names =
[
ViewProps.BORDER_RADIUS,
ViewProps.BORDER_TOP_LEFT_RADIUS,
ViewProps.BORDER_TOP_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
ViewProps.BORDER_BOTTOM_LEFT_RADIUS,
ViewProps.BORDER_TOP_START_RADIUS,
ViewProps.BORDER_TOP_END_RADIUS,
ViewProps.BORDER_BOTTOM_START_RADIUS,
ViewProps.BORDER_BOTTOM_END_RADIUS,
ViewProps.BORDER_END_END_RADIUS,
ViewProps.BORDER_END_START_RADIUS,
ViewProps.BORDER_START_END_RADIUS,
ViewProps.BORDER_START_START_RADIUS,
])
public open fun setBorderRadius(view: ReactViewGroup, index: Int, rawBorderRadius: Dynamic) {
var borderRadius = LengthPercentage.setFromDynamic(rawBorderRadius)
// We do not support percentage border radii on Paper in order to be consistent with iOS (to
// avoid developer surprise if it works on one platform but not another).
if (ViewUtil.getUIManagerType(view) != UIManagerType.FABRIC &&
borderRadius != null &&
borderRadius.type == LengthPercentageType.PERCENT) {
borderRadius = null
}
BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], borderRadius)
}
@Deprecated(
"Don't use setBorderRadius(view, int, Float) as it was deprecated in React Native 0.75.0.",
ReplaceWith("setBorderRadius(view, index, DynamicFromObject(borderRadius)"))
public open fun setBorderRadius(view: ReactViewGroup, index: Int, borderRadius: Float) {
setBorderRadius(view, index, DynamicFromObject(borderRadius))
}
@ReactProp(name = "borderStyle")
public open fun setBorderStyle(view: ReactViewGroup, borderStyle: String?) {
val parsedBorderStyle = if (borderStyle == null) null else BorderStyle.fromString(borderStyle)
BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle)
}
@ReactProp(name = "hitSlop")
public open fun setHitSlop(view: ReactViewGroup, hitSlop: Dynamic) {
when (hitSlop.type) {
ReadableType.Map -> {
val hitSlopMap = hitSlop.asMap()
view.setHitSlopRect(
Rect(
hitSlopMap.px("left"),
hitSlopMap.px("top"),
hitSlopMap.px("right"),
hitSlopMap.px("bottom"),
))
}
ReadableType.Number -> {
val hitSlopValue = hitSlop.asDouble().dpToPx().toInt()
view.setHitSlopRect(Rect(hitSlopValue, hitSlopValue, hitSlopValue, hitSlopValue))
}
ReadableType.Null -> view.setHitSlopRect(null)
else -> {
FLog.w(ReactConstants.TAG, "Invalid type for 'hitSlop' value ${hitSlop.type}")
view.setHitSlopRect(null)
}
}
}
private fun ReadableMap.px(key: String) = if (hasKey(key)) getDouble(key).dpToPx().toInt() else 0
@ReactProp(name = ViewProps.POINTER_EVENTS)
public open fun setPointerEvents(view: ReactViewGroup, pointerEventsStr: String?) {
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
}
@ReactProp(name = "nativeBackgroundAndroid")
public open fun setNativeBackground(view: ReactViewGroup, background: ReadableMap?) {
val bg =
background?.let { ReactDrawableHelper.createDrawableFromJSDescription(view.context, it) }
BackgroundStyleApplicator.setFeedbackUnderlay(view, bg)
}
@ReactProp(name = "nativeForegroundAndroid")
public open fun setNativeForeground(view: ReactViewGroup, foreground: ReadableMap?) {
view.foreground =
foreground?.let { ReactDrawableHelper.createDrawableFromJSDescription(view.context, it) }
}
@ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
public open fun setNeedsOffscreenAlphaCompositing(
view: ReactViewGroup,
needsOffscreenAlphaCompositing: Boolean
) {
view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing)
}
@ReactPropGroup(
names =
[
ViewProps.BORDER_WIDTH,
ViewProps.BORDER_LEFT_WIDTH,
ViewProps.BORDER_RIGHT_WIDTH,
ViewProps.BORDER_TOP_WIDTH,
ViewProps.BORDER_BOTTOM_WIDTH,
ViewProps.BORDER_START_WIDTH,
ViewProps.BORDER_END_WIDTH,
],
defaultFloat = Float.NaN)
public open fun setBorderWidth(view: ReactViewGroup, index: Int, width: Float) {
BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width)
}
@ReactPropGroup(
names =
[
ViewProps.BORDER_COLOR,
ViewProps.BORDER_LEFT_COLOR,
ViewProps.BORDER_RIGHT_COLOR,
ViewProps.BORDER_TOP_COLOR,
ViewProps.BORDER_BOTTOM_COLOR,
ViewProps.BORDER_START_COLOR,
ViewProps.BORDER_END_COLOR,
ViewProps.BORDER_BLOCK_COLOR,
ViewProps.BORDER_BLOCK_END_COLOR,
ViewProps.BORDER_BLOCK_START_COLOR,
],
customType = "Color")
public open fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?) {
BackgroundStyleApplicator.setBorderColor(
view, LogicalEdge.fromSpacingType(SPACING_TYPES[index]), color)
}
@ReactProp(name = ViewProps.COLLAPSABLE)
@Suppress("UNUSED_PARAMETER")
public open fun setCollapsable(view: ReactViewGroup, collapsable: Boolean) {
// no-op: it's here only so that "collapsable" property is exported to JS. The value is actually
// handled in NativeViewHierarchyOptimizer
}
@ReactProp(name = ViewProps.COLLAPSABLE_CHILDREN)
@Suppress("UNUSED_PARAMETER")
public open fun setCollapsableChildren(view: ReactViewGroup, collapsableChildren: Boolean) {
// no-op: it's here only so that "collapsableChildren" property is exported to JS.
}
@ReactProp(name = "focusable")
public open fun setFocusable(view: ReactViewGroup, focusable: Boolean) {
if (focusable) {
view.setOnClickListener {
val eventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag((view.context as ReactContext), view.id)
eventDispatcher?.dispatchEvent(
ViewGroupClickEvent(UIManagerHelper.getSurfaceId(view.context), view.id))
}
// Clickable elements are focusable. On API 26, this is taken care by setClickable.
// Explicitly calling setFocusable here for backward compatibility.
view.isFocusable = true
} else {
view.setOnClickListener(null)
view.isClickable = false
// Don't set view.setFocusable(false) because we might still want it to be focusable for
// accessibility reasons
}
}
@ReactProp(name = ViewProps.OVERFLOW)
public open fun setOverflow(view: ReactViewGroup, overflow: String?) {
view.overflow = overflow
}
@ReactProp(name = "backfaceVisibility")
public open fun setBackfaceVisibility(view: ReactViewGroup, backfaceVisibility: String) {
view.setBackfaceVisibility(backfaceVisibility)
}
override fun setOpacity(view: ReactViewGroup, opacity: Float) {
view.setOpacityIfPossible(opacity)
}
override fun setTransformProperty(
view: ReactViewGroup,
transforms: ReadableArray?,
transformOrigin: ReadableArray?
) {
super.setTransformProperty(view, transforms, transformOrigin)
view.setBackfaceVisibilityDependantOpacity()
}
override fun getName(): String = REACT_CLASS
public override fun createViewInstance(context: ThemedReactContext): ReactViewGroup =
ReactViewGroup(context)
override fun getCommandsMap(): MutableMap<String, Int> =
mutableMapOf(HOTSPOT_UPDATE_KEY to CMD_HOTSPOT_UPDATE, "setPressed" to CMD_SET_PRESSED)
@Deprecated(
"Use receiveCommand(View, String, ReadableArray)",
ReplaceWith("receiveCommand(root, commandIdString, args)"))
override fun receiveCommand(root: ReactViewGroup, commandId: Int, args: ReadableArray?) {
when (commandId) {
CMD_HOTSPOT_UPDATE -> handleHotspotUpdate(root, args)
CMD_SET_PRESSED -> handleSetPressed(root, args)
else -> {}
}
}
override fun receiveCommand(root: ReactViewGroup, commandId: String, args: ReadableArray?) {
when (commandId) {
HOTSPOT_UPDATE_KEY -> handleHotspotUpdate(root, args)
"setPressed" -> handleSetPressed(root, args)
else -> {}
}
}
private fun handleSetPressed(root: ReactViewGroup, args: ReadableArray?) {
if (args == null || args.size() != 1) {
throw JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'setPressed' command")
}
root.isPressed = args.getBoolean(0)
}
private fun handleHotspotUpdate(root: ReactViewGroup, args: ReadableArray?) {
if (args == null || args.size() != 2) {
throw JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'updateHotspot' command")
}
val x = args.getDouble(0).dpToPx()
val y = args.getDouble(1).dpToPx()
root.drawableHotspotChanged(x, y)
}
}