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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
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.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
@@ -68,17 +70,21 @@ fun ChatPane(
|
|||||||
ConversationChip(conversation, conversation.key == selected.key, onSelect, onClose)
|
ConversationChip(conversation, conversation.key == selected.key, onSelect, onClose)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Lines(selected.lines, onLink, Modifier.weight(1f))
|
// Each conversation opens at its newest line.
|
||||||
var draft by rememberSaveable(selected.key.toString()) { mutableStateOf("") }
|
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 = {
|
val send = {
|
||||||
if (draft.isNotBlank()) {
|
if (draft.isNotBlank()) {
|
||||||
onSend(draft.trim())
|
onSend(draft.trim())
|
||||||
draft = ""
|
drafts.remove(draftKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
draft, { draft = it },
|
draft, { drafts[draftKey] = it },
|
||||||
Modifier.weight(1f).onPreviewKeyEvent { e ->
|
Modifier.weight(1f).onPreviewKeyEvent { e ->
|
||||||
// A hardware keyboard's Enter sends, as on the desktop; Shift+Enter starts a new line.
|
// A hardware keyboard's Enter sends, as on the desktop; Shift+Enter starts a new line.
|
||||||
val enter = e.key == Key.Enter && !e.isShiftPressed
|
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
|
@Composable
|
||||||
private fun ConversationChip(
|
private fun ConversationChip(
|
||||||
conversation: Conversation,
|
conversation: Conversation,
|
||||||
@@ -120,7 +131,7 @@ private fun ConversationChip(
|
|||||||
/** The conversation's lines, following new ones while the newest is in view. */
|
/** The conversation's lines, following new ones while the newest is in view. */
|
||||||
@Composable
|
@Composable
|
||||||
private fun Lines(lines: List<ChatLine>, onLink: (String) -> Unit, modifier: Modifier) {
|
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 linkColor = MaterialTheme.colorScheme.primary
|
||||||
val link by rememberUpdatedState(onLink)
|
val link by rememberUpdatedState(onLink)
|
||||||
LaunchedEffect(lines.lastOrNull()?.id) {
|
LaunchedEffect(lines.lastOrNull()?.id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user