# Android (Capacitor) Ship the CRA web build as an Android app via [Capacitor](https://capacitorjs.com/). One codebase: `llm-fe/` → web + Android + iOS WebView. **Build & Play deploy steps:** [`android/README.md`](android/README.md). **iOS sibling:** [`IOS.md`](IOS.md) / [`ios/README.md`](ios/README.md). Prerequisites for blockers already merged: JWT-only auth (#22), WebSocket lifecycle (#23), HashRouter (#24). ## Prerequisites - **Node.js 20** (matches CI; Capacitor **7.x**) - **JDK 17+** (OpenJDK fine) - **Android Studio** (Ladybug+) with Android SDK Platform **35**, build-tools, and an emulator or device - Set `ANDROID_HOME` (or open the project once in Android Studio so `local.properties` is generated) ## One-time / day-to-day ```bash cd llm-fe npm ci npm run build:mobile # loads .env.mobile → CRA build → cap sync android npm run android:open # opens android/ in Android Studio ``` Or sync only after an existing `build/`: ```bash npm run android:sync ``` ## Environment | File | Purpose | |------|---------| | `.env.mobile` | Endpoints baked into the **mobile** bundle (`build:mobile`) | | `.env.production` | Web prod deploys (`build` / `build:prod`) | | `.env.beta` | Web beta deploys | Default `.env.mobile` matches production (`chatbackend.aimloperations.com`). Point it at beta to flip the shell without touching web deploys. Optional gitignored override: `.env.mobile.local`. ## Versioning In `android/app/build.gradle`: - **versionName** — user-visible string; keep aligned with `package.json` `version` (currently `0.1.0`) - **versionCode** — integer; **must increase** for every Play Store upload Bump both before each store release. ## Icons / splash Source art lives in `llm-fe/assets/`: - `icon.png` / `splash.png` / `splash-dark.png` — Capacitor inputs (Hesychia meander mark) - `logo-source.png` — cropped square master - `logo-mark-transparent.png` — circular mark with transparent corners (web / in-app) In-app UI also imports `src/llm-fe/assets/brand/hesychia-mark.png`. Regenerate Android densities after changing source art: ```bash npm run assets:generate ``` Same command also regenerates iOS `Assets.xcassets` (see [`IOS.md`](IOS.md)). ## Signing (upload keystore) **Never commit keystores.** Store outside the repo (e.g. host secrets / password manager). ```bash keytool -genkey -v -keystore ~/Documents/secrets/aiml-chat-upload.jks \ -keyalg RSA -keysize 2048 -validity 10000 \ -alias aiml-chat-upload ``` Create `llm-fe/android/keystore.properties` (gitignored): ```properties storeFile=/absolute/path/to/aiml-chat-upload.jks storePassword=... keyAlias=aiml-chat-upload keyPassword=... ``` Release App Bundle: ```bash cd llm-fe/android ./gradlew bundleRelease # → app/build/outputs/bundle/release/app-release.aab ``` Debug APK (no signing props needed): ```bash ./gradlew assembleDebug ``` ## Manual QA checklist On emulator **and** physical device: - [ ] Login / logout / password reset flows - [ ] Chat streaming + markdown / code blocks - [ ] Charts (`recharts`) - [ ] Dark / light theme - [ ] Hardware back: navigates history, exits at root - [ ] Keyboard does not cover compose input; notch/safe-area OK - [ ] Background → resume keeps or reconnects WebSocket - [ ] Airplane mode → restore reconnects ## Play Store (internal testing first) Out of band ops (not automated here): 1. Google Play developer account 2. Privacy policy URL (product site / legal) 3. Data safety form (JWT auth, chat content, analytics if Tianji runs in WebView) 4. Screenshots for phone (+ tablet if targeting) 5. Upload `.aab` to **internal testing** track before production ## Related issues - [#20](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/20) — this Android wrap - [#21](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/21) — iOS (reuses this scaffolding) - [#22](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/22) / [#23](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/23) / [#24](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/24) — auth / WS / routing