Module: Everywhere::AndroidResources

Defined in:
lib/everywhere/android_resources.rb

Overview

Compiles an app's native/android/assets/ tree into Android drawable resources so native code can reach them by name — R.drawable.logo in Kotlin, @drawable/logo in a layout.

Deliberately the same filename API as the iOS side (see AssetCatalog), so one assets/ tree can be authored once and dropped into both native/ios/ and native/android/: [@2x|@3x][~dark].. What differs is where the markers land, because Android expresses both as directory qualifiers rather than as catalog metadata:

logo.png        → res/drawable/logo.png              (density-independent baseline)
logo@2x.png     → res/drawable-xhdpi/logo.png        (2x = 320dpi)
logo@3x.png     → res/drawable-xxhdpi/logo.png       (3x = 480dpi)
logo~dark.png   → res/drawable-night/logo.png
logo@2x~dark.png→ res/drawable-night-xhdpi/logo.png

Sub-directories organise the source tree but do NOT namespace the result — res/ is flat, and asset names are global on iOS too, so assets/branding/ logo.png is R.drawable.logo either way.

Constant Summary collapse

RASTER =

Raster formats aapt2 accepts as-is. WebP is included (and PDF/SVG are not) because that is the split Android itself draws — the reverse of iOS.

%w[.png .jpg .jpeg .webp].freeze
VECTOR =

A drawable written as XML: vectors, shapes, layer lists, selectors. Scale -free, so the density qualifiers above don't apply to them.

%w[.xml].freeze
EXTENSIONS =
(RASTER + VECTOR).freeze
DENSITIES =

iOS's @2x/@3x in Android's density buckets. 1x is plain drawable, not drawable-mdpi: both mean 160dpi, but the unqualified directory is also the fallback every density falls back TO, which is what a single-file asset needs to be.

{ nil => nil, "2x" => "xhdpi", "3x" => "xxhdpi" }.freeze
RESERVED =

Written by the Android builder itself (adaptive launcher icon layers) or shipped in the frozen template's own res/. Both would collide in the merged resource table, so they're refused by name here — a merge conflict from aapt2 arrives minutes into a Gradle run and names no fix.

%w[
  ic_launcher ic_launcher_round ic_launcher_foreground ic_launcher_background
  ic_tab_placeholder
].freeze
VALID_NAME =

An Android resource entry is a field on the generated R class, so the name has to be a Java identifier — and aapt2 additionally rejects uppercase in a res/ filename. Hyphens and dots are legal on iOS and never here.

/\A[a-z][a-z0-9_]*\z/
JAVA_KEYWORDS =

Reserved words can't name a field on R. val/fun are fine (Kotlin reads R from Java), so this is the Java list, not the Kotlin one.

%w[
  abstract assert boolean break byte case catch char class const continue default do double else
  enum extends final finally float for goto if implements import instanceof int interface long
  native new package private protected public return short static strictfp super switch
  synchronized this throw throws transient try void volatile while
].freeze

Class Method Summary collapse

Class Method Details

.write!(source, res) ⇒ Object

Copy every image under source into res as a drawable resource. Returns [names, errors]; nothing is written when errors is non-empty, so a broken assets/ tree can't half-stamp the resource dirs.



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/everywhere/android_resources.rb', line 68

def write!(source, res)
  variants, errors = plan(source)
  return [[], errors] unless errors.empty?

  variants.each do |variant|
    dir = File.join(res, variant[:dir])
    FileUtils.mkdir_p(dir)
    FileUtils.cp(variant[:path], File.join(dir, "#{variant[:name]}#{variant[:extension]}"))
  end

  [variants.map { |variant| variant[:name] }.uniq.sort, []]
end