git.iko.soy/opencode-android
on master help sign in
Fix chat crash by replacing SubcomposeAsyncImage with rememberAsyncImagePainter to eliminate SubcomposeLayout from chat rows
commite439e28bce8d4c606a6f053abea3b15cd122342c authoriko <mail@iko.soy> dateWed 8 Jul 2026, 04:27 (3 months ago) parent15d51d8
2 files changed+27−17
app/src/main/java/soy/iko/opencode/ui/components/DiffView.kt+2−3 app/src/main/java/soy/iko/opencode/ui/components/RemoteImage.kt+25−14
app/src/main/java/soy/iko/opencode/ui/components/DiffView.kt+2−3
@@ -339,9 +339,8 @@ private fun rememberDiffRows(
339339): DiffRows = produceState<DiffRows>(
340340 DiffRows(emptyList(), 0),
341341 diff, expanded, collapsedHunks,
342− addColor.value.toLong(), removeColor.value.toLong(),
343− addText.value.toLong(), removeText.value.toLong(),
344− tertiary.value.toLong(), onSurface.value.toLong(), onSurfaceVariant.value.toLong(),
342+ addColor, removeColor, addText, removeText,
343+ tertiary, onSurface, onSurfaceVariant,
345344) {
346345 val result = withContext(Dispatchers.Default) {
347346 runCatchingCancellable {
app/src/main/java/soy/iko/opencode/ui/components/RemoteImage.kt+25−14
@@ -46,7 +46,9 @@ import androidx.compose.ui.text.style.TextOverflow
4646import androidx.compose.ui.unit.dp
4747import androidx.compose.ui.window.Dialog
4848import androidx.compose.ui.window.DialogProperties
49−import coil3.compose.SubcomposeAsyncImage
49+import coil3.compose.AsyncImagePainter
50+import coil3.compose.rememberAsyncImagePainter
51+import androidx.compose.foundation.Image
5052import coil3.network.NetworkHeaders
5153import coil3.network.httpHeaders
5254import coil3.request.ImageRequest
@@ -210,18 +212,20 @@ fun RemoteImage(part: FilePart, ctx: ImageLoadContext, modifier: Modifier = Modi
210212 .crossfade(true)
211213 .build()
212214 }
213− SubcomposeAsyncImage(
214− model = request,
215− contentDescription = part.filename ?: stringResource(R.string.image),
216− contentScale = ContentScale.FillWidth,
215+ // Use rememberAsyncImagePainter + Image instead of SubcomposeAsyncImage to avoid
216+ // SubcomposeLayout, which triggers draw-phase remeasures that NPE inside the LazyColumn
217+ // when the image loads and the row's size changes post-mount — the same crash class
218+ // previously fixed for animateItem(), AnimatedContent, and animateContentSize.
219+ val painter = rememberAsyncImagePainter(model = request)
220+ val painterState = painter.state.value
221+ Box(
217222 modifier = modifier
218223 .heightIn(max = NetworkConfig.inlineImageMaxHeightDp.dp)
219224 .clip(MaterialTheme.shapes.small)
220− // Tap to open a fullscreen zoomable viewer so the user can inspect details
221− // without the inline 320dp height cap.
222225 .clickable(role = Role.Button) { showFullscreen = true },
223− loading = {
224− Box(
226+ ) {
227+ when (painterState) {
228+ is AsyncImagePainter.State.Loading -> Box(
225229 modifier = Modifier.fillMaxWidth().heightIn(min = NetworkConfig.inlineImageMinHeightDp.dp),
226230 contentAlignment = Alignment.Center,
227231 ) {
@@ -230,9 +234,15 @@ fun RemoteImage(part: FilePart, ctx: ImageLoadContext, modifier: Modifier = Modi
230234 modifier = Modifier.semantics { contentDescription = loadingLabel },
231235 )
232236 }
233− },
234− error = { ImageRetry(onRetry = { retryKey++ }) },
235− )
237+ is AsyncImagePainter.State.Error -> ImageRetry(onRetry = { retryKey++ })
238+ else -> Image(
239+ painter = painter,
240+ contentDescription = part.filename ?: stringResource(R.string.image),
241+ contentScale = ContentScale.FillWidth,
242+ modifier = Modifier.fillMaxWidth(),
243+ )
244+ }
245+ }
236246 if (showFullscreen) {
237247 FullscreenImageViewer(request = request, contentDescription = part.filename ?: stringResource(R.string.image)) {
238248 showFullscreen = false
@@ -342,8 +352,9 @@ internal fun FullscreenImageViewer(
342352 },
343353 contentAlignment = Alignment.Center,
344354 ) {
345− SubcomposeAsyncImage(
346− model = request,
355+ val fullscreenPainter = rememberAsyncImagePainter(model = request)
356+ Image(
357+ painter = fullscreenPainter,
347358 contentDescription = contentDescription,
348359 contentScale = ContentScale.Fit,
349360 modifier = Modifier