Ship Android app by wrapping the web build with Capacitor (#20) (#28)
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:
2026-07-26 17:12:48 -07:00
parent b7f23eb16c
commit a4a28ef13e
94 changed files with 5541 additions and 9 deletions
+131
View File
@@ -0,0 +1,131 @@
# Hesychia — Android
Native Capacitor project for `com.aimloperations.chat`. Web UI comes from the CRA
`build/` output in the parent `llm-fe/` package (synced with `npx cap sync`).
Broader Capacitor notes (env files, icons, QA): [`../ANDROID.md`](../ANDROID.md).
## Prerequisites
- Node.js **20**
- JDK **17+**
- [Android Studio](https://developer.android.com/studio) with SDK Platform **35**
- `ANDROID_HOME` set, or open this folder once in Android Studio (writes `local.properties`)
## Build the web bundle and sync
From the **npm root** (`llm-fe/`, one level up):
```bash
cd ..
npm ci
npm run build:mobile # .env.mobile → react-scripts build → cap sync android
```
Open this project in Android Studio:
```bash
npm run android:open
# or: npx cap open android
```
Re-sync after changing web code (with an existing `build/`):
```bash
npm run android:sync
```
Backend URLs are baked in at web-build time via `../.env.mobile` (prod by default).
## Run on a device / emulator
### Android Studio
1. Sync Gradle when prompted.
2. Pick an emulator or USB device (enable USB debugging).
3. Run **app**.
### CLI (debug APK)
```bash
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
## Version before a store release
Edit `app/build.gradle``defaultConfig`:
| Field | Rule |
|-------|------|
| `versionName` | User-visible string; keep in sync with `../package.json` `version` |
| `versionCode` | Integer; **must increase** for every Play upload |
## Sign a release build
**Do not commit keystores or passwords.**
1. Create an upload keystore (once), outside the repo:
```bash
keytool -genkey -v -keystore ~/Documents/secrets/aiml-chat-upload.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias aiml-chat-upload
```
2. Create `keystore.properties` in **this** directory (`android/`, gitignored):
```properties
storeFile=/absolute/path/to/aiml-chat-upload.jks
storePassword=REPLACE_ME
keyAlias=aiml-chat-upload
keyPassword=REPLACE_ME
```
3. Build a signed App Bundle:
```bash
./gradlew bundleRelease
```
Output:
```text
app/build/outputs/bundle/release/app-release.aab
```
Signed APK (if needed):
```bash
./gradlew assembleRelease
# → app/build/outputs/apk/release/app-release.apk
```
Without `keystore.properties`, release builds are unsigned; debug builds do not need it.
## Deploy to Google Play
1. [Play Console](https://play.google.com/console) → app **Hesychia** (`com.aimloperations.chat`).
2. Complete store listing: privacy policy URL, data-safety form, screenshots.
3. **Testing → Internal testing** → create release → upload `app-release.aab`.
4. Add testers, roll out internal track, verify on devices.
5. Promote to closed / open / production only after internal QA passes.
Each new upload needs a higher `versionCode`.
## Useful Gradle tasks
| Task | Purpose |
|------|---------|
| `./gradlew assembleDebug` | Debug APK |
| `./gradlew assembleRelease` | Release APK (signed if props present) |
| `./gradlew bundleRelease` | Play Store `.aab` |
| `./gradlew clean` | Clean build outputs |
## App identity
| | |
|--|--|
| Application ID | `com.aimloperations.chat` |
| Display name | Hesychia |
| Min / target SDK | 23 / 35 (see `variables.gradle`) |