mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 06:05:21 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user