Uppdatera arkitekturen för M2 (offline-först, synk-kö, vilotimer, API-tillägg)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kf4MAbZ3c3B4Xy5XfuGsCP
100
Home.md
100
Home.md
@@ -4,57 +4,77 @@ Android-klient (Android 12+) för gym-API:t — samma backend som gym-delen i
|
|||||||
[brasse-pc.eu-v2](https://gitea.brasse-pc.eu/brasse/brasse-pc.eu-v2).
|
[brasse-pc.eu-v2](https://gitea.brasse-pc.eu/brasse/brasse-pc.eu-v2).
|
||||||
README:n täcker "få det att bygga"; här ligger arkitektur och designbeslut.
|
README:n täcker "få det att bygga"; här ligger arkitektur och designbeslut.
|
||||||
|
|
||||||
## Arkitektur
|
## Arkitektur (M2)
|
||||||
|
|
||||||
```
|
```
|
||||||
MainActivity (Compose, edge-to-edge)
|
MainActivity (Compose, edge-to-edge)
|
||||||
└── FitnessDroidApp — växlar på AuthState (Restoring/LoggedOut/LoggedIn)
|
└── FitnessDroidApp — växlar på AuthState
|
||||||
├── LoginScreen — användarnamn/lösenord + API-url under "Avancerat"
|
├── LoginScreen
|
||||||
└── HomeScreen — profil + kommande flikar (pass, logg, statistik, PB)
|
└── AppRoot — bottenrad (Hem/Pass/Statistik/Profil) + start-FAB + minispelare
|
||||||
|
├── HomeScreen — starta fritt pass, favoritpass/mallar, synk-chip
|
||||||
|
├── SessionScreen — passläget: HorizontalPager per övning, steppers,
|
||||||
|
│ vilotimer (helskärm/banner), synk-badge per set
|
||||||
|
├── ExercisePicker — sök + muskelgrupp/muskel-chips, favoriter först
|
||||||
|
├── HistoryScreen — avslutade pass (direkt mot API:t)
|
||||||
|
├── ProfileScreen — profil + Inställningar + logga ut
|
||||||
|
└── SettingsScreen — vilotimerstil, larmläge, standardvila
|
||||||
|
|
||||||
AppContainer (manuell DI i Application)
|
AppContainer (manuell DI)
|
||||||
├── TokenStore — DataStore; username/sessionId/jwt/refreshToken/expiration/apiUrl
|
├── TokenStore / SettingsStore (DataStore)
|
||||||
├── GraphQlClient — OkHttp POST {query,variables} + Bearer-header
|
├── GraphQlClient (OkHttp; 5xx → IOException = nätfel, inte auth-fel)
|
||||||
├── AuthRepository — login/restore/refresh/logout, exponerar StateFlow<AuthState>
|
├── AuthRepository — login/refresh/restore; loggar INTE ut vid nätfel
|
||||||
└── GymApi — autentiserade queries (myProfile, växer per milstolpe)
|
├── AppDatabase (Room) — LocalSession/-Exercise/-Set, PendingOp,
|
||||||
|
│ CachedExerciseType/MuscleGroup/Muscle/StartCard
|
||||||
|
├── SyncEngine — FIFO-op-kö, retry m. backoff, server-id-mappning
|
||||||
|
├── GymRepository — lokal-först-fasad som UI:t pratar med
|
||||||
|
└── RestTimerController — absolut sluttid, larm enligt inställning
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Offline-modellen
|
||||||
|
|
||||||
|
Passet lever i Room. Varje åtgärd (starta pass, lägg övning, logga set,
|
||||||
|
avsluta) blir en rad i `pending_op` som SyncEngine kör i strikt ordning mot
|
||||||
|
GraphQL-API:t; server-id:n skrivs tillbaka på raderna. Ej synkade set får en
|
||||||
|
moln-upp-ikon, synkade en moln-check; hemskärmen visar global status
|
||||||
|
(Synkat / N osynkat / Offline / Synkfel). Uppdateras ett set som ännu inte
|
||||||
|
synkats bär den väntande ADD-opsen automatiskt de nya värdena (den läser
|
||||||
|
raden när den körs). Referensdata (övningar, muskler, favoritkort) cachas
|
||||||
|
och överlever offline; senaste prestation per övning cachas för förifyllnad.
|
||||||
|
|
||||||
|
## Vilotimern
|
||||||
|
|
||||||
|
Startar automatiskt när ett set loggas. Längd: övningens
|
||||||
|
`defaultRestSeconds` från API:t, annars appens standard (inställning).
|
||||||
|
Presentation enligt inställning: helskärms-takeover eller banner längst ner.
|
||||||
|
Vid utgång: larmljud / vibration / bara visuellt / inget (inställning).
|
||||||
|
Räknar mot absolut sluttid så den överlever pausad app.
|
||||||
|
|
||||||
|
## API-tillägg (2026-07-24)
|
||||||
|
|
||||||
|
- `MuscleGroup.iconKey` — stabil ikonnyckel ("chest", "back", "legs",
|
||||||
|
"shoulders", "arms", "core", "cardio", "fullbody"); null → namnheuristik
|
||||||
|
i appen (`MuscleIcons.kt`)
|
||||||
|
- `ExerciseType.defaultRestSeconds` — förslagen vilotid; null → appens standard
|
||||||
|
- Migration `20260724000000_AddIconKeyAndDefaultRestSeconds` (körs automatiskt
|
||||||
|
vid API-uppstart)
|
||||||
|
|
||||||
## Designbeslut
|
## Designbeslut
|
||||||
|
|
||||||
- **Handskrivna GraphQL-strängar** (samma som webbens `gymTrackerApi.js`)
|
- **Handskrivna GraphQL-strängar** (samma som webbens `gymTrackerApi.js`)
|
||||||
i stället för Apollo-codegen — API-ytan ägs av samma person, och parity
|
- **Auth speglar webben:** JWT 2h + roterande refresh-token 7d, auto-refresh
|
||||||
med webben är enklast att verifiera strängvis. Kan omprövas när fler
|
2 min före utgång bakom mutex; 5xx/nätfel loggar aldrig ut
|
||||||
queries tillkommit.
|
- **Manuell DI**, **Material You** (minSdk 31), **versionCode = CI-byggnummer**
|
||||||
- **Auth speglar webben:** JWT (2 h) + roterande refresh-token (7 d) +
|
- Favoritpass startas genom att kopiera övningslistan klient-side
|
||||||
sessions-id; förnyas automatiskt 2 min före utgång bakom en mutex
|
(fungerar offline); mallar ger även auto-progression via API:t
|
||||||
(`AuthRepository.bearerToken()`). Vid nätfel vid appstart släpps
|
|
||||||
användaren in om JWT:n ännu är giltig.
|
|
||||||
- **Manuell DI** — appen är för liten för Hilt; `AppContainer` i
|
|
||||||
`FitnessDroidApplication`.
|
|
||||||
- **Material You** — dynamiska färger kan användas villkorslöst eftersom
|
|
||||||
minSdk är 31.
|
|
||||||
- **versionCode = GITHUB_RUN_NUMBER** i CI så att varje release-APK räknas
|
|
||||||
som uppdatering på telefonen.
|
|
||||||
|
|
||||||
## Signering
|
## Signering & CI
|
||||||
|
|
||||||
Nyckeln är inte incheckad (publikt repo). Lokalt: `signing/` (gitignorerad).
|
Se README + [infra-Doc](https://gitea.brasse-pc.eu/brasse/infra-Doc):
|
||||||
CI: secrets `KEYSTORE_B64` + `KEYSTORE_PASSWORD` skrivs till samma filer i
|
nyckeln aldrig incheckad (Actions-secrets), CI bygger på Pi5/arm64 med
|
||||||
jobbet. Utan nyckel faller release-bygget tillbaka på debug-signering —
|
statisk arm64-aapt2 via `android.aapt2FromMavenOverride`.
|
||||||
bygger, men kan inte uppdatera en installation från släppsidan.
|
|
||||||
|
|
||||||
## CI på arm64 (Pi5-runnern)
|
|
||||||
|
|
||||||
Googles aapt2 distribueras bara för linux x86_64. Bygget använder därför en
|
|
||||||
statiskt byggd arm64-aapt2 från
|
|
||||||
[lzhiyong/android-sdk-tools](https://github.com/lzhiyong/android-sdk-tools)
|
|
||||||
via `-Pandroid.aapt2FromMavenOverride=…`. Allt annat i Android-bygget
|
|
||||||
(kotlinc, d8, apksigner) är JVM-baserat och kör nativt på arm64.
|
|
||||||
cmdline-tools + `platforms;android-35` installeras i jobbet; gradle-cache
|
|
||||||
via `actions/cache`.
|
|
||||||
|
|
||||||
## Milstolpar
|
## Milstolpar
|
||||||
|
|
||||||
Se [doc/plan.md](https://gitea.brasse-pc.eu/brasse/FitnessDroid/src/branch/main/doc/plan.md)
|
Se [doc/plan.md](https://gitea.brasse-pc.eu/brasse/FitnessDroid/src/branch/main/doc/plan.md).
|
||||||
— 1: grund+login (klar), 2: aktivt pass, 3: passhistorik, 4: lyftlogg+PB,
|
1 (grund+login) och 2 (aktivt pass, offline, vilotimer) är klara.
|
||||||
5: statistik, 6: mallar, 7: grunddata+profil.
|
Näst: passhistorik-detaljer, lyftlogg+PB, statistik med kroppskarta, mallar.
|
||||||
|
|||||||
Reference in New Issue
Block a user