Add CommandSuggestionsBox quick access (#146)

* Add CommandSuggestionsBox quick access

* Remove unnecessary evaluation

* Use a FilledTonalIconButton for the CommandSuggestionsBox quick access button

* fix import

* quick command instead of send

* simplify

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
Héctor de Isidro
2025-08-06 01:25:15 +02:00
committed by GitHub
co-authored by callebtc
parent 1d1a91108e
commit b418aa3899
4 changed files with 90 additions and 71 deletions
@@ -15,19 +15,23 @@ 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.res.stringResource
import androidx.compose.ui.text.TextRange
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.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.style.TextAlign
import androidx.compose.ui.text.input.OffsetMapping
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 com.bitchat.android.R
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.withStyle
@@ -45,13 +49,13 @@ class SlashCommandVisualTransformation : VisualTransformation {
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(
@@ -63,16 +67,16 @@ class SlashCommandVisualTransformation : VisualTransformation {
) {
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
@@ -162,7 +166,8 @@ fun MessageInput(
Row(
modifier = modifier.padding(horizontal = 12.dp, vertical = 8.dp), // Reduced padding
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Text input with placeholder
Box(
@@ -205,48 +210,64 @@ fun MessageInput(
Spacer(modifier = Modifier.width(8.dp)) // Reduced spacing
// Send button with enabled/disabled state
IconButton(
onClick = { if (hasText) onSend() }, // Only execute if there's text
enabled = hasText, // Enable only when there's text
modifier = Modifier.size(32.dp)
) {
Box(
modifier = Modifier
.size(30.dp)
.background(
color = if (!hasText) {
// Disabled state - muted grey
colorScheme.onSurface.copy(alpha = 0.3f)
} else if (selectedPrivatePeer != null || currentChannel != null) {
// Orange for both private messages and channels when enabled
Color(0xFFFF9500).copy(alpha = 0.75f)
} else if (colorScheme.background == Color.Black) {
Color(0xFF00FF00).copy(alpha = 0.75f) // Bright green for dark theme
} else {
Color(0xFF008000).copy(alpha = 0.75f) // Dark green for light theme
},
shape = CircleShape
),
contentAlignment = Alignment.Center
// Command quick access button
if (value.text.isEmpty()) {
FilledTonalIconButton(
onClick = {
onValueChange(TextFieldValue(text = "/", selection = TextRange("/".length)))
},
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowUpward,
contentDescription = "Send message",
modifier = Modifier.size(20.dp),
tint = if (!hasText) {
// Disabled state - muted grey icon
colorScheme.onSurface.copy(alpha = 0.5f)
} else if (selectedPrivatePeer != null || currentChannel != null) {
// Black arrow on orange for both private and channel modes
Color.Black
} else if (colorScheme.background == Color.Black) {
Color.Black // Black arrow on bright green in dark theme
} else {
Color.White // White arrow on dark green in light theme
}
Text(
text = "/",
textAlign = TextAlign.Center
)
}
} else {
// Send button with enabled/disabled state
IconButton(
onClick = { if (hasText) onSend() }, // Only execute if there's text
enabled = hasText, // Enable only when there's text
modifier = Modifier.size(32.dp)
) {
// Update send button to match input field colors
Box(
modifier = Modifier
.size(30.dp)
.background(
color = if (!hasText) {
// Disabled state - muted grey
colorScheme.onSurface.copy(alpha = 0.3f)
} else if (selectedPrivatePeer != null || currentChannel != null) {
// Orange for both private messages and channels when enabled
Color(0xFFFF9500).copy(alpha = 0.75f)
} else if (colorScheme.background == Color.Black) {
Color(0xFF00FF00).copy(alpha = 0.75f) // Bright green for dark theme
} else {
Color(0xFF008000).copy(alpha = 0.75f) // Dark green for light theme
},
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Filled.ArrowUpward,
contentDescription = stringResource(id = R.string.send_message),
modifier = Modifier.size(20.dp),
tint = if (!hasText) {
// Disabled state - muted grey icon
colorScheme.onSurface.copy(alpha = 0.5f)
} else if (selectedPrivatePeer != null || currentChannel != null) {
// Black arrow on orange for both private and channel modes
Color.Black
} else if (colorScheme.background == Color.Black) {
Color.Black // Black arrow on bright green in dark theme
} else {
Color.White // White arrow on dark green in light theme
}
)
}
}
}
}
}
@@ -258,9 +279,10 @@ fun CommandSuggestionsBox(
modifier: Modifier = Modifier
) {
val colorScheme = MaterialTheme.colorScheme
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
.background(colorScheme.surface)
.border(1.dp, colorScheme.outline.copy(alpha = 0.3f), RoundedCornerShape(4.dp))
.padding(vertical = 8.dp)
@@ -280,14 +302,15 @@ fun CommandSuggestionItem(
onClick: () -> Unit
) {
val colorScheme = MaterialTheme.colorScheme
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onClick() }
.padding(horizontal = 12.dp, vertical = 3.dp)
.background(Color.Gray.copy(alpha = 0.1f)),
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Show all aliases together
val allCommands = if (suggestion.aliases.isNotEmpty()) {
@@ -295,7 +318,7 @@ fun CommandSuggestionItem(
} else {
listOf(suggestion.command)
}
Text(
text = allCommands.joinToString(", "),
style = MaterialTheme.typography.bodySmall.copy(
@@ -305,10 +328,9 @@ fun CommandSuggestionItem(
color = colorScheme.primary,
fontSize = 11.sp
)
// Show syntax if any
suggestion.syntax?.let { syntax ->
Spacer(modifier = Modifier.width(8.dp))
Text(
text = syntax,
style = MaterialTheme.typography.bodySmall.copy(
@@ -318,9 +340,7 @@ fun CommandSuggestionItem(
fontSize = 10.sp
)
}
Spacer(modifier = Modifier.weight(1f))
// Show description
Text(
text = suggestion.description,