Merge pull request #7353 from hayaksi1/fix/860-enter-sends-message

Send a message with Enter on a physical keyboard, Shift+Enter for a new line
This commit is contained in:
Benoit Marty
2026-08-13 09:19:07 +02:00
committed by GitHub
4 changed files with 98 additions and 2 deletions
@@ -161,6 +161,8 @@ fun TextComposer(
} else {
stringResource(id = R.string.rich_text_editor_composer_placeholder)
}
val canSendTextMessage = markdown.isNotBlank() || composerMode is MessageComposerMode.Attachment
val textInput: @Composable () -> Unit = when (state) {
is TextEditorState.Rich -> {
val coroutineScope = rememberCoroutineScope()
@@ -219,14 +221,13 @@ fun TextComposer(
onReceiveSuggestion = onReceiveSuggestion,
richTextEditorStyle = style,
onSelectRichContent = onSelectRichContent,
onSendMessage = { if (canSendTextMessage) onSendMessage() },
)
}
}
}
}
val canSendTextMessage = markdown.isNotBlank() || composerMode is MessageComposerMode.Attachment
val textFormattingOptions: @Composable (() -> Unit)? = (state as? TextEditorState.Rich)?.let {
@Composable { TextFormatting(state = it.richTextEditorState) }
}
@@ -9,6 +9,8 @@
package io.element.android.libraries.textcomposer.components.markdown
import android.content.Context
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.View
import androidx.appcompat.widget.AppCompatEditText
@@ -17,8 +19,27 @@ internal class MarkdownEditText(
) : AppCompatEditText(context) {
var onSelectionChangeListener: ((Int, Int) -> Unit)? = null
var onEnterKeyListener: (() -> Boolean)? = null
private var isModifyingText = false
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == KeyEvent.KEYCODE_ENTER &&
!event.isShiftPressed &&
event.deviceId != KeyCharacterMap.VIRTUAL_KEYBOARD
) {
val listener = onEnterKeyListener
if (listener != null) {
return when (event.action) {
KeyEvent.ACTION_UP -> true
KeyEvent.ACTION_DOWN -> if (event.repeatCount == 0) listener() else true
else -> super.dispatchKeyEvent(event)
}
}
}
return super.dispatchKeyEvent(event)
}
fun updateEditableText(charSequence: CharSequence) {
isModifyingText = true
editableText.clear()
@@ -54,6 +54,7 @@ fun MarkdownTextInput(
onReceiveSuggestion: (Suggestion?) -> Unit,
richTextEditorStyle: RichTextEditorStyle,
onSelectRichContent: ((Uri) -> Unit)?,
onSendMessage: (() -> Unit)? = null,
) {
// Copied from io.element.android.wysiwyg.internal.utils.UriContentListener
class ReceiveUriContentListener(
@@ -132,6 +133,12 @@ fun MarkdownTextInput(
update = { editText ->
editText.contentDescription = placeholder
editText.applyStyleInCompose(richTextEditorStyle)
editText.onEnterKeyListener = onSendMessage?.let { send ->
{
send()
true
}
}
val text = state.text.value()
mentionSpanUpdater.updateMentionSpans(text)
if (state.text.needsDisplaying()) {
@@ -10,6 +10,8 @@
package io.element.android.libraries.textcomposer.impl.components.markdown
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.widget.EditText
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
@@ -32,12 +34,51 @@ import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
import io.element.android.libraries.textcomposer.model.Suggestion
import io.element.android.libraries.textcomposer.model.SuggestionType
import io.element.android.libraries.textcomposer.model.aMarkdownTextEditorState
import io.element.android.tests.testutils.EnsureCalledOnce
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
class MarkdownTextInputTest : RobolectricTest() {
@Test
fun `pressing enter on a physical keyboard sends the message`() = runAndroidComposeUiTest {
val state = aMarkdownTextEditorState(initialText = "Hello", initialFocus = true)
val onSendMessage = EnsureCalledOnce()
setMarkdownTextInput(state = state, onSendMessage = onSendMessage)
val editor = activity!!.findEditor()
editor.pressEnter()
awaitIdle()
onSendMessage.assertSuccess()
assertThat(editor.editableText.toString()).isEqualTo("Hello")
}
@Test
fun `pressing shift enter on a physical keyboard inserts a new line`() = runAndroidComposeUiTest {
val state = aMarkdownTextEditorState(initialText = "Hello", initialFocus = true)
var sendCount = 0
setMarkdownTextInput(state = state, onSendMessage = { sendCount++ })
val editor = activity!!.findEditor()
editor.setSelection(editor.editableText.length)
editor.pressEnter(metaState = KeyEvent.META_SHIFT_ON)
awaitIdle()
assertThat(sendCount).isEqualTo(0)
assertThat(editor.editableText.toString()).isEqualTo("Hello\n")
}
@Test
fun `pressing enter on the soft keyboard does not send the message`() = runAndroidComposeUiTest {
val state = aMarkdownTextEditorState(initialText = "Hello", initialFocus = true)
var sendCount = 0
setMarkdownTextInput(state = state, onSendMessage = { sendCount++ })
val editor = activity!!.findEditor()
editor.setSelection(editor.editableText.length)
editor.pressEnter(deviceId = KeyCharacterMap.VIRTUAL_KEYBOARD)
awaitIdle()
assertThat(sendCount).isEqualTo(0)
assertThat(editor.editableText.toString()).isEqualTo("Hello\n")
}
@Test
fun `when user types onTyping is triggered with value 'true'`() = runAndroidComposeUiTest {
val state = aMarkdownTextEditorState(initialFocus = true)
@@ -150,6 +191,7 @@ class MarkdownTextInputTest : RobolectricTest() {
state: MarkdownTextEditorState = aMarkdownTextEditorState(),
onTyping: (Boolean) -> Unit = {},
onSuggestionReceived: (Suggestion?) -> Unit = {},
onSendMessage: (() -> Unit)? = null,
) {
setContent {
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus)
@@ -161,6 +203,27 @@ class MarkdownTextInputTest : RobolectricTest() {
onReceiveSuggestion = onSuggestionReceived,
richTextEditorStyle = style,
onSelectRichContent = null,
onSendMessage = onSendMessage,
)
}
}
private fun EditText.pressEnter(
metaState: Int = 0,
deviceId: Int = PHYSICAL_KEYBOARD_DEVICE_ID,
) {
listOf(KeyEvent.ACTION_DOWN, KeyEvent.ACTION_UP).forEach { action ->
dispatchKeyEvent(
KeyEvent(
0L,
0L,
action,
KeyEvent.KEYCODE_ENTER,
0,
metaState,
deviceId,
0,
)
)
}
}
@@ -168,4 +231,8 @@ class MarkdownTextInputTest : RobolectricTest() {
private fun ComponentActivity.findEditor(): EditText {
return window.decorView.findViewWithTag(TestTags.plainTextEditor.value)
}
private companion object {
const val PHYSICAL_KEYBOARD_DEVICE_ID = 1
}
}