From 3f880991aa32a504dafb7c1cf9af69273dfd6d3a Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Thu, 26 Sep 2024 15:50:26 -0700 Subject: [PATCH] Back out "Redo "[RN][Android] Convert ReactViewManager, ReactClippingViewManager to Kotlin"" Summary: Backing out the stack since a same crash that previously effected many apps appeared again, and there are changes soon landing that will add more conflicts. Reviewed By: Abbondanzo Differential Revision: D63493332 fbshipit-source-id: 4423bf41c793e00a0aa22d12a77bca69d3b1ae77 --- .../ReactAndroid/api/ReactAndroid.api | 9 +- .../views/view/ReactClippingViewManager.java | 95 ++++ .../views/view/ReactClippingViewManager.kt | 73 --- .../react/views/view/ReactViewManager.java | 425 ++++++++++++++++++ .../react/views/view/ReactViewManager.kt | 390 ---------------- 5 files changed, 522 insertions(+), 470 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 1f02b1355d8..e1909ad0345 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -8363,8 +8363,6 @@ 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 ()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; @@ -8375,7 +8373,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 - public synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View; + protected 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 @@ -8404,7 +8402,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 - public synthetic fun setTransformProperty (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V + protected 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 } @@ -8415,9 +8413,6 @@ 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 (I)V public fun (II)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java new file mode 100644 index 00000000000..525183053a0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java @@ -0,0 +1,95 @@ +/* + * 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.view.View; +import androidx.annotation.Nullable; +import com.facebook.infer.annotation.Nullsafe; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.annotations.ReactProp; + +/** + * View manager which handles clipped subviews. Useful for custom views which extends from {@link + * com.facebook.react.views.view.ReactViewGroup} + */ +@Nullsafe(Nullsafe.Mode.LOCAL) +public abstract class ReactClippingViewManager + extends ViewGroupManager { + + @ReactProp( + name = com.facebook.react.uimanager.ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS) + public void setRemoveClippedSubviews(T view, boolean removeClippedSubviews) { + UiThreadUtil.assertOnUiThread(); + + view.setRemoveClippedSubviews(removeClippedSubviews); + } + + @Override + public void addView(T parent, View child, int index) { + UiThreadUtil.assertOnUiThread(); + + boolean removeClippedSubviews = parent.getRemoveClippedSubviews(); + if (removeClippedSubviews) { + parent.addViewWithSubviewClippingEnabled(child, index); + } else { + parent.addView(child, index); + } + } + + @Override + public int getChildCount(T parent) { + boolean removeClippedSubviews = parent.getRemoveClippedSubviews(); + if (removeClippedSubviews) { + return parent.getAllChildrenCount(); + } else { + return parent.getChildCount(); + } + } + + @Override + @Nullable + public View getChildAt(T parent, int index) { + boolean removeClippedSubviews = parent.getRemoveClippedSubviews(); + if (removeClippedSubviews) { + return parent.getChildAtWithSubviewClippingEnabled(index); + } else { + return parent.getChildAt(index); + } + } + + @Override + public void removeViewAt(T parent, int index) { + UiThreadUtil.assertOnUiThread(); + + boolean removeClippedSubviews = parent.getRemoveClippedSubviews(); + if (removeClippedSubviews) { + View child = getChildAt(parent, index); + if (child != null) { + if (child.getParent() != null) { + parent.removeView(child); + } + parent.removeViewWithSubviewClippingEnabled(child); + } + } else { + parent.removeViewAt(index); + } + } + + @Override + public void removeAllViews(T parent) { + UiThreadUtil.assertOnUiThread(); + + boolean removeClippedSubviews = parent.getRemoveClippedSubviews(); + if (removeClippedSubviews) { + parent.removeAllViewsWithSubviewClippingEnabled(); + } else { + parent.removeAllViews(); + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt deleted file mode 100644 index d77075ed705..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt +++ /dev/null @@ -1,73 +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.view.View -import com.facebook.react.bridge.UiThreadUtil -import com.facebook.react.uimanager.ReactClippingViewGroupHelper -import com.facebook.react.uimanager.ViewGroupManager -import com.facebook.react.uimanager.annotations.ReactProp - -/** - * View manager which handles clipped subviews. Useful for custom views which extends from - * [com.facebook.react.views.view.ReactViewGroup] - */ -public abstract class ReactClippingViewManager : ViewGroupManager() { - - @ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS) - public open fun setRemoveClippedSubviews(view: T, removeClippedSubviews: Boolean) { - UiThreadUtil.assertOnUiThread() - view.removeClippedSubviews = removeClippedSubviews - } - - override fun addView(parent: T, child: View, index: Int) { - UiThreadUtil.assertOnUiThread() - if (parent.removeClippedSubviews) { - parent.addViewWithSubviewClippingEnabled(child, index) - } else { - parent.addView(child, index) - } - } - - override fun getChildCount(parent: T): Int = - if (parent.removeClippedSubviews) { - parent.allChildrenCount - } else { - parent.childCount - } - - override fun getChildAt(parent: T, index: Int): View? = - if (parent.removeClippedSubviews) { - parent.getChildAtWithSubviewClippingEnabled(index) - } else { - parent.getChildAt(index) - } - - override fun removeViewAt(parent: T, index: Int) { - UiThreadUtil.assertOnUiThread() - if (parent.removeClippedSubviews) { - val child = getChildAt(parent, index) ?: return - if (child.parent != null) { - parent.removeView(child) - } else { - parent.removeViewWithSubviewClippingEnabled(child) - } - } else { - parent.removeViewAt(index) - } - } - - override fun removeAllViews(parent: T) { - UiThreadUtil.assertOnUiThread() - if (parent.removeClippedSubviews) { - parent.removeAllViewsWithSubviewClippingEnabled() - } else { - parent.removeAllViews() - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java new file mode 100644 index 00000000000..548fe524399 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -0,0 +1,425 @@ +/* + * 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 androidx.annotation.ColorInt; +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 { + + @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 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) { + view.setTranslucentBackgroundDrawable( + bg == null + ? null + : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg)); + } + + @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(); + } + + @ReactProp(name = ViewProps.BOX_SHADOW, customType = "BoxShadow") + public void setBoxShadow(ReactViewGroup view, @Nullable ReadableArray shadows) { + BackgroundStyleApplicator.setBoxShadow(view, shadows); + } + + @Override + public void setBackgroundColor(ReactViewGroup view, @ColorInt int backgroundColor) { + BackgroundStyleApplicator.setBackgroundColor(view, backgroundColor); + } + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + public ReactViewGroup createViewInstance(ThemedReactContext context) { + return new ReactViewGroup(context); + } + + @Override + public Map 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); + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt deleted file mode 100644 index 4b6dc1957e2..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +++ /dev/null @@ -1,390 +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.view.View -import androidx.annotation.ColorInt -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.common.annotations.UnstableReactNativeAPI -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.BackgroundStyleApplicator -import com.facebook.react.uimanager.LengthPercentage.Companion.setFromDynamic -import com.facebook.react.uimanager.LengthPercentageType -import com.facebook.react.uimanager.PixelUtil.dpToPx -import com.facebook.react.uimanager.PointerEvents.Companion.parsePointerEvents -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.BorderStyle.Companion.fromString -import com.facebook.react.uimanager.style.LogicalEdge -import com.facebook.react.uimanager.style.LogicalEdge.Companion.fromSpacingType - -/** View manager for AndroidViews (plain React Views). */ -@ReactModule(name = ReactViewManager.REACT_CLASS) -public open class ReactViewManager : ReactClippingViewManager() { - - init { - setupViewRecycling() - } - - override fun prepareToRecycleView( - reactContext: ThemedReactContext, - view: ReactViewGroup - ): ReactViewGroup { - // BaseViewManager - super.prepareToRecycleView(reactContext, view)?.recycleView() - return view - } - - @ReactProp(name = "accessible") - public open fun setAccessible(view: ReactViewGroup, accessible: Boolean): Unit { - view.isFocusable = accessible - } - - @ReactProp(name = "hasTVPreferredFocus") - public open fun setTVPreferredFocus(view: ReactViewGroup, hasTVPreferredFocus: Boolean): Unit { - if (hasTVPreferredFocus) { - view.isFocusable = true - view.isFocusableInTouchMode = true - view.requestFocus() - } - } - - @OptIn(UnstableReactNativeAPI::class) - @ReactProp(name = ViewProps.BACKGROUND_IMAGE, customType = "BackgroundImage") - public open fun setBackgroundImage(view: ReactViewGroup, backgroundImage: ReadableArray?): Unit { - if (ViewUtil.getUIManagerType(view) == UIManagerType.FABRIC) { - val size = backgroundImage?.size() - if (size != null && size > 0) { - val backgroundImageLayers = ArrayList(size) - repeat(size) { i -> - backgroundImageLayers.add(BackgroundImageLayer(backgroundImage.getMap(i), view.context)) - } - BackgroundStyleApplicator.setBackgroundImage(view, backgroundImageLayers) - } else { - BackgroundStyleApplicator.setBackgroundImage(view, null) - } - } - } - - @ReactProp(name = "nextFocusDown", defaultInt = View.NO_ID) - public open fun nextFocusDown(view: ReactViewGroup, viewId: Int): Unit { - view.nextFocusDownId = viewId - } - - @ReactProp(name = "nextFocusForward", defaultInt = View.NO_ID) - public open fun nextFocusForward(view: ReactViewGroup, viewId: Int): Unit { - view.nextFocusForwardId = viewId - } - - @ReactProp(name = "nextFocusLeft", defaultInt = View.NO_ID) - public open fun nextFocusLeft(view: ReactViewGroup, viewId: Int): Unit { - view.nextFocusLeftId = viewId - } - - @ReactProp(name = "nextFocusRight", defaultInt = View.NO_ID) - public open fun nextFocusRight(view: ReactViewGroup, viewId: Int): Unit { - view.nextFocusRightId = viewId - } - - @ReactProp(name = "nextFocusUp", defaultInt = View.NO_ID) - public open fun nextFocusUp(view: ReactViewGroup, viewId: Int): Unit { - 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 - ): Unit { - var borderRadius = 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?.type == LengthPercentageType.PERCENT) { - borderRadius = null - } - BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], borderRadius) - } - - @Deprecated( - "Use setBorderRadius(ReactViewGroup, int, Dynamic) instead.", - ReplaceWith( - "setBorderRadius(view, index, DynamicFromObject(borderRadius))", - "com.facebook.react.bridge.DynamicFromObject.DynamicFromObject")) - public open fun setBorderRadius(view: ReactViewGroup, index: Int, borderRadius: Float): Unit { - this.setBorderRadius(view, index, DynamicFromObject(borderRadius)) - } - - @ReactProp(name = "borderStyle") - public open fun setBorderStyle(view: ReactViewGroup, borderStyle: String?): Unit { - val parsedBorderStyle = borderStyle?.let { BorderStyle.fromString(it) } - BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle) - } - - @ReactProp(name = "hitSlop") - public open fun setHitSlop(view: ReactViewGroup, hitSlop: Dynamic): Unit { - when (hitSlop.type) { - ReadableType.Map -> { - val hitSlopMap = hitSlop.asMap() - view.setHitSlopRect( - Rect( - getPixels(hitSlopMap, "left"), - getPixels(hitSlopMap, "top"), - getPixels(hitSlopMap, "right"), - getPixels(hitSlopMap, "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 getPixels(map: ReadableMap, key: String): Int = - if (map.hasKey(key)) { - map.getDouble(key).dpToPx().toInt() - } else { - 0 - } - - @ReactProp(name = ViewProps.POINTER_EVENTS) - public open fun setPointerEvents(view: ReactViewGroup, pointerEventsStr: String?): Unit { - view.setPointerEvents(parsePointerEvents(pointerEventsStr)) - } - - @ReactProp(name = "nativeBackgroundAndroid") - public open fun setNativeBackground(view: ReactViewGroup, background: ReadableMap?): Unit { - val translucentBg = - background?.let { ReactDrawableHelper.createDrawableFromJSDescription(view.context, it) } - BackgroundStyleApplicator.setFeedbackUnderlay(view, translucentBg) - } - - @ReactProp(name = "nativeForegroundAndroid") - public open fun setNativeForeground(view: ReactViewGroup, foreground: ReadableMap?): Unit { - view.foreground = - foreground?.let { ReactDrawableHelper.createDrawableFromJSDescription(view.context, it) } - } - - @ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING) - public open fun setNeedsOffscreenAlphaCompositing( - view: ReactViewGroup, - needsOffscreenAlphaCompositing: Boolean - ): Unit { - 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): Unit { - 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?): Unit { - BackgroundStyleApplicator.setBorderColor( - view, LogicalEdge.fromSpacingType(SPACING_TYPES[index]), color) - } - - @ReactProp(name = ViewProps.COLLAPSABLE) - @Suppress("UNUSED_PARAMETER") - public open fun setCollapsable(view: ReactViewGroup?, collapsable: Boolean): Unit { - // 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 - ): Unit { - // 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): Unit { - 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): Unit { - view.overflow = overflow - } - - @ReactProp(name = "backfaceVisibility") - public open fun setBackfaceVisibility(view: ReactViewGroup, backfaceVisibility: String): Unit { - 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() - } - - @ReactProp(name = ViewProps.BOX_SHADOW, customType = "BoxShadow") - public open fun setBoxShadow(view: ReactViewGroup, shadows: ReadableArray?): Unit { - BackgroundStyleApplicator.setBoxShadow(view, shadows) - } - - override fun setBackgroundColor(view: ReactViewGroup, @ColorInt backgroundColor: Int) { - BackgroundStyleApplicator.setBackgroundColor(view, backgroundColor) - } - - override fun getName(): String = REACT_CLASS - - public override fun createViewInstance(context: ThemedReactContext): ReactViewGroup = - ReactViewGroup(context) - - override fun getCommandsMap(): Map = - mapOf( - HOTSPOT_UPDATE_KEY to CMD_HOTSPOT_UPDATE, - "setPressed" to CMD_SET_PRESSED, - ) - - @Deprecated("Deprecated in ViewManager") - 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?.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?.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) - } - - 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" - } -}