feat(android): Compose app with bundled yt-dlp, theme pack, nav & progress
Turn android/ into a real Gradle app (Kotlin + Jetpack Compose + Material3) that bundles an on-device yt-dlp engine and reuses the Phase-4 Rust JNI core. - Bundled engine: youtubedl-android (Python + yt-dlp + ffmpeg + aria2c) via Engine.kt; initialises off the main thread on first launch. Requires legacy jniLib packaging so the bundled .zip.so payloads extract to disk. - Intuitive navigation: bottom NavigationBar — Download / Files / Settings. - Settings section: dedicated screen with a live theme picker, default-quality chips, engine info, and a Rust-core demo. - All 19 desktop themes ported from src/theme.rs into Theme.kt as Material3 colour schemes, shown as tappable swatches; selection persisted (Prefs.kt). - Download screen: live platform detection through the Rust core as you type, plus an improved animated determinate progress indicator (%/ETA/status/ success-error) and a scrollable log. Toolchain pinned for reproducibility: Gradle 8.9 wrapper, AGP 8.5.2, Kotlin 1.9.24, Compose BOM 2024.06.00, compileSdk 34, minSdk 24, ABIs arm64-v8a + x86_64. build-apk.sh runs Gradle on a JDK <= 21 (system JDK may break R8) and builds the Rust libs first. Verified on an Android 14 x86_64 emulator: installs, launches, engine reports "yt-dlp ready", Rust .so loads, theme switching re-themes live and persists across restart, all screens render. (A real YouTube download still depends on the anti-bot/POT question — Phases 2-3, device-only.) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
1790fd1a22
commit
3a55ff5b8a
22 changed files with 1607 additions and 83 deletions
|
|
@ -1,87 +1,76 @@
|
|||
# Catacomb Android (Stage-1 prototype)
|
||||
# Catacomb Android
|
||||
|
||||
This directory holds the Android Stage-1 feasibility prototype described in
|
||||
[`docs/superpowers/specs/2026-06-27-android-stage1-prototype-plan.md`](../docs/superpowers/specs/2026-06-27-android-stage1-prototype-plan.md).
|
||||
The Catacomb Android app: a Compose UI over a **bundled on-device yt-dlp engine**
|
||||
(Python + yt-dlp + ffmpeg + aria2c via [youtubedl-android]) plus a **shared Rust
|
||||
core** (`vtt`, `error_class`, `platform`) called over JNI.
|
||||
|
||||
## Status by phase
|
||||
## What lives here
|
||||
|
||||
- **`app/`** — the Gradle app (Kotlin + Jetpack Compose + Material3). The real,
|
||||
installable app.
|
||||
- **`rust/catacomb_core/`** — the Rust `cdylib` (Phase 4) reused verbatim from the
|
||||
desktop's pure modules, cross-compiled to a JNI `.so`. See its own README.
|
||||
- **`demo/`** — a tiny dependency-free Java app that only exercises the Rust core
|
||||
(a minimal, Gradle-free reference; predates the full app).
|
||||
|
||||
## Features
|
||||
|
||||
- **Bundled yt-dlp** — real on-device download engine, no server needed. Extracts
|
||||
its Python environment on first launch.
|
||||
- **Intuitive navigation** — bottom navigation bar: **Download**, **Files**,
|
||||
**Settings**.
|
||||
- **All desktop themes** — the 19 palettes from `src/theme.rs` (Dark, Light,
|
||||
Dracula, Trans, the Emo/goth set, Cyberpunk, Synthwave, Vaporwave, Nord,
|
||||
Gruvbox, Tokyo Night, Paper, Honey, Candlelight) selectable as live swatches;
|
||||
the choice is persisted.
|
||||
- **Live platform detection** — as you type a URL, the Rust core resolves its
|
||||
platform (chip shows icon + name + folder).
|
||||
- **Improved progress indicator** — animated determinate bar with %, ETA, live
|
||||
yt-dlp status line, success/error end states, plus a scrollable log.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
./build-apk.sh # -> app/build/outputs/apk/debug/app-debug.apk (~130 MB)
|
||||
```
|
||||
|
||||
`build-apk.sh`:
|
||||
- runs Gradle on a JDK <= 21 (the system JDK may be newer and break R8/d8;
|
||||
override with `TOOL_JAVA_HOME`),
|
||||
- builds the Rust native libs first if missing (`rust/catacomb_core/build.sh`),
|
||||
- writes `local.properties` (SDK path) and a stable `debug.keystore`,
|
||||
- runs `./gradlew assembleDebug`.
|
||||
|
||||
Toolchain (pinned): Gradle 8.9 (wrapper), AGP 8.5.2, Kotlin 1.9.24, Compose
|
||||
compiler 1.5.14, Compose BOM 2024.06.00, compileSdk 34, minSdk 24. ABIs:
|
||||
arm64-v8a (devices) + x86_64 (emulator) — the intersection of what the Rust core
|
||||
and youtubedl-android ship.
|
||||
|
||||
## Install & run
|
||||
|
||||
```bash
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
adb shell am start -n com.catacomb.spike/.MainActivity
|
||||
```
|
||||
|
||||
Verified on an Android 14 (API 34) x86_64 emulator: installs, launches, the
|
||||
bundled engine initialises ("yt-dlp ready"), the Rust `.so` loads (live platform
|
||||
detection), theme switching re-themes the whole app and persists across restart,
|
||||
and all three screens render.
|
||||
|
||||
## Status vs the Stage-1 plan
|
||||
|
||||
| Phase | What | Status |
|
||||
|-------|------|--------|
|
||||
| 1 | App shell + `yt-dlp-android` integration | Not started (needs Compose deps + the `yt-dlp-android` release) |
|
||||
| 2 | WebView-BotGuard POT generation | Blocked here — needs a real device + genuine Google session |
|
||||
| 3 | Token passing + anti-bot test | Blocked here — device-only |
|
||||
| 4 | **Rust core → Android `.so` via JNI** | ✅ **Implemented & verified** |
|
||||
| 5 | On-device integration test → Go/No-Go | Device-only |
|
||||
| 1 | App shell + yt-dlp-android integration | Done (this app) |
|
||||
| 2 | WebView-BotGuard POT generation | Not done — needs a real device + Google session |
|
||||
| 3 | Token passing + anti-bot test | Not done — device-only |
|
||||
| 4 | Rust core -> Android `.so` via JNI | Done (`rust/catacomb_core`) |
|
||||
| 5 | On-device Go/No-Go | Partial — app verified on emulator; anti-bot download test is device-only |
|
||||
|
||||
Phases 2/3/5 require a physical Android device with a logged-in Google session
|
||||
(the WebView anti-bot test), so they can't be executed in a headless CI/dev
|
||||
environment. Phase 4 — the "shared Rust core" leg the feasibility research
|
||||
marked **PROVEN** — is fully implemented and verified here.
|
||||
The engine is bundled and initialises on-device; a *successful YouTube download*
|
||||
still depends on the anti-bot/POT question (Phases 2-3), which needs a physical
|
||||
device with a signed-in Google session — the open item from the feasibility
|
||||
research.
|
||||
|
||||
## Installable demo APK (`demo/`)
|
||||
|
||||
[`demo/`](demo/) is a minimal, dependency-free app (plain Android Views, no
|
||||
Gradle/AGP/Compose) that calls the Phase-4 JNI core on-device and shows the
|
||||
results. Build it with `demo/build-apk.sh` (drives `aapt2`/`d8`/`zipalign`/
|
||||
`apksigner` directly) → `demo/out/catacomb-spike-debug.apk`. Verified on an
|
||||
Android 14 x86_64 emulator: the app launches, loads `libcatacomb_core.so`, and
|
||||
the JNI calls return correct JSON. See [`demo/README.md`](demo/README.md).
|
||||
|
||||
## Phase 4: `rust/catacomb_core`
|
||||
|
||||
A `cdylib` that reuses Catacomb's **pure** modules verbatim (`vtt`,
|
||||
`error_class`, `platform`) via `#[path]` includes — no logic fork — and exposes
|
||||
them to Kotlin over JNI. See [`rust/catacomb_core/src/lib.rs`](rust/catacomb_core/src/lib.rs).
|
||||
|
||||
Exposed entry points (all String-in / String-out), bound in
|
||||
[`app/src/main/java/com/catacomb/spike/RustCore.kt`](app/src/main/java/com/catacomb/spike/RustCore.kt):
|
||||
|
||||
| Kotlin | Returns |
|
||||
|--------|---------|
|
||||
| `RustCore.vttParse(vtt)` | JSON `[{start, text}, …]` |
|
||||
| `RustCore.classifyError(log)` | JSON `{class, label, hint}` |
|
||||
| `RustCore.platformFromUrl(url)` | JSON `{dir_name, display_name, icon}` |
|
||||
| `RustCore.platformDirName(url)` | plain folder name, e.g. `channels` |
|
||||
|
||||
### Build the native libs
|
||||
|
||||
```bash
|
||||
cd rust/catacomb_core
|
||||
./build.sh # arm64-v8a + x86_64 (release) → app/src/main/jniLibs/<abi>/
|
||||
./build.sh arm64-v8a # single ABI
|
||||
API=24 ./build.sh # override min-API linker (default 24)
|
||||
```
|
||||
|
||||
The script locates the NDK from `$ANDROID_NDK_HOME`, `$ANDROID_NDK_ROOT`, or the
|
||||
newest `ndk/<ver>` under `$ANDROID_HOME` / `$ANDROID_SDK_ROOT` / `~/Android/Sdk`.
|
||||
Only the target linker is configured — every dependency (`serde`, `serde_json`,
|
||||
`jni`) is pure Rust, so there's no C toolchain to set up.
|
||||
|
||||
Outputs (git-ignored — rebuild rather than commit binaries):
|
||||
`app/src/main/jniLibs/{arm64-v8a,x86_64}/libcatacomb_core.so`.
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
cd rust/catacomb_core
|
||||
|
||||
# 1. Logic reachable through the crate (runs the shared modules' own tests too):
|
||||
cargo test
|
||||
|
||||
# 2. Exported JNI symbols in the shipped (arm64) .so:
|
||||
NDK=~/Android/Sdk/ndk/26.3.11579264
|
||||
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf --dyn-syms \
|
||||
target/aarch64-linux-android/release/libcatacomb_core.so | grep Java_com_catacomb
|
||||
# → Java_com_catacomb_spike_RustCore_{vttParse,classifyError,platformFromUrl,platformDirName}
|
||||
```
|
||||
|
||||
An end-to-end JVM round-trip (build a host `.so`, `System.load` it, call each
|
||||
native method) was used during development to confirm the bridge works at
|
||||
runtime, not just that the symbols resolve.
|
||||
|
||||
## Notes
|
||||
|
||||
- The JNI method names in `RustCore.kt` **must** match the Rust
|
||||
`Java_com_catacomb_spike_RustCore_<name>` exports exactly.
|
||||
- The `.so` is built with `panic = "abort"` and every entry point wraps its body
|
||||
in `catch_unwind`, so a Rust panic can never unwind into the JVM (UB).
|
||||
[youtubedl-android]: https://github.com/junkfood02/youtubedl-android
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue