## Summary - The Capacitor shell buys through StoreKit (iOS 15) and Play Billing instead of RevenueCat. - After the store sheet succeeds, the app POSTs the signed transaction or Play purchase token to `/monetization/store/confirm/`. - Restore reads current store entitlements and confirms each one. Web checkout stays on Stripe. - The store account token from `GET /monetization/subscription/` is sent as `appAccountToken` / `obfuscatedAccountId`. Depends on the `chat_backend` PR for issue #78 (`feat/78-native-store-purchases`). ## Test plan - [x] `storeBilling`, `revenueCat`, `finance`, and `BillingSection` unit tests - [ ] iOS sandbox purchase and restore against a backend with `APPLE_IAP_BUNDLE_ID` set - [ ] Play license-test purchase and restore against a backend with `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` set - [ ] Web billing still opens Stripe Checkout Reviewed-on: #122
146 lines
5.1 KiB
Markdown
146 lines
5.1 KiB
Markdown
# 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
|
|
|
|
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. **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
|