fix chat focus
This commit is contained in:
+49
-17
@@ -45,6 +45,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.runtime.withFrameNanos
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -68,6 +69,7 @@ import androidx.compose.ui.text.style.TextDecoration
|
|||||||
import androidx.compose.ui.text.withLink
|
import androidx.compose.ui.text.withLink
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -83,11 +85,7 @@ import ru.shadowsparky.ui.components.isTv
|
|||||||
import ru.shadowsparky.ui.theme.focusBorder
|
import ru.shadowsparky.ui.theme.focusBorder
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatContent(
|
fun ChatContent(component: ChatComponent, paddingValues: PaddingValues, state: ChatState) {
|
||||||
component: ChatComponent,
|
|
||||||
paddingValues: PaddingValues,
|
|
||||||
state: ChatState
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
.padding(
|
.padding(
|
||||||
@@ -144,19 +142,23 @@ private fun ChatContent(
|
|||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
var textInput by remember { mutableStateOf("") }
|
var textInput by remember { mutableStateOf("") }
|
||||||
val textFieldFocusRequester = remember { FocusRequester() }
|
val textFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
LaunchedEffect(state.messages.size) {
|
LaunchedEffect(state.messages.size) {
|
||||||
if (state.messages.isNotEmpty()) {
|
if (state.messages.isNotEmpty()) {
|
||||||
listState.animateScrollToItem(0)
|
listState.animateScrollToItem(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.messages) { textInput = "" }
|
LaunchedEffect(state.messages) { textInput = "" }
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val focusRequesters = remember { mutableMapOf<Any, FocusRequester>() }
|
||||||
|
fun requesterFor(message: Message): FocusRequester {
|
||||||
|
val key = message.id ?: message.hashCode()
|
||||||
|
return focusRequesters.getOrPut(key) { FocusRequester() }
|
||||||
|
}
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
@@ -164,15 +166,48 @@ private fun ChatContent(
|
|||||||
) {
|
) {
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = state.messages,
|
items = state.messages,
|
||||||
key = { _, item -> item.id ?: item.hashCode() }
|
key = { _, item -> item.id ?: item.hashCode() },
|
||||||
) { index, message ->
|
) { index, message ->
|
||||||
|
val requester = requesterFor(message)
|
||||||
MessageBubble(
|
MessageBubble(
|
||||||
|
itemFocusRequester = requester,
|
||||||
message = message,
|
message = message,
|
||||||
onParseMessage = onParseMessage,
|
onParseMessage = onParseMessage,
|
||||||
onClickLink = onClickLink,
|
onClickLink = onClickLink,
|
||||||
onKeyEvent = {
|
onKeyEvent = { event ->
|
||||||
if (index == 0) {
|
when (event.key) {
|
||||||
if (it.key == Key.DirectionDown) textFieldFocusRequester.requestFocus()
|
Key.DirectionUp -> {
|
||||||
|
val targetIndex = index + 1
|
||||||
|
if (targetIndex < state.messages.size) {
|
||||||
|
val target = state.messages[targetIndex]
|
||||||
|
val targetRequester = requesterFor(target)
|
||||||
|
scope.launch {
|
||||||
|
listState.scrollToItem(targetIndex)
|
||||||
|
snapshotFlow {
|
||||||
|
listState.layoutInfo.visibleItemsInfo
|
||||||
|
.any { it.index == targetIndex }
|
||||||
|
}.first { it }
|
||||||
|
targetRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Key.DirectionDown -> {
|
||||||
|
val targetIndex = index - 1
|
||||||
|
if (targetIndex >= 0) {
|
||||||
|
val target = state.messages[targetIndex]
|
||||||
|
val targetRequester = requesterFor(target)
|
||||||
|
scope.launch {
|
||||||
|
listState.scrollToItem(targetIndex)
|
||||||
|
snapshotFlow {
|
||||||
|
listState.layoutInfo.visibleItemsInfo
|
||||||
|
.any { it.index == targetIndex }
|
||||||
|
}.first { it }
|
||||||
|
targetRequester.requestFocus()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textFieldFocusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -223,6 +258,7 @@ private fun ChatContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MessageBubble(
|
private fun MessageBubble(
|
||||||
|
itemFocusRequester: FocusRequester,
|
||||||
message: Message,
|
message: Message,
|
||||||
onParseMessage: (String) -> List<TextToken>,
|
onParseMessage: (String) -> List<TextToken>,
|
||||||
onClickLink: (String) -> Unit,
|
onClickLink: (String) -> Unit,
|
||||||
@@ -237,7 +273,6 @@ private fun MessageBubble(
|
|||||||
val annotatedString = remember(tokens) {
|
val annotatedString = remember(tokens) {
|
||||||
buildText(isTv, tokens, primaryColor, focusedColor, onClickLink)
|
buildText(isTv, tokens, primaryColor, focusedColor, onClickLink)
|
||||||
}
|
}
|
||||||
val itemFocusRequester = remember { FocusRequester() }
|
|
||||||
var dialog by remember { mutableStateOf(false) }
|
var dialog by remember { mutableStateOf(false) }
|
||||||
var boxModifier = Modifier.widthIn(max = 280.dp)
|
var boxModifier = Modifier.widthIn(max = 280.dp)
|
||||||
if (isTv) {
|
if (isTv) {
|
||||||
@@ -359,17 +394,14 @@ private fun ScrollableMessage(
|
|||||||
coroutineScope.launch { scrollState.animateScrollBy(scrollStep) }
|
coroutineScope.launch { scrollState.animateScrollBy(scrollStep) }
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.DirectionUp -> {
|
Key.DirectionUp -> {
|
||||||
coroutineScope.launch { scrollState.animateScrollBy(-scrollStep) }
|
coroutineScope.launch { scrollState.animateScrollBy(-scrollStep) }
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.Enter, Key.DirectionCenter -> {
|
Key.Enter, Key.DirectionCenter -> {
|
||||||
onEnter()
|
onEnter()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user