fix(chat): cursor location and enhance message input with slash command styling

- Change message input from String to TextFieldValue for better cursor control
- Add SlashCommandVisualTransformation to style slash commands with colored backgrounds
- Maintain cursor position when selecting command suggestions
This commit is contained in:
Faded
2025-07-14 15:51:50 +05:00
parent fe57c5684e
commit 21dc1a5b5e
2 changed files with 81 additions and 13 deletions
@@ -22,9 +22,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.Dp
@@ -65,7 +67,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
val commandSuggestions by viewModel.commandSuggestions.observeAsState(emptyList())
val showAppInfo by viewModel.showAppInfo.observeAsState(false)
var messageText by remember { mutableStateOf("") }
var messageText by remember { mutableStateOf(TextFieldValue("")) }
var showPasswordPrompt by remember { mutableStateOf(false) }
var showPasswordDialog by remember { mutableStateOf(false) }
var passwordInput by remember { mutableStateOf("") }
@@ -112,20 +114,24 @@ fun ChatScreen(viewModel: ChatViewModel) {
// Input area - stays at bottom
ChatInputSection(
messageText = messageText,
onMessageTextChange = { newText: String ->
onMessageTextChange = { newText: TextFieldValue ->
messageText = newText
viewModel.updateCommandSuggestions(newText)
viewModel.updateCommandSuggestions(newText.text)
},
onSend = {
if (messageText.trim().isNotEmpty()) {
viewModel.sendMessage(messageText.trim())
messageText = ""
if (messageText.text.trim().isNotEmpty()) {
viewModel.sendMessage(messageText.text.trim())
messageText = TextFieldValue("")
}
},
showCommandSuggestions = showCommandSuggestions,
commandSuggestions = commandSuggestions,
onSuggestionClick = { suggestion: CommandSuggestion ->
messageText = viewModel.selectCommandSuggestion(suggestion)
val commandText = viewModel.selectCommandSuggestion(suggestion)
messageText = TextFieldValue(
text = commandText,
selection = TextRange(commandText.length)
)
},
selectedPrivatePeer = selectedPrivatePeer,
currentChannel = currentChannel,
@@ -195,8 +201,8 @@ fun ChatScreen(viewModel: ChatViewModel) {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ChatInputSection(
messageText: String,
onMessageTextChange: (String) -> Unit,
messageText: TextFieldValue,
onMessageTextChange: (TextFieldValue) -> Unit,
onSend: () -> Unit,
showCommandSuggestions: Boolean,
commandSuggestions: List<CommandSuggestion>,
@@ -15,23 +15,79 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.withStyle
/**
* Input components for ChatScreen
* Extracted from ChatScreen.kt for better organization
*/
/**
* VisualTransformation that styles slash commands with background and color
* while preserving cursor positioning and click handling
*/
class SlashCommandVisualTransformation : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
val slashCommandRegex = Regex("(/\\w+)(?=\\s|$)")
val annotatedString = buildAnnotatedString {
var lastIndex = 0
slashCommandRegex.findAll(text.text).forEach { match ->
// Add text before the match
if (match.range.first > lastIndex) {
append(text.text.substring(lastIndex, match.range.first))
}
// Add the styled slash command
withStyle(
style = SpanStyle(
color = Color(0xFF00FF7F), // Bright green
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium,
background = Color(0xFF2D2D2D) // Dark gray background
)
) {
append(match.value)
}
lastIndex = match.range.last + 1
}
// Add remaining text
if (lastIndex < text.text.length) {
append(text.text.substring(lastIndex))
}
}
return TransformedText(
text = annotatedString,
offsetMapping = OffsetMapping.Identity
)
}
}
@Composable
fun MessageInput(
value: String,
onValueChange: (String) -> Unit,
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
onSend: () -> Unit,
selectedPrivatePeer: String?,
currentChannel: String?,
@@ -39,6 +95,7 @@ fun MessageInput(
modifier: Modifier = Modifier
) {
val colorScheme = MaterialTheme.colorScheme
val isFocused = remember { mutableStateOf(false) }
Row(
modifier = modifier.padding(horizontal = 12.dp, vertical = 8.dp), // Reduced padding
@@ -70,7 +127,12 @@ fun MessageInput(
cursorBrush = SolidColor(colorScheme.primary),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
keyboardActions = KeyboardActions(onSend = { onSend() }),
modifier = Modifier.weight(1f)
visualTransformation = SlashCommandVisualTransformation(),
modifier = Modifier
.weight(1f)
.onFocusChanged { focusState ->
isFocused.value = focusState.isFocused
}
)
Spacer(modifier = Modifier.width(8.dp)) // Reduced spacing