v1.2.6 : valeurs des pics/creux sur le graphique + import JSON en mode écrasement
- CurveChart : drawExtremum = triangle ▲▼ + VALEUR estimée de l'extremum
(au-dessus d'un pic, en dessous d'un creux), couleur de la série
- Import JSON : mode ÉCRASEMENT (bug remonté : échec dès que l'app contenait
des données — conflit d'IDs backup vs existants)
· DAOs : deleteAll() sur les 3 tables (ordre enfants → parents, FK CASCADE)
· BackupManager.importJson : wipe puis restauration (IDs conservés → FK
valides), retourne aussi le tConfig du backup
· SettingsScreen : après import → tConfig restauré dans DataStore + rappels
reprogrammés (rescheduleAll) ; dialog renommé « Effacer & restaurer »
avec avertissement explicite
- docs : §13 (écrasement), §11 (valeurs extrema), §14 #34, §16.bis CHECKLIST
DE DÉPLOIEMENT complète numérotée (version → tests → docs → commit/tag →
push 2 remotes → build 2 APK → releases 2 instances → smoke-test R8),
§20/§21, CHANGELOG, README
- versionCode 9, versionName 1.2.6
This commit is contained in:
parent
fb9806afd3
commit
ec76dc83bf
@ -9,7 +9,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.2.5 — build Android ✅, **62 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.2.6 — build Android ✅, **62 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)
|
||||||
|
|||||||
@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "com.hormonetrack"
|
applicationId = "com.hormonetrack"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 8
|
versionCode = 9
|
||||||
versionName = "1.2.5"
|
versionName = "1.2.6"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|||||||
@ -31,8 +31,26 @@ object BackupManager {
|
|||||||
return Gson().toJson(data)
|
return Gson().toJson(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ImportResult(val treatments: Int, val doseLogs: Int, val labResults: Int)
|
data class ImportResult(
|
||||||
|
val treatments: Int,
|
||||||
|
val doseLogs: Int,
|
||||||
|
val labResults: Int,
|
||||||
|
/** Réglages T du backup, à restaurer dans DataStore par l'appelant. */
|
||||||
|
val tConfig: TConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restaure un backup en mode **ÉCRASEMENT** (v1.2.6) : les données actuelles
|
||||||
|
* sont effacées AVANT l'insertion — sinon les IDs du backup (conservés pour
|
||||||
|
* les FK dose→traitement) entrent en conflit avec les IDs existants et
|
||||||
|
* l'import échouait dès que l'app contenait déjà des données.
|
||||||
|
*
|
||||||
|
* Ordre d'effacement : dose_logs et lab_results d'abord, treatments ensuite
|
||||||
|
* (FK CASCADE des doses vers les traitements).
|
||||||
|
*
|
||||||
|
* @return le nombre d'entrées restaurées par table + le tConfig du backup
|
||||||
|
* (à restaurer dans DataStore par l'appelant).
|
||||||
|
*/
|
||||||
suspend fun importJson(
|
suspend fun importJson(
|
||||||
repo: HormoneRepository,
|
repo: HormoneRepository,
|
||||||
json: String
|
json: String
|
||||||
@ -40,6 +58,11 @@ object BackupManager {
|
|||||||
val type = object : TypeToken<BackupData>() {}.type
|
val type = object : TypeToken<BackupData>() {}.type
|
||||||
val data: BackupData = Gson().fromJson(json, type)
|
val data: BackupData = Gson().fromJson(json, type)
|
||||||
|
|
||||||
|
// Écrasement : on part d'une base vide (ordre : enfants → parents)
|
||||||
|
repo.deleteAllDoseLogs()
|
||||||
|
repo.deleteAllLabResults()
|
||||||
|
repo.deleteAllTreatments()
|
||||||
|
|
||||||
data.treatments.forEach { repo.insertTreatment(it) }
|
data.treatments.forEach { repo.insertTreatment(it) }
|
||||||
data.doseLogs.forEach { repo.insertDoseLog(it) }
|
data.doseLogs.forEach { repo.insertDoseLog(it) }
|
||||||
data.labResults.forEach { repo.insertLabResult(it) }
|
data.labResults.forEach { repo.insertLabResult(it) }
|
||||||
@ -47,7 +70,8 @@ object BackupManager {
|
|||||||
return ImportResult(
|
return ImportResult(
|
||||||
data.treatments.size,
|
data.treatments.size,
|
||||||
data.doseLogs.size,
|
data.doseLogs.size,
|
||||||
data.labResults.size
|
data.labResults.size,
|
||||||
|
data.tConfig
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,10 @@ interface DoseLogDao {
|
|||||||
@Query("SELECT * FROM dose_logs ORDER BY timestamp ASC")
|
@Query("SELECT * FROM dose_logs ORDER BY timestamp ASC")
|
||||||
suspend fun getAllOnce(): List<DoseLog>
|
suspend fun getAllOnce(): List<DoseLog>
|
||||||
|
|
||||||
|
/** Efface TOUT l'historique (import en mode écrasement). */
|
||||||
|
@Query("DELETE FROM dose_logs")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
suspend fun insert(log: DoseLog): Long
|
suspend fun insert(log: DoseLog): Long
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,10 @@ interface LabResultDao {
|
|||||||
@Query("SELECT * FROM lab_results ORDER BY timestamp ASC")
|
@Query("SELECT * FROM lab_results ORDER BY timestamp ASC")
|
||||||
suspend fun getAllOnce(): List<LabResult>
|
suspend fun getAllOnce(): List<LabResult>
|
||||||
|
|
||||||
|
/** Efface toutes les analyses (import en mode écrasement). */
|
||||||
|
@Query("DELETE FROM lab_results")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
suspend fun insert(result: LabResult): Long
|
suspend fun insert(result: LabResult): Long
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,11 @@ interface TreatmentDao {
|
|||||||
@Query("SELECT * FROM treatments ORDER BY createdAt DESC")
|
@Query("SELECT * FROM treatments ORDER BY createdAt DESC")
|
||||||
suspend fun getAllOnce(): List<Treatment>
|
suspend fun getAllOnce(): List<Treatment>
|
||||||
|
|
||||||
|
/** Efface tous les traitements (import en mode écrasement).
|
||||||
|
* ⚠️ À appeler APRÈS deleteAll() des dose_logs (FK CASCADE sinon). */
|
||||||
|
@Query("DELETE FROM treatments")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
suspend fun insert(treatment: Treatment): Long
|
suspend fun insert(treatment: Treatment): Long
|
||||||
|
|
||||||
|
|||||||
@ -44,4 +44,9 @@ class HormoneRepository(
|
|||||||
suspend fun insertLabResult(result: LabResult): Long = labResultDao.insert(result)
|
suspend fun insertLabResult(result: LabResult): Long = labResultDao.insert(result)
|
||||||
suspend fun updateLabResult(result: LabResult) = labResultDao.update(result)
|
suspend fun updateLabResult(result: LabResult) = labResultDao.update(result)
|
||||||
suspend fun deleteLabResult(result: LabResult) = labResultDao.delete(result)
|
suspend fun deleteLabResult(result: LabResult) = labResultDao.delete(result)
|
||||||
|
|
||||||
|
// --- Effacement complet (import en mode écrasement, cf BackupManager) ---
|
||||||
|
suspend fun deleteAllDoseLogs() = doseLogDao.deleteAll()
|
||||||
|
suspend fun deleteAllLabResults() = labResultDao.deleteAll()
|
||||||
|
suspend fun deleteAllTreatments() = treatmentDao.deleteAll()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -168,17 +168,18 @@ fun CurveChart(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pics et creux (v1.2.3) : triangles aux extrema locaux, couleur de la série
|
// Pics et creux (v1.2.3) : triangles aux extrema locaux + valeur estimée
|
||||||
|
// (v1.2.6 : le triangle est accompagné de la valeur simulée du pic/creux)
|
||||||
if (options.showExtrema) {
|
if (options.showExtrema) {
|
||||||
PharmacokineticEngine.detectExtrema(s.points, { it.e2 }, minSwingAbs = 2.0)
|
PharmacokineticEngine.detectExtrema(s.points, { it.e2 }, minSwingAbs = 2.0)
|
||||||
.forEach { ex ->
|
.forEach { ex ->
|
||||||
drawTriangle(xOf(ex.timestamp), yE2(ex.value), ex.isPeak, s.e2Style.color)
|
drawExtremum(xOf(ex.timestamp), yE2(ex.value), ex, s.e2Style.color)
|
||||||
}
|
}
|
||||||
if (options.showT) {
|
if (options.showT) {
|
||||||
s.tStyle?.let { tStyle ->
|
s.tStyle?.let { tStyle ->
|
||||||
PharmacokineticEngine.detectExtrema(s.points, { it.t }, minSwingAbs = 0.02)
|
PharmacokineticEngine.detectExtrema(s.points, { it.t }, minSwingAbs = 0.02)
|
||||||
.forEach { ex ->
|
.forEach { ex ->
|
||||||
drawTriangle(xOf(ex.timestamp), yT(ex.value), ex.isPeak, tStyle.color)
|
drawExtremum(xOf(ex.timestamp), yT(ex.value), ex, tStyle.color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,24 +220,47 @@ fun CurveChart(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Triangle ▲ (pic, au-dessus du point) ou ▼ (creux, en dessous), couleur de la série. */
|
/**
|
||||||
private fun DrawScope.drawTriangle(cx: Float, cy: Float, isPeak: Boolean, color: Color) {
|
* Triangle ▲ (pic, au-dessus du point) ou ▼ (creux, en dessous) + la VALEUR
|
||||||
|
* estimée de l'extremum (v1.2.6) : valeur au-dessus d'un pic, en dessous d'un
|
||||||
|
* creux — dans la couleur de la série, unité implicite (celle de l'axe).
|
||||||
|
*/
|
||||||
|
private fun DrawScope.drawExtremum(cx: Float, cy: Float, extremum: com.hormonetrack.pk.PharmacokineticEngine.Extremum, color: Color) {
|
||||||
val r = 5.dp.toPx()
|
val r = 5.dp.toPx()
|
||||||
val offset = 6.dp.toPx()
|
val offset = 6.dp.toPx()
|
||||||
val path = Path()
|
val path = Path()
|
||||||
if (isPeak) {
|
val valueY: Float
|
||||||
|
if (extremum.isPeak) {
|
||||||
val y = cy - offset
|
val y = cy - offset
|
||||||
path.moveTo(cx, y - r)
|
path.moveTo(cx, y - r)
|
||||||
path.lineTo(cx - r, y + r * 0.5f)
|
path.lineTo(cx - r, y + r * 0.5f)
|
||||||
path.lineTo(cx + r, y + r * 0.5f)
|
path.lineTo(cx + r, y + r * 0.5f)
|
||||||
|
valueY = y - r - 3.dp.toPx()
|
||||||
} else {
|
} else {
|
||||||
val y = cy + offset
|
val y = cy + offset
|
||||||
path.moveTo(cx, y + r)
|
path.moveTo(cx, y + r)
|
||||||
path.lineTo(cx - r, y - r * 0.5f)
|
path.lineTo(cx - r, y - r * 0.5f)
|
||||||
path.lineTo(cx + r, y - r * 0.5f)
|
path.lineTo(cx + r, y - r * 0.5f)
|
||||||
|
valueY = y + r + 11.dp.toPx()
|
||||||
}
|
}
|
||||||
path.close()
|
path.close()
|
||||||
drawPath(path, color)
|
drawPath(path, color)
|
||||||
|
// Valeur estimée du pic/creux, dans la couleur de la courbe
|
||||||
|
val paint = android.graphics.Paint().apply {
|
||||||
|
this.color = android.graphics.Color.argb(
|
||||||
|
(color.alpha * 255).toInt(),
|
||||||
|
(color.red * 255).toInt(),
|
||||||
|
(color.green * 255).toInt(),
|
||||||
|
(color.blue * 255).toInt()
|
||||||
|
)
|
||||||
|
textSize = 9.dp.toPx()
|
||||||
|
textAlign = android.graphics.Paint.Align.CENTER
|
||||||
|
isAntiAlias = true
|
||||||
|
isFakeBoldText = true
|
||||||
|
}
|
||||||
|
drawContext.canvas.nativeCanvas.drawText(
|
||||||
|
formatValue(extremum.value), cx, valueY, paint
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DrawScope.drawGrid(color: Color, padLeft: Float, padTop: Float, w: Float, h: Float, rows: Int) { for (i in 0..rows) {
|
private fun DrawScope.drawGrid(color: Color, padLeft: Float, padTop: Float, w: Float, h: Float, rows: Int) { for (i in 0..rows) {
|
||||||
|
|||||||
@ -348,11 +348,18 @@ fun SettingsScreen(onBack: () -> Unit) {
|
|||||||
showImportConfirm = false
|
showImportConfirm = false
|
||||||
val json = importJson ?: return@TextButton
|
val json = importJson ?: return@TextButton
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
// Import en mode ÉCRASEMENT : les données actuelles sont
|
||||||
|
// effacées d'abord (BackupManager), puis le tConfig du
|
||||||
|
// backup restaure DataStore et les rappels sont reprogrammés
|
||||||
val result = try {
|
val result = try {
|
||||||
BackupManager.importJson(repo, json)
|
BackupManager.importJson(repo, json)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
result?.let {
|
||||||
|
container.settings.setTConfig(it.tConfig)
|
||||||
|
AlarmScheduler(context).rescheduleAll(repo.allTreatmentsOnce())
|
||||||
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
message = if (result != null) {
|
message = if (result != null) {
|
||||||
context.getString(R.string.import_ok, result.treatments, result.doseLogs, result.labResults)
|
context.getString(R.string.import_ok, result.treatments, result.doseLogs, result.labResults)
|
||||||
|
|||||||
@ -149,10 +149,10 @@
|
|||||||
<string name="export_ok">Sauvegarde exportée.</string>
|
<string name="export_ok">Sauvegarde exportée.</string>
|
||||||
<string name="export_fail">L\'export a échoué.</string>
|
<string name="export_fail">L\'export a échoué.</string>
|
||||||
<string name="import_fail">L\'import a échoué : fichier invalide.</string>
|
<string name="import_fail">L\'import a échoué : fichier invalide.</string>
|
||||||
<string name="import_ok">Importé : %1$d traitements, %2$d doses, %3$d analyses.</string>
|
<string name="import_ok">Restauré : %1$d traitements, %2$d doses, %3$d analyses (les données actuelles ont été remplacées, rappels reprogrammés).</string>
|
||||||
<string name="import_confirm">Restaurer</string>
|
<string name="import_confirm">Effacer & restaurer</string>
|
||||||
<string name="import_title">Importer une sauvegarde</string>
|
<string name="import_title">Importer une sauvegarde</string>
|
||||||
<string name="import_warning">Le contenu de la sauvegarde sera AJOUTÉ aux données actuelles (aucune suppression).</string>
|
<string name="import_warning">Le backup REMPLACERA toutes les données actuelles : les traitements, doses et analyses existants sont effacés d\'abord. Exporte une sauvegarde avant si tu veux les garder.</string>
|
||||||
|
|
||||||
<string name="about_title">À propos</string>
|
<string name="about_title">À propos</string>
|
||||||
<string name="models_credit">Profils PK issus des modèles d\'injection Estrannaise (EstraNase) et Transfem Science, extraits de Estrogen.ods (tables horaires D, k1–k3).</string>
|
<string name="models_credit">Profils PK issus des modèles d\'injection Estrannaise (EstraNase) et Transfem Science, extraits de Estrogen.ods (tables horaires D, k1–k3).</string>
|
||||||
|
|||||||
@ -149,10 +149,10 @@
|
|||||||
<string name="export_ok">Backup exported.</string>
|
<string name="export_ok">Backup exported.</string>
|
||||||
<string name="export_fail">Export failed.</string>
|
<string name="export_fail">Export failed.</string>
|
||||||
<string name="import_fail">Import failed: invalid file.</string>
|
<string name="import_fail">Import failed: invalid file.</string>
|
||||||
<string name="import_ok">Imported: %1$d treatments, %2$d doses, %3$d labs.</string>
|
<string name="import_ok">Restored: %1$d treatments, %2$d doses, %3$d labs (current data was replaced, reminders rescheduled).</string>
|
||||||
<string name="import_confirm">Restore</string>
|
<string name="import_confirm">Erase & restore</string>
|
||||||
<string name="import_title">Import backup</string>
|
<string name="import_title">Import backup</string>
|
||||||
<string name="import_warning">The backup content will be ADDED to the current data (no deletion).</string>
|
<string name="import_warning">The backup will REPLACE all current data: existing treatments, doses and labs are erased first. Export a backup now if you want to keep them.</string>
|
||||||
|
|
||||||
<string name="about_title">About</string>
|
<string name="about_title">About</string>
|
||||||
<string name="models_credit">PK profiles from Estrannaise (EstraNase) and Transfem Science injection models, extracted from Estrogen.ods (D, k1–k3 hourly tables).</string>
|
<string name="models_credit">PK profiles from Estrannaise (EstraNase) and Transfem Science injection models, extracted from Estrogen.ods (D, k1–k3 hourly tables).</string>
|
||||||
|
|||||||
@ -506,7 +506,10 @@ gauche (pg/mL), T droite (ng/mL). Échelle « nice » (`niceCeil` : 1/2/2.5/5/10
|
|||||||
partagée entre toutes les séries. Grille 4 lignes ; labels Y gauche/droite ; X : pas
|
partagée entre toutes les séries. Grille 4 lignes ; labels Y gauche/droite ; X : pas
|
||||||
6 h/24 h/5 j selon plage (`SimpleDateFormat` HH'h' / dd/MM). Labs : cercles (E2) et
|
6 h/24 h/5 j selon plage (`SimpleDateFormat` HH'h' / dd/MM). Labs : cercles (E2) et
|
||||||
carrés (T) orange + valeur, **T convertie en ng/mL** (`convertTToNgMl`) au rendu.
|
carrés (T) orange + valeur, **T convertie en ng/mL** (`convertTToNgMl`) au rendu.
|
||||||
Ligne verticale « maintenant ».
|
Ligne verticale « maintenant ». **Pics/creux** (v1.2.3, valeurs v1.2.6) :
|
||||||
|
triangles ▲▼ via `PharmacokineticEngine.detectExtrema` **accompagnés de la
|
||||||
|
valeur estimée** du pic (au-dessus) ou du creux (en dessous), dans la couleur
|
||||||
|
de la série (`drawExtremum`).
|
||||||
|
|
||||||
Pièges :
|
Pièges :
|
||||||
- `DrawScope` implémente `Density` → `X.dp.toPx()` direct ; ne PAS écrire de helper custom
|
- `DrawScope` implémente `Density` → `X.dp.toPx()` direct ; ne PAS écrire de helper custom
|
||||||
@ -751,6 +754,32 @@ JSON casse UNIQUEMENT en release. ⚠️ À chaque activation d'optimisation :
|
|||||||
tester sur téléphone l'export/import de backup + les graphiques (R8 ne se
|
tester sur téléphone l'export/import de backup + les graphiques (R8 ne se
|
||||||
vérifie pas en tests JVM).
|
vérifie pas en tests JVM).
|
||||||
|
|
||||||
|
### Checklist de déploiement complète (de A à Z, pour une session sans contexte)
|
||||||
|
|
||||||
|
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` — 62 au total,
|
||||||
|
44 si `local-test-data/` est absent (les régressions réelles sont skippées).
|
||||||
|
3. **Docs** : section `## [X.Y.Z]` en tête de `docs/CHANGELOG.md` (le corps des
|
||||||
|
releases Gitea en sera extrait automatiquement par le script), + §14 si bug
|
||||||
|
corrigé, + §2 (historique) si notable.
|
||||||
|
4. **Commit** (message descriptif par couche) + **tag annoté** :
|
||||||
|
`git tag -a vX.Y.Z -m "résumé"`.
|
||||||
|
5. **Push** : `git push origin main --tags` + `git push farewell main --tags`
|
||||||
|
(farewell : le repo doit exister sur l'instance ; push-to-create désactivé).
|
||||||
|
6. **Construire les 2 APK au niveau du tag** :
|
||||||
|
`git checkout vX.Y.Z` → `./gradlew assembleRelease assembleDebug` → copier
|
||||||
|
`app-release.apk` → `/tmp/apks/HormoneTrack-vX.Y.Z-release.apk` et
|
||||||
|
`app-debug.apk` → `HormoneTrack-vX.Y.Z-debug.apk` → `git checkout main`.
|
||||||
|
7. **Publier les releases** (corps = section CHANGELOG + les 2 APK attachés) :
|
||||||
|
`python3 scripts/gitea-release.py cloudyfy vX.Y.Z <release.apk>` puis
|
||||||
|
idem avec `<debug.apk>` ; répéter avec l'instance `farewell` (token
|
||||||
|
`gitea.farewell.dev` requis dans le trousseau, absent à ce jour).
|
||||||
|
8. **Smoke-test R8 sur téléphone** (le release APK n'est pas vérifiable en
|
||||||
|
tests JVM) : installation par-dessus l'existant, graphiques, export ET
|
||||||
|
import d'un backup JSON, rappel. Le debug APK est le repli (même signature).
|
||||||
|
9. **Mettre à jour §2/§8/§19/§21 si besoin** puis pusher la doc.
|
||||||
|
|
||||||
Notes :
|
Notes :
|
||||||
- L'APK est une **build debug** signée avec la clé debug locale — installable en
|
- L'APK est une **build debug** signée avec la clé debug locale — installable en
|
||||||
sideload, mises à jour entre versions OK (même signature) ;
|
sideload, mises à jour entre versions OK (même signature) ;
|
||||||
@ -864,4 +893,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 5 sept. 2026 (v1.2.5) — build OK, 62/62 tests verts (44 sans les données locales), dépôt Gitea privé + releases APK, aucune donnée de santé dans le dépôt ni l'historique.*
|
*Doc mise à jour le 5 sept. 2026 (v1.2.6) — build OK, 62/62 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