diff --git a/src/main/kotlin/androidx/compose/material/TextFieldExt.kt b/src/main/kotlin/androidx/compose/material/TextFieldExt.kt new file mode 100644 index 0000000000000000000000000000000000000000..dcaad358c11f2c4d063a8f529e9b3bd2f2d4ff47 --- /dev/null +++ b/src/main/kotlin/androidx/compose/material/TextFieldExt.kt @@ -0,0 +1,104 @@ +package androidx.compose.material + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.shape.ZeroCornerSize +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.isAltPressed +import androidx.compose.ui.input.key.isCtrlPressed +import androidx.compose.ui.input.key.isMetaPressed +import androidx.compose.ui.input.key.isShiftPressed +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun TextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + onEnter: () -> Unit = {}, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + singleLine: Boolean = false, + maxLines: Int = Int.MAX_VALUE, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + shape: Shape = + MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), + colors: TextFieldColors = TextFieldDefaults.textFieldColors() +) { + var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) } + val textFieldValue = textFieldValueState.copy(text = value) + + val modifier = Modifier.onPreviewKeyEvent { + if (it.type == KeyEventType.KeyDown && it.key == Key.Enter) { + if (it.isShiftPressed) { + textFieldValueState = textFieldValue.insertOrReplaceBy("\n") + onValueChange(textFieldValueState.text) + } else if (!it.isModifierKeyPressed) { + onEnter() + } + return@onPreviewKeyEvent true + } + false + }.then(modifier) + + TextField( + enabled = enabled, + readOnly = readOnly, + value = textFieldValue, + onValueChange = { + textFieldValueState = it + if (value != it.text) { + onValueChange(it.text) + } + }, + modifier = modifier, + singleLine = singleLine, + textStyle = textStyle, + label = label, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + isError = isError, + visualTransformation = visualTransformation, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { onEnter() }), + maxLines = maxLines, + interactionSource = interactionSource, + shape = shape, + colors = colors + ) +} + +val KeyEvent.isModifierKeyPressed: Boolean + get() = isShiftPressed || isAltPressed || isMetaPressed || isCtrlPressed + +fun TextFieldValue.insertOrReplaceBy(replacement: CharSequence) = this.copy( + text = text.replaceRange(selection.min, selection.max, replacement), + selection = TextRange(selection.min + 1, selection.min + 1) +) diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt index 8818ad76ef78de0a58575aeec1cfe817cc03475e..a19986aa1e2632ed8dbff48aa5952057063f55a3 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt @@ -1,6 +1,5 @@ package org.briarproject.briar.desktop.conversation -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -8,10 +7,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.AlertDialog +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.material.TextFieldDefaults @@ -19,22 +19,32 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Send import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import org.briarproject.briar.desktop.theme.DarkColors import org.briarproject.briar.desktop.ui.HorizontalDivider import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.PreviewUtils.preview -@Preview -@Composable -fun PreviewConversationInput() { - MaterialTheme(colors = DarkColors) { - Surface { - ConversationInput("Lorem ipsum.", {}, {}) - } +@OptIn(ExperimentalMaterialApi::class) +fun main() = preview { + val (text, updateText) = remember { mutableStateOf("Lorem ipsum.") } + var dialogVisible by remember { mutableStateOf(false) } + var sentText by remember { mutableStateOf("") } + ConversationInput(text, updateText) { dialogVisible = true; sentText = text; updateText("") } + + if (dialogVisible) { + AlertDialog( + onDismissRequest = { dialogVisible = false }, + buttons = {}, + text = { Text(sentText) }, + ) } } @@ -45,6 +55,7 @@ fun ConversationInput(text: String, updateText: (String) -> Unit, onSend: () -> TextField( value = text, onValueChange = updateText, + onEnter = onSend, maxLines = 10, textStyle = TextStyle(fontSize = 16.sp, lineHeight = 16.sp), placeholder = { Text(i18n("conversation.message.new")) },