v1.3.5 : agenda réparé de bout en bout (remontée par les logs AppLog exportés) — #50 ensureCalendar : CALLER_IS_SYNCADAPTER exige account+account_type ; #50bis deleteEvent : même défaut, 'supprimé' logué mais event restant ; #51 buildTreatment perdait calendarEventId → événements orphelins ; log agenda véridique (3 messages) ; cycle ON→save→OFF→save validé émulateur ; 90 tests verts + lint
This commit is contained in:
parent
71659b5647
commit
ef0fdbb01b
@ -16,7 +16,7 @@ sauvegarde JSON. **100 % local, aucun compte, aucun serveur.**
|
||||
> à titre informatif — ce ne sont pas des mesures. Fie-toi toujours à tes analyses de
|
||||
> sang et aux consignes de ton endocrinologue.
|
||||
|
||||
- **Statut** : v1.3.4 — build Android ✅, **lint vert** ✅, **90 tests unitaires** ✅ (72 sans les données de test locales ; 3 régressions épinglées sur données réelles **non versionnées**), intégration montre = notifications ✅, **dépôt Gitea privé + releases avec APK** ✅
|
||||
- **Statut** : v1.3.5 — build Android ✅, **lint vert** ✅, **90 tests unitaires** ✅ (72 sans les données de test locales ; 3 régressions épinglées sur données réelles **non versionnées**), intégration montre = notifications ✅, **dépôt Gitea privé + releases avec APK** ✅
|
||||
- **Journal des versions** : [docs/CHANGELOG.md](docs/CHANGELOG.md)
|
||||
- **Guide utilisateur** : [docs/GUIDE_INSTALLATION.md](docs/GUIDE_INSTALLATION.md)
|
||||
- **Doc de développement** (architecture, maths, décisions, bugs) : [docs/DEVELOPPEMENT.md](docs/DEVELOPPEMENT.md)
|
||||
@ -85,7 +85,7 @@ Pas de Play Store : l'app est sideloadée. Détails pas-à-pas : [docs/GUIDE_INS
|
||||
|
||||
Dépôts : **gitea.cloudyfy.fr** et **gitea.farewell.dev** (miroir) —
|
||||
`Siphonight/HormoneTrack` sur les deux (privé), avec
|
||||
**releases taguées** (`v1.1.0` → `v1.3.4`) et **deux APK par release** (depuis v1.2.5)
|
||||
**releases taguées** (`v1.1.0` → `v1.3.5`) et **deux APK par release** (depuis v1.2.5)
|
||||
(téléchargeables sans compiler, cf [docs/DEVELOPPEMENT.md §16.bis](docs/DEVELOPPEMENT.md)) :
|
||||
`-release.apk` (**recommandé**, optimisé R8, 2,4 Mo) et `-debug.apk` (20 Mo) :
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ android {
|
||||
// tag : v1.3.0→v1.3.2 contenaient tous versionCode 14 / "1.3.0"
|
||||
// (bump jamais commité) → BuildConfig.VERSION_NAME était faux dans
|
||||
// les APK publiés (Paramètres + titre du dialog « Nouveautés »).
|
||||
versionCode = 18
|
||||
versionName = "1.3.4"
|
||||
versionCode = 19
|
||||
versionName = "1.3.5"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
@ -64,11 +64,25 @@ object CalendarEvents {
|
||||
* Retourne l'id du calendrier local « HormoneTrack » (le crée s'il manque).
|
||||
* Nécessite WRITE_CALENDAR (l'insertion d'un calendrier local se fait avec
|
||||
* CALLER_IS_SYNCADAPTER=true).
|
||||
*
|
||||
* ⚠️ BUG fixé (v1.3.5, #50) : un URI avec CALLER_IS_SYNCADAPTER DOIT aussi
|
||||
* embarquer ACCOUNT_NAME + ACCOUNT_TYPE — sinon le CalendarProvider lève
|
||||
* `IllegalArgumentException: Sync adapters must specify an account and
|
||||
* account type` sur la requête ou l'insertion. Vu dans les AppLog exportés
|
||||
* du Pixel 9 /e/OS v1.3.4 : la permission agenda était accordée (fix
|
||||
* manifest v1.3.3 ✓) et ensureCalendar plantait juste après.
|
||||
*/
|
||||
fun ensureCalendar(context: Context): Long {
|
||||
val calUri = CalendarContract.Calendars.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(
|
||||
CalendarContract.Calendars.ACCOUNT_NAME, CALENDAR_NAME
|
||||
)
|
||||
.appendQueryParameter(
|
||||
CalendarContract.Calendars.ACCOUNT_TYPE,
|
||||
CalendarContract.ACCOUNT_TYPE_LOCAL
|
||||
)
|
||||
.build()
|
||||
|
||||
context.contentResolver.query(
|
||||
@ -140,11 +154,26 @@ object CalendarEvents {
|
||||
return ContentUris.parseId(eventUriInserted)
|
||||
}
|
||||
|
||||
/** Supprime l'événement d'agenda du traitement (si présent). */
|
||||
/**
|
||||
* Supprime l'événement d'agenda du traitement (si présent).
|
||||
*
|
||||
* ⚠️ #50 bis (v1.3.5) : CALLER_IS_SYNCADAPTER exige AUSSI les paramètres
|
||||
* ACCOUNT_NAME/ACCOUNT_TYPE ici — sans eux le provider lève
|
||||
* IllegalArgumentException (avalée par le catch), et l'événement restait
|
||||
* dans le provider après « switch OFF » + save. Validé émulateur : la
|
||||
* suppression échouait silencieusement avant ce fix.
|
||||
*/
|
||||
fun deleteEvent(context: Context, eventId: Long) {
|
||||
val uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId)
|
||||
.buildUpon()
|
||||
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(
|
||||
CalendarContract.Calendars.ACCOUNT_NAME, CALENDAR_NAME
|
||||
)
|
||||
.appendQueryParameter(
|
||||
CalendarContract.Calendars.ACCOUNT_TYPE,
|
||||
CalendarContract.ACCOUNT_TYPE_LOCAL
|
||||
)
|
||||
.build()
|
||||
try {
|
||||
context.contentResolver.delete(uri, null, null)
|
||||
|
||||
@ -110,6 +110,12 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
|
||||
var showTimePicker by remember { mutableStateOf(false) }
|
||||
var loading by remember { mutableStateOf(treatmentId > 0) }
|
||||
var loadedCreatedAt by remember { mutableStateOf(System.currentTimeMillis()) }
|
||||
// ⚠️ BUG #51 (v1.3.5) : buildTreatment() ne transmettait PAS
|
||||
// calendarEventId → l'id revenait à null à CHAQUE save : l'événement
|
||||
// d'agenda devenait ORPHELIN (plus jamais supprimable/désactivable par
|
||||
// l'app, il restait dans le provider). L'id existant est chargé ici,
|
||||
// reporté par buildTreatment(), puis recalcité par le save (upsert).
|
||||
var loadedCalendarEventId by remember { mutableStateOf<Long?>(null) }
|
||||
|
||||
LaunchedEffect(treatmentId) {
|
||||
if (treatmentId > 0) {
|
||||
@ -129,6 +135,7 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
|
||||
reminderTime = LocalTime.of(tr.reminderHour ?: 12, tr.reminderMinute ?: 0)
|
||||
active = tr.isActive
|
||||
loadedCreatedAt = tr.createdAt
|
||||
loadedCalendarEventId = tr.calendarEventId
|
||||
// Fréquence de prévision (si configurée)
|
||||
tr.forecastIntervalDays?.let { fi ->
|
||||
forecastEnabled = true
|
||||
@ -172,6 +179,9 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
|
||||
reminderEnabled = reminderEnabled,
|
||||
reminderHour = if (reminderEnabled) reminderTime.hour else null,
|
||||
reminderMinute = if (reminderEnabled) reminderTime.minute else null,
|
||||
// ⚠️ #51 : conserver l'id de l'événement d'agenda existant (cf
|
||||
// déclaration de loadedCalendarEventId) — sinon orphelin au save.
|
||||
calendarEventId = loadedCalendarEventId,
|
||||
createdAt = loadedCreatedAt
|
||||
)
|
||||
}
|
||||
@ -535,8 +545,18 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
|
||||
repo.updateTreatment(saved.copy(calendarEventId = eventToStore))
|
||||
}
|
||||
AppLog.log("agenda",
|
||||
"traitement #${saved.id} : événement agenda " +
|
||||
if (eventToStore != null) "créé/mis à jour (id=$eventToStore)" else "supprimé")
|
||||
// Log VÉRIDIQUE (v1.3.5) : « supprimé » était
|
||||
// imprimé aussi quand rien n'a été supprimé
|
||||
// (switch désactivé, posologie absente,
|
||||
// permission absente) → messages distincts.
|
||||
"traitement #${saved.id} : " + when {
|
||||
eventToStore != null ->
|
||||
"événement agenda créé/mis à jour (id=$eventToStore)"
|
||||
saved.calendarEventId != null ->
|
||||
"événement agenda supprimé"
|
||||
else ->
|
||||
"agenda non activé (switch/posologie/permission)"
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
AppLog.log("agenda", "ERREUR événement agenda : ${e.message}")
|
||||
}
|
||||
|
||||
@ -1,7 +1,42 @@
|
||||
# Changelog — HormoneTrack
|
||||
|
||||
Format : [Keep a Changelog](https://keepachangelog.com/fr-FR/1.1.0/).
|
||||
Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.3.4`, …).
|
||||
Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.3.5`, …).
|
||||
|
||||
## [1.3.5] — 2026-09-06 (versionCode 19)
|
||||
|
||||
### Corrigé
|
||||
- **Création du calendrier d'agenda impossible** (remontée via les logs de
|
||||
diagnostic — le mécanisme v1.3.1 a tout de suite nourri le diagnostic
|
||||
à distance : `Sync adapters must specify an account and account type`) :
|
||||
l'URI d'insertion `CALLER_IS_SYNCADAPTER=true` du calendrier local
|
||||
« HormoneTrack » n'embarquait pas `ACCOUNT_NAME` + `ACCOUNT_TYPE` —
|
||||
exigé par le CalendarProvider pour toute opération sync-adapter
|
||||
(`CalendarEvents.ensureCalendar`, §14 #50). Ces mêmes logs prouvent aussi
|
||||
que **la permission agenda est désormais accordée** (fix manifest v1.3.3 ✓)
|
||||
et que **l'export des logs fonctionne sur le téléphone** (fix #48 ✓) —
|
||||
la chaîne de diagnostic est opérationnelle.
|
||||
- **Suppression d'événement d'agenda silencieusement manquée** (#50 bis) :
|
||||
l'URI de DELETE avait le même défaut (`CALLER_IS_SYNCADAPTER` sans params
|
||||
account) → « switch OFF » + save loguait « supprimé » mais l'événement
|
||||
restait dans le provider. Fix : mêmes paramètres sur l'URI de delete.
|
||||
Validé émulateur : cycle complet ON→save→OFF→save = événement créé puis
|
||||
réellement supprimé (`content query` vide).
|
||||
- **Événement d'agenda orphelin après tout re-save** (#51) :
|
||||
`TreatmentEditorScreen.buildTreatment()` ne transmettait pas
|
||||
`calendarEventId` → l'id revenait à `null` à chaque sauvegarde : après le
|
||||
premier save, l'événement devenait définitivement orphelin (plus jamais
|
||||
supprimable/désactivable par l'app). L'id existant est désormais chargé à
|
||||
l'ouverture et conservé par le save.
|
||||
- **Log « événement agenda supprimé » mensonger** : imprimé aussi quand rien
|
||||
n'a été supprimé (switch désactivé, posologie absente, permission absente).
|
||||
Désormais trois messages distincts : « créé/mis à jour (id=…) »,
|
||||
« supprimé », « non activé (switch/posologie/permission) ».
|
||||
|
||||
### Ajouté
|
||||
- Validation émulateur de l'agenda end-to-end (recette §16.ter, permissions
|
||||
accordées via `pm grant`) : calendrier + événement récurrent créés et
|
||||
visibles dans le provider (`content query`).
|
||||
|
||||
## [1.3.4] — 2026-09-06 (versionCode 18)
|
||||
|
||||
|
||||
@ -81,6 +81,7 @@ Fonctionnalités v1 :
|
||||
| 6 sept. 2026 (session farewell) | Repo créé côté farewell → **push SSH (alias `farewell` : giteassh:2222) + 12 releases publiées avec APK vérifiés par téléchargement** ; piège lexicographique `v1.2.10 < v1.2.5` en comparaison de chaînes épinglé (§14 #38) ; les deux instances Gitea sont synchrones. |
|
||||
| 6 sept. 2026 (session v1.3.3) | **Audit de reprise de maintenance** (nouvelle session IA) : 3 bugs racines trouvés — permissions agenda ABSENTES du manifest (jamais déclarées, §14 #44), export logs plantait TOUJOURS (le fix v1.3.2 réimplémentait l'IO au lieu de réutiliser BackupManager, §14 #45), bump de version JAMAIS commité (tags v1.3.0–1.3.2 tous `versionCode 14 / "1.3.0"`, §14 #46). Corrigés + doc rafraîchie (DB v3, targetSdk 36, régression 3 = export v1.3.1, §19/§20 staleness) ; **téléphone de test documenté : Google Pixel 9 /e/OS** (AOSP ; SAF DocumentsUI standard). |
|
||||
| 6 sept. 2026 (session v1.3.4) | **Deux crashs v1.3.3 remontés (Doses + export logs)** → reproduits sur ÉMULATEUR avec les vraies données (recette §16.ter, désormais standard) : (a) Doses = string `hrt_duration` 3 placeholders vs 2 args (**né en v1.3.1**, §14 #47) ; (b) export logs = `LocalDate.format("…-HHmm")` levant `UnsupportedTemporalTypeException` au TAP (§14 #48) — les fixes IO v1.3.2/v1.3.3 étaient à côté du vrai problème. Lint mis en filet bloquant (aurait attrapé les deux) + #49 notif ; helper `ExportFileNames` + 3 tests (90 verts) ; fixes VÉRIFIÉS sur émulateur avec l'APK release + données réelles (flux SAF complet : SAVE → fichier + message). Publication v1.3.4. |
|
||||
| 6 sept. 2026 (session v1.3.5) | **Premier diagnostic à distance RÉUSSI via les logs exportés** : les AppLog de la v1.3.4 fournis par l'utilisatrice prouvent export logs `ok=true` (fix #48 ✓ au téléphone), permission agenda accordée (fix manifest ✓) et révèlent le bug racine EXPLICITE : `ensureCalendar` plantait car l'URI sync-adapter n'embarquait pas ACCOUNT_NAME/ACCOUNT_TYPE (§14 #50). En validant sur émulateur, DEUX bugs découverts : delete d'event sans account (#50 bis — « supprimé » logué mais l'event restait) et `buildTreatment` sans `calendarEventId` (#51 — événements orphelins). Cycle ON→save→OFF→save validé end-to-end (`content query`). Publication v1.3.5. |
|
||||
| 6 sept. 2026 (session v1.3.0) | Dialog « Nouveautés » après mise à jour (CHANGELOG embarqué en asset, tâche `copyChangelog`, version vue en DataStore) ; **événements d'agenda récurrents** (calendrier local HormoneTrack, RRULE posologie, permission runtime, Room v3 `calendarEventId`) ; Paramètres : version + lien releases ; **78 tests verts**, APK v1.3.0 + releases. |
|
||||
| 5 sept. 2026 (session v1.2.10) | Sens des boutons de zoom inversé (+ = zoom avant, convention carte — retour utilisateur) ; 67 tests verts, APK v1.2.10 + releases. |
|
||||
| 5 sept. 2026 (session v1.2.9) | **Zoom du graphique** (pinch + boutons, 6 h → 300 j, focal stable, échantillonnage adaptatif `stepForRange`, labels X 1 h/3 h) ; **README : disclaimer IA-assisté** en en-tête ; 3 tests ; **67 tests verts**, APK v1.2.9 + releases. |
|
||||
@ -879,6 +880,48 @@ aucun contexte, dans l'esprit de cette doc) :**
|
||||
théoriques `NonObservableLocale`/`LocalContextGetResourceValueCall` sont
|
||||
tombés en warning via `app/lint.xml`, cf §19).
|
||||
|
||||
**Session v1.3.5 (diagnostic à distance via les logs exportés — la boucle par
|
||||
l'exemple) :**
|
||||
|
||||
50. **Création du calendrier d'agenda impossible** (`IllegalArgumentException:
|
||||
Sync adapters must specify an account and account type`, remontée par les
|
||||
AppLog exportés du Pixel 9 v1.3.4) : `CalendarEvents.ensureCalendar`
|
||||
construisait l'URI calendars avec `CALLER_IS_SYNCADAPTER=true` SANS
|
||||
`ACCOUNT_NAME`+`ACCOUNT_TYPE` — requis par le CalendarProvider pour TOUTE
|
||||
opération sync-adapter (requête ET insertion). De plus :
|
||||
- les mêmes logs CONFIRMAIENT au passage les fixes précédents : export
|
||||
logs `ok=true` (#48 ✓ depuis v1.3.4 au téléphone), permission agenda
|
||||
accordée (manifest v1.3.3 ✓) — AppLog fait son job de diagnostic à
|
||||
distance ;
|
||||
- le log « événement agenda supprimé » était imprimé aussi quand le
|
||||
switch était inéligible (posologie absente, permission absente,
|
||||
switch off) → trois messages distincts désormais (créé-mis à jour /
|
||||
supprimé / non activé) ;
|
||||
- contexte : les « événement agenda supprimé » répétés dans les logs =
|
||||
des saves où le switch ne remplissait pas les conditions d'activation
|
||||
(l'utilisatrice avait retourné le switch après les échecs) — les trois
|
||||
messages distincts rendront les prochains logs univoques.
|
||||
FIX : append ACCOUNT_NAME/ACCOUNT_TYPE sur l'URI ; le pattern complet :
|
||||
`syncadapter=true` + account + account_type sur les URIs de CRÉATION
|
||||
(calendriers) ; l'insertion d'EVENTS l'avait déjà (upsertEvent). Validé
|
||||
émulateur : `content query --uri content://com.android.calendar/calendars`
|
||||
montre le calendrier, events avec RRULE corrects.
|
||||
50.bis. **Delete d'événement silencieusement manqué** (cité au #50) : le même
|
||||
défaut (CALLER_IS_SYNCADAPTER sans account) sur l'URI de DELETE de
|
||||
`deleteEvent` — l'exception est avalée par le catch, le log dit
|
||||
« supprimé » mais l'event reste dans le provider. FIX : mêmes params
|
||||
sur l'URI. Règle : TOUT URI avec CALLER_IS_SYNCADAPTER doit embarquer
|
||||
account+account_type — pas seulement les insertions.
|
||||
51. **Événement orphelin après re-save** (découvert en validant #50 sur
|
||||
émulateur — le provider montrait un event que la base ne référençait
|
||||
plus) : `buildTreatment()` reconstruisait le Treatment SANS le champ
|
||||
`calendarEventId` → null à chaque save → l'event existant devenait
|
||||
orphelin dès la deuxième sauvegarde, plus jamais désactivable. FIX :
|
||||
`loadedCalendarEventId` chargé dans le LaunchedEffect + renvoyé par
|
||||
buildTreatment(). Leçon : la reconstruction complete d'une entité pour
|
||||
un save doit repartir des champs NON ÉDITABLES (voir `loadedCreatedAt`,
|
||||
déjà protégé, et désormais `loadedCalendarEventId`).
|
||||
|
||||
**Leçons** : (a) ne jamais croire un build « probablement bon » sans l'avoir lancé ;
|
||||
(b) les tests sémantiques attrapent ce que la compilation ne voit pas ; (c) se méfier des
|
||||
constantes stdlib « de mémoire » (`ln2`), des mélanges Float/Double, et des APIs M3
|
||||
@ -1274,14 +1317,22 @@ Sur le téléphone de test (à compléter par l'utilisatrice) :
|
||||
(lint #49, ex format « 4.0 »)
|
||||
- [ ] **v1.3.4** : `./gradlew lint` vert — intégré à la vérification de
|
||||
release (step 2 de la checklist §16 désormais : tests + lint)
|
||||
- [ ] **v1.3.5** : **agenda end-to-end** : sur un traitement actif avec
|
||||
Posologie, activer « Événement d'agenda récurrent » → permission
|
||||
accordée → sauvegarder → l'événement existe dans le calendrier local
|
||||
« HormoneTrack » (« créé/mis à jour (id=…) » dans les logs exportés) ;
|
||||
puis **désactiver le switch → re-save → l'événement disparaît
|
||||
RÉELLEMENT** de l'app d'agenda (bugs #50 / #50 bis / #51 : en v1.3.4
|
||||
ni création ni suppression ne fonctionnaient, et les re-saves
|
||||
orphelinaient les événements — cf §14)
|
||||
|
||||
---
|
||||
*Doc mise à jour le 6 sept. 2026 (v1.3.4) — build OK, lint vert, 90/90 tests verts (72 sans
|
||||
*Doc mise à jour le 6 sept. 2026 (v1.3.5) — build OK, lint vert, 90/90 tests verts (72 sans
|
||||
les données locales), dépôts Gitea (cloudyfy + farewell) avec releases APK, aucune donnée
|
||||
de santé dans le dépôt ni l'historique. v1.3.3 = session de reprise de maintenance :
|
||||
3 bugs racines corrigés (permissions agenda, export logs, bump de version). v1.3.4 :
|
||||
2 crashs remontés reproduits sur émulateur avec les données réelles (recette §16.ter,
|
||||
scripts/seed-emulator.py versionné) — bugs #47 (stringResource arity, né v1.3.1) et #48
|
||||
(LocalDate + pattern horaire au tap), lint mis en filet bloquant (#49, app/lint.xml),
|
||||
ExportFileNames + 3 tests. Dette connue : fragments de labs dans l'historique git
|
||||
de santé dans le dépôt ni l'historique. Fil des corrections : v1.3.3 = reprise de
|
||||
maintenance (permissions agenda, export logs IO, bump de version) ; v1.3.4 = 2 crashs
|
||||
reproduits sur émulateur avec les données réelles (#47 stringResource arity, #48
|
||||
LocalDate+pattern horaire), lint filet bloquant ; v1.3.5 = premier diagnostic à distance
|
||||
RéUSSI via les logs exportés de l'utilisatrice (#50 CalendarProvider sync-adapter account,
|
||||
log agenda véridique). Dette connue : fragments de labs dans l'historique git
|
||||
(v1.1.0→v1.2.3) — cf §8.bis.*
|
||||
|
||||
@ -86,9 +86,12 @@ Les notifications de l'app remontent automatiquement sur la montre via **Huawei
|
||||
passe-le **inactif** (switch « Actif ») : il disparaît de la saisie et de tes
|
||||
rappels, mais **son historique reste simulé et calibré** sur les graphiques —
|
||||
parfait pour une transition valerate → enanthate
|
||||
- **Agenda** : active « Événement d'agenda récurrent » → un événement se
|
||||
- **Agenda** : active « Événement d'agenda récurrent » (sous Rappels) → une
|
||||
fois la permission accordée et le traitement sauvegardé, un événement se
|
||||
répétant tous les N jours (Posologie) apparaît dans le calendrier
|
||||
« HormoneTrack » de ton téléphone
|
||||
« HormoneTrack » de ton téléphone (≥ v1.3.5 ; en v1.3.4 la création du
|
||||
calendrier échouait — retour dans le guide si besoin : re-sauvegarde avec
|
||||
le switch activé)
|
||||
- Ester (EV / EU / EEn) + modèle (Estrannaise / Transfem Science) = les courbes du `.ods`
|
||||
- Pour gel/patch/oral : paramètres Bateman (temps au pic, demi-vie, biodispo)
|
||||
2. **Doses → +** → logue tes injections passées
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user