From 20a16c9fafee73192635e86ec27245fea13b47c3 Mon Sep 17 00:00:00 2001 From: juli Date: Sun, 9 Aug 2026 21:04:49 +0200 Subject: [PATCH] Build verified; add icon-generator tool and install/README notes - ./gradlew assembleDebug produces a 13 MB arm64-v8a APK (libtsjet/libllama/ libggml/libc++_shared bundled; JNI compiles cleanly against llama.cpp b10333) - Add tools/gen_launcher_icons.py so the launcher icon is reproducible from art - README: mark build verified, add sideload/install section Co-Authored-By: Claude Opus 4.8 --- README.md | 16 ++++++--- tools/gen_launcher_icons.py | 71 +++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tools/gen_launcher_icons.py diff --git a/README.md b/README.md index e6292f5..ee02fb2 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,12 @@ The APK lands in `app/build/outputs/apk/debug/`. Or just Run ▶ from Android St > First build compiles llama.cpp from source, so it takes a while and needs > network (CMake fetches the pinned llama.cpp). +## Install (sideload) + +The debug APK is signed with the debug key, so you can sideload it directly: +copy `app-debug.apk` to the phone, enable "install unknown apps" for your file +manager, and tap it. First launch downloads the ~1.9 GB model over Wi-Fi. + ## Where things live | What | Where | @@ -72,12 +78,12 @@ The APK lands in `app/build/outputs/apk/debug/`. Or just Run ▶ from Android St ## TODO / notes -- **Logo:** juli is on it. The header logo is a placeholder `🐟` in - `assets/web/index.html` (`#logo`), and there's no launcher icon yet — add an - `android:icon` in `AndroidManifest.xml` + a `mipmap` when the artwork is ready. +- **Branding:** launcher icon (adaptive + legacy densities) is generated from + `piti-icon.png` via `python3 tools/gen_launcher_icons.py`; the header wordmark is + `piti-logo.png` (copied to `assets/web/logo.png`). Source art lives at the repo root. - The model may emit `` blocks; the UI strips them from the display and from history (`stripThink` in `app.js`). - Bumping the llama.cpp tag? Re-check the C API calls in `llama-jni.cpp` against that tag's `include/llama.h` — it uses the raw C API directly. -- Not yet compiled end-to-end in CI; first real build is the acid test for the - native layer. +- **Build verified:** `./gradlew assembleDebug` produces a ~13 MB `arm64-v8a` APK + (native libs stripped; the ~1.9 GB model downloads on first launch). diff --git a/tools/gen_launcher_icons.py b/tools/gen_launcher_icons.py new file mode 100644 index 0000000..6e2c5a0 --- /dev/null +++ b/tools/gen_launcher_icons.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Regenerate Android launcher icons from piti-icon.png. + +Usage: python3 tools/gen_launcher_icons.py +Requires: Pillow (pip install Pillow) + +Produces, under app/src/main/res/: + - mipmap-/ic_launcher.png legacy square (rounded), all densities + - mipmap-/ic_launcher_round.png legacy round, all densities + - mipmap-/ic_launcher_foreground.png adaptive-icon foreground (padded) +The adaptive XMLs (mipmap-anydpi-v26/) and the black background drawable are +checked in and don't need regenerating. +""" +import os +from PIL import Image, ImageDraw + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SRC = os.path.join(ROOT, "piti-icon.png") +RES = os.path.join(ROOT, "app", "src", "main", "res") +BG = (0, 0, 0, 255) # matches the app's black theme / ic_launcher_background.xml + +brain = Image.open(SRC).convert("RGBA") + +LEGACY = {"mdpi": 48, "hdpi": 72, "xhdpi": 96, "xxhdpi": 144, "xxxhdpi": 192} +ADAPTIVE = {"mdpi": 108, "hdpi": 162, "xhdpi": 216, "xxhdpi": 324, "xxxhdpi": 432} + + +def fit(img, box): + w, h = img.size + s = box / max(w, h) + return img.resize((max(1, int(w * s)), max(1, int(h * s))), Image.LANCZOS) + + +def rounded_mask(size, radius): + m = Image.new("L", (size, size), 0) + ImageDraw.Draw(m).rounded_rectangle([0, 0, size - 1, size - 1], radius=radius, fill=255) + return m + + +def circle_mask(size): + m = Image.new("L", (size, size), 0) + ImageDraw.Draw(m).ellipse([0, 0, size - 1, size - 1], fill=255) + return m + + +def compose(size, mask, content_scale): + canvas = Image.new("RGBA", (size, size), BG) + fg = fit(brain, int(size * content_scale)) + canvas.paste(fg, ((size - fg.width) // 2, (size - fg.height) // 2), fg) + out = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + out.paste(canvas, (0, 0), mask) + return out + + +def save(img, folder, name): + d = os.path.join(RES, folder) + os.makedirs(d, exist_ok=True) + img.save(os.path.join(d, name)) + + +for bucket, size in LEGACY.items(): + save(compose(size, rounded_mask(size, int(size * 0.18)), 0.86), f"mipmap-{bucket}", "ic_launcher.png") + save(compose(size, circle_mask(size), 0.86), f"mipmap-{bucket}", "ic_launcher_round.png") + +for bucket, size in ADAPTIVE.items(): + fg_canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + fg = fit(brain, int(size * 0.64)) # keep within the adaptive-icon safe zone + fg_canvas.paste(fg, ((size - fg.width) // 2, (size - fg.height) // 2), fg) + save(fg_canvas, f"mipmap-{bucket}", "ic_launcher_foreground.png") + +print("launcher icons regenerated under app/src/main/res/")