Skip to content

Commit 519464f

Browse files
authored
[Rich text editor] Ensure keyboard opens for reply and text formatting modes (#1337)
1 parent d6dac9a commit 519464f

File tree

4 files changed

+84
-13
lines changed

4 files changed

+84
-13
lines changed

changelog.d/1337.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Rich text editor] Ensure keyboard opens for reply and text formatting modes

libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package io.element.android.libraries.androidutils.ui
1818

1919
import android.view.View
20+
import android.view.ViewTreeObserver
2021
import android.view.inputmethod.InputMethodManager
2122
import androidx.core.content.getSystemService
23+
import kotlinx.coroutines.suspendCancellableCoroutine
24+
import kotlin.coroutines.resume
2225

2326
fun View.hideKeyboard() {
2427
val imm = context?.getSystemService<InputMethodManager>()
@@ -41,3 +44,24 @@ fun View.setHorizontalPadding(padding: Int) {
4144
paddingBottom
4245
)
4346
}
47+
48+
suspend fun View.awaitWindowFocus() = suspendCancellableCoroutine { continuation ->
49+
if (hasWindowFocus()) {
50+
continuation.resume(Unit)
51+
} else {
52+
val listener = object : ViewTreeObserver.OnWindowFocusChangeListener {
53+
override fun onWindowFocusChanged(hasFocus: Boolean) {
54+
if (hasFocus) {
55+
viewTreeObserver.removeOnWindowFocusChangeListener(this)
56+
continuation.resume(Unit)
57+
}
58+
}
59+
}
60+
61+
viewTreeObserver.addOnWindowFocusChangeListener(listener)
62+
63+
continuation.invokeOnCancellation {
64+
viewTreeObserver.removeOnWindowFocusChangeListener(listener)
65+
}
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.libraries.textcomposer
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.LaunchedEffect
21+
import androidx.compose.ui.platform.LocalView
22+
import androidx.compose.ui.viewinterop.AndroidView
23+
import io.element.android.libraries.androidutils.ui.awaitWindowFocus
24+
import io.element.android.libraries.androidutils.ui.showKeyboard
25+
26+
/**
27+
* Shows the soft keyboard when a given key changes to meet the required condition.
28+
*
29+
* Uses [showKeyboard] to show the keyboard for compatibility with [AndroidView].
30+
*
31+
* @param T
32+
* @param key The key to watch for changes.
33+
* @param onRequestFocus A callback to request focus to the view that will receive the keyboard input.
34+
* @param predicate The predicate that [key] must meet before showing the keyboard.
35+
*/
36+
@Composable
37+
internal fun <T> SoftKeyboardEffect(
38+
key: T,
39+
onRequestFocus: () -> Unit,
40+
predicate: (T) -> Boolean,
41+
) {
42+
val view = LocalView.current
43+
LaunchedEffect(key) {
44+
if (predicate(key)) {
45+
// Await window focus in case returning from a dialog
46+
view.awaitWindowFocus()
47+
48+
// Show the keyboard, temporarily using the root view for focus
49+
view.showKeyboard(andRequestFocus = true)
50+
51+
// Refocus to the correct view
52+
onRequestFocus()
53+
}
54+
}
55+
}

libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import androidx.compose.material.icons.filled.Close
4343
import androidx.compose.material.ripple.rememberRipple
4444
import androidx.compose.material3.MaterialTheme
4545
import androidx.compose.runtime.Composable
46-
import androidx.compose.runtime.LaunchedEffect
4746
import androidx.compose.runtime.derivedStateOf
4847
import androidx.compose.runtime.getValue
4948
import androidx.compose.runtime.remember
@@ -52,7 +51,6 @@ import androidx.compose.ui.Modifier
5251
import androidx.compose.ui.draw.clip
5352
import androidx.compose.ui.graphics.Color
5453
import androidx.compose.ui.graphics.vector.ImageVector
55-
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
5654
import androidx.compose.ui.res.stringResource
5755
import androidx.compose.ui.res.vectorResource
5856
import androidx.compose.ui.text.style.TextAlign
@@ -84,7 +82,6 @@ import io.element.android.wysiwyg.compose.RichTextEditor
8482
import io.element.android.wysiwyg.compose.RichTextEditorDefaults
8583
import io.element.android.wysiwyg.compose.RichTextEditorState
8684
import io.element.android.wysiwyg.view.models.InlineFormat
87-
import kotlinx.coroutines.android.awaitFrame
8885
import uniffi.wysiwyg_composer.ActionState
8986
import uniffi.wysiwyg_composer.ComposerAction
9087

@@ -223,17 +220,11 @@ fun TextComposer(
223220
}
224221
}
225222

226-
// Request focus when changing mode, and show keyboard.
227-
val keyboard = LocalSoftwareKeyboardController.current
228-
LaunchedEffect(composerMode) {
229-
if (composerMode is MessageComposerMode.Special) {
230-
onRequestFocus()
231-
keyboard?.let {
232-
awaitFrame()
233-
it.show()
234-
}
235-
}
223+
SoftKeyboardEffect(composerMode, onRequestFocus) {
224+
it is MessageComposerMode.Special
236225
}
226+
227+
SoftKeyboardEffect(showTextFormatting, onRequestFocus) { it }
237228
}
238229

239230
@Composable

0 commit comments

Comments
 (0)