diff --git a/android/app/src/main/java/com/ts3client/android/ui/ChatPane.kt b/android/app/src/main/java/com/ts3client/android/ui/ChatPane.kt index add201b..54299ce 100644 --- a/android/app/src/main/java/com/ts3client/android/ui/ChatPane.kt +++ b/android/app/src/main/java/com/ts3client/android/ui/ChatPane.kt @@ -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() } + 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, HashMap>( + save = { HashMap(it) }, + restore = { mutableStateMapOf().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, 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) {