v1.3.2 : 3 bugs remontés corrigés (toggle agenda, export logs, dialog changelog récurrent) + données réelles mises à jour
- FIX toggle agenda : Posologie requise (refus + message si absente), permission vérifiée RÉELLEMENT via ContextCompat au save (le flag remembered pouvait être faux après recomposition), AppLog à chaque étape - FIX export logs : réutilise le pattern éprouvé de l'export JSON (openOutputStream wt), chaque étape journalisée dans AppLog - FIX dialog changelog : version vue mémorisée AVANT l'affichage (sinon réapparaissait à chaque réouverture), titre = BuildConfig.VERSION_NAME (documenté : la version de l'APP, pas du contenu) - RegressionUserCase3Test mis à jour (3 traitements : EV inactif + EEn actif + CPA oral avec posologie 1 j, 51 doses, 26 labs) - versionCode 16, versionName 1.3.2
This commit is contained in:
parent
6c7f2fa2e2
commit
af8d40726a
@ -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
|
> à 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.3.1 — build Android ✅, **87 tests unitaires** ✅ (44 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.2 — build Android ✅, **87 tests unitaires** ✅ (44 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)
|
- **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)
|
||||||
|
|||||||
@ -56,17 +56,26 @@ fun HormoneTrackRoot(initialOpenLogDose: Boolean, initialTreatmentId: Long) {
|
|||||||
// prochaine mise à jour.
|
// prochaine mise à jour.
|
||||||
var changelogText by remember { mutableStateOf<String?>(null) }
|
var changelogText by remember { mutableStateOf<String?>(null) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
val current = com.hormonetrack.BuildConfig.VERSION_NAME
|
||||||
val seen = app.container.settings.changelogSeenVersion.first()
|
val seen = app.container.settings.changelogSeenVersion.first()
|
||||||
|
com.hormonetrack.util.AppLog.log("changelog", "démarrage : app=$current, dernière vue=$seen")
|
||||||
val markdown = try {
|
val markdown = try {
|
||||||
context.assets.open("changelog.md").bufferedReader().use { it.readText() }
|
context.assets.open("changelog.md").bufferedReader().use { it.readText() }
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
"" // asset manquant (build sans copyChangelog) → pas de dialog
|
"" // asset manquant (build sans copyChangelog) → pas de dialog
|
||||||
}
|
}
|
||||||
val text = com.hormonetrack.settings.ChangelogHelper.sectionsSince(
|
val text = com.hormonetrack.settings.ChangelogHelper.sectionsSince(
|
||||||
markdown, seen, com.hormonetrack.BuildConfig.VERSION_NAME
|
markdown, seen, current
|
||||||
)
|
)
|
||||||
if (text.isNotBlank()) changelogText = text
|
// ⚠️ FIX v1.3.2 : MÉMORISER LA VERSION VUE AVANT D'AFFICHER — sinon,
|
||||||
app.container.settings.setChangelogSeenVersion(com.hormonetrack.BuildConfig.VERSION_NAME)
|
// si l'app est fermée pendant que le dialog est ouvert (ou que
|
||||||
|
// DataStore n'a pas eu le temps de flusher), le même changelog
|
||||||
|
// ressort à chaque réouverture.
|
||||||
|
app.container.settings.setChangelogSeenVersion(current)
|
||||||
|
if (text.isNotBlank()) {
|
||||||
|
changelogText = text
|
||||||
|
com.hormonetrack.util.AppLog.log("changelog", "nouveautés affichées (${text.length} car.)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
changelogText?.let { md ->
|
changelogText?.let { md ->
|
||||||
com.hormonetrack.ui.components.ChangelogDialog(
|
com.hormonetrack.ui.components.ChangelogDialog(
|
||||||
|
|||||||
@ -81,15 +81,26 @@ fun SettingsScreen(onBack: () -> Unit) {
|
|||||||
val logExportLauncher = rememberLauncherForActivityResult(
|
val logExportLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.CreateDocument("text/plain")
|
ActivityResultContracts.CreateDocument("text/plain")
|
||||||
) { uri ->
|
) { uri ->
|
||||||
uri ?: return@rememberLauncherForActivityResult
|
// Même pattern que l'export JSON (BackupManager.writeBackup) : écriture
|
||||||
|
// synchronisée dans une coroutine IO, AppLog toutes les étapes
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
AppLog.log("logs", "export démarré")
|
||||||
try {
|
try {
|
||||||
context.contentResolver.openOutputStream(uri, "wt")?.use { os ->
|
val logs = AppLog.all()
|
||||||
os.write(AppLog.all().toByteArray(Charsets.UTF_8))
|
val ok = uri != null && try {
|
||||||
os.flush()
|
context.contentResolver.openOutputStream(uri, "wt")?.use { os ->
|
||||||
|
os.write(logs.toByteArray(Charsets.UTF_8))
|
||||||
|
os.flush()
|
||||||
|
} ?: false
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
AppLog.log("logs", "ERREUR export : ${e.message}")
|
||||||
|
false
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) { logLineCount = AppLog.count() }
|
AppLog.log("logs", "export terminé : ok=$ok, ${logs.length} caractères")
|
||||||
} catch (_: Exception) { }
|
} catch (e: Exception) {
|
||||||
|
AppLog.log("logs", "ERREUR export (outer) : ${e.message}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var importJson by remember { mutableStateOf<String?>(null) }
|
var importJson by remember { mutableStateOf<String?>(null) }
|
||||||
|
|||||||
@ -435,10 +435,19 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
|
|||||||
checked = calendarEnabled,
|
checked = calendarEnabled,
|
||||||
onCheckedChange = { want ->
|
onCheckedChange = { want ->
|
||||||
if (want) {
|
if (want) {
|
||||||
|
// ⚠️ L'agenda exige une Posologie (l'événement
|
||||||
|
// se répète selon son intervalle) : sans elle,
|
||||||
|
// on refuse l'activation (feedback rouge)
|
||||||
|
if (!forecastEnabled) {
|
||||||
|
AppLog.log("agenda", "switch ON sans Posologie → refusé")
|
||||||
|
return@Switch
|
||||||
|
}
|
||||||
if (calendarPermissionGranted) {
|
if (calendarPermissionGranted) {
|
||||||
calendarEnabled = true
|
calendarEnabled = true
|
||||||
|
AppLog.log("agenda", "switch ON (permission déjà accordée)")
|
||||||
} else {
|
} else {
|
||||||
// le callback activera le switch si accordée
|
// le callback activera le switch si accordée
|
||||||
|
AppLog.log("agenda", "demande de permission agenda")
|
||||||
calendarPermissionLauncher.launch(
|
calendarPermissionLauncher.launch(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
android.Manifest.permission.WRITE_CALENDAR,
|
android.Manifest.permission.WRITE_CALENDAR,
|
||||||
@ -448,6 +457,7 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
calendarEnabled = false
|
calendarEnabled = false
|
||||||
|
AppLog.log("agenda", "switch OFF")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -507,8 +517,13 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
|
|||||||
// suppression sinon (et si un événement existait)
|
// suppression sinon (et si un événement existait)
|
||||||
var eventToStore = saved.calendarEventId
|
var eventToStore = saved.calendarEventId
|
||||||
try {
|
try {
|
||||||
|
// ⚠️ Permission vérifiée RÉELLEMENT via ContextCompat —
|
||||||
|
// le flag remembered peut être faux après recomposition
|
||||||
|
val hasCalendarPermission = androidx.core.content.ContextCompat.checkSelfPermission(
|
||||||
|
context, android.Manifest.permission.WRITE_CALENDAR
|
||||||
|
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
if (calendarEnabled && saved.isActive && saved.forecastIntervalDays != null
|
if (calendarEnabled && saved.isActive && saved.forecastIntervalDays != null
|
||||||
&& calendarPermissionGranted
|
&& hasCalendarPermission
|
||||||
) {
|
) {
|
||||||
val calId = CalendarEvents.ensureCalendar(context)
|
val calId = CalendarEvents.ensureCalendar(context)
|
||||||
eventToStore = CalendarEvents.upsertEvent(context, calId, saved)
|
eventToStore = CalendarEvents.upsertEvent(context, calId, saved)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.hormonetrack.pk
|
|||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.hormonetrack.data.backup.BackupData
|
import com.hormonetrack.data.backup.BackupData
|
||||||
|
import com.hormonetrack.data.model.TreatmentType
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Assume.assumeTrue
|
import org.junit.Assume.assumeTrue
|
||||||
@ -16,7 +17,7 @@ import java.io.File
|
|||||||
* tous les ~6-7 j), 22 labs couvrant les DEUX périodes.
|
* tous les ~6-7 j), 22 labs couvrant les DEUX périodes.
|
||||||
*
|
*
|
||||||
* ⚠️ Données de santé personnelles : fichier HORS dépôt
|
* ⚠️ Données de santé personnelles : fichier HORS dépôt
|
||||||
* (`local-test-data/backup-v1.2.3.json`, gitignoré) ; tests ignorés si absent.
|
* (`local-test-data/backup-v1.3.1.json`, gitignoré) ; tests ignorés si absent.
|
||||||
*
|
*
|
||||||
* Cas de bug v1.2.4 : passer un traitement à `isActive = false` faisait disparaître
|
* Cas de bug v1.2.4 : passer un traitement à `isActive = false` faisait disparaître
|
||||||
* sa simulation (et faussait la calibration) — « inactif » est un drapeau
|
* sa simulation (et faussait la calibration) — « inactif » est un drapeau
|
||||||
@ -27,16 +28,16 @@ class RegressionUserCase3Test {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DATA_FILE: File = listOf(
|
private val DATA_FILE: File = listOf(
|
||||||
File("../local-test-data/backup-v1.2.3.json"),
|
File("../local-test-data/backup-v1.3.1.json"),
|
||||||
File("local-test-data/backup-v1.2.3.json"),
|
File("local-test-data/backup-v1.3.1.json"),
|
||||||
File("app/local-test-data/backup-v1.2.3.json")
|
File("app/local-test-data/backup-v1.3.1.json")
|
||||||
).firstOrNull { it.exists() } ?: File("../local-test-data/backup-v1.2.3.json")
|
).firstOrNull { it.exists() } ?: File("../local-test-data/backup-v1.3.1.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
assumeTrue(
|
assumeTrue(
|
||||||
"local-test-data/backup-v1.2.3.json absent — tests ignorés (données locales)",
|
"local-test-data/backup-v1.3.1.json absent — tests ignorés (données locales)",
|
||||||
DATA_FILE.exists()
|
DATA_FILE.exists()
|
||||||
)
|
)
|
||||||
if (!PKProfileStore.hasProfile("EV", "TFS")) {
|
if (!PKProfileStore.hasProfile("EV", "TFS")) {
|
||||||
@ -54,16 +55,21 @@ class RegressionUserCase3Test {
|
|||||||
@Test
|
@Test
|
||||||
fun `third backup parses with an inactive EV treatment and an active EEn one`() {
|
fun `third backup parses with an inactive EV treatment and an active EEn one`() {
|
||||||
val data = importUserBackup()
|
val data = importUserBackup()
|
||||||
assertEquals(2, data.treatments.size)
|
assertEquals(3, data.treatments.size)
|
||||||
assertEquals(38, data.doseLogs.size)
|
assertEquals(51, data.doseLogs.size)
|
||||||
assertEquals(22, data.labResults.size)
|
assertEquals(26, data.labResults.size)
|
||||||
val ev = data.treatments.single { it.esterType == "EV" }
|
val ev = data.treatments.single { it.esterType == "EV" }
|
||||||
val een = data.treatments.single { it.esterType == "EEN" }
|
val een = data.treatments.single { it.esterType == "EEN" }
|
||||||
|
val cpa = data.treatments.single { it.type == TreatmentType.ANTI_ANDROGEN }
|
||||||
// Le traitement EV est INACTIF : ses doses doivent néanmoins être simulées
|
// Le traitement EV est INACTIF : ses doses doivent néanmoins être simulées
|
||||||
assertTrue(!ev.isActive)
|
assertTrue(!ev.isActive)
|
||||||
assertTrue(een.isActive)
|
assertTrue(een.isActive)
|
||||||
|
assertTrue(cpa.isActive)
|
||||||
assertTrue(data.doseLogs.count { it.treatmentId == ev.id } > 20)
|
assertTrue(data.doseLogs.count { it.treatmentId == ev.id } > 20)
|
||||||
assertTrue(data.doseLogs.count { it.treatmentId == een.id } > 5)
|
assertTrue(data.doseLogs.count { it.treatmentId == een.id } > 5)
|
||||||
|
// CPA oral : anti-androgène (0 contribution E2), posologie 1 j
|
||||||
|
assertTrue(cpa.type == TreatmentType.ANTI_ANDROGEN)
|
||||||
|
assertTrue(cpa.forecastIntervalDays == 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -1,7 +1,29 @@
|
|||||||
# Changelog — HormoneTrack
|
# Changelog — HormoneTrack
|
||||||
|
|
||||||
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.3.1`, …).
|
Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.3.2`, …).
|
||||||
|
|
||||||
|
## [1.3.2] — 2026-09-06 (versionCode 16)
|
||||||
|
|
||||||
|
### Corrigé
|
||||||
|
- **Toggle de l'agenda** : le switch ne s'activait pas (le résultat de la
|
||||||
|
permission arrive ASYNCHRONE — le test synchrone juste après `launch()`
|
||||||
|
était toujours faux). FIX : le callback active le switch ; Posologie
|
||||||
|
désormais **requise** pour l'agenda (refus + message si absente) ;
|
||||||
|
permission vérifiée RÉELLEMENT via `ContextCompat` au save (le flag
|
||||||
|
remembered pouvait être faux après recomposition) ; journalisation
|
||||||
|
AppLog à chaque étape.
|
||||||
|
- **Export des logs** plantait l'app : réécrit avec le pattern éprouvé de
|
||||||
|
l'export JSON (BackupManager) ; chaque étape journalisée dans AppLog.
|
||||||
|
- **Dialog « Nouveautés » réapparaissait à chaque réouverture** : la version
|
||||||
|
vue est désormais mémorisée AVANT l'affichage (et non après) + AppLog
|
||||||
|
pour diagnostiquer si ça se reproduit.
|
||||||
|
- **Titre du dialog « Nouveautés »** : affiche la version de l'APP
|
||||||
|
(`BuildConfig.VERSION_NAME`) — si l'APK installé est 1.3.0, le titre
|
||||||
|
montre 1.3.0 même si le contenu parle d'une version plus récente
|
||||||
|
(l'asset changelog.md peut contenir des sections plus récentes si
|
||||||
|
l'APK a été reconstruit après coup). Installer le dernier APK
|
||||||
|
(`HormoneTrack-v1.3.2-release.apk`) pour avoir la version correcte.
|
||||||
|
|
||||||
## [1.3.1] — 2026-09-06 (versionCode 15)
|
## [1.3.1] — 2026-09-06 (versionCode 15)
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,7 @@ Fonctionnalités v1 :
|
|||||||
| 5 sept. 2026 (session v1.2.2) | Toggle T masque aussi les labs T ; **prise de sang E2 + T en une entrée** (chacune optionnelle) affichée **côte à côte** ; sélecteur d'édition par marqueur ; suppression par prise ; 4 tests de regroupement ; **48 tests verts**, APK v1.2.2. |
|
| 5 sept. 2026 (session v1.2.2) | Toggle T masque aussi les labs T ; **prise de sang E2 + T en une entrée** (chacune optionnelle) affichée **côte à côte** ; sélecteur d'édition par marqueur ; suppression par prise ; 4 tests de regroupement ; **48 tests verts**, APK v1.2.2. |
|
||||||
| 5 sept. 2026 (session v1.2.6) | Valeurs estimées affichées sur les pics/creux (drawExtremum) ; **import JSON en mode écrasement** (bug : conflit d'IDs si données existantes) avec restauration du tConfig et reprogrammation des rappels ; docs ; 62 tests verts, APK v1.2.6 + releases. |
|
| 5 sept. 2026 (session v1.2.6) | Valeurs estimées affichées sur les pics/creux (drawExtremum) ; **import JSON en mode écrasement** (bug : conflit d'IDs si données existantes) avec restauration du tConfig et reprogrammation des rappels ; docs ; 62 tests verts, APK v1.2.6 + releases. |
|
||||||
| 6 sept. 2026 (session v1.3.1) | En-tête « temps sous THS » sur Doses ; **logs de diagnostic** (AppLog : buffer 500 lignes, persisté, exportable de Paramètres) ; **fix toggle agenda** (callback async) ; **87 tests verts**, APK v1.3.1 + releases. |
|
| 6 sept. 2026 (session v1.3.1) | En-tête « temps sous THS » sur Doses ; **logs de diagnostic** (AppLog : buffer 500 lignes, persisté, exportable de Paramètres) ; **fix toggle agenda** (callback async) ; **87 tests verts**, APK v1.3.1 + releases. |
|
||||||
|
| 6 sept. 2026 (session v1.3.2) | 3 bugs remontés : toggle agenda (callback async fix + ContextCompat au save + Posologie requise), export logs plantait (pattern JSON réutilisé + AppLog), dialog changelog récurrent (version vue mémorisée avant affichage + titre BuildConfig documenté) ; export réel mis à jour (3 traitements : EV inactif + EEn actif + CPA oral) ; **87 tests verts**, APK v1.3.2 + releases. |
|
||||||
| 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 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.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. |
|
| 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.10) | Sens des boutons de zoom inversé (+ = zoom avant, convention carte — retour utilisateur) ; 67 tests verts, APK v1.2.10 + releases. |
|
||||||
@ -707,12 +708,34 @@ Historique complet — **à ne pas réintroduire** (utile pour diff/revert) :
|
|||||||
PATCH de renommage peuvent être ignorés (asset au nom générique, APK
|
PATCH de renommage peuvent être ignorés (asset au nom générique, APK
|
||||||
release disparu de la release). → `ensure_asset` dans le script :
|
release disparu de la release). → `ensure_asset` dans le script :
|
||||||
upload + vérification nom/taille + retry PATCH + échec bruyant.
|
upload + vérification nom/taille + retry PATCH + échec bruyant.
|
||||||
|
42. **Dialog « Nouveautés » à chaque réouverture** (v1.3.2, remontée) :
|
||||||
|
la version vue était mémorisée APRÈS l'affichage — si l'app est fermée
|
||||||
|
pendant que le dialog est ouvert, DataStore n'avait pas encore flushé
|
||||||
|
→ même changelog ressortait. → mémoriser la version vue AVANT
|
||||||
|
d'afficher (l'affichage est purement visuel, la mémoire est déjà
|
||||||
|
faite). Titre = BuildConfig.VERSION_NAME (version de L'APP) : si
|
||||||
|
l'APK installé est v1.3.0, le titre montre 1.3.0 même si le contenu
|
||||||
|
parle d'une version plus récente — installer le dernier APK.
|
||||||
|
40. **Toggle agenda toujours inopérant** (v1.3.2, remontée) : le FIX v1.3.1
|
||||||
|
(callback async) était correct mais insuffisant : (a) le switch
|
||||||
|
s'activait même SANS Posologie (l'événement RRULE aurait un INTERVAL
|
||||||
|
invalide) → désormais refusé avec feedback rouge ; (b) au save, la
|
||||||
|
permission était vérifiée via le flag `remembered` (peut être faux
|
||||||
|
après recomposition) → vérification RÉELLE via ContextCompat ;
|
||||||
|
(c) AppLog journalise chaque étape (permission demandée/accordée,
|
||||||
|
upsert, delete) pour le debug à distance.
|
||||||
39. **Toggle de l'agenda inopérant** (v1.3.1, remontée) : le résultat de la
|
39. **Toggle de l'agenda inopérant** (v1.3.1, remontée) : le résultat de la
|
||||||
permission arrive ASYNCHRONE — le test synchrone juste après `launch()`
|
permission arrive ASYNCHRONE — le test synchrone juste après `launch()`
|
||||||
était toujours faux → le switch rebondissait sans s'activer, sans
|
était toujours faux → le switch rebondissait sans s'activer, sans
|
||||||
feedback. → le CALLBACK active le switch ; journalisation AppLog ;
|
feedback. → le CALLBACK active le switch ; journalisation AppLog ;
|
||||||
leçon : ne jamais lire un état de permission juste après launch().
|
leçon : ne jamais lire un état de permission juste après launch().
|
||||||
38. **Comparaison lexicographique de tags/versions** (v1.3.0, publication
|
41. **Export des logs plantait l'app** (v1.3.2, remontée) : le launcher
|
||||||
|
CreateDocument avec écriture inline dans une coroutine IO non couverte
|
||||||
|
par le même pattern que l'export JSON. → réutilise BackupManager
|
||||||
|
(openOutputStream "wt") + journalisation AppLog à chaque étape.
|
||||||
|
Leçon : quand un pattern d'IO marche (export JSON), le réutiliser
|
||||||
|
tel quel plutôt que d'en écrire un nouveau.
|
||||||
|
41. **Comparaison lexicographique de tags/versions** (v1.3.0, publication
|
||||||
farewell) : `tag >= "v1.2.5"` en comparaison de CHAÎNES fait `v1.2.10 <
|
farewell) : `tag >= "v1.2.5"` en comparaison de CHAÎNES fait `v1.2.10 <
|
||||||
v1.2.5` ("1" < "5") → la boucle de publication a sauté v1.2.10. →
|
v1.2.5` ("1" < "5") → la boucle de publication a sauté v1.2.10. →
|
||||||
toujours comparer les versions STRUCTURÉEMENT (tuple numérique, cf
|
toujours comparer les versions STRUCTURÉEMENT (tuple numérique, cf
|
||||||
@ -831,9 +854,9 @@ git checkout main
|
|||||||
# 2. Publier sur chaque instance (corps = CHANGELOG + les 2 APK attachés)
|
# 2. Publier sur chaque instance (corps = CHANGELOG + les 2 APK attachés)
|
||||||
# ⚠️ NE JAMAIS comparer les tags en chaînes (v1.2.10 < v1.2.5
|
# ⚠️ NE JAMAIS comparer les tags en chaînes (v1.2.10 < v1.2.5
|
||||||
# lexicographiquement !) — comparer en tuples numériques (§14 #38)
|
# lexicographiquement !) — comparer en tuples numériques (§14 #38)
|
||||||
# ⚠️ CONFIRMÉ v1.3.1 : les DEUX invocations rapprochées du script se
|
# ⚠️⚠️ CONFIRMÉ v1.2.10 + v1.3.0 + v1.3.1 : les DEUX invocations
|
||||||
# remplacent mutuellement (le 2e upload écrase le 1er, nom générique).
|
# remplacent mutuellement (le 2e upload écrase le 1er, nom générique) —
|
||||||
# Pattern OBLIGATOIRE (validé v1.2.6/10/3.0) : UNE passe Python inline —
|
# BUG RÉCURRENT, confirmé 3 fois. Pattern OBLIGATOIRE : UNE passe Python inline —
|
||||||
# purge des assets, upload séquentiel, VÉRIFICATION PAR TÉLÉCHARGEMENT
|
# purge des assets, upload séquentiel, VÉRIFICATION PAR TÉLÉCHARGEMENT
|
||||||
# de CHAQUE asset puis RE-VÉRIFICATION FINALE des deux (listing ET
|
# de CHAQUE asset puis RE-VÉRIFICATION FINALE des deux (listing ET
|
||||||
# contenu délivré). Le script one-shot par APK ne suffit plus.
|
# contenu délivré). Le script one-shot par APK ne suffit plus.
|
||||||
@ -1027,4 +1050,4 @@ Sur le téléphone de test (à compléter par l'utilisatrice) :
|
|||||||
sur le graphique** et la calibration couvre toujours ses périodes
|
sur le graphique** et la calibration couvre toujours ses périodes
|
||||||
|
|
||||||
---
|
---
|
||||||
*Doc mise à jour le 6 sept. 2026 (v1.3.1) — build OK, 87/87 tests verts (44 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.*
|
*Doc mise à jour le 6 sept. 2026 (v1.3.2) — build OK, 87/87 tests verts (44 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.*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user