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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user