Keep chat drafts per conversation and open each at its newest line

Switching conversations dropped the message being written, and showed
the next one at the scroll position of the last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 15:12:50 +00:00
parent 70c6eb769c
commit 967fcadf77

View File

@@ -27,11 +27,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
@@ -68,17 +70,21 @@ fun ChatPane(
ConversationChip(conversation, conversation.key == selected.key, onSelect, onClose)
}
}
Lines(selected.lines, onLink, Modifier.weight(1f))
var draft by rememberSaveable(selected.key.toString()) { mutableStateOf("") }
// Each conversation opens at its newest line.
key(selected.key) { Lines(selected.lines, onLink, Modifier.weight(1f)) }
// One unsent message per conversation, kept while another one is on screen.
val drafts = rememberSaveable(saver = DraftsSaver) { mutableStateMapOf<String, String>() }
val draftKey = selected.key.toString()
val draft = drafts[draftKey].orEmpty()
val send = {
if (draft.isNotBlank()) {
onSend(draft.trim())
draft = ""
drafts.remove(draftKey)
}
}
Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField(
draft, { draft = it },
draft, { drafts[draftKey] = it },
Modifier.weight(1f).onPreviewKeyEvent { e ->
// A hardware keyboard's Enter sends, as on the desktop; Shift+Enter starts a new line.
val enter = e.key == Key.Enter && !e.isShiftPressed
@@ -98,6 +104,11 @@ fun ChatPane(
}
}
private val DraftsSaver = Saver<SnapshotStateMap<String, String>, HashMap<String, String>>(
save = { HashMap(it) },
restore = { mutableStateMapOf<String, String>().apply { putAll(it) } },
)
@Composable
private fun ConversationChip(
conversation: Conversation,
@@ -120,7 +131,7 @@ private fun ConversationChip(
/** The conversation's lines, following new ones while the newest is in view. */
@Composable
private fun Lines(lines: List<ChatLine>, onLink: (String) -> Unit, modifier: Modifier) {
val listState = rememberLazyListState()
val listState = rememberLazyListState(initialFirstVisibleItemIndex = (lines.size - 1).coerceAtLeast(0))
val linkColor = MaterialTheme.colorScheme.primary
val link by rememberUpdatedState(onLink)
LaunchedEffect(lines.lastOrNull()?.id) {