docs(android): M8 step 1 built — API 36, the bundle and the Play upload #376
@@ -494,7 +494,7 @@ Native, Android-only:
|
|||||||
| Live feed | OkHttp SSE (`EventSource`) for `/public/shard/stream` |
|
| Live feed | OkHttp SSE (`EventSource`) for `/public/shard/stream` |
|
||||||
| Images | Coil |
|
| Images | Coil |
|
||||||
| Min SDK | **Android 10 (API 29)** — ~95% device reach with a modern baseline (biometric, storage, TLS) and no compat shims |
|
| Min SDK | **Android 10 (API 29)** — ~95% device reach with a modern baseline (biometric, storage, TLS) and no compat shims |
|
||||||
| Target/compile SDK | Latest stable (35) |
|
| Target/compile SDK | Latest stable (36, Android 16: Google Play's floor since 2026-08-31; M8) |
|
||||||
| Telemetry | **None in v1** — no crash/analytics SDK (privacy-first). Revisit self-hosted crash reporting later. |
|
| Telemetry | **None in v1** — no crash/analytics SDK (privacy-first). Revisit self-hosted crash reporting later. |
|
||||||
| Localization | **Strings externalized from day one** (`res/values/strings.xml`); English is the only bundled locale, but the structure invites community translations. No hardcoded UI strings. |
|
| Localization | **Strings externalized from day one** (`res/values/strings.xml`); English is the only bundled locale, but the structure invites community translations. No hardcoded UI strings. |
|
||||||
| Web hand-off | Chrome Custom Tabs — opens the website for registration / invite / password reset / SSO (§4.2) |
|
| Web hand-off | Chrome Custom Tabs — opens the website for registration / invite / password reset / SSO (§4.2) |
|
||||||
@@ -966,6 +966,30 @@ push, and Play (M6–M8) follow the designed app.
|
|||||||
**Order of work:**
|
**Order of work:**
|
||||||
1. **App + CI** (one PR into `edge`): API 36 + AGP bump, AAB, Play upload step. Walked on the
|
1. **App + CI** (one PR into `edge`): API 36 + AGP bump, AAB, Play upload step. Walked on the
|
||||||
emulator and the S22 before the PR.
|
emulator and the S22 before the PR.
|
||||||
|
|
||||||
|
**Built 2026-10-23.** AGP 8.13.2 (the last 8.x; 9.x changes the DSL) with the Gradle 8.14.3
|
||||||
|
wrapper; Kotlin, KSP and Hilt unchanged. AGP 8.13 still defaults to build-tools 35.0.0, so
|
||||||
|
the workflows only add `platforms;android-36`. The code had none of Android 16's hotspots:
|
||||||
|
no edge-to-edge opt-out, no `onBackPressed` override (Navigation Compose takes predictive
|
||||||
|
back), no orientation lock. The bundle goes on the Gitea release as well, which is where
|
||||||
|
step 2's hand upload takes it from. Checks: the debug gate (unit tests, lint, debug build)
|
||||||
|
passes; a release APK and bundle built with a throwaway keystore carry the same
|
||||||
|
certificate, `targetSdkVersion 36`, versionCode 601; the Play step ran against a mock of
|
||||||
|
the token and Play endpoints in `ubuntu:latest` (JWT signature checked against the key's
|
||||||
|
public half; success, a rejected token, a refused upload, a refused commit, a mismatched
|
||||||
|
versionCode, and an unset secret). Walked on an Android 16 emulator (API 36.1): connect,
|
||||||
|
sign in as admin, notification settings saved, edge-swipe back, Rust servers, Dashboard,
|
||||||
|
no crash; the minified release build refuses `http` and survives a TLS round trip. On the
|
||||||
|
`s22_ultra` emulator (API 35) the build updates in place, connects and opens the menu. Not
|
||||||
|
walked: the push foreground service (the walk site has no ntfy), and a signed-in release
|
||||||
|
build (no local HTTPS site); the internal-track install in step 2 covers both.
|
||||||
|
|
||||||
|
**Before the first CI upload (org lead, by hand):** in Google Cloud, a service account
|
||||||
|
with a JSON key, and the Google Play Android Developer API enabled; in Play Console →
|
||||||
|
Users and permissions, invite the service account's address with release rights on this
|
||||||
|
app only; then the whole JSON file as the `PLAY_SERVICE_ACCOUNT_JSON` secret on
|
||||||
|
`RunicGateway/Android-app`. Until a release has been rolled out by hand (step 2), Play
|
||||||
|
refuses CI's upload with "Only releases with status draft may be created on draft app".
|
||||||
2. **First bundle**, uploaded by hand from the next tag, with the signing key enrolled.
|
2. **First bundle**, uploaded by hand from the next tag, with the signing key enrolled.
|
||||||
3. **Console declarations**: target audience 18+ (runicgateway.com D31), advertising ID none,
|
3. **Console declarations**: target audience 18+ (runicgateway.com D31), advertising ID none,
|
||||||
data safety from runicgateway.com's generated `PLAY_DATA_SAFETY.md`, and sign-in details
|
data safety from runicgateway.com's generated `PLAY_DATA_SAFETY.md`, and sign-in details
|
||||||
@@ -2051,6 +2075,14 @@ repos use), on a bare `ubuntu:latest` container.
|
|||||||
Gitea release (mirrors `link/`'s release job). The **keystore is a base64 Gitea Actions secret**
|
Gitea release (mirrors `link/`'s release job). The **keystore is a base64 Gitea Actions secret**
|
||||||
decoded in CI; store/key passwords are secrets. The keystore never lives in the repo. Keep the
|
decoded in CI; store/key passwords are secrets. The keystore never lives in the repo. Keep the
|
||||||
signing identity stable from the first release (Play later requires consistency).
|
signing identity stable from the first release (Play later requires consistency).
|
||||||
|
- **Google Play** (M8, same workflow): the keystore also signs an app bundle (`bundleRelease`),
|
||||||
|
attached to the Gitea release beside the APK. After the Gitea release exists, the bundle goes to
|
||||||
|
Play's **internal** track through the Play Developer API v3 edit flow (open an edit → upload the
|
||||||
|
bundle → set the internal track → commit), with an OAuth token from a service-account JWT signed
|
||||||
|
by `openssl`. The key is the `PLAY_SERVICE_ACCOUNT_JSON` secret; while it is unset, the step
|
||||||
|
warns and skips. The step runs last, so a Play failure fails the run without holding back the
|
||||||
|
APK. Play rejects a `completed` release on an app that has never rolled one out ("Only releases
|
||||||
|
with status draft may be created on draft app"); the first bundle is therefore uploaded by hand.
|
||||||
- Semantic `versionName` + monotonic `versionCode`; tag releases.
|
- Semantic `versionName` + monotonic `versionCode`; tag releases.
|
||||||
|
|
||||||
### 12.1 Code quality — SonarQube (`Runic-Gateway-Android-app`)
|
### 12.1 Code quality — SonarQube (`Runic-Gateway-Android-app`)
|
||||||
@@ -2084,7 +2116,7 @@ Compose/nav cognitive-complexity + hoisted-callback param-count smells (idiomati
|
|||||||
|
|
||||||
**Decided (recorded here for context):** single shard per install (§3); native auth is
|
**Decided (recorded here for context):** single shard per install (§3); native auth is
|
||||||
password+TOTP only, with registration/invite/reset/SSO **handled by the website** (§4); **password
|
password+TOTP only, with registration/invite/reset/SSO **handled by the website** (§4); **password
|
||||||
reset built on backend + web first**, before app work (§8); minSdk 29, compile/target 35 (§2); no
|
reset built on backend + web first**, before app work (§8); minSdk 29, compile/target 36 (§2, raised from 35 at M8); no
|
||||||
telemetry in v1 (§2); strings externalized from day one, English-only bundled (§2); **text-only** game
|
telemetry in v1 (§2); strings externalized from day one, English-only bundled (§2); **text-only** game
|
||||||
data in v1, pretty paperdoll is future (§6.3); **no offline cache in v1** (§7); push via self-hosted
|
data in v1, pretty paperdoll is future (§6.3); **no offline cache in v1** (§7); push via self-hosted
|
||||||
ntfy / UnifiedPush (§11) with the **distributor embedded in the app — no second app to install**
|
ntfy / UnifiedPush (§11) with the **distributor embedded in the app — no second app to install**
|
||||||
|
|||||||
Reference in New Issue
Block a user