From a1a9343bf65b16ec2e2787233fbf9bdcf4923aba Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Tue, 1 Sep 2026 18:07:09 +0800 Subject: [PATCH] fix(android): recognize clipped address cards (#193) --- .../automation/PurchaseLiveAutomation.kt | 36 ++++++++++--------- .../agent/PurchaseLiveAutomationTest.kt | 17 +++++++-- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt index 8a77d56..1d4c370 100644 --- a/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt +++ b/android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt @@ -314,22 +314,26 @@ class PurchaseLiveAutomation( } private fun addressEntryTargets(snapshot: UiSnapshot, panelBounds: NodeBounds): List { - val candidates = snapshot.nodes.filter { node -> - node.visible && node.enabled && MASKED_PHONE.containsMatchIn(node.label) && fullyInside(node.bounds, panelBounds) - }.distinctBy { it.path } - if (candidates.size <= 1) return candidates val byPath = snapshot.nodes.associateBy { it.path } - val cards = candidates.map { source -> - var ancestor = source.parentPath?.let(byPath::get) - while (ancestor != null && !ancestor.clickable) ancestor = ancestor.parentPath?.let(byPath::get) - ancestor?.path?.let { it to source } - } - if (cards.all { it != null }) { - return cards.filterNotNull().groupBy({ it.first }, { it.second }).values.map { sources -> - sources.minBy { it.bounds.width.toLong() * it.bounds.height } + val cards = snapshot.nodes.filter { node -> + node.visible && node.enabled && MASKED_PHONE.containsMatchIn(node.label) + }.mapNotNull { source -> + var target: SnapshotNode? = source + while (target != null && !target.clickable) target = target.parentPath?.let(byPath::get) + target?.takeIf { card -> + card.visible && card.enabled && visiblyIntersects(card.bounds, panelBounds) } - } - return mergedDirect(candidates) + }.distinctBy { it.path } + return mergedDirect(cards) + } + + private fun visiblyIntersects(target: NodeBounds, scope: NodeBounds): Boolean { + val overlapWidth = (minOf(target.right, scope.right) - maxOf(target.left, scope.left)).coerceAtLeast(0) + val overlapHeight = (minOf(target.bottom, scope.bottom) - maxOf(target.top, scope.top)).coerceAtLeast(0) + val comparableWidth = minOf(target.width, scope.width) + return comparableWidth > 0 && target.height > 0 && + overlapWidth * 100 >= comparableWidth * 60 && + overlapHeight >= minOf(ADDRESS_CARD_MIN_VISIBLE_HEIGHT, target.height) } private fun verifiedPurchaseSurfaceBounds(snapshot: UiSnapshot, input: PurchaseExecutionInput): NodeBounds? { @@ -372,9 +376,6 @@ class PurchaseLiveAutomation( private fun inside(child: NodeBounds, parent: NodeBounds): Boolean = child.centerX in parent.left..parent.right && child.centerY in parent.top..parent.bottom - private fun fullyInside(child: NodeBounds, parent: NodeBounds): Boolean = - child.left >= parent.left && child.top >= parent.top && child.right <= parent.right && child.bottom <= parent.bottom - private fun uniqueClickable(snapshot: UiSnapshot, nodes: List): List { val byPath = snapshot.nodes.associateBy { it.path } return nodes.mapNotNull { source -> @@ -494,6 +495,7 @@ class PurchaseLiveAutomation( const val PDD_PACKAGE = "com.xunmeng.pinduoduo" const val WECHAT_PACKAGE = "com.tencent.mm" val MASKED_PHONE = Regex("(? nodes += node("phone", "138****5678", bounds = NodeBounds(20, 650, 300, 710)) + addressAtTopAfterReveal -> { + nodes += node("address-card", "", clickable = true, bounds = NodeBounds(0, 300, 1080, 520)) + nodes += node("phone", "138****5678", bounds = NodeBounds(300, 330, 800, 380), parentPath = "address-card") + } + else -> { + nodes += node("address-card", "", clickable = true, bounds = NodeBounds(0, 620, 1080, 850)) + nodes += node("phone", "138****5678", bounds = NodeBounds(20, 650, 300, 710), parentPath = "address-card") + } } val suffixStart = address.lastIndexOf("_cg") val addressBody = if (suffixStart >= 0) address.substring(0, suffixStart) else address @@ -550,8 +560,9 @@ class PurchaseLiveAutomationTest { override fun tapPurchaseFresh(target: SnapshotNode): FreshActionResult { addressTaps++ + lastAddressTapPath = target.path clicked += target.label - if (target.label == "138****5678") page = "panel" + if (target.path.startsWith("address-card")) page = "panel" return FreshActionResult.SUCCESS }