From 3f0d5812b8a0296ed600fc355f21bc7a638746cc Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 24 Oct 2023 16:05:56 +0200 Subject: [PATCH 1/3] Do not iterate in while when history cursor is outside of list range --- aztec/src/main/kotlin/org/wordpress/aztec/History.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/History.kt b/aztec/src/main/kotlin/org/wordpress/aztec/History.kt index dcbd4ac50..008b0aef9 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/History.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/History.kt @@ -61,7 +61,7 @@ class History(val historyEnabled: Boolean, val historySize: Int) { return } - while (historyCursor != historyList.size && historyCursor >= 0) { + while (historyCursor in 0 until historyList.size) { historyList.removeAt(historyCursor) } From 79d476fe39f20f92185957e594bb65a91ff70bcb Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Mon, 13 Nov 2023 09:01:53 -0500 Subject: [PATCH 2/3] Add back SamsungInputConnection --- .../kotlin/org/wordpress/aztec/AztecText.kt | 8 +- .../wordpress/aztec/SamsungInputConnection.kt | 187 ++++++++++++++++++ 2 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 aztec/src/main/kotlin/org/wordpress/aztec/SamsungInputConnection.kt diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt index 51ea796fe..a5d97bfec 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt @@ -698,12 +698,14 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection { val inputConnection = requireNotNull(super.onCreateInputConnection(outAttrs)).wrapWithBackSpaceHandler() - if (shouldOverridePredictiveTextBehavior()) { + return if (shouldOverridePredictiveTextBehavior()) { AppLog.d(AppLog.T.EDITOR, "Disabling autocorrect on Samsung device with Samsung Keyboard with API 33") outAttrs.inputType = outAttrs.inputType or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS - } - return inputConnection + SamsungInputConnection(this, inputConnection) + } else { + inputConnection + } } private fun InputConnection.wrapWithBackSpaceHandler(): InputConnection { diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/SamsungInputConnection.kt b/aztec/src/main/kotlin/org/wordpress/aztec/SamsungInputConnection.kt new file mode 100644 index 000000000..01b8f3dbd --- /dev/null +++ b/aztec/src/main/kotlin/org/wordpress/aztec/SamsungInputConnection.kt @@ -0,0 +1,187 @@ +package org.wordpress.aztec + +import android.os.Build +import android.os.Bundle +import android.text.Editable +import android.text.Selection +import android.text.Spanned +import android.text.style.SuggestionSpan +import android.view.KeyEvent +import android.view.inputmethod.BaseInputConnection +import android.view.inputmethod.CompletionInfo +import android.view.inputmethod.CorrectionInfo +import android.view.inputmethod.ExtractedText +import android.view.inputmethod.ExtractedTextRequest +import android.view.inputmethod.InputConnection +import android.view.inputmethod.InputContentInfo + +/** + * Wrapper around proprietary Samsung InputConnection. Forwards all the calls to it, except for getExtractedText and + * some custom logic in commitText + */ +class SamsungInputConnection( + private val mTextView: AztecText, + private val baseInputConnection: InputConnection +) : BaseInputConnection(mTextView, true) { + + override fun getEditable(): Editable { + return mTextView.editableText + } + + override fun beginBatchEdit(): Boolean { + return baseInputConnection.beginBatchEdit() + } + + override fun endBatchEdit(): Boolean { + return baseInputConnection.endBatchEdit() + } + + override fun clearMetaKeyStates(states: Int): Boolean { + return baseInputConnection.clearMetaKeyStates(states) + } + + override fun sendKeyEvent(event: KeyEvent?): Boolean { + return super.sendKeyEvent(event) + } + + override fun commitCompletion(text: CompletionInfo?): Boolean { + return baseInputConnection.commitCompletion(text) + } + + override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean { + return baseInputConnection.commitCorrection(correctionInfo) + } + + override fun performEditorAction(actionCode: Int): Boolean { + return baseInputConnection.performEditorAction(actionCode) + } + + override fun performContextMenuAction(id: Int): Boolean { + return baseInputConnection.performContextMenuAction(id) + } + + // Extracted text on Samsung devices on Android 13 is somehow used for Grammarly suggestions which causes a lot of + // issues with spans and cursors. We do not use extracted text, so returning null + // (default behavior of BaseInputConnection) prevents Grammarly from messing up content most of the time + override fun getExtractedText(request: ExtractedTextRequest?, flags: Int): ExtractedText? { + return null + } + + override fun performPrivateCommand(action: String?, data: Bundle?): Boolean { + return baseInputConnection.performPrivateCommand(action, data) + } + + override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean { + return baseInputConnection.setComposingText(text, newCursorPosition) + } + + override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean { + val incomingTextHasSuggestions = text is Spanned && + text.getSpans(0, text.length, SuggestionSpan::class.java).isNotEmpty() + + // Sometime spellchecker tries to commit partial text with suggestions. This mostly works ok, + // but Aztec spans are finicky, and tend to get messed when content of the editor is replaced. + // In this method we do everything replaceText method of EditableInputConnection does, apart from actually + // replacing text. Instead we copy the suggestions from incoming text into editor directly. + if (incomingTextHasSuggestions) { + // delete composing text set previously. + var composingSpanStart = getComposingSpanStart(editable) + var composingSpanEnd = getComposingSpanEnd(editable) + + if (composingSpanEnd < composingSpanStart) { + val tmp = composingSpanStart + composingSpanStart = composingSpanEnd + composingSpanEnd = tmp + } + + if (composingSpanStart != -1 && composingSpanEnd != -1) { + removeComposingSpans(editable) + } else { + composingSpanStart = Selection.getSelectionStart(editable) + composingSpanEnd = Selection.getSelectionEnd(editable) + if (composingSpanStart < 0) composingSpanStart = 0 + if (composingSpanEnd < 0) composingSpanEnd = 0 + if (composingSpanEnd < composingSpanStart) { + val tmp = composingSpanStart + composingSpanStart = composingSpanEnd + composingSpanEnd = tmp + } + } + + var cursorPosition = newCursorPosition + cursorPosition += if (cursorPosition > 0) { + composingSpanEnd - 1 + } else { + composingSpanStart + } + if (newCursorPosition < 0) cursorPosition = 0 + if (newCursorPosition > editable.length) cursorPosition = editable.length + Selection.setSelection(editable, cursorPosition) + + (text as Spanned).getSpans(0, text.length, SuggestionSpan::class.java).forEach { + val st: Int = text.getSpanStart(it) + val en: Int = text.getSpanEnd(it) + val fl: Int = text.getSpanFlags(it) + + if (editable.length > composingSpanStart + en) { + editable.setSpan(it, composingSpanStart + st, composingSpanStart + en, fl) + } + } + + return true + } + return baseInputConnection.commitText(text, newCursorPosition) + } + + override fun commitContent(inputContentInfo: InputContentInfo, flags: Int, opts: Bundle?): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + baseInputConnection.commitContent(inputContentInfo, flags, opts) + } else { + super.commitContent(inputContentInfo, flags, opts) + } + } + + override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean { + return baseInputConnection.deleteSurroundingText(beforeLength, afterLength) + } + + override fun requestCursorUpdates(cursorUpdateMode: Int): Boolean { + return baseInputConnection.requestCursorUpdates(cursorUpdateMode) + } + + override fun reportFullscreenMode(enabled: Boolean): Boolean { + return baseInputConnection.reportFullscreenMode(enabled) + } + + override fun setSelection(start: Int, end: Int): Boolean { + return baseInputConnection.setSelection(start, end) + } + + override fun finishComposingText(): Boolean { + return baseInputConnection.finishComposingText() + } + + override fun setComposingRegion(start: Int, end: Int): Boolean { + return baseInputConnection.setComposingRegion(start, end) + } + + override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean { + return baseInputConnection.deleteSurroundingTextInCodePoints(beforeLength, afterLength) + } + + override fun getCursorCapsMode(reqModes: Int): Int { + return baseInputConnection.getCursorCapsMode(reqModes) + } + + override fun getSelectedText(flags: Int): CharSequence? { + return baseInputConnection.getSelectedText(flags) + } + + override fun getTextAfterCursor(length: Int, flags: Int): CharSequence? { + return baseInputConnection.getTextAfterCursor(length, flags) + } + + override fun getTextBeforeCursor(length: Int, flags: Int): CharSequence? { + return baseInputConnection.getTextBeforeCursor(length, flags) + } +} From 6dd6bc5768a6e8eb60945171a1ee65346bf921b4 Mon Sep 17 00:00:00 2001 From: Siobhan Date: Mon, 4 Dec 2023 13:40:13 +0000 Subject: [PATCH 3/3] refactor: Guard previousFontMetrics against null --- .../wordpress/aztec/spans/AztecHeadingSpan.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt index b8e4e2a67..fa28f9a6b 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt @@ -127,10 +127,10 @@ open class AztecHeadingSpan( // save original font metrics if (previousFontMetrics == null) { previousFontMetrics = Paint.FontMetricsInt() - previousFontMetrics!!.top = fm.top - previousFontMetrics!!.ascent = fm.ascent - previousFontMetrics!!.bottom = fm.bottom - previousFontMetrics!!.descent = fm.descent + previousFontMetrics?.top = fm.top + previousFontMetrics?.ascent = fm.ascent + previousFontMetrics?.bottom = fm.bottom + previousFontMetrics?.descent = fm.descent } var addedTopPadding = false @@ -151,13 +151,17 @@ open class AztecHeadingSpan( // apply original font metrics to lines that should not have vertical padding if (!addedTopPadding) { - fm.ascent = previousFontMetrics!!.ascent - fm.top = previousFontMetrics!!.top + previousFontMetrics?.let { + fm.ascent = it.ascent + fm.top = it.top + } } if (!addedBottomPadding) { - fm.descent = previousFontMetrics!!.descent - fm.bottom = previousFontMetrics!!.bottom + previousFontMetrics?.let { + fm.descent = it.descent + fm.bottom = it.bottom + } } }