11package org.wordpress.aztec
22
33import android.os.Build
4- import android.widget.TextView
5- import android.view.inputmethod.BaseInputConnection
4+ import android.os.Bundle
65import android.text.Editable
6+ import android.text.SpannableStringBuilder
7+ import android.text.Spanned
8+ import android.text.TextUtils
9+ import android.text.style.SuggestionSpan
10+ import android.view.KeyEvent
11+ import android.view.inputmethod.BaseInputConnection
712import android.view.inputmethod.CompletionInfo
813import android.view.inputmethod.CorrectionInfo
9- import android.view.inputmethod.ExtractedTextRequest
1014import android.view.inputmethod.ExtractedText
15+ import android.view.inputmethod.ExtractedTextRequest
1116import android.view.inputmethod.InputConnection
12- import android.os.Bundle
13- import android.view.KeyEvent
1417import android.view.inputmethod.InputContentInfo
18+ import org.wordpress.aztec.spans.IAztecSpan
1519
1620/* *
1721 * Wrapper around proprietary Samsung InputConnection. Forwards all the calls to it, except for getExtractedText
1822 */
1923class SamsungInputConnection (
20- private val mTextView : TextView ,
24+ private val mTextView : AztecText ,
2125 private val baseInputConnection : InputConnection ,
2226) : BaseInputConnection(mTextView, true ) {
2327
@@ -59,7 +63,7 @@ class SamsungInputConnection(
5963
6064 // Extracted text on Samsung devices on Android 13 is somehow used for Grammarly suggestions which causes a lot of
6165 // issues with spans and cursors. We do not use extracted text, so returning null
62- // (default behavior of BaseInputConnection) fixes the problem
66+ // (default behavior of BaseInputConnection) prevents Grammarly from messing up content most of the time
6367 override fun getExtractedText (request : ExtractedTextRequest ? , flags : Int ): ExtractedText ? {
6468 return null
6569 }
@@ -73,6 +77,34 @@ class SamsungInputConnection(
7377 }
7478
7579 override fun commitText (text : CharSequence? , newCursorPosition : Int ): Boolean {
80+ val isSameStringValue = text.toString() == editable.toString()
81+ val incomingTextHasSuggestions = text is Spanned &&
82+ text.getSpans(0 , text.length, SuggestionSpan ::class .java).isNotEmpty()
83+
84+ // Despite returning null from getExtractedText, in some cases Grammarly still tries to replace content of the
85+ // editor with own content containing suggestions. This mostly works ok, but Aztec spans are finicky, and tend
86+ // to get messed when content of the editor is replaced. In this method we remove Aztec spans before committing
87+ // the change, and reapply them afterward
88+ if (isSameStringValue && incomingTextHasSuggestions) {
89+ // create a clean spannable string from editable to temporarily hold spans
90+ val tempString = SpannableStringBuilder (editable.toString())
91+
92+ // store Aztec and Suggestions spans in temp string
93+ TextUtils .copySpansFrom(editable, 0 , editable.length, IAztecSpan ::class .java, tempString, 0 )
94+ TextUtils .copySpansFrom(text as Spanned , 0 , editable.length, SuggestionSpan ::class .java, tempString, 0 )
95+
96+ // remove all the Aztec spans from the current content of editor
97+ editable.getSpans(0 , editable.length, IAztecSpan ::class .java).forEach { editable.removeSpan(it) }
98+
99+ // commit the text
100+ val result = baseInputConnection.commitText(text, newCursorPosition)
101+
102+ // re-add the spans we removed before committing the text
103+ TextUtils .copySpansFrom(tempString, 0 , editable.length, IAztecSpan ::class .java, editable, 0 )
104+ TextUtils .copySpansFrom(tempString, 0 , editable.length, SuggestionSpan ::class .java, editable, 0 )
105+
106+ return result
107+ }
76108 return baseInputConnection.commitText(text, newCursorPosition)
77109 }
78110
0 commit comments