v1.11.0 : inactifs regroupés en bas de la page Traitements + cartes atténuées
- 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).
This commit is contained in:
parent
5cd1023af1
commit
ee2a606a1f
@ -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
|
> for informational purposes — they are not measurements. Always rely on your
|
||||||
> blood tests and on your endocrinologist's guidance.
|
> blood tests and on your endocrinologist's guidance.
|
||||||
|
|
||||||
- **Status**: v1.10.0 — Android build ✅, **lint clean** ✅, **259 unit tests** ✅
|
- **Status**: v1.11.0 — Android build ✅, **lint clean** ✅, **264 unit tests** ✅
|
||||||
(225 without the local test data; 6 regressions pinned on real data
|
(230 without the local test data; 6 regressions pinned on real data
|
||||||
**not committed to the repo**), **14 Compose UI tests** ✅ (emulator),
|
**not committed to the repo**), **14 Compose UI tests** ✅ (emulator),
|
||||||
smartwatch integration = notifications ✅, **private Gitea repo + releases
|
smartwatch integration = notifications ✅, **private Gitea repo + releases
|
||||||
with APK** ✅
|
with APK** ✅
|
||||||
@ -70,7 +70,8 @@ or Huawei Health) and JSON backup. **100% local, no account, no server.**
|
|||||||
**interval in days between doses** displayed
|
**interval in days between doses** displayed
|
||||||
- **Inactive treatments**: an archived treatment disappears from input and
|
- **Inactive treatments**: an archived treatment disappears from input and
|
||||||
reminders, but **its whole history stays simulated and calibrated** — useful
|
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
|
- **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
|
**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
|
entry); T units: ng/mL, ng/dL, ng/L, nmol/L
|
||||||
@ -129,7 +130,7 @@ Gradle 9.7.1.
|
|||||||
git clone <repo> && cd HormoneTrack
|
git clone <repo> && cd HormoneTrack
|
||||||
echo "sdk.dir=/path/to/android-sdk" > local.properties # or ANDROID_HOME
|
echo "sdk.dir=/path/to/android-sdk" > local.properties # or ANDROID_HOME
|
||||||
./gradlew assembleDebug # APK: app/build/outputs/apk/debug/app-debug.apk
|
./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 connectedDebugAndroidTest # 14 UI tests (emulator/device required)
|
||||||
./gradlew lint # clean lint required before a release
|
./gradlew lint # clean lint required before a release
|
||||||
```
|
```
|
||||||
|
|||||||
@ -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
|
> à titre informatif — ce ne sont pas des mesures. Fie-toi toujours à tes analyses de
|
||||||
> sang et aux consignes de ton endocrinologue.
|
> 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)
|
- **Journal des versions** : [docs/CHANGELOG.md](docs/CHANGELOG.md)
|
||||||
- **Guide utilisateur** : [docs/GUIDE_INSTALLATION.md](docs/GUIDE_INSTALLATION.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)
|
- **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é
|
**intervalle en jours entre dosages** affiché
|
||||||
- **Traitements inactifs** : un traitement archivé n'apparaît plus dans la saisie
|
- **Traitements inactifs** : un traitement archivé n'apparaît plus dans la saisie
|
||||||
ni dans les rappels, mais **tout son historique reste simulé et calibré** —
|
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
|
- **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
|
**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
|
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 <repo> && cd HormoneTrack
|
git clone <repo> && cd HormoneTrack
|
||||||
echo "sdk.dir=/chemin/vers/android-sdk" > local.properties # ou ANDROID_HOME
|
echo "sdk.dir=/chemin/vers/android-sdk" > local.properties # ou ANDROID_HOME
|
||||||
./gradlew assembleDebug # APK : app/build/outputs/apk/debug/app-debug.apk
|
./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 connectedDebugAndroidTest # 14 tests UI (émulateur/appareil requis)
|
||||||
./gradlew lint # lint vert obligatoire avant release
|
./gradlew lint # lint vert obligatoire avant release
|
||||||
```
|
```
|
||||||
|
|||||||
@ -33,8 +33,8 @@ android {
|
|||||||
// tag : v1.3.0→v1.3.2 contenaient tous versionCode 14 / "1.3.0"
|
// tag : v1.3.0→v1.3.2 contenaient tous versionCode 14 / "1.3.0"
|
||||||
// (bump jamais commité) → BuildConfig.VERSION_NAME était faux dans
|
// (bump jamais commité) → BuildConfig.VERSION_NAME était faux dans
|
||||||
// les APK publiés (Paramètres + titre du dialog « Nouveautés »).
|
// les APK publiés (Paramètres + titre du dialog « Nouveautés »).
|
||||||
versionCode = 47
|
versionCode = 48
|
||||||
versionName = "1.10.1"
|
versionName = "1.11.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.hormonetrack.R
|
import com.hormonetrack.R
|
||||||
@ -36,6 +37,21 @@ import com.hormonetrack.data.model.modelLabelRes
|
|||||||
import com.hormonetrack.ui.LocalAppContainer
|
import com.hormonetrack.ui.LocalAppContainer
|
||||||
import com.hormonetrack.ui.components.formatDose
|
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<Treatment>): List<Treatment> =
|
||||||
|
treatments.sortedBy { !it.isActive }
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TreatmentsScreen(
|
fun TreatmentsScreen(
|
||||||
@ -45,6 +61,11 @@ fun TreatmentsScreen(
|
|||||||
val container = LocalAppContainer.current
|
val container = LocalAppContainer.current
|
||||||
val repo = container.repository
|
val repo = container.repository
|
||||||
val treatments by repo.allTreatments.collectAsState(initial = emptyList())
|
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(
|
Scaffold(
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
@ -58,22 +79,44 @@ fun TreatmentsScreen(
|
|||||||
title = { Text(stringResource(R.string.nav_treatments)) }
|
title = { Text(stringResource(R.string.nav_treatments)) }
|
||||||
)
|
)
|
||||||
LazyColumn(Modifier.fillMaxSize()) {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(treatments, key = { it.id }) { tr ->
|
items(activeTreatments, key = { it.id }) { tr ->
|
||||||
TreatmentCard(tr, onClick = { onEdit(tr.id) })
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TreatmentCard(tr: Treatment, onClick: () -> Unit) {
|
private fun TreatmentCard(tr: Treatment, dimmed: Boolean = false, onClick: () -> Unit) {
|
||||||
Card(
|
Card(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 6.dp)
|
.padding(horizontal = 16.dp, vertical = 6.dp)
|
||||||
|
.alpha(if (dimmed) 0.55f else 1f)
|
||||||
) {
|
) {
|
||||||
Column(Modifier.padding(12.dp)) {
|
Column(Modifier.padding(12.dp)) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
|||||||
@ -144,6 +144,9 @@
|
|||||||
<string name="dose_unit">Unité</string>
|
<string name="dose_unit">Unité</string>
|
||||||
<string name="active">Actif</string>
|
<string name="active">Actif</string>
|
||||||
<string name="inactive">inactif</string>
|
<string name="inactive">inactif</string>
|
||||||
|
<!-- v1.11.0 : en-tête de la section des traitements inactifs (regroupés
|
||||||
|
en bas de la page Traitements) -->
|
||||||
|
<string name="inactive_section">Traitements inactifs</string>
|
||||||
<string name="no_treatment_hint">Crée d\'abord un traitement (onglet Traitements).</string>
|
<string name="no_treatment_hint">Crée d\'abord un traitement (onglet Traitements).</string>
|
||||||
|
|
||||||
<string name="type_estradiol">Œstradiol</string>
|
<string name="type_estradiol">Œstradiol</string>
|
||||||
|
|||||||
@ -144,6 +144,9 @@
|
|||||||
<string name="dose_unit">Unit</string>
|
<string name="dose_unit">Unit</string>
|
||||||
<string name="active">Active</string>
|
<string name="active">Active</string>
|
||||||
<string name="inactive">inactive</string>
|
<string name="inactive">inactive</string>
|
||||||
|
<!-- v1.11.0 : en-tête de la section des traitements inactifs (regroupés
|
||||||
|
en bas de la page Traitements) -->
|
||||||
|
<string name="inactive_section">Inactive treatments</string>
|
||||||
<string name="no_treatment_hint">Create a treatment first (Treatments tab).</string>
|
<string name="no_treatment_hint">Create a treatment first (Treatments tab).</string>
|
||||||
|
|
||||||
<string name="type_estradiol">Estradiol</string>
|
<string name="type_estradiol">Estradiol</string>
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,31 @@
|
|||||||
Format : [Keep a Changelog](https://keepachangelog.com/fr-FR/1.1.0/).
|
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`, …).
|
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)
|
## [1.10.1] — 2026-09-28 (versionCode 47)
|
||||||
|
|
||||||
### Amélioré — durcissement du système de rappels (revue de code post-v1.10.0)
|
### Amélioré — durcissement du système de rappels (revue de code post-v1.10.0)
|
||||||
|
|||||||
@ -63,6 +63,7 @@ Fonctionnalités v1 :
|
|||||||
|
|
||||||
| Date | Événement |
|
| 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). |
|
| 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<Long?>` 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). |
|
| 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<Long?>` 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). |
|
| 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
|
## 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
|
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.
|
skippées via `Assume`). Dépendance : JUnit 4.13.2.
|
||||||
Emplacement : `app/src/test/java/com/hormonetrack/`. Répertoire de travail d'exécution =
|
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).
|
dans le code (le scanner refuserait, et ce serait une fuite).
|
||||||
- les tests font `Assume.assumeTrue(file.exists())` dans le `@Before` : **sans le
|
- 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
|
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,
|
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
|
- le workdir des tests Gradle est le dossier du module (`app/`) → les tests
|
||||||
cherchent les fichiers à plusieurs chemins (`../local-test-data/…` en premier) ;
|
cherchent les fichiers à plusieurs chemins (`../local-test-data/…` en premier) ;
|
||||||
- **pour les lancer** : exporter un backup JSON depuis l'app → l'enregistrer sous
|
- **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
|
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
|
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 / ⏰,
|
- `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 ;
|
- `TreatmentEditorScreen` : 12 presets (`PKPresets`, cf `nameRes`) pré-remplissent tout ;
|
||||||
champs conditionnels (ester+modèle si injection, Bateman sinon) ; carte Calibration
|
champs conditionnels (ester+modèle si injection, Bateman sinon) ; carte Calibration
|
||||||
(scaleFactor + « Calibrer avec les analyses ») ; **section « Posologie » (v1.2 :
|
(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`,
|
1. **Bumper la version** dans `app/build.gradle.kts` : `versionCode = N+1`,
|
||||||
`versionName = "X.Y.Z+1"` (SemVer : fix = Z, feature = Y).
|
`versionName = "X.Y.Z+1"` (SemVer : fix = Z, feature = Y).
|
||||||
2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 259 au
|
2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 264 au
|
||||||
total, 225 si `local-test-data/` est absent (les 6 classes de régression
|
total, 230 si `local-test-data/` est absent (les 6 classes de régression
|
||||||
réelles sont skippées via `Assume`) ; **lint vert
|
réelles sont skippées via `Assume`) ; **lint vert
|
||||||
obligatoire** : `./gradlew lint` (v1.3.4 — `StringFormatMatches` aurait
|
obligatoire** : `./gradlew lint` (v1.3.4 — `StringFormatMatches` aurait
|
||||||
attrapé les crashs #47/#48 dès v1.3.1).
|
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)
|
cohérente avec le créneau affiché (~juste avant)
|
||||||
- [ ] **v1.10.0** : logger la dose du jour AVANT l'heure de rappel → la
|
- [ ] **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
|
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** : graphique panoramique (glisser → passé, bouton « Revenir à maintenant »)
|
||||||
- [ ] **v1.2.x** : toggles Estrannaise/TFS indépendants (les deux courbes superposées)
|
- [ ] **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)
|
- [ ] **v1.2.x** : chip « Prévision » (configurer la Posologie d'un traitement d'abord)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user