From 263b4e3bdd9e55dfa327b58e4e8ac92676cfcfe1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 23:14:17 +0000 Subject: [PATCH] =?UTF-8?q?Corrige=20l'=C3=A9chec=20de=20build=20APK=20:?= =?UTF-8?q?=20setKeepOnScreenCondition=20n'existe=20pas=20c=C3=B4t=C3=A9?= =?UTF-8?q?=20natif?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI cassée sur main (compileReleaseJavaWithJavac) : "cannot find symbol: method setKeepOnScreenCondition" sur SplashScreen. Cette méthode n'existe que sur androidx.core.splashscreen.SplashScreen (bibliothèque de compatibilité) — jamais sur android.window.SplashScreen, la classe native utilisée jusqu'ici (contrainte "pas d'AndroidX" du projet). Confusion entre les deux API lors de l'implémentation initiale, vérifiée après coup auprès de la documentation Android officielle. Remplacé par ViewTreeObserver.OnPreDrawListener sur la vue de contenu (findViewById(android.R.id.content), juste après setContentView) : reporter le dessin de la toute première frame de l'activité reporte de fait la disparition du splash, quelle que soit l'API — c'est la technique native documentée par Android pour ce cas d'usage précis (attente d'un chargement asynchrone avant la première frame), sans classe ni attribut spécifiques à l'API 31+, contrairement à la tentative précédente. Documentation corrigée dans docs/ANDROID_PACKAGING.md et docs/spec/packaging-lancement.md pour refléter le mécanisme réel. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012rz3jgonqVHP4fzQsJh8oP --- .../com/skohscripts/kairos/MainActivity.java | 50 ++++++++++++++----- docs/ANDROID_PACKAGING.md | 34 +++++++++---- docs/spec/packaging-lancement.md | 8 +-- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/android/app/src/main/java/com/skohscripts/kairos/MainActivity.java b/android/app/src/main/java/com/skohscripts/kairos/MainActivity.java index 96e4fb5..fca01e5 100644 --- a/android/app/src/main/java/com/skohscripts/kairos/MainActivity.java +++ b/android/app/src/main/java/com/skohscripts/kairos/MainActivity.java @@ -5,6 +5,8 @@ import android.app.AlertDialog; import android.os.Build; import android.os.Bundle; +import android.view.View; +import android.view.ViewTreeObserver; import android.webkit.JsResult; import android.webkit.WebChromeClient; import android.webkit.WebView; @@ -36,10 +38,16 @@ public class MainActivity extends Activity { private WebView webView; private KairosNotificationBridge notificationBridge; // Tenu à `false` jusqu'à ce que la première page ait fini de charger dans la - // WebView (voir `onPageFinished` ci-dessous) : sans ce verrou, le splash natif - // se ferme dès la première frame dessinée par `setContentView` ci-dessous, donc - // avant même que Python/uvicorn n'ait démarré — l'utilisateur voit alors une - // WebView blanche à la place du splash pendant toute l'attente du serveur. + // WebView (voir `onPageFinished` ci-dessous). Sans mécanisme de retenue, le + // splash (natif API 31+, ou simple fond `windowBackground` en dessous) se + // ferme/laisse place à la première frame dessinée dès `setContentView` + // ci-dessous, donc avant même que Python/uvicorn n'ait démarré — l'utilisateur + // voit alors une WebView blanche pendant toute l'attente du serveur. Technique + // retenue : `ViewTreeObserver.OnPreDrawListener` (voir onCreate) — reporter la + // toute première frame reporte de fait la disparition du splash, quelle que + // soit l'API, sans dépendre d'une classe spécifique à l'API 31+ ni d'AndroidX + // (`androidx.core.splashscreen.SplashScreen.setKeepOnScreenCondition`, la + // seule à porter ce nom, est hors périmètre — voir docs/ANDROID_PACKAGING.md). private final AtomicBoolean uiReady = new AtomicBoolean(false); @SuppressLint("SetJavaScriptEnabled") @@ -47,14 +55,6 @@ public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Doit être appelé avant `setContentView` (voir plus bas) pour pouvoir - // retenir le splash natif — sans effet sur API < 31 (pas d'AndroidX, cf. - // docs/ANDROID_PACKAGING.md : `getSplashScreen()` n'existe pas avant cette - // API, d'où la garde explicite). - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - getSplashScreen().setKeepOnScreenCondition(() -> !uiReady.get()); - } - if (!Python.isStarted()) { Python.start(new AndroidPlatform(this)); } @@ -74,7 +74,12 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); - uiReady.set(true); // libère le splash natif (voir champ `uiReady`) + uiReady.set(true); + // Force une nouvelle passe de dessin pour que le pre-draw listener + // (voir onCreate) soit ré-évalué maintenant que `uiReady` est vrai — + // sans ça, rien ne garantit qu'une invalidation survienne d'elle-même + // pendant que la WebView est restée vide en attendant le serveur. + view.invalidate(); } }); webView.setWebChromeClient(new WebChromeClient() { @@ -109,6 +114,25 @@ public boolean onJsConfirm(WebView view, String url, String message, JsResult re webView.addJavascriptInterface(notificationBridge, "KairosAndroid"); setContentView(webView); + + // Reporte le dessin de la toute première frame tant que `uiReady` est faux + // (voir champ `uiReady` et `onPageFinished` ci-dessus) : technique native + // documentée par Android pour ce cas précis (attente d'un chargement + // asynchrone avant la première frame), sans AndroidX ni dépendance de + // version — fonctionne identiquement sur toutes les API, contrairement à + // une éventuelle API dédiée à l'écran de démarrage natif (API 31+ seulement). + final View content = findViewById(android.R.id.content); + content.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (!uiReady.get()) { + return false; + } + content.getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } + }); + registerPredictiveBackCallback(); loadWhenServerReady(); diff --git a/docs/ANDROID_PACKAGING.md b/docs/ANDROID_PACKAGING.md index cca7102..7c6a800 100644 --- a/docs/ANDROID_PACKAGING.md +++ b/docs/ANDROID_PACKAGING.md @@ -98,17 +98,31 @@ Points notables : appareil : le thème seul ne suffisait pas — le splash disparaissait dès la première frame dessinée par `setContentView(webView)`, bien avant que Python/uvicorn n'ait fini de démarrer, laissant place à une WebView - blanche pendant toute l'attente). `MainActivity` retient le splash via - `Activity.getSplashScreen().setKeepOnScreenCondition(...)` (natif, - `android.window`, API 31+ seulement, pas AndroidX), condition liée à un + blanche pendant toute l'attente). **Piège documenté ici pour ne pas le + retrancher deux fois** : une première tentative a utilisé + `Activity.getSplashScreen().setKeepOnScreenCondition(...)` — cette méthode + **n'existe pas** sur `android.window.SplashScreen` (la classe **native**, + seule autorisée par la contrainte « pas d'AndroidX » ci-dessus) ; elle + n'existe que sur `androidx.core.splashscreen.SplashScreen`, la bibliothèque + de compatibilité, hors périmètre. Erreur de compilation constatée en CI + (`cannot find symbol: method setKeepOnScreenCondition(...)`), corrigée + avant tout usage réel. Mécanisme retenu à la place : + `ViewTreeObserver.OnPreDrawListener` sur la vue de contenu + (`findViewById(android.R.id.content)`, posé juste après `setContentView`) — + reporter le dessin de la toute première frame de l'activité reporte de + fait la disparition du splash (natif API 31+, ou simplement l'affichage du + contenu sous `windowBackground` en dessous), puisque c'est justement ce + dessin qui déclenche cette disparition. La condition de report est un champ `uiReady` (`AtomicBoolean`) mis à `true` par - `WebViewClient.onPageFinished` — donc jusqu'à ce que la première page ait - réellement fini de charger dans la WebView, pas seulement jusqu'à la - réponse du serveur (`loadWhenServerReady`/sonde `/favicon.ico`, qui ne - fait que déclencher le `loadUrl`). Appelé avant `setContentView`, seule - séquence valide pour que la condition prenne effet. En dessous de l'API - 31, `windowBackground` reste le seul mécanisme (pas de retenue possible, - l'API `getSplashScreen()` n'existe pas sur ces versions). + `WebViewClient.onPageFinished` (+ `view.invalidate()` pour forcer une + nouvelle passe de dessin et faire réévaluer le listener) — donc jusqu'à ce + que la première page ait réellement fini de charger dans la WebView, pas + seulement jusqu'à la réponse du serveur (`loadWhenServerReady`/sonde + `/favicon.ico`, qui ne fait que déclencher le `loadUrl`). Technique + documentée par Android pour ce cas d'usage précis (attente d'un + chargement asynchrone avant la première frame) ; ni classe ni attribut + spécifiques à l'API 31+, fonctionne identiquement à toutes les API sans + garde de version. - **Icône animée** (`android:windowSplashScreenAnimatedIcon`, API 31+) : plutôt que l'icône de lanceur adaptative statique par défaut, un `AnimatedVectorDrawable` dédié diff --git a/docs/spec/packaging-lancement.md b/docs/spec/packaging-lancement.md index 23a967b..1bd55c8 100644 --- a/docs/spec/packaging-lancement.md +++ b/docs/spec/packaging-lancement.md @@ -330,9 +330,11 @@ cette spec (pas de duplication du reste) : l'app — évite le flash blanc générique pendant le démarrage de Python+uvicorn, sans dépendance `androidx.core:splashscreen` (voir « pas d'AndroidX » dans `docs/ANDROID_PACKAGING.md`). Sa durée réelle d'affichage est contrôlée depuis - `MainActivity` (`getSplashScreen().setKeepOnScreenCondition(...)`, natif, API - 31+) : sans ce contrôle, le thème seul ne suffit pas à couvrir l'attente du - serveur, le splash se ferme dès la première frame dessinée. Son icône est un + `MainActivity` par un `ViewTreeObserver.OnPreDrawListener` qui reporte la + toute première frame de l'activité (technique native standard, pas de classe + spécifique à l'API 31+) : sans ce report, le thème seul ne suffit pas à + couvrir l'attente du serveur, le splash se ferme dès la première frame + dessinée. Son icône est un `AnimatedVectorDrawable` dédié (`res/drawable/kairos_splash_icon*.xml` + `res/animator/kairos_splash_wedge_sweep.xml`, natif, API 21+) : le secteur du logo balaie depuis midi jusqu'à sa position finale plutôt que d'apparaître