detox 20.44.0-smoke.1 → 20.44.0-smoke.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/Detox-android/com/wix/detox/{20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar → 20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar +0 -0
  7. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.44.0-smoke.1/detox-20.44.0-smoke.1.pom → 20.44.0-smoke.3/detox-20.44.0-smoke.3.pom} +1 -1
  12. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  17. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  18. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  19. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  20. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  21. package/Detox-ios-framework.tbz +0 -0
  22. package/Detox-ios-src.tbz +0 -0
  23. package/Detox-ios-xcuitest.tbz +0 -0
  24. package/android/detox/src/full/java/com/wix/detox/adapters/server/DetoxActionHandlers.kt +1 -7
  25. package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt +14 -63
  26. package/android/detox/src/full/java/com/wix/detox/inquiry/FabricAnimationsInquirer.kt +440 -0
  27. package/android/detox/src/full/java/com/wix/detox/inquiry/ViewLifecycleRegistry.kt +83 -173
  28. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/animations/AnimatedModuleIdlingResource.kt +7 -2
  29. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/fabric/FabricUIManagerIdlingResources.kt +1 -170
  30. package/package.json +2 -2
  31. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.md5 +0 -1
  32. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.sha1 +0 -1
  33. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.sha256 +0 -1
  34. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.sha512 +0 -1
  35. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar +0 -0
  36. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.md5 +0 -1
  37. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.sha1 +0 -1
  38. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.sha256 +0 -1
  39. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.sha512 +0 -1
  40. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.md5 +0 -1
  41. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.sha1 +0 -1
  42. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.sha256 +0 -1
  43. package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.sha512 +0 -1
  44. package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxAnimationTracker.kt +0 -70
  45. package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxFabricAnimationHook.kt +0 -83
  46. package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxFabricIntegration.kt +0 -99
  47. package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxFabricUIManagerWrapper.kt +0 -37
@@ -0,0 +1 @@
1
+ 6c1c629a4bb9fb113a38d3f2babcdaf9
@@ -0,0 +1 @@
1
+ 016b3ca18886178b7a615db51c442a411d0dcc76
@@ -0,0 +1 @@
1
+ 25c112c9be84680ae6ea75a3ad09ac53ad18d42f0e66ffd362047cf4cadeec13
@@ -0,0 +1 @@
1
+ 2bae6abf332074d5a949d58ecd2499af1493b953d7ee8b2b322ab7b384c03608171d997dceac4d114973636703748f480a189a7ccaa27865665ec7c2e051b090
@@ -0,0 +1 @@
1
+ c3f8d3dc2d7edb9ccf2e95efca9450ca
@@ -0,0 +1 @@
1
+ e0bcddf47775a5112e413fea3790fa565092df86
@@ -0,0 +1 @@
1
+ 2230846f74335e85d6edd07294869e1604efdda6b98fcf0abaff34b85b18f3fb
@@ -0,0 +1 @@
1
+ bcff1f76753fe3725089dc1e4130aa2f4100907b1912122bf461ebe6bd588f7cf5b4c6cdb8a7eeaa84b957e511d206563d8a2cf7c0d0cef1357959448e86c483
@@ -3,7 +3,7 @@
3
3
  <modelVersion>4.0.0</modelVersion>
4
4
  <groupId>com.wix</groupId>
5
5
  <artifactId>detox</artifactId>
6
- <version>20.44.0-smoke.1</version>
6
+ <version>20.44.0-smoke.3</version>
7
7
  <packaging>aar</packaging>
8
8
  <name>Detox</name>
9
9
  <description>Gray box end-to-end testing and automation library for mobile apps</description>
@@ -0,0 +1 @@
1
+ d1b54d72ec1e32909012654ebb143147
@@ -0,0 +1 @@
1
+ bfaa5fed850535443ca38a8d7bb765f0629ffa37
@@ -0,0 +1 @@
1
+ 4ad653fa6e356e01536665021591f24d8fddc7d9e37490b9136d3ed8ed48e09f
@@ -0,0 +1 @@
1
+ 0e53116005919ab7a58d00f77e3649e31c1592b18837fa91b5ed998c5eea52d08f2067b3f54d15ba053d683f54158f5404321e00cf2e67ff64f99455ca1d05df
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.44.0-smoke.1</latest>
7
- <release>20.44.0-smoke.1</release>
6
+ <latest>20.44.0-smoke.3</latest>
7
+ <release>20.44.0-smoke.3</release>
8
8
  <versions>
