From cfb9cc3edc882b705d7b0ca022afea65b9ff2c76 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 10 Jun 2025 18:39:26 +0200 Subject: [PATCH] Fix for message composer losing focus in Compose 1.8.0 (#4853) Co-authored-by: ElementBot --- .../impl/ExpandableBottomSheetScaffold.kt | 9 ++++++--- .../android/libraries/androidutils/ui/View.kt | 5 +++++ .../textcomposer/SoftKeyboardEffect.kt | 17 +++++++++++++---- .../libraries/textcomposer/TextComposer.kt | 6 +++++- .../components/markdown/MarkdownEditText.kt | 5 ----- .../components/markdown/MarkdownTextInput.kt | 5 +++++ ...ures.messages.impl_MessagesView_Day_6_en.png | 4 ++-- ...es.messages.impl_MessagesView_Night_6_en.png | 4 ++-- ...tComposerFormattingNotEncrypted_Day_0_en.png | 4 ++-- ...omposerFormattingNotEncrypted_Night_0_en.png | 4 ++-- ...composer_TextComposerFormatting_Day_0_en.png | 4 ++-- ...mposer_TextComposerFormatting_Night_0_en.png | 4 ++-- 12 files changed, 46 insertions(+), 25 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt index ca70ca55de..28908498ff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt @@ -10,7 +10,10 @@ package io.element.android.features.messages.impl import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetValue import androidx.compose.material3.rememberBottomSheetScaffoldState @@ -112,7 +115,7 @@ internal fun ExpandableBottomSheetScaffold( } SubcomposeLayout( - modifier = modifier, + modifier = modifier.windowInsetsPadding(WindowInsets.ime), measurePolicy = { constraints: Constraints -> val sheetContentSub = subcompose(Slot.SheetContent(sheetContentKey)) { sheetContent(true) }.map { it.measure(Constraints(maxWidth = constraints.maxWidth)) @@ -123,7 +126,7 @@ internal fun ExpandableBottomSheetScaffold( val dragHandleHeight = dragHandleSub?.height?.toDp() ?: 0.dp val maxHeight = constraints.maxHeight.toDp() - val contentHeight = sheetContentSub.height.toDp() + dragHandleHeight + val contentHeight = sheetContentSub.measuredHeight.toDp() + dragHandleHeight contentOverflows = contentHeight > maxHeight @@ -140,7 +143,7 @@ internal fun ExpandableBottomSheetScaffold( measurePolicy = { measurables, constraints -> val constraintHeight = constraints.maxHeight val offset = tryOrNull { scaffoldState.bottomSheetState.requireOffset() } ?: 0f - val height = Integer.max(0, constraintHeight - offset.roundToInt()) + val height = Integer.max(peekHeight.roundToPx(), constraintHeight - offset.roundToInt()) val top = measurables[0].measure( constraints.copy( minHeight = height, diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt index 1081b6a3fb..cead1384c9 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt @@ -27,6 +27,11 @@ fun View.showKeyboard(andRequestFocus: Boolean = false) { imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } +fun View.isKeyboardVisible(): Boolean { + val imm = context?.getSystemService() + return imm?.isAcceptingText == true +} + suspend fun View.awaitWindowFocus() = suspendCancellableCoroutine { continuation -> if (hasWindowFocus()) { continuation.resume(Unit) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt index 108e969b04..8f6ca0530d 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt @@ -7,6 +7,8 @@ package io.element.android.libraries.textcomposer +import android.os.Build +import android.view.WindowInsets import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -14,6 +16,7 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.platform.LocalView import androidx.compose.ui.viewinterop.AndroidView import io.element.android.libraries.androidutils.ui.awaitWindowFocus +import io.element.android.libraries.androidutils.ui.isKeyboardVisible import io.element.android.libraries.androidutils.ui.showKeyboard /** @@ -40,11 +43,17 @@ internal fun SoftKeyboardEffect( // Await window focus in case returning from a dialog view.awaitWindowFocus() - // Show the keyboard, temporarily using the root view for focus - view.showKeyboard(andRequestFocus = true) + if (!view.isKeyboardVisible()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + view.windowInsetsController?.show(WindowInsets.Type.ime()) + } else { + // Show the keyboard, temporarily using the root view for focus + view.showKeyboard(andRequestFocus = true) + } - // Refocus to the correct view - latestOnRequestFocus() + // Refocus to the correct view + latestOnRequestFocus() + } } } } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 83d51612d2..29a5f22449 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -18,12 +18,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -35,6 +37,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -432,8 +435,9 @@ private fun TextFormattingLayout( sendButton: @Composable () -> Unit, modifier: Modifier = Modifier ) { + val bottomPadding = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() + 8.dp } Column( - modifier = modifier.padding(vertical = 4.dp), + modifier = modifier.padding(vertical = 4.dp).padding(bottom = bottomPadding), verticalArrangement = Arrangement.spacedBy(4.dp), ) { if (isRoomEncrypted == false) { diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt index 7d7a2c00e9..4e66e6c79b 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt @@ -8,7 +8,6 @@ package io.element.android.libraries.textcomposer.components.markdown import android.content.Context -import android.view.View import androidx.appcompat.widget.AppCompatEditText internal class MarkdownEditText( @@ -37,8 +36,4 @@ internal class MarkdownEditText( onSelectionChangeListener?.invoke(selStart, selEnd) } } - - override fun focusSearch(direction: Int): View? { - return null - } } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt index 81b2aab287..58cd9c29d2 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt @@ -118,6 +118,11 @@ fun MarkdownTextInput( ) } state.requestFocusAction = { this.requestFocus() } + } else { + isEnabled = false + isFocusable = false + isFocusableInTouchMode = false + isClickable = false } } }, diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png index 1526024ad2..57f5891983 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:390f7f3c699ecd843eb79adcefc64a66e6bbb6740dcd3bccf0f90c2f2b70e7e2 -size 54255 +oid sha256:70ab6c6c738a96fd2db7901482cf4110dfcc69a6289e1db734a753751690747a +size 51622 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png index 1a2a97e582..72dbaca2b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6465d7f2e552193d51dd1701d6b4f5726cb616e35cdcd3e219ebf31c22941b9 -size 53750 +oid sha256:237ee8f45cfd01821287669abc4fe7125c0d2828ff60224706eef855910821f0 +size 51048 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png index bdf24a86a5..a13eab8827 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c86b3656e5913338c2513af8a56dc725a63d4fda9c5478f429ec684203c68115 -size 64550 +oid sha256:17f126215961643b6b7c3b41fcc5ea970923229b76bb7c918db191cb5c0ef8b9 +size 64672 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png index f3a93f5a27..ea9a142676 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b491d682990192c54ebdae58045caab1ff653d3d6c89ca5f15e93bbb45c6fc0 -size 62052 +oid sha256:f72fdf44bd35b6d62b5e75ec1bf2206429e0861da684d370259bea684eb2a76e +size 62158 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png index 29a5c83fc0..7b011af46e 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2bc5c13d2a1e100bdcd0142b7f03b458ba80d0d6ded82de1ebb1b6aa055a344 -size 53893 +oid sha256:d5226d7599af16015858523607a829d9cbbc0ffa21174f9c528e725673e2e31c +size 53982 diff --git a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png index bc91fe1061..ccb1438c05 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.textcomposer_TextComposerFormatting_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb2210aa87440b2e87217ff53448091078bdff658a6b36fad12cb1a57caf59e2 -size 51187 +oid sha256:31b2d698e78fa11dc6af9dbc20b07533afd16c26b8f9969756057f07ea4785d0 +size 51371