Skip to content

ADFA-3617 | Support horizontal widgets in XML generation#1204

Merged
jatezzz merged 4 commits intostagefrom
feat/ADFA-3617-horizontal-widgets-xml-experimental
Apr 20, 2026
Merged

ADFA-3617 | Support horizontal widgets in XML generation#1204
jatezzz merged 4 commits intostagefrom
feat/ADFA-3617-horizontal-widgets-xml-experimental

Conversation

@jatezzz
Copy link
Copy Markdown
Collaborator

@jatezzz jatezzz commented Apr 17, 2026

Description

This PR introduces the ability to generate horizontal alignments in the Android XML output.

  • What: Added support for parsing and rendering multiple widgets placed horizontally on the same row.
  • How: Refactored the monolithic YoloToXmlConverter into smaller, specialized domain components (AndroidXmlGenerator, LayoutGeometryProcessor, and WidgetAnnotationMatcher). Added groupIntoRows logic to geometry processing which groups overlapping or adjacent boxes, and updated the XML generation to output a horizontal LinearLayout when multiple items fall into the same row.
  • Why: The previous behavior incorrectly rendered every detected widget on its own individual row, breaking the actual visual layout detected by the CV model.

Details

  • Created LayoutGeometryProcessor to handle row grouping thresholds and overlaps.
  • Created AndroidXmlGenerator with an appendHorizontalRow function to map horizontal widgets with correct margins.
  • Extracted ScaledBox into its own domain model file.
document_5123232377321032509.mp4

Ticket

ADFA-3617

Observation

The refactor heavily decouples the geometry parsing, annotation matching, and XML string building into isolated, testable classes, making future layout enhancements much easier to implement.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

Warning

Rate limit exceeded

@jatezzz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 4 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 4 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cce8d17-5665-47d2-b2d3-7b7e05111448

📥 Commits

Reviewing files that changed from the base of the PR and between 609430b and bd4eecd.

📒 Files selected for processing (5)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/AndroidXmlGenerator.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/LayoutGeometryProcessor.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/WidgetAnnotationMatcher.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/YoloToXmlConverter.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/model/ScaledBox.kt
📝 Walkthrough

Walkthrough

Refactors the YOLO→XML pipeline into smaller components: adds ScaledBox model, LayoutGeometryProcessor (scaling/grouping/assignment), WidgetAnnotationMatcher (tag-to-widget matching), and AndroidXmlGenerator (XML rendering). Converts YoloToXmlConverter from a singleton to an injectable class that orchestrates these collaborators.

Changes

Cohort / File(s) Summary
Data Model
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/model/ScaledBox.kt
Adds ScaledBox data class capturing label, mutable text, integer geometry (x,y,w,h,centerX,centerY) and an Android Rect.
Geometry Processing
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/LayoutGeometryProcessor.kt
New geometry utilities: scaleDetection (normalize/scale/clamp/ensure mins), groupIntoRows (vertical row grouping), and assignTextToParents (match text boxes to parent widgets).
Annotation Matching
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/WidgetAnnotationMatcher.kt
New matcher that normalizes/deduplicates OCR tags, groups by widget type, extracts ordinals, and assigns annotations to widget candidates with ordinal or proximity fallback.
XML Generation
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/AndroidXmlGenerator.kt
New renderer building Android XML from ScaledBox rows: optional ScrollView wrapping, row/layout decisions, per-widget tag selection, id generation, attribute merging (via fuzzy parser), and XML-escaping.
Core Converter Refactor
cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/YoloToXmlConverter.kt
Replaces monolithic object with DI class taking LayoutGeometryProcessor, WidgetAnnotationMatcher, and AndroidXmlGenerator; orchestration now delegates to collaborators; adds companion factory for defaults.

Sequence Diagram(s)

sequenceDiagram
  participant Client as YoloToXmlConverter
  participant Geo as LayoutGeometryProcessor
  participant Matcher as WidgetAnnotationMatcher
  participant XML as AndroidXmlGenerator
  participant Detections as DetectionResults
  Client->>Geo: scaleDetection(...) for each detection
  Geo-->>Client: List<ScaledBox>
  Client->>Geo: assignTextToParents(parents, texts, allBoxes)
  Geo-->>Client: populated boxes with text
  Client->>Matcher: matchAnnotationsToElements(canvasTags, uiElements, annotations)
  Matcher-->>Client: Map<ScaledBox, String>
  Client->>XML: buildXml(boxes, annotationsMap, targetDpHeight, wrapInScroll)
  XML-->>Client: Android XML string
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • hal-eisen-adfa
  • jomen-adfa
  • avestaadfa
  • Daniel-ADFA

