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.
- 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
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar +0 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.aar.sha512 +1 -0
- 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
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.3/detox-20.44.0-smoke.3.pom.sha512 +1 -0
- package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
- package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
- package/Detox-ios-framework.tbz +0 -0
- package/Detox-ios-src.tbz +0 -0
- package/Detox-ios-xcuitest.tbz +0 -0
- package/android/detox/src/full/java/com/wix/detox/adapters/server/DetoxActionHandlers.kt +1 -7
- package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt +14 -63
- package/android/detox/src/full/java/com/wix/detox/inquiry/FabricAnimationsInquirer.kt +440 -0
- package/android/detox/src/full/java/com/wix/detox/inquiry/ViewLifecycleRegistry.kt +83 -173
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/animations/AnimatedModuleIdlingResource.kt +7 -2
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/fabric/FabricUIManagerIdlingResources.kt +1 -170
- package/package.json +2 -2
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar +0 -0
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.44.0-smoke.1/detox-20.44.0-smoke.1.pom.sha512 +0 -1
- package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxAnimationTracker.kt +0 -70
- package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxFabricAnimationHook.kt +0 -83
- package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxFabricIntegration.kt +0 -99
- package/android/detox/src/full/java/com/wix/detox/inquiry/DetoxFabricUIManagerWrapper.kt +0 -37
|
Binary file
|
|
@@ -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.
|
|
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.
|
|
7
|
-
<release>20.44.0-smoke.
|
|
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.
|
|
9
|
+
<version>20.44.0-smoke.3</version>
|
|
10
10
|
</versions>
|
|
11
|
-
<lastUpdated>
|
|
11
|
+
<lastUpdated>20251009174711</lastUpdated>
|
|
12
12
|
</versioning>
|
|
13
13
|
</metadata>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c8acd83b4c64653b9e9ba87a7e37abf1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
fa3e431bbd58b9c608fe0396caa81eb7b2123391
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4c4fe8daadb8a5ecad499fd25cf6b5556dbfab190c98280df02898da11bf7b71
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
da8e523ed536ed9515efe564b4497e1a2872a5860aacd2388fe37ab6b12a1df3d40c3a30c5e58a33b03fec0a585d89807ea9e23cae35408024ade6c199b09daa
|
package/Detox-ios-framework.tbz
CHANGED
|
Binary file
|
package/Detox-ios-src.tbz
CHANGED
|
Binary file
|
package/Detox-ios-xcuitest.tbz
CHANGED
|
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
|
-
|
|
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(
|
package/android/detox/src/full/java/com/wix/detox/espresso/hierarchy/ViewHierarchyGenerator.kt
CHANGED
|
@@ -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
|
+
}
|