9
- <version>20.44.0-smoke.1</version>
9
+ <version>20.44.0-smoke.3</version>
10
10
  </versions>
11
- <lastUpdated>20251009104839</lastUpdated>
11
+ <lastUpdated>20251009174711</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 4baad9ede6680e5d2b46f1246a48c8f9
1
+ c8acd83b4c64653b9e9ba87a7e37abf1
@@ -1 +1 @@
1
- b07f36a5c0ae07b6a7961b318ca15b8acc44d602
1
+ fa3e431bbd58b9c608fe0396caa81eb7b2123391
@@ -1 +1 @@
1
- a95b41be51ad53267864aae6fc777db00648abe236c7bdbd46c645543e9c052f
1
+ 4c4fe8daadb8a5ecad499fd25cf6b5556dbfab190c98280df02898da11bf7b71
@@ -1 +1 @@
1
- 03f466d7a8a4a9f3a5b82b78cc9fcfc0020dd9bb7e3c7d821096887eb241118db40ce09f2f4912a6de05bd2b4c4d75ad48133665e37f68c37bae4a3b227d08ca
1
+ da8e523ed536ed9515efe564b4497e1a2872a5860aacd2388fe37ab6b12a1df3d40c3a30c5e58a33b03fec0a585d89807ea9e23cae35408024ade6c199b09daa
Binary file
package/Detox-ios-src.tbz CHANGED
Binary file
Binary file
@@ -69,17 +69,11 @@ class InvokeActionHandler @JvmOverloads constructor(
69
69
  val viewHierarchy = if (error is DetoxExceptionWithHierarchy) {
70
70
  error.xmlHierarchy
71
71
  } else {
72
- generateFallbackViewHierarchy()
72
+ null
73
73
  }
74
74
 
75
75
  return mapOf<String, Any?>("details" to "${errorMessage}\n", "viewHierarchy" to viewHierarchy)
76
76
  }
77
-
78
- private fun generateFallbackViewHierarchy() = try {
79
- ViewHierarchyGenerator.generateXml(shouldInjectTestIds = false)
80
- } catch (e: Exception) {
81
- null
82
- }
83
77
  }
84
78
 
