trainlog/docs/android.md

385 lines
12 KiB
Markdown

# Android application
## 1. Purpose
The Android application is Trainlog's low-friction capture client.
It is a native Kotlin/Jetpack Compose application with local SQLite persistence.
The desktop remains the canonical long-term history and analytics store.
## Session exchange V2
Completed session occurrences persist an `entry_id`; it is never regenerated
for exchange. Android publishes `trainlog-mobile-export-v2.json` as the active
desktop snapshot and imports `trainlog-pc-mobile-export-v2.json` after the PC
catalogue. The artifact preserves occurrence order, continuous metrics, set
weights and equipment. The legacy V1 contract remains separate and readable.
## 2. Implemented navigation
```text
Accueil
├── Reprendre la séance en cours (si un brouillon existe)
├── Enregistrer une séance
├── Enregistrer un exercice
├── Enregistrer des mensurations
├── Historique des séances
└── Synchroniser avec le PC
```
## 3. Local persistence
Android local database version:
```text
7
```
Domain tables cover:
```text
exercises
sessions
session_exercises
performed_sets
continuous_activity
body_observations
```
This database is Android-local. It is not copied to the PC.
Schema v4 introduced `active_session_draft`, `draft_session_exercises`,
`draft_performed_sets` and `draft_continuous_activity`. The implemented
additive v4 -> v7 chain preserves catalog, completed sessions/actuals, body
observations and the draft while adding the shared equipment catalogue,
occurrence-level equipment links and stable completed/draft `entry_id` values.
Exactly one active draft is supported; it is separate from completed history.
## 4. Exercise catalog
Exercise creation records:
```text
name
recording_mode
tracking_mode
data_fields
```
Stable identity:
```text
ex_<uuid-v4>
```
The UI rejects invalid profile combinations and local normalized-name
collisions.
An exercise may be created standalone or inline while building a session.
### Editing an exercise
Every existing catalog item exposes **Modifier**. Editing a name trims its
input, recomputes `normalized_name`, and rejects a normalized-name collision.
The row retains its existing `exercise_id`; naming is presentation metadata,
not identity. Completed session rows and the active draft retain their catalog
row relationship and immediately resolve the renamed display text after reopen.
Profile fields (`recording_mode`, `tracking_mode`, `data_fields`) are editable
only while an exercise has no completed-session or active-draft reference. Once
referenced, Android displays the lock and returns an explicit incompatible
profile result rather than silently reinterpreting work or creating another
exercise. Renaming remains available independently.
The shared Compose `TrainlogScreen` header is used by Accueil, Séance,
Exercice, Mensurations, Historique, Détail séance and Sync. Its compact
`◆ TRAINLOG ◆` accent plaque and muted subtitle intentionally mirror the
Notcurses TUI identity in a flat mobile layout.
## 5. Session recording
Stable session identity:
```text
se_<uuid-v4>
```
Session entry is profile-aware.
### Sets + repetitions
Actual set values may be heterogeneous.
Compact entry supports:
```text
5x10
4,5,6,7,8,9,10,9,8,7,6,5,4
4..10..4
```
### Sets + duration
Each performed set stores its own duration.
### Continuous + duration
The form asks for duration and only the configured supplemental fields such as
speed or distance.
Continuous work does not create fake sets.
### Occurrences, equipment and actual loads
The same catalogue `exercise_id` may be added more than once to a session.
Every occurrence receives its own stable `entry_id`, position, actual sets and
optional equipment selection. Editing one occurrence replaces only that entry;
it does not merge or alter another passage of the same exercise.
`Machine / équipement (optionnel)` searches the shared manifest by display
name, physical-machine label and aliases. A selected equipment identity is
stored on that occurrence in both the active draft and completed session.
For `SETS + REPS`, `Charge (kg)` accepts one value for all sets or `;`-separated
per-set values; French decimal commas are accepted. `Assistance (kg)` is an
explicit alternative load semantic, not an external charge. Empty load and an
entered zero remain distinct.
## 6. Session draft editing
The repository durably saves every meaningful mutation, including session type,
exercise selection/addition/removal, actual values and raw form edits. Partial
text such as `4,5,6,` is retained without normalization. A failed write displays
a specific error and does not claim the latest change was saved.
Home shows **Reprendre la séance en cours** and an exercise-count/type summary.
The ordinary new-session action opens an existing draft without overwriting it.
Back returns Home and preserves the draft. Backgrounding, switching apps,
Activity/configuration recreation, background process death and force-stop with
relaunch preserve the draft; these paths were validated on the Samsung SM_G990B.
**Retirer <exercice>** removes only that draft exercise and its actual values.
It does not change the catalog or completed history. Removal survives restart.
**Supprimer la séance en cours** requires deliberate confirmation; cancellation
preserves the draft. Confirmed deletion leaves no completed session or stale
resume action after relaunch.
Final save validates the durable draft, inserts the completed session and actual
values, and removes the draft in one SQLite transaction. Failure rolls back and
retains the draft for retry; repeated completion does not create duplicates.
The existing completed-session save-time timestamp behavior is unchanged.
PC catalog reconciliation preserves draft references through catalog row
ownership. If an editing selection no longer resolves, only the selection is
cleared; added exercises and raw text remain, with a specific diagnostic.
When a received or exported catalog entry has the same `exercise_id`, a changed
display name is reconciled in that same row. A different-ID normalized-name
collision is rejected, so a rename cannot become a duplicate exercise.
## 7. Session history
Android exposes persisted local session history and profile-aware detail.
Set-based history renders ordered performed sets.
Continuous history renders its one activity record with configured supplemental
values.
## 8. Body measurements
Supported metrics:
```text
weight
neck
shoulders
chest
waist
hips
left/right arm
left/right forearm
left/right thigh
left/right calf
```
Rules:
```text
empty field = not measured
at least one positive metric required
comma or dot accepted for decimal entry
```
Stable identity:
```text
bo_<uuid-v4>
```
## 9. Automatic mobile snapshot
Android maintains:
```text
Download/Trainlog/trainlog-mobile-export-v2.json
```
The V2 snapshot is refreshed after relevant local changes, including exercise,
session, body-observation, equipment association and PC-catalog updates. It
preserves `entry_id`, occurrence position, optional equipment and actual
per-set weights. Android also publishes the V2 companion
`trainlog-equipment-associations-v2.json`; its `set` and `cleared` states are
targeted by `(session_id, entry_id)`.
The user does not need a separate manual export step before synchronization.
An active draft is never included in completed history, session detail or this
snapshot. Synchronization continues to exchange completed data while the draft
stays local; no draft fields were added to the frozen mobile artifact.
## 10. PC catalog access
PC-created files are accessed through a persistent Storage Access Framework
grant.
The selected folder must be:
```text
Download/Trainlog
```
The Sync screen always permits changing the stored folder selection.
No application-data reset is required to fix a wrong folder choice.
## 11. Android-triggered synchronization
The Sync screen exposes:
```text
Synchroniser maintenant
```
Android writes:
```text
trainlog-sync-request-v1.json
```
and waits for a matching:
```text
trainlog-sync-receipt-v1.json
```
The receipt is matched by `request_id`.
On success Android then applies the latest PC catalog and displays the final
result.
A receipt belonging to another request is ignored as pending rather than
misreported as the current result.
## 12. Synchronization ownership
Android does not initiate raw MTP operations itself.
MTP is host-initiated:
```text
Android request
-> PC trainlog-syncd
-> shared desktop sync engine
-> receipt
```
## 13. Build
Example local configuration:
```bash
cd android
printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties
JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
./gradlew assembleDebug
```
Install to a connected test device:
```bash
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
`local.properties` is local machine configuration and must not be committed.
Host regression suite: 8 tests. Device instrumentation: 5 tests (2 repository,
3 production-screen UI tests using an isolated database and no shared export).
The real device matrix additionally exercised production `MainActivity`,
including verified process exit with `am kill`, force-stop, configuration
relaunch, raw-form recovery, removal, discard and unchanged user data. Final-save
UI checks use isolated data so fictitious workouts do not enter user history.
See [tests](tests.md) for commands and the precise validation boundary.
## 14. Non-goals
Android is not intended to own:
- canonical long-term analytics;
- complex body/performance graphs;
- cloud accounts;
- direct SQLite-file synchronization;
- exercise-name heuristics;
- a mounted-filesystem dependency.
## 15. Test-max sessions
## 16. Equipment and multi-occurrence exchange V2
During exercise entry, `Machine / équipement (optionnel)` searches the shared
catalogue by display name, physical-machine label and aliases. The selected
canonical ID belongs to that session exercise entry, is durable in the active
draft and completed session, and is visible in session detail. It may be
cleared. The active V2 exchange preserves multiple ordered occurrences of the
same exercise in one session through `entry_id`. The frozen V1 artifacts remain
readable only as legacy artifacts and keep their historical one-exercise
identity assumptions; V1 is not rewritten to claim V2 support.
For a `SETS + REPS` exercise, selecting equipment never changes that exercise
profile: the form retains per-set repetitions and exposes `Charge (kg)`. French
decimal input is accepted (`12,5`); one value applies to all sets or values may
be separated with `;`. Assisted equipment is explicitly labelled
`Assistance (kg)`. Empty load remains distinct from an entered zero.
`Nouvelle machine` in that same selector creates a persistent local custom
equipment entry with a generated stable `eq_…` ID and selects it immediately.
The shared bundled catalogue is synchronized by canonical IDs; a custom ID is
not silently converted to null on the PC and is rejected until its definition
is available to the receiving catalogue.
The active-session list exposes `Modifier <exercice>`; saving replaces that
entry in place, while cancelling only discards the form and preserves it.
Android session entry exposes:
```text
Entraînement
Test max
```
The selection is persisted in the existing Android `sessions.session_type`
column and exported in the mobile snapshot as:
```text
training
max_test
```
History and detail visibly identify max-test sessions.
Selecting `Test max` is explicit metadata; Trainlog does not infer max tests
from large repetition or duration values.
Android's current session form still records the exercise data fields it
supports. Measured-max classification on the desktop uses only actual values
that were truly captured and synchronized.