git.iko.soy/opencode-android
on master help sign in
Fix chat crash by skipping animateItem on the actively-streaming message row
2987128iko
app/src/main/java/soy/iko/opencode/ui/chat/ChatScreen.kt+19−1
@@ -1486,9 +1486,27 @@ fun ChatScreen(
1486 // Default placement animation so inserted/moved rows glide in. Skipped
1486 // Default placement animation so inserted/moved rows glide in. Skipped
1487 // entirely under reduced motion so a streaming message growing in place
1487 // entirely under reduced motion so a streaming message growing in place
1488 // doesn't animate on every token (and respects the a11y preference).
1488 // doesn't animate on every token (and respects the a11y preference).
1489+ //
1490+ // Also skipped for the actively-streaming message even at full motion.
1491+ // animateItem() is backed by SizeAnimationModifierNode, which forces a
1492+ // draw-phase remeasure (the display-list rebuild triggered by
1493+ // AndroidComposeView.dispatchDraw) when its target's size changes. While
1494+ // the streaming bubble grows token-by-token, that remeasure runs inside
1495+ // the draw pass and reads Placeable.getWidth()/getHeight() of a child
1496+ // whose measured size hasn't been committed yet — an NPE in
1497+ // PaddingValuesModifier.measure / FillNode.measure (the R8 map marks both
1498+ // sites with a throws(NullPointerException) rewriteFrame). This is the
1499+ // same class of draw-phase-remeasure crash previously fixed for the
1500+ // markdown library's animateContentSize and MessageBubble's send-status
1501+ // AnimatedContent; animateItem() on a continuously-resizing row is the
1502+ // remaining instance. Non-streaming rows (including the one that just
1503+ // finished streaming) keep the glide animation since their size is stable.
1504+ val isStreamingMessage = item is MessageListItem.Message &&
1505+ running &&
1506+ item.message.info.id == lastMessageId
1489 Box(
1507 Box(
1490 Modifier
1508 Modifier
1491− .then(reducedMotionAnimateItem())
1509+ .then(if (isStreamingMessage) Modifier else reducedMotionAnimateItem())
1492 .then(
1510 .then(
1493 if (isFocusedMatch) {
1511 if (isFocusedMatch) {
1494 Modifier.background(
1512 Modifier.background(