diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 7b0f4d98e53..77eb9602e6b 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 ()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 (I)V public fun (II)V 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 deleted file mode 100644 index 5df8615292a..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ /dev/null @@ -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 { - - @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) { - 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 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 new file mode 100644 index 00000000000..9c87b54b06c --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt @@ -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() { + + 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(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 = + 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) + } +}