feat(android): migrate ReactSwitchManager to Kotlin (#48003)

Summary:
This PR migrates `ReactSwitchManager` to Kotlin

Also it moves it's shadow node to a separate file (`ReactSwitchShadowNode.kt`)

## Changelog:

[ANDROID] [CHANGED] - Migrate `ReactSwitchManager` to Kotlin

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

Test Plan: Make sure that `Switch` example in `RNTester` works correctly

Reviewed By: cortinico

Differential Revision: D66594606

Pulled By: javache

fbshipit-source-id: 774641c4cf57d6d5f770df1fed4fcafef2af7ceb
This commit is contained in:
Kacper Rozniata
2024-12-02 02:41:31 -08:00
committed by Facebook GitHub Bot
parent 91e217ff54
commit b886bc4db9
4 changed files with 204 additions and 271 deletions
@@ -7137,45 +7137,6 @@ public final class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout
public fun setRefreshing (Z)V
}
public class com/facebook/react/views/switchview/ReactSwitchManager : com/facebook/react/uimanager/SimpleViewManager, com/facebook/react/viewmanagers/AndroidSwitchManagerInterface {
public static final field REACT_CLASS Ljava/lang/String;
public fun <init> ()V
protected synthetic fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)V
protected fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/switchview/ReactSwitch;)V
public fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/LayoutShadowNode;
public synthetic fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/ReactShadowNode;
protected synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
protected fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/switchview/ReactSwitch;
protected fun getDelegate ()Lcom/facebook/react/uimanager/ViewManagerDelegate;
public fun getName ()Ljava/lang/String;
public fun getShadowNodeClass ()Ljava/lang/Class;
public fun measure (Landroid/content/Context;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;FLcom/facebook/yoga/YogaMeasureMode;FLcom/facebook/yoga/YogaMeasureMode;[F)J
public synthetic fun receiveCommand (Landroid/view/View;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
public fun receiveCommand (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
public synthetic fun setBackgroundColor (Landroid/view/View;I)V
public fun setBackgroundColor (Lcom/facebook/react/views/switchview/ReactSwitch;I)V
public synthetic fun setDisabled (Landroid/view/View;Z)V
public fun setDisabled (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
public synthetic fun setEnabled (Landroid/view/View;Z)V
public fun setEnabled (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
public synthetic fun setNativeValue (Landroid/view/View;Z)V
public fun setNativeValue (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
public synthetic fun setOn (Landroid/view/View;Z)V
public fun setOn (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
public synthetic fun setThumbColor (Landroid/view/View;Ljava/lang/Integer;)V
public fun setThumbColor (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
public synthetic fun setThumbTintColor (Landroid/view/View;Ljava/lang/Integer;)V
public fun setThumbTintColor (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
public synthetic fun setTrackColorForFalse (Landroid/view/View;Ljava/lang/Integer;)V
public fun setTrackColorForFalse (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
public synthetic fun setTrackColorForTrue (Landroid/view/View;Ljava/lang/Integer;)V
public fun setTrackColorForTrue (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
public synthetic fun setTrackTintColor (Landroid/view/View;Ljava/lang/Integer;)V
public fun setTrackTintColor (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
public synthetic fun setValue (Landroid/view/View;Z)V
public fun setValue (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
}
public final class com/facebook/react/views/text/DefaultStyleValuesUtil {
public static final field INSTANCE Lcom/facebook/react/views/text/DefaultStyleValuesUtil;
public static final fun getDefaultTextColor (Landroid/content/Context;)Landroid/content/res/ColorStateList;
@@ -1,232 +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.
*/
// switchview because switch is a keyword
package com.facebook.react.views.switchview;
import android.content.Context;
import android.view.View;
import android.widget.CompoundButton;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.viewmanagers.AndroidSwitchManagerDelegate;
import com.facebook.react.viewmanagers.AndroidSwitchManagerInterface;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureOutput;
import com.facebook.yoga.YogaNode;
/** View manager for {@link ReactSwitch} components. */
public class ReactSwitchManager extends SimpleViewManager<ReactSwitch>
implements AndroidSwitchManagerInterface<ReactSwitch> {
public static final String REACT_CLASS = "AndroidSwitch";
static class ReactSwitchShadowNode extends LayoutShadowNode implements YogaMeasureFunction {
private int mWidth;
private int mHeight;
private boolean mMeasured;
private ReactSwitchShadowNode() {
initMeasureFunction();
}
private void initMeasureFunction() {
setMeasureFunction(this);
}
@Override
public long measure(
YogaNode node,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode) {
if (!mMeasured) {
// Create a switch with the default config and measure it; since we don't (currently)
// support setting custom switch text, this is fine, as all switches will measure the same
// on a specific device/theme/locale combination.
ReactSwitch reactSwitch = new ReactSwitch(getThemedContext());
reactSwitch.setShowText(false);
final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
reactSwitch.measure(spec, spec);
mWidth = reactSwitch.getMeasuredWidth();
mHeight = reactSwitch.getMeasuredHeight();
mMeasured = true;
}
return YogaMeasureOutput.make(mWidth, mHeight);
}
}
private static final CompoundButton.OnCheckedChangeListener ON_CHECKED_CHANGE_LISTENER =
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ReactContext reactContext = (ReactContext) buttonView.getContext();
int reactTag = buttonView.getId();
UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag)
.dispatchEvent(
new ReactSwitchEvent(
UIManagerHelper.getSurfaceId(reactContext), reactTag, isChecked));
}
};
private final ViewManagerDelegate<ReactSwitch> mDelegate;
public ReactSwitchManager() {
mDelegate = new AndroidSwitchManagerDelegate<>(this);
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
return new ReactSwitchShadowNode();
}
@Override
public Class getShadowNodeClass() {
return ReactSwitchShadowNode.class;
}
@Override
protected ReactSwitch createViewInstance(ThemedReactContext context) {
ReactSwitch view = new ReactSwitch(context);
view.setShowText(false);
return view;
}
@Override
public void setBackgroundColor(ReactSwitch view, @ColorInt int backgroundColor) {
view.setBackgroundColor(backgroundColor);
}
@Override
@ReactProp(name = "disabled", defaultBoolean = false)
public void setDisabled(ReactSwitch view, boolean disabled) {
view.setEnabled(!disabled);
}
@Override
@ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
public void setEnabled(ReactSwitch view, boolean enabled) {
view.setEnabled(enabled);
}
@Override
@ReactProp(name = ViewProps.ON)
public void setOn(ReactSwitch view, boolean on) {
setValueInternal(view, on);
}
@Override
@ReactProp(name = "value")
public void setValue(ReactSwitch view, boolean value) {
setValueInternal(view, value);
}
@Override
@ReactProp(name = "thumbTintColor", customType = "Color")
public void setThumbTintColor(ReactSwitch view, @Nullable Integer color) {
this.setThumbColor(view, color);
}
@Override
@ReactProp(name = "thumbColor", customType = "Color")
public void setThumbColor(ReactSwitch view, @Nullable Integer color) {
view.setThumbColor(color);
}
@Override
@ReactProp(name = "trackColorForFalse", customType = "Color")
public void setTrackColorForFalse(ReactSwitch view, @Nullable Integer color) {
view.setTrackColorForFalse(color);
}
@Override
@ReactProp(name = "trackColorForTrue", customType = "Color")
public void setTrackColorForTrue(ReactSwitch view, @Nullable Integer color) {
view.setTrackColorForTrue(color);
}
@Override
@ReactProp(name = "trackTintColor", customType = "Color")
public void setTrackTintColor(ReactSwitch view, @Nullable Integer color) {
view.setTrackColor(color);
}
@Override
public void setNativeValue(ReactSwitch view, boolean value) {
setValueInternal(view, value);
}
@Override
public void receiveCommand(
@NonNull ReactSwitch view, String commandId, @Nullable ReadableArray args) {
switch (commandId) {
case "setNativeValue":
setValueInternal(view, args != null && args.getBoolean(0));
break;
}
}
@Override
protected void addEventEmitters(final ThemedReactContext reactContext, final ReactSwitch view) {
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER);
}
@Override
protected ViewManagerDelegate<ReactSwitch> getDelegate() {
return mDelegate;
}
@Override
public long measure(
Context context,
ReadableMap localData,
ReadableMap props,
ReadableMap state,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode,
@Nullable float[] attachmentsPositions) {
ReactSwitch view = new ReactSwitch(context);
view.setShowText(false);
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(measureSpec, measureSpec);
return YogaMeasureOutput.make(
PixelUtil.toDIPFromPixel(view.getMeasuredWidth()),
PixelUtil.toDIPFromPixel(view.getMeasuredHeight()));
}
private static void setValueInternal(ReactSwitch view, boolean value) {
// we set the checked change listener to null and then restore it so that we don't fire an
// onChange event to JS when JS itself is updating the value of the switch
view.setOnCheckedChangeListener(null);
view.setOn(value);
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER);
}
}
@@ -0,0 +1,152 @@
/*
* 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.switchview
import android.content.Context
import android.view.View
import android.widget.CompoundButton
import androidx.annotation.ColorInt
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.BaseViewManager
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.ViewProps
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.viewmanagers.AndroidSwitchManagerDelegate
import com.facebook.react.viewmanagers.AndroidSwitchManagerInterface
import com.facebook.yoga.YogaMeasureMode
import com.facebook.yoga.YogaMeasureOutput
internal class ReactSwitchManager :
BaseViewManager<ReactSwitch, ReactSwitchShadowNode>(),
AndroidSwitchManagerInterface<ReactSwitch> {
private val delegate: ViewManagerDelegate<ReactSwitch> = AndroidSwitchManagerDelegate(this)
override fun getName(): String = REACT_CLASS
override fun createShadowNodeInstance(): ReactSwitchShadowNode = ReactSwitchShadowNode()
override fun getShadowNodeClass(): Class<ReactSwitchShadowNode> =
ReactSwitchShadowNode::class.java
override fun createViewInstance(context: ThemedReactContext): ReactSwitch =
ReactSwitch(context).apply { showText = false }
override fun setBackgroundColor(view: ReactSwitch, @ColorInt backgroundColor: Int) {
view.setBackgroundColor(backgroundColor)
}
@ReactProp(name = "disabled", defaultBoolean = false)
override fun setDisabled(view: ReactSwitch, disabled: Boolean) {
view.isEnabled = !disabled
}
@ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
override fun setEnabled(view: ReactSwitch, enabled: Boolean) {
view.isEnabled = enabled
}
@ReactProp(name = ViewProps.ON)
override fun setOn(view: ReactSwitch, on: Boolean) {
setValueInternal(view, on)
}
@ReactProp(name = "value")
override fun setValue(view: ReactSwitch, value: Boolean) {
setValueInternal(view, value)
}
@ReactProp(name = "thumbTintColor", customType = "Color")
override fun setThumbTintColor(view: ReactSwitch, color: Int?) {
setThumbColor(view, color)
}
@ReactProp(name = "thumbColor", customType = "Color")
override fun setThumbColor(view: ReactSwitch, color: Int?) {
view.setThumbColor(color)
}
@ReactProp(name = "trackColorForFalse", customType = "Color")
override fun setTrackColorForFalse(view: ReactSwitch, color: Int?) {
view.setTrackColorForFalse(color)
}
@ReactProp(name = "trackColorForTrue", customType = "Color")
override fun setTrackColorForTrue(view: ReactSwitch, color: Int?) {
view.setTrackColorForTrue(color)
}
@ReactProp(name = "trackTintColor", customType = "Color")
override fun setTrackTintColor(view: ReactSwitch, color: Int?) {
view.setTrackColor(color)
}
override fun setNativeValue(view: ReactSwitch, value: Boolean) {
setValueInternal(view, value)
}
override fun receiveCommand(view: ReactSwitch, commandId: String, args: ReadableArray?) {
when (commandId) {
"setNativeValue" -> setValueInternal(view, args?.getBoolean(0) ?: false)
}
}
override fun addEventEmitters(reactContext: ThemedReactContext, view: ReactSwitch) {
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER)
}
override fun updateExtraData(root: ReactSwitch, extraData: Any) {
// Do nothing
}
override fun getDelegate(): ViewManagerDelegate<ReactSwitch> = delegate
override fun measure(
context: Context,
localData: ReadableMap?,
props: ReadableMap?,
state: ReadableMap?,
width: Float,
widthMode: YogaMeasureMode,
height: Float,
heightMode: YogaMeasureMode,
attachmentsPositions: FloatArray?
): Long {
val view = ReactSwitch(context).apply { showText = false }
val measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
view.measure(measureSpec, measureSpec)
return YogaMeasureOutput.make(
PixelUtil.toDIPFromPixel(view.measuredWidth.toFloat()),
PixelUtil.toDIPFromPixel(view.measuredHeight.toFloat()))
}
private fun setValueInternal(view: ReactSwitch, value: Boolean) {
// Temporarily remove the listener to avoid triggering JS events
view.setOnCheckedChangeListener(null)
view.setOn(value)
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER)
}
internal companion object {
public const val REACT_CLASS: String = "AndroidSwitch"
private val ON_CHECKED_CHANGE_LISTENER =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
val reactContext = buttonView.context as ReactContext
val reactTag = buttonView.id
UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag)
?.dispatchEvent(
ReactSwitchEvent(UIManagerHelper.getSurfaceId(reactContext), reactTag, isChecked))
}
}
}
@@ -0,0 +1,52 @@
/*
* 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.switchview
import android.view.View
import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.yoga.YogaMeasureFunction
import com.facebook.yoga.YogaMeasureMode
import com.facebook.yoga.YogaMeasureOutput
import com.facebook.yoga.YogaNode
internal class ReactSwitchShadowNode : LayoutShadowNode(), YogaMeasureFunction {
private var width = 0
private var height = 0
private var measured = false
init {
initMeasureFunction()
}
private fun initMeasureFunction() {
setMeasureFunction(this)
}
override fun measure(
node: YogaNode,
width: Float,
widthMode: YogaMeasureMode,
height: Float,
heightMode: YogaMeasureMode
): Long {
if (!measured) {
// Create a switch with the default config and measure it; since we don't (currently)
// support setting custom switch text, this is fine, as all switches will measure the same
// on a specific device/theme/locale combination.
val reactSwitch = ReactSwitch(themedContext)
reactSwitch.showText = false
val spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
reactSwitch.measure(spec, spec)
this.width = reactSwitch.measuredWidth
this.height = reactSwitch.measuredHeight
measured = true
}
return YogaMeasureOutput.make(this.width, this.height)
}
}