From bd703be68557ced0f60e402c70975ad63eb60f55 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Sun, 23 Aug 2026 22:28:00 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20solder=20deux=20=C3=A9carts=20avant=20?= =?UTF-8?q?d'attaquer=20Android=20Auto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le client d'images de Coil n'avait aucun plafond d'appel, là où celui de l'API en porte un de 30 s. Les délais par défaut d'OkHttp portent sur chaque étape prise à part ; aucun ne borne l'appel entier, donc une pochette qui n'arrive jamais retenait son fil de bout en bout. `ServerHttp.imageClient` le construit désormais, avec la même constante que `defaultClient` : le plafond se décide à un seul endroit. L'écran du compte annonçait encore « La lecture à distance arrive dans une prochaine version ». C'est faux depuis la #17. Écarté après examen, contrairement à ce que le tableau de bord prévoyait : le `catch (Throwable)` de `CoilBitmapLoader`. Il n'avale pas l'erreur, il la transmet par le futur, et la portée des pochettes n'a pas de `CoroutineExceptionHandler` — le restreindre à `Exception` ferait donc remonter un `OutOfMemoryError` de décodage jusqu'au gestionnaire global, c'est-à-dire planter le service de lecture au lieu de perdre une vignette. Le décodage d'images est précisément le lieu de cette erreur-là. Retrait du plafond → seul le nouveau test tombe, sur « le plafond doit être posé ». Claude-Session: https://claude.ai/code/session_01CHnmk73TFtaDDJWoCsLHUD --- app/src/main/java/app/waveflow/WaveFlowApp.kt | 5 +---- .../java/app/waveflow/data/remote/ServerHttp.kt | 14 ++++++++++++++ .../java/app/waveflow/ui/server/ServerScreen.kt | 4 ++-- .../data/remote/ServerImageAuthInterceptorTest.kt | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/waveflow/WaveFlowApp.kt b/app/src/main/java/app/waveflow/WaveFlowApp.kt index 558e346..159bc8c 100644 --- a/app/src/main/java/app/waveflow/WaveFlowApp.kt +++ b/app/src/main/java/app/waveflow/WaveFlowApp.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch -import okhttp3.OkHttpClient /** * Point d'entrée de l'application. @@ -52,9 +51,7 @@ class WaveFlowApp : Application(), ImageLoaderFactory { */ override fun newImageLoader(): ImageLoader = ImageLoader.Builder(this) .okHttpClient { - OkHttpClient.Builder() - .addInterceptor(ServerImageAuthInterceptor(container.serverSessionRepository)) - .build() + ServerHttp.imageClient(ServerImageAuthInterceptor(container.serverSessionRepository)) } .build() } diff --git a/app/src/main/java/app/waveflow/data/remote/ServerHttp.kt b/app/src/main/java/app/waveflow/data/remote/ServerHttp.kt index 64568cf..b792dd9 100644 --- a/app/src/main/java/app/waveflow/data/remote/ServerHttp.kt +++ b/app/src/main/java/app/waveflow/data/remote/ServerHttp.kt @@ -11,6 +11,7 @@ import okhttp3.Call import okhttp3.Callback import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request @@ -199,6 +200,19 @@ class ServerHttp( fun defaultClient(): OkHttpClient = OkHttpClient.Builder() .callTimeout(CALL_TIMEOUT) .build() + + /** + * Le client des pochettes, muni du même plafond que les appels d'API. + * + * Coil construit son propre client : sans passer par ici, il repartirait + * sur les délais par étape d'OkHttp, dont aucun ne borne l'appel entier. + * Une pochette qui n'arrive jamais retiendrait alors son fil de bout en + * bout. Le plafond se décide à un seul endroit, celui-ci. + */ + fun imageClient(auth: Interceptor): OkHttpClient = OkHttpClient.Builder() + .callTimeout(CALL_TIMEOUT) + .addInterceptor(auth) + .build() } } diff --git a/app/src/main/java/app/waveflow/ui/server/ServerScreen.kt b/app/src/main/java/app/waveflow/ui/server/ServerScreen.kt index 1dd7134..f4d461d 100644 --- a/app/src/main/java/app/waveflow/ui/server/ServerScreen.kt +++ b/app/src/main/java/app/waveflow/ui/server/ServerScreen.kt @@ -279,8 +279,8 @@ private fun ConnectedAccount( Spacer(Modifier.height(32.dp)) Text( - text = "Le catalogue de ce serveur est consultable depuis l'onglet Serveur. " + - "La lecture à distance arrive dans une prochaine version.", + text = "Le catalogue de ce serveur est consultable depuis l'onglet Serveur, " + + "et ses pistes se lisent comme celles de l'appareil.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/app/src/test/java/app/waveflow/data/remote/ServerImageAuthInterceptorTest.kt b/app/src/test/java/app/waveflow/data/remote/ServerImageAuthInterceptorTest.kt index c98ede4..f45d373 100644 --- a/app/src/test/java/app/waveflow/data/remote/ServerImageAuthInterceptorTest.kt +++ b/app/src/test/java/app/waveflow/data/remote/ServerImageAuthInterceptorTest.kt @@ -5,6 +5,7 @@ import app.waveflow.testing.FakeServerApi import app.waveflow.testing.FakeSessionStore import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest +import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.mockwebserver.Dispatcher @@ -14,6 +15,7 @@ import okhttp3.mockwebserver.RecordedRequest import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -77,6 +79,18 @@ class ServerImageAuthInterceptorTest { client.newCall(Request.Builder().url(target).build()).execute().close() } + @Test + fun `le client des pochettes borne l'appel entier`() { + // Les délais par défaut d'OkHttp portent sur chaque étape prise à part ; + // aucun ne borne l'appel. Une pochette qui n'arrive jamais retiendrait + // alors son fil de bout en bout. Le plafond est celui des appels d'API : + // les comparer garantit qu'un seul endroit continue d'en décider. + val client = ServerHttp.imageClient(Interceptor { it.proceed(it.request()) }) + + assertTrue("le plafond doit être posé", client.callTimeoutMillis > 0) + assertEquals(ServerHttp.defaultClient().callTimeoutMillis, client.callTimeoutMillis) + } + @Test fun `une pochette du serveur connecte porte le jeton`() = runTest { server.enqueue(MockResponse().setBody("image"))