diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java deleted file mode 100644 index ede7f5a6a8d476..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.java +++ /dev/null @@ -1,131 +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.animated; - -import android.content.Context; -import android.graphics.Color; -import android.view.View; -import com.facebook.react.bridge.ColorPropConverter; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.views.view.ColorUtil; - -/** Animated node that represents a color. */ -/*package*/ class ColorAnimatedNode extends AnimatedNode - implements AnimatedNodeWithUpdateableConfig { - - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final ReactApplicationContext mReactApplicationContext; - private int mRNodeId; - private int mGNodeId; - private int mBNodeId; - private int mANodeId; - private ReadableMap mNativeColor; - private boolean mNativeColorApplied; - - public ColorAnimatedNode( - ReadableMap config, - NativeAnimatedNodesManager nativeAnimatedNodesManager, - ReactApplicationContext reactApplicationContext) { - mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - mReactApplicationContext = reactApplicationContext; - onUpdateConfig(config); - } - - public int getColor() { - tryApplyNativeColor(); - - ValueAnimatedNode rNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mRNodeId); - ValueAnimatedNode gNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mGNodeId); - ValueAnimatedNode bNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mBNodeId); - ValueAnimatedNode aNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mANodeId); - - double r = rNode.getValue(); - double g = gNode.getValue(); - double b = bNode.getValue(); - double a = aNode.getValue(); - - return ColorUtil.normalize(r, g, b, a); - } - - public void onUpdateConfig(ReadableMap config) { - mRNodeId = config.getInt("r"); - mGNodeId = config.getInt("g"); - mBNodeId = config.getInt("b"); - mANodeId = config.getInt("a"); - mNativeColor = config.getMap("nativeColor"); - mNativeColorApplied = false; - tryApplyNativeColor(); - } - - @Override - public String prettyPrint() { - return "ColorAnimatedNode[" - + tag - + "]: r: " - + mRNodeId - + " g: " - + mGNodeId - + " b: " - + mBNodeId - + " a: " - + mANodeId; - } - - private void tryApplyNativeColor() { - if (mNativeColor == null || mNativeColorApplied) { - return; - } - - Context context = getContext(); - if (context == null) { - return; - } - - int color = ColorPropConverter.getColor(mNativeColor, context); - - ValueAnimatedNode rNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mRNodeId); - ValueAnimatedNode gNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mGNodeId); - ValueAnimatedNode bNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mBNodeId); - ValueAnimatedNode aNode = (ValueAnimatedNode) mNativeAnimatedNodesManager.getNodeById(mANodeId); - - rNode.nodeValue = Color.red(color); - gNode.nodeValue = Color.green(color); - bNode.nodeValue = Color.blue(color); - aNode.nodeValue = Color.alpha(color) / 255.0; - - mNativeColorApplied = true; - } - - private Context getContext() { - Context context = mReactApplicationContext.getCurrentActivity(); - if (context != null) { - return context; - } - - // There are cases where the activity may not exist (such as for VRShell panel apps). In this - // case we will search for a view associated with a PropsAnimatedNode to get the context. - return getContextHelper(this); - } - - private static Context getContextHelper(AnimatedNode node) { - // Search children depth-first until we get to a PropsAnimatedNode, from which we can - // get the view and its context - if (node.children != null) { - for (AnimatedNode child : node.children) { - if (child instanceof PropsAnimatedNode) { - View view = ((PropsAnimatedNode) child).getConnectedView(); - return view != null ? view.getContext() : null; - } else { - return getContextHelper(child); - } - } - } - return null; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.kt new file mode 100644 index 00000000000000..d24ad6bfa5a952 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/ColorAnimatedNode.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.animated + +import android.content.Context +import android.graphics.Color +import com.facebook.react.bridge.ColorPropConverter +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.views.view.ColorUtil.normalize + +/** Animated node that represents a color. */ +internal class ColorAnimatedNode( + config: ReadableMap, + private val nativeAnimatedNodesManager: NativeAnimatedNodesManager, + private val reactApplicationContext: ReactApplicationContext +) : AnimatedNode(), AnimatedNodeWithUpdateableConfig { + private var rNodeId = 0 + private var gNodeId = 0 + private var bNodeId = 0 + private var aNodeId = 0 + private var nativeColor: ReadableMap? = null + private var nativeColorApplied = false + + init { + onUpdateConfig(config) + } + + val color: Int + get() { + tryApplyNativeColor() + val rNode = nativeAnimatedNodesManager.getNodeById(rNodeId) as ValueAnimatedNode? + val gNode = nativeAnimatedNodesManager.getNodeById(gNodeId) as ValueAnimatedNode? + val bNode = nativeAnimatedNodesManager.getNodeById(bNodeId) as ValueAnimatedNode? + val aNode = nativeAnimatedNodesManager.getNodeById(aNodeId) as ValueAnimatedNode? + val r = rNode?.nodeValue ?: 0.0 + val g = gNode?.nodeValue ?: 0.0 + val b = bNode?.nodeValue ?: 0.0 + val a = aNode?.nodeValue ?: 0.0 + return normalize(r, g, b, a) + } + + override fun onUpdateConfig(config: ReadableMap?) { + if (config != null) { + rNodeId = config.getInt("r") + gNodeId = config.getInt("g") + bNodeId = config.getInt("b") + aNodeId = config.getInt("a") + nativeColor = config.getMap("nativeColor") + nativeColorApplied = false + tryApplyNativeColor() + } else { + rNodeId = 0 + gNodeId = 0 + bNodeId = 0 + aNodeId = 0 + nativeColor = null + nativeColorApplied = false + } + } + + override fun prettyPrint(): String = + "ColorAnimatedNode[$tag]: r: $rNodeId g: $gNodeId b: $bNodeId a: $aNodeId" + + private fun tryApplyNativeColor() { + if (nativeColor == null || nativeColorApplied) { + return + } + val context = context ?: return + val color = ColorPropConverter.getColor(nativeColor, context) + val rNode = nativeAnimatedNodesManager.getNodeById(rNodeId) as ValueAnimatedNode? + val gNode = nativeAnimatedNodesManager.getNodeById(gNodeId) as ValueAnimatedNode? + val bNode = nativeAnimatedNodesManager.getNodeById(bNodeId) as ValueAnimatedNode? + val aNode = nativeAnimatedNodesManager.getNodeById(aNodeId) as ValueAnimatedNode? + rNode?.nodeValue = Color.red(color).toDouble() + gNode?.nodeValue = Color.green(color).toDouble() + bNode?.nodeValue = Color.blue(color).toDouble() + aNode?.nodeValue = Color.alpha(color) / 255.0 + nativeColorApplied = true + } + + private val context: Context? + get() { + // There are cases where the activity may not exist (such as for VRShell panel apps). In this + // case we will search for a view associated with a PropsAnimatedNode to get the context. + return reactApplicationContext.currentActivity ?: getContextHelper(this) + } + + companion object { + private fun getContextHelper(node: AnimatedNode): Context? { + // Search children depth-first until we get to a PropsAnimatedNode, from which we can + // get the view and its context + node.children?.let { children -> + for (child in children) { + return if (child is PropsAnimatedNode) { + val view = child.connectedView + view?.context + } else { + getContextHelper(child) + } + } + } + return null + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java deleted file mode 100644 index 1aa5d903c4eeeb..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java +++ /dev/null @@ -1,64 +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.animated; - -import com.facebook.react.bridge.JSApplicationCausedNativeException; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; - -/** - * Animated node which takes two or more value node as an input and outputs an in-order division of - * their values. - */ -/*package*/ class DivisionAnimatedNode extends ValueAnimatedNode { - - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final int[] mInputNodes; - - public DivisionAnimatedNode( - ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { - mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - ReadableArray inputNodes = config.getArray("input"); - mInputNodes = new int[inputNodes.size()]; - for (int i = 0; i < mInputNodes.length; i++) { - mInputNodes[i] = inputNodes.getInt(i); - } - } - - @Override - public void update() { - for (int i = 0; i < mInputNodes.length; i++) { - AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]); - if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) { - double value = ((ValueAnimatedNode) animatedNode).getValue(); - if (i == 0) { - nodeValue = value; - continue; - } - if (value == 0) { - throw new JSApplicationCausedNativeException( - "Detected a division by zero in Animated.divide node with Animated ID " + tag); - } - nodeValue /= value; - } else { - throw new JSApplicationCausedNativeException( - "Illegal node ID set as an input for Animated.divide node with Animated ID " + tag); - } - } - } - - @Override - public String prettyPrint() { - return "DivisionAnimatedNode[" - + tag - + "]: input nodes: " - + (mInputNodes != null ? mInputNodes.toString() : "null") - + " - super: " - + super.prettyPrint(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.kt new file mode 100644 index 00000000000000..b2625bf0f00e21 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.kt @@ -0,0 +1,55 @@ +/* + * 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.animated + +import com.facebook.react.bridge.JSApplicationCausedNativeException +import com.facebook.react.bridge.ReadableMap + +/** + * Animated node which takes two or more value node as an input and outputs an in-order division of + * their values. + */ +internal class DivisionAnimatedNode( + config: ReadableMap, + private val nativeAnimatedNodesManager: NativeAnimatedNodesManager +) : ValueAnimatedNode() { + private val inputNodes: IntArray + + init { + val input = config.getArray("input") + inputNodes = + if (input == null) { + IntArray(0) + } else { + IntArray(input.size()) { i -> input.getInt(i) } + } + } + + override fun update() { + inputNodes.forEachIndexed { i, inputNode -> + val animatedNode = nativeAnimatedNodesManager.getNodeById(inputNode) + if (animatedNode != null && animatedNode is ValueAnimatedNode) { + val v = animatedNode.nodeValue + if (i == 0) { + nodeValue = v + } else if (v == 0.0) { + throw JSApplicationCausedNativeException( + "Detected a division by zero in Animated.divide node with Animated ID $tag") + } else { + nodeValue /= v + } + } else { + throw JSApplicationCausedNativeException( + "Illegal node ID set as an input for Animated.divide node with Animated ID $tag") + } + } + } + + override fun prettyPrint(): String = + "DivisionAnimatedNode[$tag]: input nodes: $inputNodes - super: ${super.prettyPrint()}" +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java deleted file mode 100644 index 06296ea7eb5f49..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java +++ /dev/null @@ -1,56 +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.animated; - -import com.facebook.react.bridge.JSApplicationCausedNativeException; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; - -/** - * Animated node which takes two or more value node as an input and outputs a product of their - * values - */ -/*package*/ class MultiplicationAnimatedNode extends ValueAnimatedNode { - - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final int[] mInputNodes; - - public MultiplicationAnimatedNode( - ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { - mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - ReadableArray inputNodes = config.getArray("input"); - mInputNodes = new int[inputNodes.size()]; - for (int i = 0; i < mInputNodes.length; i++) { - mInputNodes[i] = inputNodes.getInt(i); - } - } - - @Override - public void update() { - nodeValue = 1; - for (int i = 0; i < mInputNodes.length; i++) { - AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]); - if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) { - nodeValue *= ((ValueAnimatedNode) animatedNode).getValue(); - } else { - throw new JSApplicationCausedNativeException( - "Illegal node ID set as an input for Animated.multiply node"); - } - } - } - - @Override - public String prettyPrint() { - return "MultiplicationAnimatedNode[" - + tag - + "]: input nodes: " - + (mInputNodes != null ? mInputNodes.toString() : "null") - + " - super: " - + super.prettyPrint(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.kt new file mode 100644 index 00000000000000..fa1f7d4a8e18ba --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.kt @@ -0,0 +1,50 @@ +/* + * 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.animated + +import com.facebook.react.bridge.JSApplicationCausedNativeException +import com.facebook.react.bridge.ReadableMap + +/** + * Animated node which takes two or more value node as an input and outputs a product of their + * values + */ +internal class MultiplicationAnimatedNode( + config: ReadableMap, + private val nativeAnimatedNodesManager: NativeAnimatedNodesManager +) : ValueAnimatedNode() { + private var inputNodes: IntArray + + init { + val input = config.getArray("input") + inputNodes = + if (input == null) { + IntArray(0) + } else { + IntArray(input.size()) { i -> input.getInt(i) } + } + } + + override fun update() { + nodeValue = 1.0 + for (i in inputNodes.indices) { + val animatedNode = nativeAnimatedNodesManager.getNodeById(inputNodes[i]) + val multiplier = + if (animatedNode != null && animatedNode is ValueAnimatedNode) { + animatedNode.getValue() + } else { + throw JSApplicationCausedNativeException( + "Illegal node ID set as an input for Animated.multiply node") + } + nodeValue *= multiplier + } + } + + override fun prettyPrint(): String = + "MultiplicationAnimatedNode[$tag]: input nodes: $inputNodes - super: ${super.prettyPrint()}" +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java deleted file mode 100644 index 312e9218ba98a6..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java +++ /dev/null @@ -1,145 +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.animated; - -import android.view.View; -import androidx.annotation.Nullable; -import com.facebook.react.bridge.JSApplicationIllegalArgumentException; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.uimanager.IllegalViewOperationException; -import com.facebook.react.uimanager.common.UIManagerType; -import com.facebook.react.uimanager.common.ViewUtil; -import java.util.HashMap; -import java.util.Map; - -/** - * Animated node that represents view properties. There is a special handling logic implemented for - * the nodes of this type in {@link NativeAnimatedNodesManager} that is responsible for extracting a - * map of updated properties, which can be then passed down to the view. - */ -/*package*/ class PropsAnimatedNode extends AnimatedNode { - - private int mConnectedViewTag = -1; - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final Map mPropNodeMapping; - private final JavaOnlyMap mPropMap; - @Nullable private UIManager mUIManager; - - PropsAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { - ReadableMap props = config.getMap("props"); - ReadableMapKeySetIterator iter = props.keySetIterator(); - mPropNodeMapping = new HashMap<>(); - while (iter.hasNextKey()) { - String propKey = iter.nextKey(); - int nodeIndex = props.getInt(propKey); - mPropNodeMapping.put(propKey, nodeIndex); - } - mPropMap = new JavaOnlyMap(); - mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - } - - public void connectToView(int viewTag, UIManager uiManager) { - if (mConnectedViewTag != -1) { - throw new JSApplicationIllegalArgumentException( - "Animated node " + tag + " is " + "already attached to a view: " + mConnectedViewTag); - } - mConnectedViewTag = viewTag; - mUIManager = uiManager; - } - - public void disconnectFromView(int viewTag) { - if (mConnectedViewTag != viewTag && mConnectedViewTag != -1) { - throw new JSApplicationIllegalArgumentException( - "Attempting to disconnect view that has " - + "not been connected with the given animated node: " - + viewTag - + " but is connected to view " - + mConnectedViewTag); - } - - mConnectedViewTag = -1; - } - - public void restoreDefaultValues() { - // Cannot restore default values if this view has already been disconnected. - if (mConnectedViewTag == -1) { - return; - } - // Don't restore default values in Fabric. - // In Non-Fabric this had the effect of "restore the value to whatever the value was on the - // ShadowNode instead of in the View hierarchy". However, "synchronouslyUpdateViewOnUIThread" - // will not have that impact on Fabric, because the FabricUIManager doesn't have access to the - // ShadowNode layer. - if (ViewUtil.getUIManagerType(mConnectedViewTag) == UIManagerType.FABRIC) { - return; - } - - ReadableMapKeySetIterator it = mPropMap.keySetIterator(); - while (it.hasNextKey()) { - mPropMap.putNull(it.nextKey()); - } - - mUIManager.synchronouslyUpdateViewOnUIThread(mConnectedViewTag, mPropMap); - } - - public final void updateView() { - if (mConnectedViewTag == -1) { - return; - } - for (Map.Entry entry : mPropNodeMapping.entrySet()) { - @Nullable AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(entry.getValue()); - if (node == null) { - throw new IllegalArgumentException("Mapped property node does not exist"); - } else if (node instanceof StyleAnimatedNode) { - ((StyleAnimatedNode) node).collectViewUpdates(mPropMap); - } else if (node instanceof ValueAnimatedNode) { - Object animatedObject = ((ValueAnimatedNode) node).getAnimatedObject(); - if (animatedObject instanceof Integer) { - mPropMap.putInt(entry.getKey(), (Integer) animatedObject); - } else if (animatedObject instanceof String) { - mPropMap.putString(entry.getKey(), (String) animatedObject); - } else { - mPropMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); - } - } else if (node instanceof ColorAnimatedNode) { - mPropMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor()); - } else if (node instanceof ObjectAnimatedNode) { - ((ObjectAnimatedNode) node).collectViewUpdates(entry.getKey(), mPropMap); - } else { - throw new IllegalArgumentException( - "Unsupported type of node used in property node " + node.getClass()); - } - } - - mUIManager.synchronouslyUpdateViewOnUIThread(mConnectedViewTag, mPropMap); - } - - public View getConnectedView() { - try { - return mUIManager.resolveView(mConnectedViewTag); - } catch (IllegalViewOperationException ex) { - // resolveView throws an {@link IllegalViewOperationException} when the view doesn't exist - // (this can happen if the surface is being deallocated). - return null; - } - } - - public String prettyPrint() { - return "PropsAnimatedNode[" - + tag - + "] connectedViewTag: " - + mConnectedViewTag - + " mPropNodeMapping: " - + (mPropNodeMapping != null ? mPropNodeMapping.toString() : "null") - + " mPropMap: " - + (mPropMap != null ? mPropMap.toString() : "null"); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.kt new file mode 100644 index 00000000000000..9c66900aaf5090 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.kt @@ -0,0 +1,120 @@ +/* + * 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.animated + +import android.view.View +import com.facebook.react.bridge.JSApplicationIllegalArgumentException +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.UIManager +import com.facebook.react.uimanager.common.UIManagerType +import com.facebook.react.uimanager.common.ViewUtil.getUIManagerType + +/** + * Animated node that represents view properties. There is a special handling logic implemented for + * the nodes of this type in [NativeAnimatedNodesManager] that is responsible for extracting a map + * of updated properties, which can be then passed down to the view. + */ +internal class PropsAnimatedNode( + config: ReadableMap, + private val nativeAnimatedNodesManager: NativeAnimatedNodesManager +) : AnimatedNode() { + private var connectedViewTag = -1 + private val propNodeMapping: MutableMap + private val propMap = JavaOnlyMap() + private var connectedViewUIManager: UIManager? = null + + init { + val props = config.getMap("props") + val iter = props?.keySetIterator() + propNodeMapping = mutableMapOf() + while (iter != null && iter.hasNextKey()) { + val propKey = iter.nextKey() + val nodeIndex = props.getInt(propKey) + propNodeMapping[propKey] = nodeIndex + } + } + + public fun connectToView(viewTag: Int, uiManager: UIManager?) { + if (connectedViewTag != -1) { + throw JSApplicationIllegalArgumentException( + "Animated node $tag is already attached to a view: $connectedViewTag") + } + connectedViewTag = viewTag + connectedViewUIManager = uiManager + } + + public fun disconnectFromView(viewTag: Int) { + if (connectedViewTag != viewTag && connectedViewTag != -1) { + throw JSApplicationIllegalArgumentException( + "Attempting to disconnect view that has " + + "not been connected with the given animated node: $viewTag " + + "but is connected to view $connectedViewTag") + } + connectedViewTag = -1 + } + + public fun restoreDefaultValues() { + // Cannot restore default values if this view has already been disconnected. + if (connectedViewTag == -1) { + return + } + // Don't restore default values in Fabric. + // In Non-Fabric this had the effect of "restore the value to whatever the value was on the + // ShadowNode instead of in the View hierarchy". However, "synchronouslyUpdateViewOnUIThread" + // will not have that impact on Fabric, because the FabricUIManager doesn't have access to the + // ShadowNode layer. + if (getUIManagerType(connectedViewTag) == UIManagerType.FABRIC) { + return + } + val it = propMap.keySetIterator() + while (it.hasNextKey()) { + propMap.putNull(it.nextKey()) + } + connectedViewUIManager?.synchronouslyUpdateViewOnUIThread(connectedViewTag, propMap) + } + + public fun updateView() { + if (connectedViewTag == -1) { + return + } + for ((key, value) in propNodeMapping) { + val node = nativeAnimatedNodesManager.getNodeById(value) + requireNotNull(node) { "Mapped property node does not exist" } + if (node is StyleAnimatedNode) { + node.collectViewUpdates(propMap) + } else if (node is ValueAnimatedNode) { + val animatedObject = node.getAnimatedObject() + if (animatedObject is Int) { + propMap.putInt(key, animatedObject) + } else if (animatedObject is String) { + propMap.putString(key, animatedObject) + } else { + propMap.putDouble(key, node.getValue()) + } + } else if (node is ColorAnimatedNode) { + propMap.putInt(key, node.color) + } else if (node is ObjectAnimatedNode) { + node.collectViewUpdates(key, propMap) + } else { + throw IllegalArgumentException( + "Unsupported type of node used in property node ${node.javaClass}") + } + } + connectedViewUIManager?.synchronouslyUpdateViewOnUIThread(connectedViewTag, propMap) + } + + public val connectedView: View? + // resolveView throws an {@link IllegalViewOperationException} when the view doesn't exist + // (this can happen if the surface is being deallocated). + get() = runCatching { connectedViewUIManager?.resolveView(connectedViewTag) }.getOrNull() + + override fun prettyPrint(): String = + "PropsAnimatedNode[$tag] connectedViewTag: $connectedViewTag " + + "propNodeMapping: $propNodeMapping propMap: $propMap" +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java deleted file mode 100644 index ec87b2f2c9c813..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java +++ /dev/null @@ -1,70 +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.animated; - -import androidx.annotation.Nullable; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import java.util.HashMap; -import java.util.Map; - -/** - * Native counterpart of style animated node (see AnimatedStyle class in AnimatedImplementation.js) - */ -/*package*/ class StyleAnimatedNode extends AnimatedNode { - - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final Map mPropMapping; - - StyleAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { - ReadableMap style = config.getMap("style"); - ReadableMapKeySetIterator iter = style.keySetIterator(); - mPropMapping = new HashMap<>(); - while (iter.hasNextKey()) { - String propKey = iter.nextKey(); - int nodeIndex = style.getInt(propKey); - mPropMapping.put(propKey, nodeIndex); - } - mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - } - - public void collectViewUpdates(JavaOnlyMap propsMap) { - for (Map.Entry entry : mPropMapping.entrySet()) { - @Nullable AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(entry.getValue()); - if (node == null) { - throw new IllegalArgumentException("Mapped style node does not exist"); - } else if (node instanceof TransformAnimatedNode) { - ((TransformAnimatedNode) node).collectViewUpdates(propsMap); - } else if (node instanceof ValueAnimatedNode) { - Object animatedObject = ((ValueAnimatedNode) node).getAnimatedObject(); - if (animatedObject instanceof Integer) { - propsMap.putInt(entry.getKey(), (Integer) animatedObject); - } else if (animatedObject instanceof String) { - propsMap.putString(entry.getKey(), (String) animatedObject); - } else { - propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); - } - } else if (node instanceof ColorAnimatedNode) { - propsMap.putInt(entry.getKey(), ((ColorAnimatedNode) node).getColor()); - } else if (node instanceof ObjectAnimatedNode) { - ((ObjectAnimatedNode) node).collectViewUpdates(entry.getKey(), propsMap); - } else { - throw new IllegalArgumentException( - "Unsupported type of node used in property node " + node.getClass()); - } - } - } - - public String prettyPrint() { - return "StyleAnimatedNode[" - + tag - + "] mPropMapping: " - + (mPropMapping != null ? mPropMapping.toString() : "null"); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.kt new file mode 100644 index 00000000000000..bc76ca3a99edca --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.kt @@ -0,0 +1,61 @@ +/* + * 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.animated + +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap + +/** + * Native counterpart of style animated node (see AnimatedStyle class in AnimatedImplementation.js) + */ +internal class StyleAnimatedNode( + config: ReadableMap, + private val nativeAnimatedNodesManager: NativeAnimatedNodesManager +) : AnimatedNode() { + private val propMapping: Map + + init { + val style = config.getMap("style") + val iter = style?.keySetIterator() + propMapping = + buildMap() { + while (iter != null && iter.hasNextKey()) { + val propKey = iter.nextKey() + put(propKey, style.getInt(propKey)) + } + } + } + + public fun collectViewUpdates(propsMap: JavaOnlyMap) { + for ((key, value) in propMapping) { + val node = nativeAnimatedNodesManager.getNodeById(value) + requireNotNull(node) { "Mapped style node does not exist" } + if (node is TransformAnimatedNode) { + node.collectViewUpdates(propsMap) + } else if (node is ValueAnimatedNode) { + val animatedObject = node.getAnimatedObject() + if (animatedObject is Int) { + propsMap.putInt(key, animatedObject) + } else if (animatedObject is String) { + propsMap.putString(key, animatedObject) + } else { + propsMap.putDouble(key, node.getValue()) + } + } else if (node is ColorAnimatedNode) { + propsMap.putInt(key, node.color) + } else if (node is ObjectAnimatedNode) { + node.collectViewUpdates(key, propsMap) + } else { + throw IllegalArgumentException( + "Unsupported type of node used in property node ${node.javaClass}") + } + } + } + + override fun prettyPrint(): String = "StyleAnimatedNode[$tag] mPropMapping: $propMapping" +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/SubtractionAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/SubtractionAnimatedNode.java deleted file mode 100644 index f3b6ac8cab8269..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/SubtractionAnimatedNode.java +++ /dev/null @@ -1,60 +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.animated; - -import com.facebook.react.bridge.JSApplicationCausedNativeException; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; - -/** - * Animated node that plays a role of value aggregator. It takes two or more value nodes as an input - * and outputs a difference of values outputted by those nodes. - */ -/*package*/ class SubtractionAnimatedNode extends ValueAnimatedNode { - - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final int[] mInputNodes; - - public SubtractionAnimatedNode( - ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { - mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - ReadableArray inputNodes = config.getArray("input"); - mInputNodes = new int[inputNodes.size()]; - for (int i = 0; i < mInputNodes.length; i++) { - mInputNodes[i] = inputNodes.getInt(i); - } - } - - @Override - public void update() { - for (int i = 0; i < mInputNodes.length; i++) { - AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]); - if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) { - double value = ((ValueAnimatedNode) animatedNode).getValue(); - if (i == 0) { - nodeValue = value; - } else { - nodeValue -= value; - } - } else { - throw new JSApplicationCausedNativeException( - "Illegal node ID set as an input for Animated.subtract node"); - } - } - } - - @Override - public String prettyPrint() { - return "SubtractionAnimatedNode[" - + tag - + "]: input nodes: " - + (mInputNodes != null ? mInputNodes.toString() : "null") - + " - super: " - + super.prettyPrint(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/SubtractionAnimatedNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/SubtractionAnimatedNode.kt new file mode 100644 index 00000000000000..a87eaacc193eb0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/SubtractionAnimatedNode.kt @@ -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.animated + +import com.facebook.react.bridge.JSApplicationCausedNativeException +import com.facebook.react.bridge.ReadableMap + +/** + * Animated node that plays a role of value aggregator. It takes two or more value nodes as an input + * and outputs a difference of values outputted by those nodes. + */ +internal class SubtractionAnimatedNode( + config: ReadableMap, + private val nativeAnimatedNodesManager: NativeAnimatedNodesManager +) : ValueAnimatedNode() { + private val inputNodes: IntArray + + init { + val input = config.getArray("input") + inputNodes = + if (input == null) { + IntArray(0) + } else { + IntArray(input.size()) { i -> input.getInt(i) } + } + } + + override fun update() { + for (i in inputNodes.indices) { + val animatedNode = nativeAnimatedNodesManager.getNodeById(inputNodes[i]) + if (animatedNode != null && animatedNode is ValueAnimatedNode) { + val value = animatedNode.getValue() + if (i == 0) { + nodeValue = value + } else { + nodeValue -= value + } + } else { + throw JSApplicationCausedNativeException( + "Illegal node ID set as an input for Animated.subtract node") + } + } + } + + override fun prettyPrint(): String = + "SubtractionAnimatedNode[$tag]: input nodes: $inputNodes - super: ${super.prettyPrint()}" +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.java deleted file mode 100644 index 10ea209124718b..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.java +++ /dev/null @@ -1,93 +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.animated; - -import com.facebook.react.bridge.JavaOnlyArray; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import java.util.ArrayList; -import java.util.List; - -/** - * Native counterpart of transform animated node (see AnimatedTransform class in - * AnimatedImplementation.js) - */ -/* package */ class TransformAnimatedNode extends AnimatedNode { - - private class TransformConfig { - public String mProperty; - } - - private class AnimatedTransformConfig extends TransformConfig { - public int mNodeTag; - } - - private class StaticTransformConfig extends TransformConfig { - public double mValue; - } - - private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; - private final List mTransformConfigs; - - TransformAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) { - ReadableArray transforms = config.getArray("transforms"); - mTransformConfigs = new ArrayList<>(transforms.size()); - for (int i = 0; i < transforms.size(); i++) { - ReadableMap transformConfigMap = transforms.getMap(i); - String property = transformConfigMap.getString("property"); - String type = transformConfigMap.getString("type"); - if (type.equals("animated")) { - AnimatedTransformConfig transformConfig = new AnimatedTransformConfig(); - transformConfig.mProperty = property; - transformConfig.mNodeTag = transformConfigMap.getInt("nodeTag"); - mTransformConfigs.add(transformConfig); - } else { - StaticTransformConfig transformConfig = new StaticTransformConfig(); - transformConfig.mProperty = property; - transformConfig.mValue = transformConfigMap.getDouble("value"); - mTransformConfigs.add(transformConfig); - } - } - mNativeAnimatedNodesManager = nativeAnimatedNodesManager; - } - - public void collectViewUpdates(JavaOnlyMap propsMap) { - List transforms = new ArrayList<>(mTransformConfigs.size()); - - for (TransformConfig transformConfig : mTransformConfigs) { - double value; - if (transformConfig instanceof AnimatedTransformConfig) { - int nodeTag = ((AnimatedTransformConfig) transformConfig).mNodeTag; - AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(nodeTag); - if (node == null) { - throw new IllegalArgumentException("Mapped style node does not exist"); - } else if (node instanceof ValueAnimatedNode) { - value = ((ValueAnimatedNode) node).getValue(); - } else { - throw new IllegalArgumentException( - "Unsupported type of node used as a transform child " + "node " + node.getClass()); - } - } else { - value = ((StaticTransformConfig) transformConfig).mValue; - } - - transforms.add(JavaOnlyMap.of(transformConfig.mProperty, value)); - } - - propsMap.putArray("transform", JavaOnlyArray.from(transforms)); - } - - @Override - public String prettyPrint() { - return "TransformAnimatedNode[" - + tag - + "]: mTransformConfigs: " - + (mTransformConfigs != null ? mTransformConfigs.toString() : "null"); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.kt new file mode 100644 index 00000000000000..5672168c2f0ac7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.kt @@ -0,0 +1,87 @@ +/* + * 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.animated + +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap + +/** + * Native counterpart of transform animated node (see AnimatedTransform class in + * AnimatedImplementation.js) + */ +internal class TransformAnimatedNode( + config: ReadableMap, + private val nativeAnimatedNodesManager: NativeAnimatedNodesManager +) : AnimatedNode() { + private val transformConfigs: MutableList + + init { + val transforms = config.getArray("transforms") + transformConfigs = + if (transforms == null) mutableListOf() + else + MutableList(transforms.size()) { i -> + val transformConfigMap = transforms.getMap(i) + val property = transformConfigMap.getString("property") + val type = transformConfigMap.getString("type") + if (type == "animated") { + val transformConfig = AnimatedTransformConfig() + transformConfig.property = property + transformConfig.nodeTag = transformConfigMap.getInt("nodeTag") + transformConfig + } else { + val transformConfig = StaticTransformConfig() + transformConfig.property = property + transformConfig.value = transformConfigMap.getDouble("value") + transformConfig + } + } + } + + public fun collectViewUpdates(propsMap: JavaOnlyMap) { + val transforms = + MutableList(transformConfigs.size) { i -> + val transformConfig = transformConfigs[i] + val transform = + if (transformConfig is AnimatedTransformConfig) { + val nodeTag = transformConfig.nodeTag + val node = nativeAnimatedNodesManager.getNodeById(nodeTag) + if (node == null) { + throw IllegalArgumentException("Mapped style node does not exist") + } else if (node is ValueAnimatedNode) { + node.getValue() + } else { + throw IllegalArgumentException( + "Unsupported type of node used as a transform child " + + "node " + + node.javaClass) + } + } else { + (transformConfig as StaticTransformConfig).value + } + JavaOnlyMap.of(transformConfig.property, transform) + } + propsMap.putArray("transform", JavaOnlyArray.from(transforms)) + } + + override fun prettyPrint(): String = + "TransformAnimatedNode[$tag]: transformConfigs: $transformConfigs" + + private open inner class TransformConfig { + var property: String? = null + } + + private inner class AnimatedTransformConfig : TransformConfig() { + var nodeTag = 0 + } + + private inner class StaticTransformConfig : TransformConfig() { + var value = 0.0 + } +}