85
79
  class CleanupActionHandler(
@@ -163,9 +163,6 @@ object ViewHierarchyGenerator {
163
163
  "label" to (view.getAccessibilityLabel()?.toString() ?: "")
164
164
  )
165
165
 
166
- // Add lifecycle information from ViewLifecycleRegistry
167
- addLifecycleAttributes(view, attributes)
168
-
169
166
  val location = IntArray(2).apply { view.getLocationInWindow(this) }
170
167
  attributes["x"] = location[0].toString()
171
168
  attributes["y"] = location[1].toString()
@@ -174,6 +171,20 @@ object ViewHierarchyGenerator {
174
171
  attributes["text"] = view.text.toString()
175
172
  }
176
173
 
174
+ // Inject animation metadata
175
+ val animationMetadata = ViewLifecycleRegistry.getAnimationMetadata(view)
176
+ if (animationMetadata != null) {
177
+ animationMetadata.animated?.let {
178
+ attributes["lastAnimated"] = animationMetadata.getAnimationDurationMs()?.toString() ?: "0"
179
+ }
180
+ animationMetadata.updated?.let {
181
+ attributes["lastUpdated"] = animationMetadata.getUpdateDurationMs()?.toString() ?: "0"
182
+ }
183
+ if (ViewLifecycleRegistry.isAnimating(view)) {
184
+ attributes["animating"] = "true"
185
+ }
186
+ }
187
+
177
188
  val currentTestId = view.tag?.toString() ?: ""
178
189
 
179
190
  val injectedPrefix = "detox_temp_"
@@ -204,64 +215,4 @@ object ViewHierarchyGenerator {
204
215
  View.GONE -> "gone"
205
216
  else -> "unknown"
206
217
  }
207
-
208
- /**
209
- * Add lifecycle attributes to the view attributes map.
210
- * This includes information about recent animations, updates, and mounts.
211
- */
212
- private fun addLifecycleAttributes(view: View, attributes: MutableMap<String, String>) {
213
- try {
214
- // Check if view was recently animated (within last 1.5 seconds)
215
- if (ViewLifecycleRegistry.wasRecentlyAnimated(view, 1500)) {
216
- attributes["recentlyAnimated"] = "true"
217
-
218
- // Add animation count if available
219
- val lifecycleInfo = ViewLifecycleRegistry.getLifecycleInfo(view)
220
- lifecycleInfo?.let { info ->
221
- attributes["animationCount"] = info.animationCount.toString()
222
- attributes["lastAnimateTime"] = info.lastAnimateTime.toString()
223
- }
224
- }
225
-
226
- // Check if view was recently updated (within last 1 second)
227
- if (ViewLifecycleRegistry.wasRecentlyUpdated(view, 1000)) {
228
- attributes["recentlyUpdated"] = "true"
229
-
230
- val lifecycleInfo = ViewLifecycleRegistry.getLifecycleInfo(view)
231
- lifecycleInfo?.let { info ->
232
- attributes["updateCount"] = info.updateCount.toString()
233
- attributes["lastUpdateTime"] = info.lastUpdateTime.toString()
234
- }
235
- }
236
-
237
- // Check if view was recently mounted (within last 5 seconds)
238
- if (ViewLifecycleRegistry.wasRecentlyMounted(view, 5000)) {
239
- attributes["recentlyMounted"] = "true"
240
-
241
- val lifecycleInfo = ViewLifecycleRegistry.getLifecycleInfo(view)
242
- lifecycleInfo?.let { info ->
243
- attributes["mountTime"] = info.mountTime.toString()
244
- }
245
- }
246
-
247
- // Check for problematic animations
248
- if (ViewLifecycleRegistry.hasCustomEvent(view, "problematic_animation", 2000)) {
249
- attributes["problematicAnimation"] = "true"
250
- }
251
-
252
- // Add any other custom events
253
- val lifecycleInfo = ViewLifecycleRegistry.getLifecycleInfo(view)
254
- lifecycleInfo?.let { info ->
255
- if (info.customEvents.isNotEmpty()) {
256
- val customEvents = info.customEvents.keys.joinToString(",")
257
- attributes["customEvents"] = customEvents
258
- }
259
- }
260
-
261
- } catch (e: Exception) {
262
- // Silently ignore errors to avoid breaking the hierarchy generation
263
- // Log at debug level for debugging
264
- android.util.Log.d("ViewHierarchyGenerator", "Failed to add lifecycle attributes", e)
265
- }
266
- }
267
218
  }
