Ship Android app by wrapping the web build with Capacitor (#20) (#28)
Unit Tests / test (push) Successful in 11s
Unit Tests / test (push) Successful in 11s
## Summary - Closes #20 - Add Capacitor 7 to `llm-fe/` (`capacitor.config.ts`, appId `com.aimloperations.chat`, `webDir: build`) and commit the generated `android/` project - npm scripts: `build:mobile` (`.env.mobile` + CRA build + `cap sync`), `android:open`, `android:sync`, `assets:generate` - `.env.mobile` defaults to prod backend; override independently of web deploys - Native chrome: Android back button (history / exit at root), status bar + keyboard resize, safe-area CSS; Preferences registered for JWT mirror (#22) - Icons/splash via `@capacitor/assets`; signing via optional `keystore.properties`; docs in `llm-fe/ANDROID.md` + root README Blockers #22 / #23 / #24 already on master. ## Test plan - [x] `npm run test:ci` (79 tests) - [x] `npm run build` + `npx cap add android` / `cap sync` - [ ] `npm run build:mobile` + open in Android Studio on a machine with SDK - [ ] Emulator/device QA: login, chat stream, theme, back button, keyboard/safe-area, WS resume - [ ] Generate upload keystore outside repo; `./gradlew bundleRelease` for internal testing trackReviewed-on: #28
This commit was merged in pull request #28.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# Android (Capacitor)
|
||||
|
||||
Ship the CRA web build as an Android app via [Capacitor](https://capacitorjs.com/).
|
||||
One codebase: `llm-fe/` → web + Android WebView.
|
||||
|
||||
**Build & Play deploy steps:** [`android/README.md`](android/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`). Regenerate Android densities:
|
||||
|
||||
```bash
|
||||
npm run assets:generate
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user