feat: cursor at end when editing and open keyboard

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-10-01 00:53:41 +02:00
parent 5a6a25c113
commit 3652d01ea8
3 changed files with 61 additions and 40 deletions

View File

@ -29,6 +29,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -39,6 +40,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -51,8 +53,8 @@ import chat.revolt.api.schemas.ChannelType
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MessageField( fun MessageField(
messageContent: String, value: TextFieldValue,
onMessageContentChange: (String) -> Unit, onValueChange: (TextFieldValue) -> Unit,
onAddAttachment: () -> Unit, onAddAttachment: () -> Unit,
onSendMessage: () -> Unit, onSendMessage: () -> Unit,
channelType: ChannelType, channelType: ChannelType,
@ -73,16 +75,23 @@ fun MessageField(
ChannelType.SavedMessages -> R.string.message_field_placeholder_notes ChannelType.SavedMessages -> R.string.message_field_placeholder_notes
} }
val sendButtonVisible = (messageContent.isNotBlank() || forceSendButton) && !disabled val sendButtonVisible = (value.text.isNotBlank() || forceSendButton) && !disabled
LaunchedEffect(editMode) {
if (editMode) {
focusRequester.requestFocus()
} else {
focusRequester.freeFocus()
}
}
Row( Row(
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
) { ) {
BasicTextField( BasicTextField(
value = messageContent, value = value,
onValueChange = onMessageContentChange, onValueChange = onValueChange,
singleLine = false, singleLine = false,
enabled = !disabled, enabled = !disabled,
textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface), textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface),
@ -97,21 +106,21 @@ fun MessageField(
keyboardActions = KeyboardActions.Default, keyboardActions = KeyboardActions.Default,
decorationBox = @Composable { innerTextField -> decorationBox = @Composable { innerTextField ->
TextFieldDefaults.DecorationBox( TextFieldDefaults.DecorationBox(
value = messageContent, value = value.text,
innerTextField = innerTextField, innerTextField = innerTextField,
enabled = !disabled, enabled = !disabled,
singleLine = false, singleLine = false,
visualTransformation = VisualTransformation.None, visualTransformation = VisualTransformation.None,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
placeholder = { placeholder = {
Text( Text(
text = stringResource( text = stringResource(
id = placeholderResource, id = placeholderResource,
channelName channelName
), ),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
}, },
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
@ -127,28 +136,28 @@ fun MessageField(
), ),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(16.dp),
leadingIcon = { leadingIcon = {
Icon( Icon(
when { when {
editMode -> Icons.Default.Close editMode -> Icons.Default.Close
else -> Icons.Default.Add else -> Icons.Default.Add
}, },
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
contentDescription = stringResource(id = R.string.add_attachment_alt), contentDescription = stringResource(id = R.string.add_attachment_alt),
modifier = Modifier modifier = Modifier
.clip(CircleShape) .clip(CircleShape)
.size(32.dp) .size(32.dp)
.clickable { .clickable {
when { when {
editMode -> cancelEdit() editMode -> cancelEdit()
else -> { else -> {
focusRequester.freeFocus() // hide keyboard because it's annoying focusRequester.freeFocus() // hide keyboard because it's annoying
onAddAttachment() onAddAttachment()
}
} }
} }
.padding(4.dp) }
.testTag("add_attachment") .padding(4.dp)
) .testTag("add_attachment")
)
}, },
trailingIcon = { trailingIcon = {
AnimatedVisibility(sendButtonVisible, AnimatedVisibility(sendButtonVisible,
@ -186,8 +195,8 @@ fun MessageField(
@Composable @Composable
fun MessageFieldPreview() { fun MessageFieldPreview() {
MessageField( MessageField(
messageContent = "Hello world!", value = TextFieldValue("Hello world!"),
onMessageContentChange = {}, onValueChange = {},
onSendMessage = {}, onSendMessage = {},
onAddAttachment = {}, onAddAttachment = {},
channelType = ChannelType.TextChannel, channelType = ChannelType.TextChannel,

View File

@ -54,6 +54,7 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
@ -173,6 +174,13 @@ fun ChannelScreen(
label = "ScrollDownFABPadding" label = "ScrollDownFABPadding"
) )
val fieldContent = remember(viewModel.pendingMessageContent, viewModel.textSelection) {
TextFieldValue(
viewModel.pendingMessageContent,
viewModel.textSelection
)
}
LaunchedEffect(channelId) { LaunchedEffect(channelId) {
viewModel.fetchChannel(channelId) viewModel.fetchChannel(channelId)
@ -465,9 +473,10 @@ fun ChannelScreen(
) )
} else { } else {
MessageField( MessageField(
messageContent = viewModel.pendingMessageContent, value = fieldContent,
onMessageContentChange = { onValueChange = {
viewModel.pendingMessageContent = it viewModel.pendingMessageContent = it.text
viewModel.textSelection = it.selection
}, },
onSendMessage = viewModel::sendPendingMessage, onSendMessage = viewModel::sendPendingMessage,
onAddAttachment = { onAddAttachment = {

View File

@ -8,6 +8,7 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.text.TextRange
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import chat.revolt.R import chat.revolt.R
@ -64,6 +65,7 @@ class ChannelScreenViewModel : ViewModel() {
var hasNoMoreMessages by mutableStateOf(false) var hasNoMoreMessages by mutableStateOf(false)
var pendingMessageContent by mutableStateOf("") var pendingMessageContent by mutableStateOf("")
var textSelection by mutableStateOf(TextRange(0))
var pendingReplies = mutableStateListOf<SendMessageReply>() var pendingReplies = mutableStateListOf<SendMessageReply>()
var pendingAttachments = mutableStateListOf<FileArgs>() var pendingAttachments = mutableStateListOf<FileArgs>()
@ -394,6 +396,7 @@ class ChannelScreenViewModel : ViewModel() {
msg.id == it.messageId msg.id == it.messageId
} ?: return@onEach } ?: return@onEach
pendingMessageContent = message.content ?: "" pendingMessageContent = message.content ?: ""
textSelection = TextRange(message.content?.length ?: 0)
} }
} }
}.catch { }.catch {