Skip to content

Conversation

@rbro112
Copy link
Member

@rbro112 rbro112 commented Oct 27, 2025

Adds adaptive icon support for XML icons from APKs. Admittedly used claude to migrate this from existing typescript code but rearchitected quite a bit. Checked work against quite a few local apps, all working well.

Copy link
Member Author

rbro112 commented Oct 27, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@codecov
Copy link

codecov bot commented Oct 27, 2025

Codecov Report

❌ Patch coverage is 57.06052% with 149 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.66%. Comparing base (9ad6247) to head (8c4dee8).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...parsers/android/icon/binary_xml_drawable_parser.py 55.73% 96 Missing and 43 partials ⚠️
src/launchpad/artifacts/android/apk.py 50.00% 6 Missing and 2 partials ⚠️
src/launchpad/artifacts/android/manifest/axml.py 81.81% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #435      +/-   ##
==========================================
- Coverage   81.13%   80.66%   -0.47%     
==========================================
  Files         158      159       +1     
  Lines       12900    13219     +319     
  Branches     1304     1378      +74     
==========================================
+ Hits        10466    10663     +197     
- Misses       1964     2038      +74     
- Partials      470      518      +48     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

items: list[GradientItem] | None = None


# TODO: Base abstraction to use for proto
Copy link
Member

Choose a reason for hiding this comment

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

Try to attach a Linear ticket if you can 😉

with open(xml_file_path, "rb") as f:
vector_buffer = f.read()
vector_node = AndroidBinaryParser(vector_buffer).parse_xml()
if not vector_node:
Copy link
Member

Choose a reason for hiding this comment

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

Hmm def parse_xml(self) -> XmlNode doesn't return an optional and internally already raises a ValueError for this case, surprised this didn't trigger a type error.


if not foreground_path and not background_path:
logger.warning(
"Could not resolve resource paths\nforeground ref: %s\nbackground ref: %s",
Copy link
Member

Choose a reason for hiding this comment

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

nit: would just pass the logger args as extras

Copy link
Member Author

Choose a reason for hiding this comment

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

Claude lol

self,
root_element: XmlNode,
) -> bytes | None:
try:
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think all these extra try/except's per method are redundant since everything is already caught in render_from_path()

) -> bytes | None:
try:
# Load background layer
background_img = None
Copy link
Member

Choose a reason for hiding this comment

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

nit: there's probably a nice pythonic way of handling this background_img and foreground_img code but not really sure lol, maybe a nested method would help but up to you

return None

# Use standard adaptive icon size (108x108 with 72x72 safe area)
size = 108
Copy link
Member

Choose a reason for hiding this comment

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

I see this 108 is duplicated in a few spots, maybe extract into a constant?


# Helper methods

def _get_optional_attr_value(
Copy link
Member

Choose a reason for hiding this comment

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

Could any of this be pushed down into our other XML code?


binary_xml_drawable_utils = BinaryXmlDrawableParser(self._extract_dir, binary_res_tables)

icon = binary_xml_drawable_utils.render_from_path(icon_path)
Copy link
Member

Choose a reason for hiding this comment

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

I recommend adding a test case for get_app_icon () like I did for the iOS app icon parsing which basically just checks whether this creates a valid PNG image from our HackerNews fixture.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do, good idea

self.extract_dir = extract_dir
self.binary_res_tables = binary_res_tables

# TODO: Just render the XML file, don't care if it's a vector, shape, or adaptive icon
Copy link
Contributor

@runningcode runningcode Oct 28, 2025

Choose a reason for hiding this comment

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

is this a TODO or just explaining what this method does?

After reading it, it seems this only parses vectors, so this is a to-do to parse adaptive icons and shapes?

Copy link
Member Author

Choose a reason for hiding this comment

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

A todo I forgot to remove

logger.exception("Error parsing gradient XML")
return None

def _render_shape_to_buffer(
Copy link
Contributor

Choose a reason for hiding this comment

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

where is this called? 🤔

logger.exception("Error parsing adaptive icon binary XML")
return None

def _render_standard_icon(
Copy link
Contributor

Choose a reason for hiding this comment

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

i think this assumes we are passing in a vector xml node so should the method be called render_vector?

@runningcode
Copy link
Contributor

Amazingly fast turnaround with that fancy AI!
If you tested all the app icons in the emerge repo then I think this should handle all the edge cases.
Open the file and double check that it rendered both the foreground AND the background for all those icons. There are some edge cases with the background in some of them.

@rbro112 rbro112 force-pushed the ryan/adaptive_icon_support_for_apks branch from 89721af to a87abdf Compare October 28, 2025 17:09
@rbro112 rbro112 merged commit 83bf8d8 into main Oct 28, 2025
22 checks passed
@rbro112 rbro112 deleted the ryan/adaptive_icon_support_for_apks branch October 28, 2025 17:58
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.

4 participants