diff --git a/changelog.d/1337.bugfix b/changelog.d/1337.bugfix new file mode 100644 index 0000000000..b7d6c300dd --- /dev/null +++ b/changelog.d/1337.bugfix @@ -0,0 +1 @@ +[Rich text editor] Ensure keyboard opens for reply and text formatting modes \ No newline at end of file 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 0639f29d1f..8bbaf1e5c3 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 @@ -17,8 +17,11 @@ package io.element.android.libraries.androidutils.ui import android.view.View +import android.view.ViewTreeObserver import android.view.inputmethod.InputMethodManager import androidx.core.content.getSystemService +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume fun View.hideKeyboard() { val imm = context?.getSystemService() @@ -41,3 +44,24 @@ fun View.setHorizontalPadding(padding: Int) { paddingBottom ) } + +suspend fun View.awaitWindowFocus() = suspendCancellableCoroutine { continuation -> + if (hasWindowFocus()) { + continuation.resume(Unit) + } else { + val listener = object : ViewTreeObserver.OnWindowFocusChangeListener { + override fun onWindowFocusChanged(hasFocus: Boolean) { + if (hasFocus) { + viewTreeObserver.removeOnWindowFocusChangeListener(this) + continuation.resume(Unit) + } + } + } + + viewTreeObserver.addOnWindowFocusChangeListener(listener) + + continuation.invokeOnCancellation { + viewTreeObserver.removeOnWindowFocusChangeListener(listener) + } + } +} 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 new file mode 100644 index 0000000000..96b48dca6e --- /dev/null +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.textcomposer + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.showKeyboard + +/** + * Shows the soft keyboard when a given key changes to meet the required condition. + * + * Uses [showKeyboard] to show the keyboard for compatibility with [AndroidView]. + * + * @param T + * @param key The key to watch for changes. + * @param onRequestFocus A callback to request focus to the view that will receive the keyboard input. + * @param predicate The predicate that [key] must meet before showing the keyboard. + */ +@Composable +internal fun SoftKeyboardEffect( + key: T, + onRequestFocus: () -> Unit, + predicate: (T) -> Boolean, +) { + val view = LocalView.current + LaunchedEffect(key) { + if (predicate(key)) { + // 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) + + // Refocus to the correct view + onRequestFocus() + } + } +} 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 df51d5d1ef..40bf27dd5f 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 @@ -43,7 +43,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -52,7 +51,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign @@ -84,7 +82,6 @@ import io.element.android.wysiwyg.compose.RichTextEditor import io.element.android.wysiwyg.compose.RichTextEditorDefaults import io.element.android.wysiwyg.compose.RichTextEditorState import io.element.android.wysiwyg.view.models.InlineFormat -import kotlinx.coroutines.android.awaitFrame import uniffi.wysiwyg_composer.ActionState import uniffi.wysiwyg_composer.ComposerAction @@ -223,17 +220,11 @@ fun TextComposer( } } - // Request focus when changing mode, and show keyboard. - val keyboard = LocalSoftwareKeyboardController.current - LaunchedEffect(composerMode) { - if (composerMode is MessageComposerMode.Special) { - onRequestFocus() - keyboard?.let { - awaitFrame() - it.show() - } - } + SoftKeyboardEffect(composerMode, onRequestFocus) { + it is MessageComposerMode.Special } + + SoftKeyboardEffect(showTextFormatting, onRequestFocus) { it } } @Composable