From a0f3ac59f980cb84680b573de217eb00977096db Mon Sep 17 00:00:00 2001 From: Siphonight Date: Tue, 29 Sep 2026 07:31:25 +0200 Subject: [PATCH] =?UTF-8?q?v1.11.0=20:=20inactifs=20regroup=C3=A9s=20en=20?= =?UTF-8?q?bas=20de=20la=20page=20Traitements=20+=20cartes=20att=C3=A9nu?= =?UTF-8?q?=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Demande : « les traitements inactifs sont peu discernables — les placer à la fin (tout en bas), et peut-être les rendre plus distincts ». - Helper pur treatmentsForDisplay (tri STABLE : actifs d'abord, inactifs après, ordre relatif conservé dans chaque groupe). - LazyColumn en 3 sections : actifs / en-tête inactive_section (seulement si des inactifs existent) / inactifs — cartes alpha 0.55 en plus du badge, lisibles et tappables (l'édition d'un archivé reste possible). - Miroir web : treatmentsForDisplay dans js/data/models.js + rendu sections + opacity 0.55 + i18n inactive_section FR/EN (web v1.11.0 sync). - 5 tests JVM TreatmentsDisplayTest + 4 tests web (miroir). 264 tests JVM + 14 UI + lint verts ; web 184 tests + E2E verts (check.sh). Validé émulateur sur APK RELEASE avec données réelles seedées (5 actifs en haut, en-tête + EV-old/EEn-old tout en bas, 0 crash). --- README.en.md | 9 +- README.md | 7 +- app/build.gradle.kts | 4 +- .../ui/screens/TreatmentsScreen.kt | 47 +++++++++- app/src/main/res/values-fr/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + .../hormonetrack/ui/TreatmentsDisplayTest.kt | 86 +++++++++++++++++++ docs/CHANGELOG.md | 25 ++++++ docs/DEVELOPPEMENT.md | 21 +++-- 9 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 app/src/test/java/com/hormonetrack/ui/TreatmentsDisplayTest.kt diff --git a/README.en.md b/README.en.md index baaf549..6b72b0d 100644 --- a/README.en.md +++ b/README.en.md @@ -18,8 +18,8 @@ or Huawei Health) and JSON backup. **100% local, no account, no server.** > for informational purposes — they are not measurements. Always rely on your > blood tests and on your endocrinologist's guidance. -- **Status**: v1.10.0 — Android build ✅, **lint clean** ✅, **259 unit tests** ✅ - (225 without the local test data; 6 regressions pinned on real data +- **Status**: v1.11.0 — Android build ✅, **lint clean** ✅, **264 unit tests** ✅ + (230 without the local test data; 6 regressions pinned on real data **not committed to the repo**), **14 Compose UI tests** ✅ (emulator), smartwatch integration = notifications ✅, **private Gitea repo + releases with APK** ✅ @@ -70,7 +70,8 @@ or Huawei Health) and JSON backup. **100% local, no account, no server.** **interval in days between doses** displayed - **Inactive treatments**: an archived treatment disappears from input and reminders, but **its whole history stays simulated and calibrated** — useful - for a valerate → enanthate transition + for a valerate → enanthate transition; **grouped at the very bottom** of the + Treatments page under a dedicated header, dimmed cards (v1.11.0) - **Blood tests**: **E2 + T in a single entry** (each optional), displayed **side by side** when they share the same date/time, editable (tap → pick the entry); T units: ng/mL, ng/dL, ng/L, nmol/L @@ -129,7 +130,7 @@ Gradle 9.7.1. git clone && cd HormoneTrack echo "sdk.dir=/path/to/android-sdk" > local.properties # or ANDROID_HOME ./gradlew assembleDebug # APK: app/build/outputs/apk/debug/app-debug.apk -./gradlew testDebugUnitTest # 259 tests (225 without local test data) +./gradlew testDebugUnitTest # 264 tests (230 without local test data) ./gradlew connectedDebugAndroidTest # 14 UI tests (emulator/device required) ./gradlew lint # clean lint required before a release ``` diff --git a/README.md b/README.md index a49eac9..a13cd42 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,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.10.0 — build Android ✅, **lint vert** ✅, **259 tests unitaires** ✅ (225 sans les données de test locales ; 6 régressions épinglées sur données réelles **non versionnées**), **14 tests UI Compose** ✅ (émulateur), intégration montre = notifications ✅, **dépôt Gitea privé + releases avec APK** ✅ +- **Statut** : v1.11.0 — build Android ✅, **lint vert** ✅, **264 tests unitaires** ✅ (230 sans les données de test locales ; 6 régressions épinglées sur données réelles **non versionnées**), **14 tests UI Compose** ✅ (émulateur), 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) @@ -70,7 +70,8 @@ sauvegarde JSON. **100 % local, aucun compte, aucun serveur.** **intervalle en jours entre dosages** affiché - **Traitements inactifs** : un traitement archivé n'apparaît plus dans la saisie ni dans les rappels, mais **tout son historique reste simulé et calibré** — - utile pour une transition valerate → enanthate + utile pour une transition valerate → enanthate ; **regroupés tout en bas** de la + page Traitements sous un en-tête dédié, cartes atténuées (v1.11.0) - **Analyses de sang** : **E2 + T en une seule entrée** (chacune optionnelle), affichées **côte à côte** quand elles partagent la même date/heure, éditables (tap → choix de l'entrée) ; unités T : ng/mL, ng/dL, ng/L, nmol/L @@ -128,7 +129,7 @@ par AGP si les licences sont signées). Le wrapper télécharge Gradle 9.7.1. git clone && cd HormoneTrack echo "sdk.dir=/chemin/vers/android-sdk" > local.properties # ou ANDROID_HOME ./gradlew assembleDebug # APK : app/build/outputs/apk/debug/app-debug.apk -./gradlew testDebugUnitTest # 259 tests (225 sans les données locales) +./gradlew testDebugUnitTest # 264 tests (230 sans les données locales) ./gradlew connectedDebugAndroidTest # 14 tests UI (émulateur/appareil requis) ./gradlew lint # lint vert obligatoire avant release ``` diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1fb2a81..dfd8cbb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,8 +33,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 = 47 - versionName = "1.10.1" + versionCode = 48 + versionName = "1.11.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt b/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt index c7aa4d7..6fbd2c8 100644 --- a/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt +++ b/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.hormonetrack.R @@ -36,6 +37,21 @@ import com.hormonetrack.data.model.modelLabelRes import com.hormonetrack.ui.LocalAppContainer import com.hormonetrack.ui.components.formatDose +/** + * ORDRE D'AFFICHAGE de la page Traitements (v1.11.0, demande utilisatrice) : + * les ACTIFS d'abord, les INACTIFS regroupés TOUT EN BAS — l'ancien ordre + * brut du DAO mélangeait les deux groupes et le badge discret ne suffisait + * plus à les discerner (« peu discernables », remontée). + * + * Tri STABLE : l'ordre relatif À L'INTÉRIEUR de chaque groupe est conservé + * (le tri ne réordonne jamais les actifs entre eux, ni les inactifs entre + * eux). PUR (aucune dépendance Android) → testable en JVM + * (TreatmentsDisplayTest) ; miroir web `treatmentsForDisplay` + * (js/data/models.js, mêmes sémantiques). + */ +fun treatmentsForDisplay(treatments: List): List = + treatments.sortedBy { !it.isActive } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun TreatmentsScreen( @@ -45,6 +61,11 @@ fun TreatmentsScreen( val container = LocalAppContainer.current val repo = container.repository val treatments by repo.allTreatments.collectAsState(initial = emptyList()) + // v1.11.0 : actifs d'abord, inactifs regroupés tout en bas + // (cf [treatmentsForDisplay]) — mémoïsé sur la liste brute. + val displayTreatments = remember(treatments) { treatmentsForDisplay(treatments) } + val activeTreatments = displayTreatments.filter { it.isActive } + val inactiveTreatments = displayTreatments.filter { !it.isActive } Scaffold( floatingActionButton = { @@ -58,22 +79,44 @@ fun TreatmentsScreen( title = { Text(stringResource(R.string.nav_treatments)) } ) LazyColumn(Modifier.fillMaxSize()) { - items(treatments, key = { it.id }) { tr -> + items(activeTreatments, key = { it.id }) { tr -> TreatmentCard(tr, onClick = { onEdit(tr.id) }) } + // En-tête de section SEULEMENT s'il existe des inactifs — + // la séparation visuelle (en-tête + atténuation, cf + // TreatmentCard) rend le groupe lisible d'un coup d'œil. + if (inactiveTreatments.isNotEmpty()) { + item(key = "inactive_section") { + Text( + stringResource(R.string.inactive_section), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp) + ) + } + } + items(inactiveTreatments, key = { it.id }) { tr -> + TreatmentCard(tr, dimmed = true, onClick = { onEdit(tr.id) }) + } } } } } +/** + * Carte d'un traitement. @param dimmed : carte INACTIVE (v1.11.0) → contenu + * atténué (alpha réduit) en PLUS du badge : second signal visuel demandé + * (« plus distincts des actifs ») — la carte reste lisible et tappable. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun TreatmentCard(tr: Treatment, onClick: () -> Unit) { +private fun TreatmentCard(tr: Treatment, dimmed: Boolean = false, onClick: () -> Unit) { Card( onClick = onClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 6.dp) + .alpha(if (dimmed) 0.55f else 1f) ) { Column(Modifier.padding(12.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 47f28b0..783c546 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -144,6 +144,9 @@ Unité Actif inactif + + Traitements inactifs Crée d\'abord un traitement (onglet Traitements). Œstradiol diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f41f345..bfe2695 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,6 +144,9 @@ Unit Active inactive + + Inactive treatments Create a treatment first (Treatments tab). Estradiol diff --git a/app/src/test/java/com/hormonetrack/ui/TreatmentsDisplayTest.kt b/app/src/test/java/com/hormonetrack/ui/TreatmentsDisplayTest.kt new file mode 100644 index 0000000..7567825 --- /dev/null +++ b/app/src/test/java/com/hormonetrack/ui/TreatmentsDisplayTest.kt @@ -0,0 +1,86 @@ +package com.hormonetrack.ui + +import com.hormonetrack.data.model.AdministrationRoute +import com.hormonetrack.data.model.Treatment +import com.hormonetrack.data.model.TreatmentType +import com.hormonetrack.ui.screens.treatmentsForDisplay +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Tests de l'ORDRE D'AFFICHAGE de la page Traitements (v1.11.0, demande + * utilisatrice : « les traitements inactifs faudrait les placer à la fin + * (tout en bas) de la liste ») — [treatmentsForDisplay] : + * - les ACTIFS d'abord, les INACTIFS regroupés après ; + * - tri STABLE : l'ordre relatif DANS chaque groupe est conservé (le tri + * ne réordonne jamais les actifs entre eux, ni les inactifs entre eux) ; + * - cas limites : liste vide, tout actif, tout inactif (identité). + * + * PUR (aucune dépendance Android) ; miroir web `treatmentsForDisplay` + * (js/data/models.js, tests/treatments-display.test.js). + */ +class TreatmentsDisplayTest { + + private fun tr(id: Long, name: String, isActive: Boolean) = Treatment( + id = id, name = name, type = TreatmentType.ESTRADIOL, + route = AdministrationRoute.INJECTION_IM, + doseAmount = 5.0, doseUnit = "mg", + isActive = isActive, createdAt = 0 + ) + + @Test + fun `inactive treatments are grouped at the end`() { + val mixed = listOf( + tr(1, "EEn", true), + tr(2, "EV-old", false), + tr(3, "CPA", true), + tr(4, "EEn-old", false), + tr(5, "Ritaline", true) + ) + val display = treatmentsForDisplay(mixed) + assertEquals( + "actifs d'abord (ordre conservé), inactifs après (ordre conservé)", + listOf(1L, 3L, 5L, 2L, 4L), + display.map { it.id } + ) + assertEquals( + "aucun inactif avant un actif", + 0, + display.indexOfFirst { !it.isActive }.let { firstInactive -> + if (firstInactive < 0) 0 + else display.subList(firstInactive, display.size).count { it.isActive } + } + ) + } + + @Test + fun `ordering inside each group is preserved (stable sort)`() { + val mixed = listOf( + tr(10, "A-inactif", false), + tr(11, "B-actif", true), + tr(12, "C-inactif", false), + tr(13, "D-actif", true) + ) + assertEquals( + listOf(11L, 13L, 10L, 12L), + treatmentsForDisplay(mixed).map { it.id } + ) + } + + @Test + fun `all active - order unchanged`() { + val all = listOf(tr(3, "C", true), tr(1, "A", true), tr(2, "B", true)) + assertEquals(listOf(3L, 1L, 2L), treatmentsForDisplay(all).map { it.id }) + } + + @Test + fun `all inactive - order unchanged`() { + val all = listOf(tr(2, "B", false), tr(1, "A", false)) + assertEquals(listOf(2L, 1L), treatmentsForDisplay(all).map { it.id }) + } + + @Test + fun `empty list stays empty`() { + assertEquals(0, treatmentsForDisplay(emptyList()).size) + } +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1708f31..a9bba9d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,31 @@ Format : [Keep a Changelog](https://keepachangelog.com/fr-FR/1.1.0/). Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.4.10`, …). +## [1.11.0] — 2026-09-29 (versionCode 48) + +### Ajouté — les traitements inactifs regroupés en bas de la page Traitements + +- **Demande** : « les traitements inactifs sont peu discernables par rapport + aux traitements actifs — les placer à la fin (tout en bas) de la liste + pour éviter les confusions ». +- **Tri** : les ACTIFS d'abord, les INACTIFS regroupés **tout en bas**, sous + un en-tête de section « Traitements inactifs » (n'apparaît que s'il + existe des inactifs). Tri STABLE : l'ordre relatif à l'intérieur de + chaque groupe est conservé — le tri ne réordonne jamais les actifs entre + eux, ni les inactifs entre eux. +- **Distinction visuelle renforcée** : les cartes inactives sont ATTÉNUÉES + (alpha 0,55) en plus du badge « inactif » — second signal demandé + (« peut-être les rendre plus distincts ») ; elles restent lisibles et + tappables (l'édition d'un traitement archivé doit rester possible). +- Helper pur `treatmentsForDisplay` (Kotlin + **miroir web** en + `js/data/models.js`) — 5 tests JVM (`TreatmentsDisplayTest`) + 4 tests + web (`tests/treatments-display.test.js`). +- Vérifié émulateur avec données réelles seedées : les 5 actifs en haut, + en-tête puis les 2 inactifs (EV-old, EEn-old) tout en bas. + +Vérifié : **264 tests JVM** (230 sans données locales) + **14 tests UI** + +lint verts ; **web 184 tests + E2E verts** (`check.sh`). + ## [1.10.1] — 2026-09-28 (versionCode 47) ### Amélioré — durcissement du système de rappels (revue de code post-v1.10.0) diff --git a/docs/DEVELOPPEMENT.md b/docs/DEVELOPPEMENT.md index 8681901..862586b 100644 --- a/docs/DEVELOPPEMENT.md +++ b/docs/DEVELOPPEMENT.md @@ -63,6 +63,7 @@ Fonctionnalités v1 : | Date | Événement | |---|---| +| 29 sept. 2026 (session v1.11.0) | **Page Traitements : inactifs regroupés en bas + atténués** (demande : « peu discernables — les placer à la fin, et peut-être les rendre plus distincts »). Helper PUR `treatmentsForDisplay` (tri STABLE : actifs d'abord, inactifs après, ordre relatif conservé DANS chaque groupe) + LazyColumn en 3 sections (actifs / en-tête `inactive_section` SEULEMENT si des inactifs existent / inactifs `dimmed = true`) + cartes inactives **alpha 0,55** en plus du badge (lisibles et tappables — l'édition d'un archivé doit rester possible). **Miroir web** : `treatmentsForDisplay` dans `js/data/models.js` (node-safe, pas de DOM) + rendu sections + `opacity:0.55` + clés i18n `inactive_section` FR/EN. Tests : **5 JVM** (`TreatmentsDisplayTest` : regroupement, stabilité, tout-actif, tout-inactif, vide) + **4 web** (`tests/treatments-display.test.js`) — pas de test UI instrumenté pour l'en-tête (dépendrait de données inactives, contre la convention data-indépendance §8.ter) : couverture JVM + validation émulateur (5 actifs en haut, en-tête + EV-old/EEn-old tout en bas, dumps tr1/tr2). 264 tests JVM + lint ; web 184 tests + E2E verts (check.sh). | | 28 sept. 2026 (session v1.10.1) | **Durcissement rappels — revue de code post-v1.10.0** (audit demandé : « optimisations ou problèmes, web et Android »). (a) **`launchMode="singleTop"`** : le combo `standard`+`CLEAR_TOP` recréait l'activité au tap notification app-ouverte et rendait le `onNewIntent` de la v1.10.0 inopérant (code mort) — désormais délivré SANS recreation (état UI préservé). (b) **`EXTRA_PLANNED_AT`** : le garde « créneau déjà honoré » évaluait le jour au moment du FEU — une alarme inexacte (`setWindow`/Doze) glissant 23:58→00:02 évaluait le mauvais jour ; l'instant planifié voyage dans l'intent (scheduleFor), le snooze le FORWARDE, le garde évalue le jour du CRÉNEAU ; vérifié émulateur (broadcast `planned_at` hier → notif sautée). (c) **`BootReceiver` consolidé** (dette §20.bis #2 FAIT) : `goAsync`+coroutine, `runBlocking` supprimés. (d) Style (import `mutableStateOf`) + audit doc (comptes 223 résiduels §14/#64 et §16.ter → 259 ; §9 et §20.bis à jour). Web NON concerné (miroir lab-timing laissé tel quel — boucle correcte et testée ; `importJson` par lots reste en dette). 259 tests JVM + 14 UI + lint verts. **Publication** : commit d531eca + tag v1.10.1 poussés sur les 2 Gitea, APK release (2,7 Mo) + debug (21 Mo) publiés et vérifiés par téléchargement (cloudyfy id 78, farewell id 1588586) ; validation §16.ter sur l'APK RELEASE AVANT tag (accueil + données, singleTop, planned_at A/B, 0 crash). | | 27-28 sept. 2026 (session v1.10.0) | **2 fixes + 1 amélioration rappels** (remontées + nouvelles données réelles → 6ᵉ régression épinglée `backup-v1.9.8.json`). (a) **#68 — reco de prise de sang antérieure à la stabilisation** : le « creux » = minimum de la FENÊTRE ENTIÈRE tombait juste après l'injection PRÉCÉDENTE pour les esters à montée lente (EEn pic ~J+5 ≈ intervalle 7 j → creux d'absorption ~3 h post-injection) — date gouvernée par l'injection N−1 (antérieure à la stab) et « juste avant ton injection du … » faux de plusieurs jours (réel : creux 5 oct 02:32 < stab 7 oct 04:03). FIX : creux = niveau PRÉ-INJECTION du créneau (restaure le principe v1.8.0 documenté ; EV inchangé — le point pré-injection EST son min de fenêtre) ; **miroir web appliqué** (lab-timing.js). Tests : RegressionUserCase6Test data-driven dont balayage de `now` ±60 j (invariant « creux ≥ stab » à TOUT INSTANT) ; les 17 LabTimingTest passent inchangés. (b) **#69 — dialog fantôme** : tap notification → dialog ; le fermer + changer d'onglet + revenir sur l'accueil le ROUVRAIT à chaque fois (paramètre d'activité vs consommation dans les remember de Home, détruits à chaque sortie d'onglet — reproduit sur émulateur AVANT fix). FIX : `MutableState` détenu par MainActivity, consommé UNE fois (extras retirés de l'intent après lecture ; bonus : tap notification app-ouverte ouvre aussi le dialog). Test UI `LogDoseRequestTest` + vérification émulateur. (c) **Rappel sauté si dose déjà loggée** (demande) : garde au déclenchement — `hasDoseLoggedOnDay` (pur, jour calendaire LOCAL) dans ReminderReceiver → notif sautée + re-programmation créneau suivant dans tous les cas ; 6 tests JVM + vérification A/B émulateur (EEn dose-du-jour → 0 notif, Fluoxetine sans dose → 1 notif). **259 tests JVM + 14 UI + lint** ; **web 180 tests + E2E verts** (check.sh). **Publication** : commit a4f859f + tag v1.10.0 poussés sur les 2 Gitea, APK release (2,7 Mo) + debug (21 Mo) publiés et vérifiés par téléchargement (cloudyfy id 77, farewell id 1588583). **Web miroir v1.10.0 sync** (fix #68 lab-timing, WEB_VERSION alignée, commit b865b16 + tag poussés ; #69/garde rappel = Android-only, notés dans le changelog web). | | 22 sept. 2026 (session v1.9.8) | **Socle qualité : tests UI Compose + signature release dédiée + découpage Settings** (revue de code du 21 sept. : « pas de CI, pas de tests UI, APK signé clé debug, écrans qui grandissent »). (a) **13 tests UI instrumentés** (`app/src/androidTest/uitest/`, §8.ter) : navigation 5 onglets (preuves = éléments inconditionnels : chips 24 h, FAB), dialog Nouveautés (épinglage du fix v1.3.2 : ne réapparaît pas après dismiss+restart), sections Paramètres + version installée = BuildConfig (le bug #46 ne peut plus revenir) ; déterminisme = pm grant shell pré-launch + markChangelogSeen + assertions via targetContext (indépendant FR/EN) + zéro dépendance aux données Room ; deps androidTest (BOM + ui-test-junit4 + ext:junit-ktx + junit explicite). (b) **Signature release conditionnelle** (§16.quater) : `scripts/make-release-keystore.sh` (shellcheck+shfmt) génère keystore + keystore.properties (gitignorés), `assembleRelease` l'utilise s'il existe, sinon fallback clé debug inchangé ; migration backup→désinstall→réimport documentée. (c) **SettingsScreen découpé** (840 → 377 + 11 fichiers 33–164 lignes dans `ui/screens/settings/`, §10) — déplacement pur, commentaires historiques conservés, ChartScreen volontairement entier (garde de source #54). **Vérifications** : 249 tests JVM + 13 UI verts, lint vert, assembleDebug/Release OK, **smoke test RELEASE émulateur** (accueil, 5 onglets, Paramètres complet scroll compris — version 1.9.8 visible, éditeur traitement, dialog dose + garde « crée un traitement », 0 crash logcat — anti-#64). **README EN** (README.en.md, liens FR/EN croisés ; DEVELOPPEMENT.md reste FR par choix assumé). **Publication** : commit 09cddfc + tag v1.9.8 poussés sur les 2 Gitea, APK release (2,7 Mo) + debug (21 Mo) publiés et vérifiés par téléchargement (cloudyfy id 75, farewell id 1588581). Web miroir NON concerné (v1.9.8 = socle Android uniquement : tests instrumentés, signature APK, découpage Compose, README du dépôt Android — rien à porter côté web, qui reste à v1.9.7). | @@ -754,7 +755,7 @@ devient à son tour**. ## 8. Tests unitaires -**259 tests JVM, tous verts** (`./gradlew testDebugUnitTest`) — **225 sans +**264 tests JVM, tous verts** (`./gradlew testDebugUnitTest`) — **230 sans les données de test locales** (cf §8.bis : les 6 classes de régression sont skippées via `Assume`). Dépendance : JUnit 4.13.2. Emplacement : `app/src/test/java/com/hormonetrack/`. Répertoire de travail d'exécution = @@ -987,9 +988,9 @@ dépôt (ni maintenant, ni si le repo devient public un jour). dans le code (le scanner refuserait, et ce serait une fuite). - les tests font `Assume.assumeTrue(file.exists())` dans le `@Before` : **sans le fichier, la classe est IGNORÉE** (skipped, pas failed) — un clone neuf ou une CI - exécute 225 tests au lieu de 259 (v1.10.0 ; chiffres historiques : 44/87 à v1.3.x, + exécute 230 tests au lieu de 264 (v1.11.0 ; chiffres historiques : 44/87 à v1.3.x, 150/172 à v1.4.x, 160/183 à v1.5.0, 168/191 à v1.6.0, 177/200 à v1.7.0, - 181/211 à v1.7.1, 190/220 à v1.8.0, 219/249 à v1.9.x) ; + 181/211 à v1.7.1, 190/220 à v1.8.0, 219/249 à v1.9.x, 225/259 à v1.10.x) ; - le workdir des tests Gradle est le dossier du module (`app/`) → les tests cherchent les fichiers à plusieurs chemins (`../local-test-data/…` en premier) ; - **pour les lancer** : exporter un backup JSON depuis l'app → l'enregistrer sous @@ -1199,7 +1200,12 @@ Sur la montre : remontée par Gadgetbridge **ou** Huawei Health (cf §17). les valeurs — choix de design : les labs sont prélevés ensemble) ; suggestions d'unités pg/mL, ng/mL, ng/dL, ng/L, nmol/L, mIU/L - `TreatmentsScreen` : cartes (nom, route, dose, chips ester·modèle / Tmax / ×scale / ⏰, - badge inactif), FAB → éditeur + badge inactif), FAB → éditeur. **v1.11.0** : ordre d'affichage = + `treatmentsForDisplay` (pur, tri STABLE — actifs d'abord, inactifs + regroupés TOUT EN BAS sous un en-tête `inactive_section` qui n'apparaît + que s'il existe des inactifs) + cartes inactives **atténuées** (alpha + 0,55) — 5 tests `TreatmentsDisplayTest` ; miroir web + (`js/data/models.js` + `tests/treatments-display.test.js`) - `TreatmentEditorScreen` : 12 presets (`PKPresets`, cf `nameRes`) pré-remplissent tout ; champs conditionnels (ester+modèle si injection, Bateman sinon) ; carte Calibration (scaleFactor + « Calibrer avec les analyses ») ; **section « Posologie » (v1.2 : @@ -2036,8 +2042,8 @@ de signature est requise — elle est désormais câblée : 1. **Bumper la version** dans `app/build.gradle.kts` : `versionCode = N+1`, `versionName = "X.Y.Z+1"` (SemVer : fix = Z, feature = Y). -2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 259 au - total, 225 si `local-test-data/` est absent (les 6 classes de régression +2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 264 au + total, 230 si `local-test-data/` est absent (les 6 classes de régression réelles sont skippées via `Assume`) ; **lint vert obligatoire** : `./gradlew lint` (v1.3.4 — `StringFormatMatches` aurait attrapé les crashs #47/#48 dès v1.3.1). @@ -2334,6 +2340,9 @@ Sur le téléphone de test (à compléter par l'utilisatrice) : cohérente avec le créneau affiché (~juste avant) - [ ] **v1.10.0** : logger la dose du jour AVANT l'heure de rappel → la notification ne sonne pas ; le rappel suivant repart au créneau suivant +- [ ] **v1.11.0** : page Traitements — les inactifs sont TOUT EN BAS sous + l'en-tête « Traitements inactifs », cartes atténuées ; éditer un + inactif fonctionne toujours - [ ] **v1.2.x** : graphique panoramique (glisser → passé, bouton « Revenir à maintenant ») - [ ] **v1.2.x** : toggles Estrannaise/TFS indépendants (les deux courbes superposées) - [ ] **v1.2.x** : chip « Prévision » (configurer la Posologie d'un traitement d'abord)