@@ -0,0 +1,440 @@
1
+ package com.wix.detox.inquiry
2
+
3
+ import android.util.Log
4
+ import android.util.SparseArray
5
+ import android.view.View
6
+ import com.facebook.react.animated.AnimatedNode
7
+ import com.facebook.react.animated.NativeAnimatedModule
8
+ import com.facebook.react.animated.NativeAnimatedNodesManager
9
+ import com.facebook.react.bridge.ReactApplicationContext
10
+ import com.facebook.react.uimanager.UIManagerHelper
11
+ import com.facebook.react.uimanager.common.UIManagerType
12
+ import java.lang.reflect.Field
13
+ import java.util.LinkedList
14
+ import java.util.concurrent.atomic.AtomicReference
15
+
16
+ object FabricAnimationsInquirer {
17
+ private const val LOG_TAG = "FabricAnimationsInquirer"
18
+
19
+ // Cache fields to avoid repeated reflection lookups
20
+ private var mActiveAnimationsField: Field? = null
21
+ private var mUpdatedNodesField: Field? = null
22
+ private var mAnimatedNodesField: Field? = null
23
+ private var animatedValueField: Field? = null
24
+ private var childrenField: Field? = null
25
+ private var connectedViewTagField: Field? = null
26
+ private var propNodeMappingField: Field? = null
27
+ private var propMappingField: Field? = null
28
+ private var nodeValueField: Field? = null
29
+ private var offsetField: Field? = null
30
+
31
+ fun logAnimatingViews(reactContext: ReactApplicationContext) {
32
+ try {
33
+ Log.d(LOG_TAG, "Starting animation inquiry...")
34
+
35
+ // Clear previous animated views - fresh start for this inquiry
36
+ ViewLifecycleRegistry.clearAnimatedViews()
37
+
38
+ val nodesManager = getNodesManager(reactContext) ?: return
39
+ Log.d(LOG_TAG, "Got nodesManager: ${nodesManager.javaClass.simpleName}")
40
+
41
+ // Check if there are any active animations first
42
+ val hasActive = nodesManager.hasActiveAnimations()
43
+ Log.d(LOG_TAG, "hasActiveAnimations() returned: $hasActive")
44
+
45
+ if (!hasActive) {
46
+ Log.d(LOG_TAG, "No active animations detected")
47
+ return
48
+ }
49
+
50
+ // Get all animated nodes from the graph
51
+ val allNodes = getAllAnimatedNodes(nodesManager)
52
+ Log.d(LOG_TAG, "Found ${allNodes.size()} total animated nodes")
53
+
54
+ // Log the field names we're using for debugging
55
+ Log.d(LOG_TAG, "Using field names: mActiveAnimations, mUpdatedNodes, mAnimatedNodes")
56
+
57
+ // Find nodes that are currently animating (have active drivers or are updated)
58
+ val animatingNodes = findAnimatingNodes(nodesManager, allNodes)
59
+ Log.d(LOG_TAG, "Found ${animatingNodes.size} animating nodes")
60
+
61
+ if (animatingNodes.isEmpty()) {
62
+ Log.d(LOG_TAG, "No animating nodes found, exiting")
63
+ return
64
+ }
65
+
66
+ // Find all relevant animated nodes (PropsAnimatedNode, StyleAnimatedNode, ValueAnimatedNode)
67
+ val relevantNodes = findPropsNodes(animatingNodes, allNodes)
68
+ Log.d(LOG_TAG, "Found ${relevantNodes.size} relevant animated nodes")
69
+
70
+ if (relevantNodes.isEmpty()) {
71
+ Log.d(LOG_TAG, "No relevant animated nodes found, exiting")
72
+ return
73
+ }
74
+
75
+ val viewTags = getViewTags(relevantNodes, allNodes)
76
+ Log.d(LOG_TAG, "Found ${viewTags.size} view tags: $viewTags")
77
+
78
+ if (viewTags.isEmpty()) {
79
+ Log.d(LOG_TAG, "No view tags found, exiting")
80
+ return
81
+ }
82
+
83
+ logViews(reactContext, viewTags)
84
+ } catch (e: Exception) {
85
+ Log.e(LOG_TAG, "Failed to inquire animating views", e)
86
+ }
87
+ }
88
+
89
+ private fun getNodesManager(reactContext: ReactApplicationContext): NativeAnimatedNodesManager? {
90
+ val nativeAnimatedModule = reactContext.getNativeModule(NativeAnimatedModule::class.java)
91
+ if (nativeAnimatedModule == null) {
92
+ Log.d(LOG_TAG, "NativeAnimatedModule not found")
93
+ return null
94
+ }
95
+
96
+ return try {
97
+ // Use the public getNodesManager() method instead of reflection
98
+ nativeAnimatedModule.nodesManager
99
+ } catch (e: Exception) {
100
+ Log.e(LOG_TAG, "Failed to get NativeAnimatedNodesManager via getNodesManager()", e)
101
+ null
102
+ }
103
+ }
104
+
105
+ private fun getAllAnimatedNodes(nodesManager: NativeAnimatedNodesManager): SparseArray<AnimatedNode> {
106
+ val allNodes = SparseArray<AnimatedNode>()
107
+ try {
108
+ // Access mAnimatedNodes field using reflection
109
+ val animatedNodesField = findOrCacheField(nodesManager.javaClass, "mAnimatedNodes", "mAnimatedNodesField")
110
+ @Suppress("UNCHECKED_CAST")
111
+ val animatedNodes = animatedNodesField?.get(nodesManager) as? SparseArray<AnimatedNode>
112
+ if (animatedNodes != null) {
113
+ Log.d(LOG_TAG, "Found ${animatedNodes.size()} animated nodes in graph")
114
+ for (i in 0 until animatedNodes.size()) {
115
+ val node = animatedNodes.valueAt(i)
116
+ allNodes.put(animatedNodes.keyAt(i), node)
117
+ }
118
+ } else {
119
+ Log.w(LOG_TAG, "Could not access mAnimatedNodes field")
120
+ }
121
+ } catch (e: Exception) {
122
+ Log.e(LOG_TAG, "Failed to get all animated nodes", e)
123
+ }
124
+ return allNodes
125
+ }
126
+
127
+ private fun findAnimatingNodes(nodesManager: NativeAnimatedNodesManager, allNodes: SparseArray<AnimatedNode>): Set<AnimatedNode> {
128
+ val animatingNodes = mutableSetOf<AnimatedNode>()
129
+
130
+ try {
131
+ // Get nodes from active animations
132
+ val activeAnimationsField = findOrCacheField(nodesManager.javaClass, "mActiveAnimations", "mActiveAnimationsField")
133
+ @Suppress("UNCHECKED_CAST")
134
+ val activeAnimations = activeAnimationsField?.get(nodesManager) as? SparseArray<Any>
135
+ if (activeAnimations != null) {
136
+ Log.d(LOG_TAG, "Found ${activeAnimations.size()} active animations")
137
+ for (i in 0 until activeAnimations.size()) {
138
+ val driver = activeAnimations.valueAt(i)
139
+ Log.d(LOG_TAG, "Active animation driver: ${driver.javaClass.simpleName}")
140
+ val animatedValueField = findOrCacheField(driver.javaClass, "animatedValue", "animatedValueField")
141
+ val valueNode = animatedValueField?.get(driver) as? AnimatedNode
142
+ if (valueNode != null) {
143
+ animatingNodes.add(valueNode)
144
+ Log.d(LOG_TAG, "Added animating node from active animation: ${valueNode.javaClass.simpleName}")
145
+ }
146
+ }
147
+ }
148
+
149
+ // Get nodes from updated nodes
150
+ val updatedNodesField = findOrCacheField(nodesManager.javaClass, "mUpdatedNodes", "mUpdatedNodesField")
151
+ @Suppress("UNCHECKED_CAST")
152
+ val updatedNodes = updatedNodesField?.get(nodesManager) as? SparseArray<AnimatedNode>
153
+ if (updatedNodes != null) {
154
+ Log.d(LOG_TAG, "Found ${updatedNodes.size()} updated nodes")
155
+ for (i in 0 until updatedNodes.size()) {
156
+ val node = updatedNodes.valueAt(i)
157
+ animatingNodes.add(node)
158
+ Log.d(LOG_TAG, "Added updated node: ${node.javaClass.simpleName}")
159
+ }
160
+ }
161
+ } catch (e: Exception) {
162
+ Log.e(LOG_TAG, "Failed to find animating nodes", e)
163
+ }
164
+
165
+ return animatingNodes
166
+ }
167
+
168
+ private fun findPropsNodes(animatingNodes: Set<AnimatedNode>, allNodes: SparseArray<AnimatedNode>): Set<Any> {
169
+ val allRelevantNodes = mutableSetOf<Any>()
170
+ val queue = LinkedList<AnimatedNode>(animatingNodes)
171
+ val visited = mutableSetOf<AnimatedNode>()
172
+
173
+ while (queue.isNotEmpty()) {
174
+ val node = queue.poll()
175
+ if (node in visited) {
176
+ continue
177
+ }
178
+ visited.add(node)
179
+
180
+ // Check what type of node this is and log accordingly
181
+ val nodeType = node.javaClass.simpleName
182
+ when (nodeType) {
183
+ "PropsAnimatedNode" -> {
184
+ allRelevantNodes.add(node)
185
+ Log.d(LOG_TAG, "Found PropsAnimatedNode: $nodeType")
186
+ }
187
+ "StyleAnimatedNode" -> {
188
+ allRelevantNodes.add(node)
189
+ Log.d(LOG_TAG, "Found StyleAnimatedNode: $nodeType")
190
+ }
191
+ "ValueAnimatedNode" -> {
192
+ allRelevantNodes.add(node)
193
+ Log.d(LOG_TAG, "Found ValueAnimatedNode: $nodeType")
194
+ }
195
+ else -> {
196
+ Log.d(LOG_TAG, "Found other animated node: $nodeType")
197
+ }
198
+ }
199
+
200
+ // Traverse children to find more nodes
201
+ try {
202
+ val childrenField = findOrCacheField(node.javaClass, "children", "childrenField")
203
+ @Suppress("UNCHECKED_CAST")
204
+ val children = childrenField?.get(node) as? List<AnimatedNode>
205
+ if (children != null) {
206
+ queue.addAll(children)
207
+ }
208
+ } catch (e: Exception) {
209
+ // Ignored - not all nodes have children
210
+ }
211
+ }
212
+
213
+ return allRelevantNodes
214
+ }
215
+
216
+ private fun isPropsAnimatedNode(node: AnimatedNode): Boolean {
217
+ return try {
218
+ // Check if this is actually a PropsAnimatedNode by checking the class name
219
+ // and verifying it has the connectedViewTag field
220
+ val isPropsNode = node.javaClass.simpleName == "PropsAnimatedNode"
221
+ if (isPropsNode) {
222
+ val connectedViewTagField = findOrCacheField(node.javaClass, "connectedViewTag", "connectedViewTagField")
223
+ connectedViewTagField != null
224
+ } else {
225
+ false
226
+ }
227
+ } catch (e: Exception) {
228
+ false
229
+ }
230
+ }
231
+
232
+ private fun getViewTags(relevantNodes: Set<Any>, allNodes: SparseArray<AnimatedNode>): Set<Int> {
233
+ val viewTags = mutableSetOf<Int>()
234
+ for (node in relevantNodes) {
235
+ try {
236
+ val nodeType = node.javaClass.simpleName
237
+ Log.d(LOG_TAG, "Processing $nodeType for view tags")
238
+
239
+ when (nodeType) {
240
+ "PropsAnimatedNode" -> {
241
+ val connectedViewTagField = findOrCacheField(node.javaClass, "connectedViewTag", "connectedViewTagField")
242
+ val viewTag = connectedViewTagField?.get(node) as? Int
243
+ if (viewTag != null && viewTag != -1) {
244
+ viewTags.add(viewTag)
245
+ Log.d(LOG_TAG, "PropsAnimatedNode connected to view tag: $viewTag")
246
+
247
+ // Log the property mapping to see what properties are being animated
248
+ try {
249
+ val propNodeMappingField = findOrCacheField(node.javaClass, "propNodeMapping", "propNodeMappingField")
250
+ val propNodeMapping = propNodeMappingField?.get(node) as? Map<String, Int>
251
+ if (propNodeMapping != null) {
252
+ Log.d(LOG_TAG, "View $viewTag has animated properties: ${propNodeMapping.keys}")
253
+ }
254
+ } catch (e: Exception) {
255
+ Log.d(LOG_TAG, "Could not access propNodeMapping for PropsAnimatedNode")
256
+ }
257
+ }
258
+ }
259
+ "StyleAnimatedNode" -> {
260
+ // StyleAnimatedNode doesn't have connectedViewTag field - it's connected through PropsAnimatedNode
261
+ Log.d(LOG_TAG, "StyleAnimatedNode has no direct view connection (connected through PropsAnimatedNode)")
262
+
263
+ // Try to access propMapping to see what properties it handles
264
+ try {
265
+ val propMappingField = findOrCacheField(node.javaClass, "propMapping", "propMappingField")
266
+ val propMapping = propMappingField?.get(node) as? Map<String, Int>
267
+ if (propMapping != null) {
268
+ Log.d(LOG_TAG, "StyleAnimatedNode handles properties: ${propMapping.keys}")
269
+ }
270
+ } catch (e: Exception) {
271
+ Log.d(LOG_TAG, "Could not access StyleAnimatedNode propMapping: ${e.message}")
272
+ }
273
+
274
+ // Find the connected view by traversing the graph to find PropsAnimatedNode
275
+ val connectedViewTag = findConnectedViewThroughGraph(node as AnimatedNode, allNodes)
276
+ if (connectedViewTag != -1) {
277
+ viewTags.add(connectedViewTag)
278
+ Log.d(LOG_TAG, "StyleAnimatedNode connected to view tag through graph: $connectedViewTag")
279
+ } else {
280
+ Log.w(LOG_TAG, "StyleAnimatedNode has no connected view - this could mean:")
281
+ Log.w(LOG_TAG, " 1. No PropsAnimatedNode found in graph traversal")
282
+ Log.w(LOG_TAG, " 2. PropsAnimatedNode exists but not connected to any view")
283
+ Log.w(LOG_TAG, " 3. Graph traversal failed due to reflection errors")
284
+ Log.w(LOG_TAG, " 4. Circular references or disconnected graph")
285
+ }
286
+ }
287
+ "ValueAnimatedNode" -> {
288
+ // ValueAnimatedNode typically doesn't have direct view connection
289
+ // but we can log its value for debugging
290
+ try {
291
+ val nodeValueField = findOrCacheField(node.javaClass, "nodeValue", "nodeValueField")
292
+ val nodeValue = nodeValueField?.get(node) as? Double
293
+ val offsetField = findOrCacheField(node.javaClass, "offset", "offsetField")
294
+ val offset = offsetField?.get(node) as? Double
295
+ Log.d(LOG_TAG, "ValueAnimatedNode value: $nodeValue, offset: $offset")
296
+ } catch (e: Exception) {
297
+ Log.d(LOG_TAG, "Could not access ValueAnimatedNode values: ${e.message}")
298
+ }
299
+ }
300
+ else -> {
301
+ Log.d(LOG_TAG, "Unknown node type: $nodeType")
302
+ }
303
+ }
304
+ } catch (e: Exception) {
305
+ Log.e(LOG_TAG, "Failed to process node: ${node.javaClass.simpleName}", e)
306
+ }
307
+ }
308
+ return viewTags
309
+ }
310
+
311
+ private fun logViews(reactContext: ReactApplicationContext, viewTags: Set<Int>) {
312
+ val uiManager = UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC)
313
+ if (uiManager == null) {
314
+ Log.w(LOG_TAG, "Fabric UIManager not found.")
315
+ return
316
+ }
317
+
318
+ for (tag in viewTags) {
319
+ try {
320
+ reactContext.runOnUiQueueThread {
321
+ val view = uiManager.resolveView(tag)
322
+ if (view != null) {
323
+ ViewLifecycleRegistry.markAnimated(view)
324
+
325
+ // Get view coordinates and dimensions
326
+ val left = view.left
327
+ val top = view.top
328
+ val right = view.right
329
+ val bottom = view.bottom
330
+ val width = right - left
331
+ val height = bottom - top
332
+
333
+ Log.i(LOG_TAG, "Animating view: tag=$tag, class=${view.javaClass.simpleName}, id=${view.id}, " +
334
+ "bounds=[$left,$top,$right,$bottom], size=${width}x${height}")
335
+ } else {
336
+ Log.w(LOG_TAG, "Could not resolve view for tag: $tag")
337
+ }
338
+ }
339
+ } catch (e: Exception) {
340
+ Log.e(LOG_TAG, "Failed to resolve or log view for tag: $tag", e)
341
+ }
342
+ }
343
+ }
344
+
345
+ private fun findOrCacheField(clazz: Class<*>, fieldName: String, cacheFieldName: String): Field? {
346
+ try {
347
+ val cacheField = FabricAnimationsInquirer::class.java.getDeclaredField(cacheFieldName).apply { isAccessible = true }
348
+ var field = cacheField.get(this) as? Field
349
+ if (field == null) {
350
+ field = findFieldRecursive(clazz, fieldName)
351
+ if (field != null) {
352
+ cacheField.set(this, field)
353
+ }
354
+ }
355
+ return field
356
+ } catch (e: Exception) {
357
+ Log.w(LOG_TAG, "Could not find or cache field $fieldName", e)
358
+ return null
359
+ }
360
+ }
361
+
362
+ private fun findFieldRecursive(clazz: Class<*>, fieldName: String): Field? {
363
+ var currentClass: Class<*>? = clazz
364
+ while (currentClass != null && currentClass != Any::class.java) {
365
+ try {
366
+ return currentClass.getDeclaredField(fieldName).apply { isAccessible = true }
367
+ } catch (e: NoSuchFieldException) {
368
+ // Not in this class, check superclass
369
+ }
370
+ currentClass = currentClass.superclass
371
+ }
372
+ Log.w(LOG_TAG, "Field '$fieldName' not found in class hierarchy for '${clazz.simpleName}'")
373
+ return null
374
+ }
375
+
376
+ private fun findConnectedViewThroughGraph(startNode: AnimatedNode, allNodes: SparseArray<AnimatedNode>): Int {
377
+ val queue = LinkedList<AnimatedNode>()
378
+ val visited = mutableSetOf<AnimatedNode>()
379
+ var propsNodesFound = 0
380
+ var propsNodesWithView = 0
381
+ var propsNodesWithoutView = 0
382
+
383
+ Log.d(LOG_TAG, "Starting graph traversal from ${startNode.javaClass.simpleName}")
384
+
385
+ queue.add(startNode)
386
+ visited.add(startNode)
387
+
388
+ while (queue.isNotEmpty()) {
389
+ val node = queue.poll()
390
+ val nodeType = node.javaClass.simpleName
391
+
392
+ // Check if this is a PropsAnimatedNode with a connected view
393
+ if (nodeType == "PropsAnimatedNode") {
394
+ propsNodesFound++
395
+ try {
396
+ val connectedViewTagField = findOrCacheField(node.javaClass, "connectedViewTag", "connectedViewTagField")
397
+ val viewTag = connectedViewTagField?.get(node) as? Int
398
+ if (viewTag != null && viewTag != -1) {
399
+ propsNodesWithView++
400
+ Log.d(LOG_TAG, "Found PropsAnimatedNode with connected view: $viewTag")
401
+ return viewTag
402
+ } else {
403
+ propsNodesWithoutView++
404
+ Log.d(LOG_TAG, "Found PropsAnimatedNode but no connected view (viewTag: $viewTag)")
405
+ }
406
+ } catch (e: Exception) {
407
+ Log.w(LOG_TAG, "Failed to access connectedViewTag from PropsAnimatedNode: ${e.message}")
408
+ }
409
+ }
410
+
411
+ // Traverse children to find PropsAnimatedNode
412
+ try {
413
+ val childrenField = findOrCacheField(node.javaClass, "children", "childrenField")
414
+ @Suppress("UNCHECKED_CAST")
415
+ val children = childrenField?.get(node) as? List<AnimatedNode>
416
+ if (children != null) {
417
+ Log.d(LOG_TAG, "Traversing ${children.size} children from $nodeType")
418
+ for (child in children) {
419
+ if (child !in visited) {
420
+ visited.add(child)
421
+ queue.add(child)
422
+ }
423
+ }
424
+ } else {
425
+ Log.d(LOG_TAG, "$nodeType has no children")
426
+ }
427
+ } catch (e: Exception) {
428
+ Log.d(LOG_TAG, "Could not access children from $nodeType: ${e.message}")
429
+ }
430
+ }
431
+
432
+ Log.w(LOG_TAG, "Graph traversal completed:")
433
+ Log.w(LOG_TAG, " - Total nodes visited: ${visited.size}")
434
+ Log.w(LOG_TAG, " - PropsAnimatedNodes found: $propsNodesFound")
435
+ Log.w(LOG_TAG, " - PropsAnimatedNodes with view: $propsNodesWithView")
436
+ Log.w(LOG_TAG, " - PropsAnimatedNodes without view: $propsNodesWithoutView")
437
+
438
+ return -1
439
+ }
440
+ }