Преглед на файлове

Corrige l'écran noir de l'app sur montre

Cause : AppNavHost utilisait androidx.navigation.compose.composable au lieu de
androidx.wear.compose.navigation.composable. Avec ce mauvais import, les
destinations de SwipeDismissableNavHost ne sont jamais rendues : l'app démarre
sur un écran noir, sans exception ni crash.

- AppNavHost : import androidx.wear.compose.navigation.composable + modifier
  Modifier.fillMaxSize() sur SwipeDismissableNavHost.
- Compose BOM 2025.05.00 -> 2025.08.00 (Compose 1.9.0), version attendue par
  Wear Compose 1.6.2 ; évite des versions Compose mixtes 1.8/1.9.
- Supprime le disable lint RememberInComposition (inutile avec AGP 8.9.2).
- Ajoute scripts/screencap-check.sh pour diagnostiquer un écran noir via
  l'analyse des pixels d'une capture adb.
- Met à jour AGENTS.md (pièges Wear Compose, épinglages).

Vérifié sur montre (Android 14, 466x466) : HomeScreen rend correctement.
aston преди 1 месец
родител
ревизия
2be2b640f7
променени са 5 файла, в които са добавени 60 реда и са изтрити 13 реда
  1. 21 5
      AGENTS.md
  2. 1 1
      gradle/libs.versions.toml
  3. 34 0
      scripts/screencap-check.sh
  4. 0 6
      wear/build.gradle.kts
  5. 4 1
      wear/src/main/java/fr/barteam/watchosm/ui/AppNavHost.kt

+ 21 - 5
AGENTS.md

@@ -37,12 +37,28 @@ java -jar /tmp/opencode/plantuml.jar -checkonly documentation/puml/*.puml
 - **Épinglages à ne pas bouger sans raisons** :
   - AGP < 8.9.2 → son lint plante (UAST vs Kotlin 2.1,
     `KaCallableMemberCall`). Ne pas revenir à 8.7.x.
+  - **Compose BOM = `2025.08.00`** (Compose 1.9.0) : c'est la version attendue
+    par Wear Compose 1.6.2 (qui dépend de `compose.ui:1.9.0`). Avec une BOM
+    plus ancienne (1.8.x) les versions Compose deviennent mixtes et le rendu
+    casse. Ne pas redescendre.
   - Wear Compose **1.6.2** : la 1.7.x exige AGP 9.1 + compileSdk 37.
-  - core-ktx / lifecycle / compose-bom : versions inférieures aux dernières
-    car les suivantes demandent compileSdk ≥ 36. Monter compileSdk/AGP est un
-    chantier à part entière (AGP 9 change le DSL).
-- `lint { disable += "RememberInComposition" }` dans `wear/build.gradle.kts` :
-  détecteur en crash avec cette paire AGP/Kotlin, laisser tel quel.
+  - core-ktx / lifecycle : versions inférieures aux dernières car les suivantes
+    demandent compileSdk ≥ 36. Monter compileSdk/AGP est un chantier à part.
+
+## Pièges de rendu Wear Compose (déjà rencontrés)
+
+- Dans `SwipeDismissableNavHost`, utiliser **impérativement**
+  `androidx.wear.compose.navigation.composable` — et NON
+  `androidx.navigation.compose.composable`. Avec le mauvais import les
+  destinations ne s'affichent jamais : l'app démarre sur un **écran noir**
+  (aucune exception, surface dessinée mais vide). C'était la cause du bug
+  d'écran noir d'octobre 2026.
+- Toujours passer `modifier = Modifier.fillMaxSize()` à
+  `SwipeDismissableNavHost` : sans contrainte de taille explicite il peut
+  mesurer 0.
+- Pour déboguer un écran noir sur montre : `adb exec-out screencap -p` +
+  analyse des pixels (voir `scripts/`), puis comparer avec le `NavHost`
+  standard `androidx.navigation.compose.NavHost` qui, lui, rend correctement.
 
 ## Diagrammes UML
 

+ 1 - 1
gradle/libs.versions.toml

@@ -5,7 +5,7 @@ coroutines = "1.10.1"
 coreKtx = "1.15.0"
 lifecycle = "2.9.0"
 activityCompose = "1.10.1"
-composeBom = "2025.05.00"
+composeBom = "2025.08.00"
 wearCompose = "1.6.2"
 junit = "4.13.2"
 

+ 34 - 0
scripts/screencap-check.sh

@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# Capture l'écran d'une montre connectée en adb et indique la part de pixels
+# non-noirs. Un écran noir uniforme (0 %) alors que l'app est au premier plan
+# trahit un problème de rendu Compose (voir AGENTS.md).
+#
+# Usage : scripts/screencap-check.sh [ip:port]
+set -euo pipefail
+
+ADB="${ANDROID_HOME:-$HOME/Android/Sdk}/platform-tools/adb"
+SERIAL="${1:-${WATCH_SERIAL:-}}"
+[ -n "$SERIAL" ] || { echo "Usage: $0 <ip:port>   (ou WATCH_SERIAL=ip:port)"; exit 1; }
+
+OUT="$(mktemp --suffix=.png)"
+"$ADB" -s "$SERIAL" exec-out screencap -p > "$OUT"
+
+python3 - "$OUT" <<'PY'
+import sys
+from PIL import Image
+
+path = sys.argv[1]
+im = Image.open(path).convert("RGB")
+px = im.load()
+w, h = im.size
+total = nonblack = 0
+for y in range(0, h, 2):
+    for x in range(0, w, 2):
+        total += 1
+        if px[x, y] != (0, 0, 0):
+            nonblack += 1
+ratio = 100.0 * nonblack / total if total else 0.0
+print(f"capture {path} {w}x{h} : {ratio:.1f}% de pixels non-noirs ({nonblack}/{total})")
+if ratio == 0.0:
+    print("-> écran entièrement noir : suspect (veille ? rendu Compose cassé ?)")
+PY

+ 0 - 6
wear/build.gradle.kts

@@ -34,12 +34,6 @@ android {
     buildFeatures {
         compose = true
     }
-
-    lint {
-        // Le détecteur RememberInComposition plante (IncompatibleClassChangeError)
-        // avec la paire AGP 8.7.3 / Kotlin 2.1.20 : incompatibilité UAST connue.
-        disable += "RememberInComposition"
-    }
 }
 
 kotlin {

+ 4 - 1
wear/src/main/java/fr/barteam/watchosm/ui/AppNavHost.kt

@@ -1,16 +1,18 @@
 package fr.barteam.watchosm.ui
 
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.compose.composable
 import androidx.wear.compose.navigation.SwipeDismissableNavHost
+import androidx.wear.compose.navigation.composable
 import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
 import fr.barteam.watchosm.ui.screens.HomeScreen
 import fr.barteam.watchosm.ui.screens.NavigationScreen
@@ -54,6 +56,7 @@ fun AppNavHost(
     SwipeDismissableNavHost(
         navController = navController,
         startDestination = ROUTE_HOME,
+        modifier = Modifier.fillMaxSize(),
     ) {
         composable(ROUTE_HOME) {
             HomeScreen(