Poem

🐰 In boxes small and labels bright,
I hop and match them left and right,
Scale and group, then stitch with care—
XML blooms from my strong hare! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly corresponds to the main change: adding support for horizontal widget layouts in XML generation via row grouping.
Description check ✅ Passed The description comprehensively explains the what, how, and why of the changes, relating directly to the refactoring and horizontal layout support implemented in the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ADFA-3617-horizontal-widgets-xml-experimental

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/AndroidXmlGenerator.kt`:
- Around line 215-216: The current code in AndroidXmlGenerator.kt
unconditionally writes android:contentDescription using the image label and then
adds "android:contentDescription" to writtenAttrs, which discards any
contentDescription parsed earlier by FuzzyAttributeParser; change the logic in
the method that contains the xml.append("$indent   
android:contentDescription=\"${escapeXmlAttr(label)}\"\n") and
writtenAttrs.add("android:contentDescription") so it only writes the label
fallback when no parsed contentDescription exists and the attribute hasn't
already been written — i.e., check the parsed attributes from
FuzzyAttributeParser (the parsed contentDescription key) and writtenAttrs first,
write parsed value if present, otherwise write the label, and only then add
"android:contentDescription" to writtenAttrs.
- Around line 65-71: The current loop always uses DEFAULT_SPACING_DP for end
margin; change the index < row.lastIndex branch in row.forEachIndexed to compute
the actual horizontal gap between the current box and the next box (e.g., gapPx
= nextBox.right - currentBox.left or next.boundingBox.left -
(box.boundingBox.left + box.boundingBox.width) depending on your box model),
convert gapPx to dp (round and clamp to >= 0), format it as "${gapDp}dp" and
place it in extraAttrs ("android:layout_marginEnd"), falling back to
DEFAULT_SPACING_DP if conversion fails or gap is negative; keep calling
appendSimpleView(xml, box, counters, "            ", annotations, extraAttrs)
unchanged.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/model/ScaledBox.kt`:
- Around line 5-8: ScaledBox currently has mutable fields (var text and mutable
Rect) which break Map/Set membership when mutated; make text immutable (change
var text to val) and stop mutating Rect instances — replace the rect field with
an immutable copy (or store immutable coordinates/Rect-like data) so
equals/hashCode remain stable; update callers such as LayoutGeometryProcessor
(where parent.text = it.text is used) to create new ScaledBox instances with the
updated text instead of mutating, and ensure usages in WidgetAnnotationMatcher
and AndroidXmlGenerator continue to construct/lookup ScaledBox by the stable,
immutable fields.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/WidgetAnnotationMatcher.kt`:
- Around line 87-110: The normalizeWidgetType function must map "icon" into the
image-placeholder group so P-* parsed annotations apply to icon detections:
update normalizeWidgetType (and keep getTagType as-is) to include a branch that
returns "image_placeholder" when label.startsWith("icon") (place it near the
other image_placeholder branch so "icon" is normalized to "image_placeholder").
- Around line 126-135: The code currently prefers zero-based lookup before
one-based which causes e.g. "B-1" to match candidates[1] instead of
candidates[0]; in the matching logic around the variables ordinal, candidates
and claimedWidgets (in WidgetAnnotationMatcher), reverse the checks so you first
compute and test oneBasedMatch = candidates.getOrNull(ordinal - 1) against
claimedWidgets and return it if available, and only if that fails fall back to
zeroBasedMatch = candidates.getOrNull(ordinal); keep the ordinal != null guard
and claimedWidgets exclusion intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84293198-706f-4d26-8972-ee71757e1ab7

📥 Commits

Reviewing files that changed from the base of the PR and between fb067ea and c2684ea.

📒 Files selected for processing (5)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/AndroidXmlGenerator.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/LayoutGeometryProcessor.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/WidgetAnnotationMatcher.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/YoloToXmlConverter.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/model/ScaledBox.kt

@jatezzz jatezzz force-pushed the feat/ADFA-3617-horizontal-widgets-xml-experimental branch 3 times, most recently from 16923fc to 97116aa Compare April 20, 2026 13:51
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/AndroidXmlGenerator.kt`:
- Around line 82-89: The escapeXmlAttr function currently strips pipe characters
which corrupts valid Android attribute values (e.g., android:inputType flags);
remove only the .replace("|", "") call from escapeXmlAttr so pipes are
preserved, leaving the trim() and the escaping of &, <, >, ", ' intact (ensure
the method named escapeXmlAttr still returns a String with the same chaining of
trim() and replace calls for XML characters).

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/LayoutGeometryProcessor.kt`:
- Around line 99-106: normW and normH use integer division causing truncation;
change their calculations to use floating-point division by converting the
numerator or denominator to Float (e.g., (rect.right - rect.left).toFloat() /
sourceWidth or / sourceWidth.toFloat()) so normW and normH are Float values
consistent with normCx/normCy; update the two lines that define normW and normH
in LayoutGeometryProcessor.kt accordingly and keep subsequent math (multiplying
by targetW/targetH and roundToInt()) unchanged.

In
`@cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/WidgetAnnotationMatcher.kt`:
- Line 8: The prefix group in TAG_EXTRACT_REGEX inside WidgetAnnotationMatcher
doesn't capture the two-character prefix "S8", so tags like "S8-1" aren't
normalized to "SW-1"; update TAG_EXTRACT_REGEX to explicitly allow "S8" as an
alternative in the prefix group (for example use
(?i)((?:S8|[BPDTCRS]\s*W?))[^a-zA-Z0-9]*([\d lIoO!]+)$ equivalent replacement)
so the normalization logic used later (around the S8 -> SW recovery in the
normalization code) will see "S8" as the prefix and normalize it to "SW" as
intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c8398a54-5d2e-4922-84bd-d5b6c22ca741

📥 Commits

Reviewing files that changed from the base of the PR and between c2684ea and 609430b.

📒 Files selected for processing (5)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/AndroidXmlGenerator.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/LayoutGeometryProcessor.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/WidgetAnnotationMatcher.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/YoloToXmlConverter.kt
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/model/ScaledBox.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/model/ScaledBox.kt

@Daniel-ADFA Daniel-ADFA self-requested a review April 20, 2026 15:51
jatezzz added 4 commits April 20, 2026 11:10
Refactor XML generation into specific domain components to group and render widgets in horizontal rows.
- Model: Make `ScaledBox` properties immutable (`val text`) to prevent
  silently breaking `hashCode` lookups in Maps and Sets.
- Geometry: Refactor `assignTextToParents` to safely update widget text
  using `.copy()` instead of mutating instances directly.
- Generator: Calculate `android:layout_marginEnd` dynamically from
  bounding box geometry to preserve actual horizontal gaps, replacing
  the hardcoded 16dp margin.
- Generator: Honor parsed `android:contentDescription` for ImageViews
  so user-provided annotations are not overwritten by default labels.
- Matcher: Prioritize 1-based ordinal matching over 0-based fallback
  so tags like "B-1" correctly map to the first element in the list.
- Matcher: Include "icon" in "image_placeholder" widget normalization.
- Rename `appendTextViewAttributes` to `appendTextWidgetAttributes` to accurately reflect that it handles multiple text-based widgets (TextView, Button, CheckBox, RadioButton, Switch).
- Simplify the default text fallback logic by introducing a `WIDGET_TAGS` constant set, replacing the verbose `when` block.
…lc `normCx` and `normCy` values and improve `TAG_EXTRACT_REGEX` regex to detect tags like `S8-1`
@jatezzz jatezzz force-pushed the feat/ADFA-3617-horizontal-widgets-xml-experimental branch from 609430b to bd4eecd Compare April 20, 2026 16:10
@jatezzz jatezzz merged commit 418de16 into stage Apr 20, 2026
2 checks passed
@jatezzz jatezzz deleted the feat/ADFA-3617-horizontal-widgets-xml-experimental branch April 20, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants