Draw a long user message in slices, through a shared card-piece surface
The occasional bump left in an otherwise smooth transcript was the last unbounded item: a pasted log in a user bubble is one Text whose layout runs in the frame the row scrolls into -- 93,808px on the fixture, reported from the phone as a 112ms worst measure. Per frame it was already cheap (one node); the cost was entirely the entry. A message past USER_SPLIT_CHARS is now cut at line starts into slices of roughly 2,500 characters, each its own list unit. Lines lay out independently, so slices that own whole lines stack back into exactly the lines the single Text drew; the threshold is also what guarantees the bubble was at full width, which the slices must share to read as one card. Measured on the emulator, same fixture and gestures: worst transcript measure 57.6ms -> 12.6ms. Fill continuity across slice seams and uniform 63px line pitch verified from full-resolution screenshots; a short message keeps the ordinary wrapping bubble. The corner-and-padding geometry that lets one visual card be several list items now lives once, in Modifier.cardPiece -- Bryan asked for exactly this generalization so future row types are cheap to add. An opened peer message and a long user message are its two users; a new sliced kind needs only a unit type, a flatten branch, and a body wrapped in cardPiece. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7a48f8ff1f
commit
333d2de92b
4 files changed
+163
-15
No files matched your search
@@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@@ -44,7 +45,14 @@ fun PeerHeadRow(
|
||||
onToggle: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier.peerSurface(top = true, bottom = !open, onToggle = onToggle)) {
|
||||
Column(
|
||||
modifier.cardPiece(
|
||||
top = true,
|
||||
bottom = !open,
|
||||
fill = CardDefaults.cardColors().containerColor,
|
||||
onPress = onToggle,
|
||||
)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Message from ${item.from}", style = MaterialTheme.typography.titleSmall)
|
||||
if (!open) {
|
||||
@@ -77,7 +85,14 @@ fun PeerBlockRow(
|
||||
onToggle: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier.peerSurface(top = false, bottom = last, onToggle = onToggle)) {
|
||||
Column(
|
||||
modifier.cardPiece(
|
||||
top = false,
|
||||
bottom = last,
|
||||
fill = CardDefaults.cardColors().containerColor,
|
||||
onPress = onToggle,
|
||||
)
|
||||
) {
|
||||
// The gap the card's own column used to provide between its heading and its prose, and
|
||||
// between one block and the next. Uniform, because both of those were 6dp already.
|
||||
MarkdownText(text, replies, Modifier.padding(top = BLOCK_SPACING))
|
||||
@@ -85,19 +100,26 @@ fun PeerBlockRow(
|
||||
}
|
||||
|
||||
/**
|
||||
* One piece of a peer message's card: the fill, the corners it owns, and the room inside it.
|
||||
* One piece of a card drawn in slices: the fill, the corners it owns, and the room inside it.
|
||||
*
|
||||
* A filled Material card is elevation zero ([CardDefaults] takes it from `FilledCardTokens`, which
|
||||
* is `Level0`), so there is no shadow that a seam would show through -- which is the whole reason
|
||||
* the card can be cut up at all. Each piece paints the same container colour a
|
||||
* is `Level0`), so there is no shadow that a seam would show through -- which is the whole reason a
|
||||
* card can be cut up at all. Each piece paints the caller's container colour the way a
|
||||
* [androidx.compose .material3.Card] would and rounds only the corners at the ends of the message,
|
||||
* so the pieces abut into one continuous card.
|
||||
* so the pieces abut into one continuous card. Shared by the two rows that are cut this way -- an
|
||||
* opened peer message and a long user message -- because two copies of the corner logic is how one
|
||||
* of them grows a seam.
|
||||
*
|
||||
* The padding is the other half of it: 12dp all round was the card's own, so the top piece keeps
|
||||
* the top of it, the bottom piece the bottom, and the middle pieces neither.
|
||||
*/
|
||||
@Composable
|
||||
private fun Modifier.peerSurface(top: Boolean, bottom: Boolean, onToggle: () -> Unit): Modifier {
|
||||
fun Modifier.cardPiece(
|
||||
top: Boolean,
|
||||
bottom: Boolean,
|
||||
fill: Color,
|
||||
onPress: (() -> Unit)? = null,
|
||||
): Modifier {
|
||||
val square = CornerSize(0.dp)
|
||||
val shape =
|
||||
MaterialTheme.shapes.medium.copy(
|
||||
@@ -108,15 +130,15 @@ private fun Modifier.peerSurface(top: Boolean, bottom: Boolean, onToggle: () ->
|
||||
)
|
||||
return fillMaxWidth()
|
||||
.clip(shape)
|
||||
.background(CardDefaults.cardColors().containerColor)
|
||||
.clickable(onClick = onToggle)
|
||||
.background(fill)
|
||||
.then(if (onPress == null) Modifier else Modifier.clickable(onClick = onPress))
|
||||
.padding(
|
||||
start = PEER_PADDING,
|
||||
end = PEER_PADDING,
|
||||
top = if (top) PEER_PADDING else 0.dp,
|
||||
bottom = if (bottom) PEER_PADDING else 0.dp,
|
||||
start = CARD_PADDING,
|
||||
end = CARD_PADDING,
|
||||
top = if (top) CARD_PADDING else 0.dp,
|
||||
bottom = if (bottom) CARD_PADDING else 0.dp,
|
||||
)
|
||||
}
|
||||
|
||||
/** The room inside a peer message's card, which was `Card { Column(padding(12.dp)) }`. */
|
||||
private val PEER_PADDING = 12.dp
|
||||
/** The room inside a sliced card, which was `Card { Column(padding(12.dp)) }`. */
|
||||
private val CARD_PADDING = 12.dp
|
||||
Reference in new issue
Block a user