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
@@ -1317,6 +1317,8 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
unit.last,
|
||||
onToggle = { togglePeer(unit.seq) },
|
||||
)
|
||||
is TranscriptUnit.UserChunk ->
|
||||
UserChunkRow(unit, settings, summary.id, ::openImage)
|
||||
is TranscriptUnit.Memory ->
|
||||
MemoryNote(
|
||||
unit.part,
|
||||
@@ -1860,6 +1862,38 @@ private fun ProcessAction.perform(settings: ServerSettings, sessionId: String) =
|
||||
ProcessAction.Start -> startSession(settings, sessionId)
|
||||
}
|
||||
|
||||
/**
|
||||
* One slice of a long user message, on the same bubble the first slice starts.
|
||||
*
|
||||
* Full width, unlike the wrapping bubble: slices have to share a width to read as one card, and a
|
||||
* message long enough to be sliced has lines that wrap, so its bubble was at full width anyway --
|
||||
* see [USER_SPLIT_CHARS], which is what guarantees that.
|
||||
*/
|
||||
@Composable
|
||||
private fun UserChunkRow(
|
||||
unit: TranscriptUnit.UserChunk,
|
||||
settings: ServerSettings,
|
||||
sessionId: String,
|
||||
onOpenImage: (String) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(start = 48.dp)
|
||||
.cardPiece(
|
||||
top = unit.first,
|
||||
bottom = unit.last,
|
||||
fill = MaterialTheme.colorScheme.primaryContainer,
|
||||
)
|
||||
) {
|
||||
Text(unit.text, color = MaterialTheme.colorScheme.onPrimaryContainer)
|
||||
// The same arrangement [UserBubble] gives them: under the words, on the last slice
|
||||
// because that is the bubble's bottom.
|
||||
unit.images.forEachIndexed { index, ref ->
|
||||
if (index > 0 || unit.text.isNotEmpty()) Spacer(Modifier.height(4.dp))
|
||||
SessionImage(settings, sessionId, ref, onOpenImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A message the person holding the phone sent, in a bubble at their end of the conversation.
|
||||
*
|
||||
@@ -1870,6 +1904,11 @@ private fun ProcessAction.perform(settings: ServerSettings, sessionId: String) =
|
||||
* reads it, and [refusal] is what came back when it would not. The refusal is drawn here rather
|
||||
* than with the screen's other errors because this is where the reader pressed -- the error row is
|
||||
* under the header, a screen away from the bubble they were looking at.
|
||||
*
|
||||
* A settled message longer than [USER_SPLIT_CHARS] is drawn as [UserChunkRow] slices instead -- one
|
||||
* `Text` holding a pasted log is a hundred-thousand-pixel layout in the frame the row scrolls into.
|
||||
* Everything else keeps this bubble: short messages wrap their content, and the pending one keeps
|
||||
* its take-back control.
|
||||
*/
|
||||
@Composable
|
||||
private fun UserBubble(
|
||||
|
||||
Reference in new issue
Block a user