Files
chat_web_app/llm-fe/ANDROID.md
T
westfarn 0d233b077c
Unit Tests / test (pull_request) Successful in 14s
Publish a signed Play AAB from Deploy Prod.
Closes #121. Supersedes the separate Deploy Android workflow in #58 by attaching hesychia-<version>-<code>.aab to an android-v* Gitea release after the prod web deploy.
2026-10-19 10:26:36 -05:00

187 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.
### Play Billing (#78)
Native billing uses the local Capacitor plugin `native-store-billing` (Play Billing on Android, StoreKit on iOS). Web keeps Stripe Checkout / Customer Portal.
The Play product id is the plan's `store_product_id`. The backend verifies the purchase token with the Play Developer API (`GOOGLE_PLAY_SERVICE_ACCOUNT_JSON`). More detail: [`MONETIZATION.md`](MONETIZATION.md).
After plugin changes: `npm run build:mobile` (runs `cap sync`).
## Versioning
`android/app/build.gradle` keeps a floor:
- `hesychiaVersionNameFloor` — `"0.1.0"` (user-visible string)
- `hesychiaVersionCodeFloor` — `1` (integer; Play requires a higher code on every upload)
Local `./gradlew bundleRelease` uses that floor when you do not pass properties. **Deploy Prod** does not edit the file. After the web deploy it looks up existing `android-v*` Gitea releases, reuses the pair when the tag already points at this commit, otherwise ships the next patch and the next `versionCode` via `-PhesychiaVersionName` / `-PhesychiaVersionCode`.
Raise the floor in git only when you want the next automatic release to jump ahead of older tags.
## Deploy Prod publishes the Play bundle
**Deploy Prod** (`.gitea/workflows/deploy-prod.yml`) runs `android-release` after the prod web deploy of the same commit. The job:
1. Resolves the Play version (`llm-fe/scripts/android-version.mjs`)
2. `npm run build:android:store` — `.env.mobile` (prod API) plus `cap sync android`
3. Signs with the upload keystore on the runner (`~/.android/`, not in git)
4. `jarsigner -verify` on `app-release.aab`
5. Creates or reuses a Gitea release tagged `android-v<versionName>-<versionCode>` and attaches `hesychia-<versionName>-<versionCode>.aab`
Example of the same shape on RealPath: [android-v1.0.2-3](https://git.aimloperations.com/Ditch_The_Agent/dta_webapp/releases/tag/android-v1.0.2-3).
Download that AAB from the Gitea release and upload it to the Play **internal** track. Deploy Prod does not call the Play Developer API. A separate **Deploy Android** workflow is not used ([#58](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/58) is superseded by this job, [#121](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/121)).
Manual store binary (same web bundle the job builds):
```bash
cd llm-fe
npm run build:android:store
cd android
./gradlew bundleRelease
```
## 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 or `keystore.properties`.** Passwords stay on the runner, not in Gitea Actions secrets.
Deploy Prod expects these host files (separate from RealPath’s `keystore.properties`, so the two apps do not share one symlink target):
```text
/home/westfarn/.android/hesychia-upload.jks
/home/westfarn/.android/hesychia-keystore.properties # mode 600
```
Create the upload key **once**. If Play already has an upload key for `ai.hesychia.chat`, reuse that keystore. A second keystore will not upload.
```bash
keytool -genkey -v \
-keystore /home/westfarn/.android/hesychia-upload.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias aiml-chat-upload
```
`hesychia-keystore.properties` (`storeFile` may be absolute):
```properties
storeFile=/home/westfarn/.android/hesychia-upload.jks
storePassword=...
keyAlias=aiml-chat-upload
keyPassword=...
```
The workflow symlinks that file to `llm-fe/android/keystore.properties` and writes `android/local.properties` with `sdk.dir=/home/westfarn/Android/Sdk`. For a local release build, put the same `keystore.properties` in `llm-fe/android/` (gitignored).
Also on the runner: JDK 17+ and Android SDK Platform 35 at `/home/westfarn/Android/Sdk`.
Gitea Actions secret on `chat_web_app`: `GITHUB_TOKEN` (repo release write). The workflow maps it to `GITEA_TOKEN` for version lookup and release upload. Purchase verification uses backend `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON`, not this keystore.
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. **Account deletion URL** (Data safety / Account deletion) → public page:
- Canonical: `https://hesychia.ai/account-deletion/`
- Legacy host until DNS cutover: `https://chat.aimloperations.com/account-deletion/`
- Capacitor HashRouter: `/#/account-deletion/`
4. Data safety form (JWT auth, chat content, analytics if Tianji runs in WebView)
5. Screenshots for phone (+ tablet if targeting)
6. Upload `.aab` to **internal testing** track before production
In-app delete remains Account → **Delete my account** (`DELETE /api/user/`). The public URL
above is for Play reviewers and users who never open the app (#103).
## 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
- [#103](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/103) — Play public account-deletion URL