docs(android): record M6 (release mechanics) landed #19
@@ -1,6 +1,6 @@
|
|||||||
# Android App — Plan
|
# Android App — Plan
|
||||||
|
|
||||||
Status: **M0–M5 landed; the functional build + design pass are complete (M6 release hardening next).** This document is the
|
Status: **M0–M6 landed; the functional build, design pass, and release mechanics are complete (cut the v1 tag, then M7 push notifications).** This document is the
|
||||||
design contract for the `RunicGateway/Android-app` repo. It was written before implementation so the
|
design contract for the `RunicGateway/Android-app` repo. It was written before implementation so the
|
||||||
API changes it depends on could be landed in `website/` and `docs/` first. The authoritative API
|
API changes it depends on could be landed in `website/` and `docs/` first. The authoritative API
|
||||||
reference is the committed OpenAPI spec at `website/server/swagger/swagger-output.json` (regenerated
|
reference is the committed OpenAPI spec at `website/server/swagger/swagger-output.json` (regenerated
|
||||||
@@ -70,10 +70,11 @@ plain credential `401`), the `SessionManager` lifecycle over a fake store, and t
|
|||||||
+ role mapping. **No backend/API change** — the app is a pure consumer of the existing mobile bearer +
|
+ role mapping. **No backend/API change** — the app is a pure consumer of the existing mobile bearer +
|
||||||
`/auth/me` surface.
|
`/auth/me` surface.
|
||||||
|
|
||||||
> **Biometric app-lock — deferred (decided at M3).** §4.3/§9 flag an *optional* biometric app-lock;
|
> **Biometric app-lock — descoped from v1 (decided at M6).** §4.3/§9 flagged an *optional* biometric
|
||||||
> it is **deferred to M6 (release hardening)**. Tokens are already encrypted at rest (Tink/AES-256-GCM),
|
> app-lock, deferred from M3 to M6. At M6 it was **descoped from v1 entirely**: tokens are already
|
||||||
> so an app-lock is a UX layer, not a security requirement, and adding it in the functional pass would
|
> encrypted at rest (Tink/AES-256-GCM), so an app-lock is a pure UX convenience, not a security
|
||||||
> widen scope without changing the data flow. Revisit as an opt-in setting during M6.
|
> requirement, and it changes no data flow. It is **not** in the first release; revisit only if it
|
||||||
|
> becomes a requested feature.
|
||||||
|
|
||||||
✅ **M4 — player self-service & game data** (2026-07-19, `RunicGateway/Android-app#9`, functional Kotlin
|
✅ **M4 — player self-service & game data** (2026-07-19, `RunicGateway/Android-app#9`, functional Kotlin
|
||||||
pass): the signed-in player surface, all as a pure consumer of the existing bearer-gated API.
|
pass): the signed-in player surface, all as a pure consumer of the existing bearer-gated API.
|
||||||
@@ -115,7 +116,27 @@ launch theme and system bars are darkened so the first frame matches (no white f
|
|||||||
`:app:assembleDebug` + `:app:testDebugUnitTest` (green); an on-device visual pass against the mockup is
|
`:app:assembleDebug` + `:app:testDebugUnitTest` (green); an on-device visual pass against the mockup is
|
||||||
the one open QA item noted on the PR.
|
the one open QA item noted on the PR.
|
||||||
|
|
||||||
**The functional build (M0–M4) and design pass (M5) are complete; M6 (release hardening) is next.**
|
✅ **M6 — polish & release mechanics** (2026-07-20, `RunicGateway/Android-app#11`): the release
|
||||||
|
plumbing to ship v1 as a signed, sideloadable APK, with **no architecture, data-flow, or endpoint
|
||||||
|
change**. **Default brand app icons** — a gateway-medallion adaptive launcher icon (all densities +
|
||||||
|
round + Play Store icon) over the deep-indigo brand background (the Image Asset wizard's default
|
||||||
|
green grid was replaced, and the legacy square/round bitmaps + 512 Play icon recomposited to match);
|
||||||
|
plus an "RG" notification icon staged for M7. A **version-mismatch guard** (§3): the first-run connect
|
||||||
|
probe refuses a backend whose API version this build can't speak (a future `v2`) with a clear
|
||||||
|
"app out of date" message rather than mis-rendering (lenient on an older backend that omits `api`).
|
||||||
|
**Release build hardening** (§7, §12) — R8 full-mode minify + resource shrink (~31 MB debug → ~4 MB
|
||||||
|
signed release) with keep-rules for the kotlinx.serialization serializers, the wire DTOs, and the
|
||||||
|
Retrofit interfaces; a release `signingConfig` that reads keystore material from a **gitignored**
|
||||||
|
`keystore.properties` or env vars (absent → unsigned; the keystore is never committed); and
|
||||||
|
`versionName`/`versionCode` overridable via `-P` so a release tag + CI run number drive them (§10).
|
||||||
|
**CI `release.yml`** — on a `v*` tag, builds a **signed** APK (keystore decoded from a base64 Gitea
|
||||||
|
secret) and attaches it + `SHA256SUMS` to a Gitea release; `workflow_dispatch` is a signing dry run.
|
||||||
|
HTTPS-only in release (M1), no token logging (logging is debug-gated, M3), and the Settings → Server
|
||||||
|
hard reset (M3) were already in place. Biometric app-lock is **descoped from v1** (see the note below).
|
||||||
|
|
||||||
|
**The functional build (M0–M4), design pass (M5), and release mechanics (M6) are complete. Cutting
|
||||||
|
the first `v*` release tag (once the signing secrets are set + the on-device QA pass is done) and M7
|
||||||
|
push notifications are what remain.**
|
||||||
|
|
||||||
**Prerequisite progress (§8):** all v1 prerequisites are **done** (2026-07-19) — ✅ password reset
|
**Prerequisite progress (§8):** all v1 prerequisites are **done** (2026-07-19) — ✅ password reset
|
||||||
(item 2; website#75 + docs#8), ✅ role-agnostic `/auth/me/*` self surface (item 1; website#76 + docs#10),
|
(item 2; website#75 + docs#8), ✅ role-agnostic `/auth/me/*` self surface (item 1; website#76 + docs#10),
|
||||||
@@ -285,8 +306,8 @@ completes them in a Custom Tab, then returns and signs in natively (§4.1):
|
|||||||
- **Logout:** `POST /auth/mobile/logout` `{ refreshToken?, all? }` (requires bearer) — this session or
|
- **Logout:** `POST /auth/mobile/logout` `{ refreshToken?, all? }` (requires bearer) — this session or
|
||||||
all sessions ("sign out everywhere").
|
all sessions ("sign out everywhere").
|
||||||
- **Storage:** access + refresh tokens live in EncryptedSharedPreferences, never in plain prefs/logs.
|
- **Storage:** access + refresh tokens live in EncryptedSharedPreferences, never in plain prefs/logs.
|
||||||
The base URL may live in plain DataStore; tokens must not. Optional **biometric app-lock** (available
|
The base URL may live in plain DataStore; tokens must not. An optional **biometric app-lock** was
|
||||||
cleanly at API 29) gates access to the stored session — decide at M3.
|
considered here but **descoped from v1** (tokens are already encrypted at rest; see the M3 note).
|
||||||
- **Role for the menu** comes from the login response `user.role` and is re-validated via
|
- **Role for the menu** comes from the login response `user.role` and is re-validated via
|
||||||
`GET /auth/me` on app resume (roles can change server-side; admin access is re-checked every
|
`GET /auth/me` on app resume (roles can change server-side; admin access is re-checked every
|
||||||
request on the backend, so the app treats role as *advisory for menu rendering* and lets the server
|
request on the backend, so the app treats role as *advisory for menu rendering* and lets the server
|
||||||
@@ -469,8 +490,8 @@ push, and Play (M6–M8) follow the designed app.
|
|||||||
4. **M3 — Auth (§4)** *(functional pass)*: native password+TOTP login (429 handling), token storage,
|
4. **M3 — Auth (§4)** *(functional pass)*: native password+TOTP login (429 handling), token storage,
|
||||||
refresh interceptor, logout, `/auth/me` role re-validation, the access-level menu. Custom-Tab
|
refresh interceptor, logout, `/auth/me` role re-validation, the access-level menu. Custom-Tab
|
||||||
**hand-offs** to the website for register / invite / password-reset / SSO (no native screens for
|
**hand-offs** to the website for register / invite / password-reset / SSO (no native screens for
|
||||||
those). Optional biometric app-lock. *Prerequisite:* the website password-reset flow (§8) is
|
those). (An optional biometric app-lock was considered here, then descoped from v1 at M6.)
|
||||||
already built.
|
*Prerequisite:* the website password-reset flow (§8) is already built.
|
||||||
5. **M4 — Player self-service & game data** *(functional pass)*: account management (via
|
5. **M4 — Player self-service & game data** *(functional pass)*: account management (via
|
||||||
`/auth/me/*`), game-account linking, own roster/characters/vendors/houses/sales — **text-only**
|
`/auth/me/*`), game-account linking, own roster/characters/vendors/houses/sales — **text-only**
|
||||||
presentation (§6.3).
|
presentation (§6.3).
|
||||||
@@ -482,9 +503,11 @@ push, and Play (M6–M8) follow the designed app.
|
|||||||
(§6.3) still holds — this is visual design of the data screens, not paperdoll art. **Landed**
|
(§6.3) still holds — this is visual design of the data screens, not paperdoll art. **Landed**
|
||||||
2026-07-20 (`RunicGateway/Android-app#10`): dark-only shard-website theme, Cinzel display face,
|
2026-07-20 (`RunicGateway/Android-app#10`): dark-only shard-website theme, Cinzel display face,
|
||||||
reusable pill/label/card/meter components; brand-accent seeding retained (see §9 build progress).
|
reusable pill/label/card/meter components; brand-accent seeding retained (see §9 build progress).
|
||||||
7. **M6 — Polish & first release**: settings (server switch = hard reset), version-mismatch guard,
|
7. **M6 — Polish & release mechanics**: settings (server switch = hard reset, done M3),
|
||||||
release build hardening (HTTPS-only, no token logging). No offline cache in v1 (§7). **Ship v1 as a
|
version-mismatch guard, release build hardening (HTTPS-only, no token logging, R8 minify + resource
|
||||||
signed APK attached to a Gitea release** (see §10).
|
shrink, release signing). No offline cache in v1 (§7). **Ships v1 as a signed APK attached to a Gitea
|
||||||
|
release** via `release.yml` on a `v*` tag (see §10). Biometric app-lock **descoped** (below).
|
||||||
|
**Landed** 2026-07-20 (`RunicGateway/Android-app#11`).
|
||||||
8. **M7 — Push notifications** (post-v1): add the self-hosted `ntfy` service to
|
8. **M7 — Push notifications** (post-v1): add the self-hosted `ntfy` service to
|
||||||
`website/docker-compose.yml` (declarative, zero-interaction config), UnifiedPush integration in the
|
`website/docker-compose.yml` (declarative, zero-interaction config), UnifiedPush integration in the
|
||||||
app, device registration, the subscriptions UI, and the content-free-tickle backend fan-out (see
|
app, device registration, the subscriptions UI, and the content-free-tickle backend fan-out (see
|
||||||
@@ -597,8 +620,8 @@ password+TOTP only, with registration/invite/reset/SSO **handled by the website*
|
|||||||
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 35 (§2); 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); **biometric app-lock deferred to M6** (tokens already encrypted at rest, so
|
ntfy / UnifiedPush (§11); **biometric app-lock descoped from v1** (tokens already encrypted at rest, so
|
||||||
it is opt-in UX, not a v1 requirement — decided at M3).
|
it is a UX convenience, not a v1 requirement — deferred at M3, descoped at M6; revisit only if requested).
|
||||||
|
|
||||||
**Still open:**
|
**Still open:**
|
||||||
- **App identity / domain.** Target application ID **`com.runicgateway.app`** — pending securing the
|
- **App identity / domain.** Target application ID **`com.runicgateway.app`** — pending securing the
|
||||||
|
|||||||
Reference in New Issue
Block a user