diff --git a/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/Extensions.kt b/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/Extensions.kt index b5ec284..1b8c23b 100644 --- a/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/Extensions.kt +++ b/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/Extensions.kt @@ -1,6 +1,9 @@ package com.infomaniak.lib.richhtmleditor import android.content.Context +import android.os.Build +import android.os.Bundle +import android.view.AbsSavedState import android.webkit.WebView import java.io.BufferedReader @@ -33,3 +36,11 @@ internal fun WebView.injectCss(css: String) { evaluateJavascript(addCssJs, null) } + +fun Bundle.getParcelableCompat(key: String, clazz: Class): AbsSavedState? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getParcelable(key, clazz) + } else { + getParcelable(key) + } +} diff --git a/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/RichHtmlEditorWebView.kt b/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/RichHtmlEditorWebView.kt index 847a222..6a73d5d 100644 --- a/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/RichHtmlEditorWebView.kt +++ b/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/RichHtmlEditorWebView.kt @@ -3,10 +3,14 @@ package com.infomaniak.lib.richhtmleditor import android.annotation.SuppressLint import android.content.Context import android.graphics.Rect +import android.os.Bundle +import android.os.Parcelable import android.util.AttributeSet +import android.view.AbsSavedState import android.view.ViewGroup import android.webkit.WebView import android.webkit.WebViewClient +import androidx.core.os.bundleOf import androidx.core.view.updateLayoutParams import com.infomaniak.lib.richhtmleditor.executor.JsExecutableMethod import com.infomaniak.lib.richhtmleditor.executor.JsExecutor @@ -38,6 +42,8 @@ class RichHtmlEditorWebView @JvmOverloads constructor( defStyleAttr: Int = 0, ) : WebView(context, attrs, defStyleAttr) { + private var keepKeyboardOpenedOnConfigurationChanged: Boolean = false + private val documentInitializer = DocumentInitializer() private val jsExecutor = JsExecutor(this) private val scriptCssInjector = ScriptCssInjector(this) @@ -135,6 +141,7 @@ class RichHtmlEditorWebView @JvmOverloads constructor( } fun requestFocusAndOpenKeyboard() { + keepKeyboardOpenedOnConfigurationChanged = true keyboardOpener.executeWhenDomIsLoaded(Unit) } @@ -143,13 +150,32 @@ class RichHtmlEditorWebView @JvmOverloads constructor( jsExecutor.executeWhenDomIsLoaded(JsExecutableMethod("exportHtml")) } + override fun onSaveInstanceState(): Parcelable { + val superState = super.onSaveInstanceState() + return bundleOf( + KEYBOARD_SHOULD_REOPEN_KEY to keepKeyboardOpenedOnConfigurationChanged, + SUPER_STATE_KEY to superState, + ) + } + + override fun onRestoreInstanceState(state: Parcelable?) { + (state as Bundle?)?.getBoolean(KEYBOARD_SHOULD_REOPEN_KEY)?.let { keepKeyboardOpenedOnConfigurationChanged = it } + super.onRestoreInstanceState(state?.getParcelableCompat(SUPER_STATE_KEY, AbsSavedState::class.java)) + } + override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) { super.onFocusChanged(focused, direction, previouslyFocusedRect) - if (focused) jsExecutor.executeWhenDomIsLoaded(JsExecutableMethod("requestFocus")) + if (focused) { + jsExecutor.executeWhenDomIsLoaded(JsExecutableMethod("requestFocus")) + if (keepKeyboardOpenedOnConfigurationChanged) keyboardOpener.executeWhenDomIsLoaded(Unit) + } else { + keepKeyboardOpenedOnConfigurationChanged = false + } } override fun onDetachedFromWindow() { super.onDetachedFromWindow() + keyboardOpener.removePendingListener() jsBridgeJob.cancel() } @@ -206,4 +232,9 @@ class RichHtmlEditorWebView @JvmOverloads constructor( private fun unsupported() { throw UnsupportedOperationException("Use setHtml() instead") } + + companion object { + private const val KEYBOARD_SHOULD_REOPEN_KEY = "keyboardShouldReopen" + private const val SUPER_STATE_KEY = "superState" + } } diff --git a/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/executor/KeyboardOpener.kt b/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/executor/KeyboardOpener.kt index 7402f79..774ffa7 100644 --- a/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/executor/KeyboardOpener.kt +++ b/rich-html-editor/src/main/java/com/infomaniak/lib/richhtmleditor/executor/KeyboardOpener.kt @@ -2,13 +2,42 @@ package com.infomaniak.lib.richhtmleditor.executor import android.app.Activity import android.view.View +import android.view.ViewTreeObserver import android.view.inputmethod.InputMethodManager internal class KeyboardOpener(private val view: View) : JsLifecycleAwareExecutor() { + + private var listener: ViewTreeObserver.OnWindowFocusChangeListener? = null + override fun executeImmediately(value: Unit) { if (view.requestFocus()) { - val inputMethodManager = view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - inputMethodManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) + if (view.hasWindowFocus()) { + openKeyboard() + } else { + // The window won't have the focus most of the time when the configuration changes and we want to reopen the + // keyboard right away. When this happen, we need to wait for the window to get the focus before opening the + // keyboard. + listener = object : ViewTreeObserver.OnWindowFocusChangeListener { + override fun onWindowFocusChanged(hasFocus: Boolean) { + if (hasFocus) { + openKeyboard() + view.viewTreeObserver.removeOnWindowFocusChangeListener(this) + } + } + } + + view.viewTreeObserver.addOnWindowFocusChangeListener(listener) + } } } + + fun removePendingListener() { + view.viewTreeObserver.removeOnWindowFocusChangeListener(listener) + listener = null + } + + private fun openKeyboard() { + val inputMethodManager = view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) + } }