Commit 1046c05c authored by MozLando's avatar MozLando
Browse files

Merge #6291



6291: For #6276 - Fix for autocomplete add or remove logic. r=rocketsroger a=codrut-topliceanu



Co-authored-by: default avatarcodrut.topliceanu <codrut.topliceanu@softvision.ro>
parents eca774ae 98821318
Loading
Loading
Loading
Loading
+50 −18
Original line number Diff line number Diff line
@@ -601,40 +601,72 @@ open class InlineAutocompleteEditText @JvmOverloads constructor(
        return inputMethod ?: ""
    }

    /**
     * This class watches for text changes and adds or removes autocomplete text accordingly.
     * Using this class is preferred when making text changes as it will not interfere
     * with any composing text at the same time as custom keyboards.
     *
     * Known issue: autocomplete will not be added when replacing the current text with one
     * that has a text length equal to the one being replaced minus 1.
     * */
    private inner class TextChangeListener : TextWatcher {

        /**
         * Holds the value of the non-autocomplete text before any changes have been made.
         * */
        private var beforeChangedTextNonAutocomplete: String = ""

        /**
         * The start index of the characters about to be replaced.
         * When using keyboards that have their own text correction enabled this value
         * will be 0 if no text has been selected beforehand.
         * This is because text correction from keyboards usually works by replacing the
         * whole text when the user is typing.
         * (e.g.: a text of 5 chars is replaced by a text of 6 when inputting a character
         * or by a text of 4 when backspacing.)
         * */
        private var beforeTextChangedIndex: Int = 0

        /**
         * The number of characters that have been changed in [onTextChanged].
         * When using keyboards that do not have their own text correction enabled
         * and the user is pressing backspace this value will be 0.
         * */
        private var textChangedCount: Int = 0

        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
            if (!isEnabled || settingAutoComplete) return
            beforeChangedTextNonAutocomplete = getNonAutocompleteText(text)
            beforeTextChangedIndex = start
        }

        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
            // do nothing
            if (!isEnabled || settingAutoComplete) return

            textChangedCount = count
        }

        override fun afterTextChanged(editable: Editable) {
            if (!isEnabled || settingAutoComplete) {
                return
            }
            if (!isEnabled || settingAutoComplete) return

            val afterNonAutocompleteText = getNonAutocompleteText(editable)

            val hasTextBeenRemoved: Boolean =
                    (beforeChangedTextNonAutocomplete.contains(afterNonAutocompleteText) &&
                            beforeChangedTextNonAutocomplete.length > afterNonAutocompleteText.length)
            val hasTextShortenedByOne: Boolean =
                    beforeChangedTextNonAutocomplete.length == afterNonAutocompleteText.length + 1

            // Covers both keyboards with text correction activated and those without.
            val hasBackspaceBeenPressed =
                    (textChangedCount == 0) || (hasTextShortenedByOne && beforeTextChangedIndex == 0)

            // No autocompleting when typing a search query
            val afterTextIsSearch = afterNonAutocompleteText.contains(" ")

            val hasTextBeenAdded: Boolean =
                    (afterNonAutocompleteText.contains(beforeChangedTextNonAutocomplete) &&
                            afterNonAutocompleteText.length > beforeChangedTextNonAutocomplete.length)

            // If true autocomplete text will be added, if false autocomplete text will be removed.
            var shouldAddAutocomplete: Boolean =
                    // Remove autocomplete if search query
                    (!afterNonAutocompleteText.contains(" ") &&
                            // ... or if user is hitting a backspace (the string is getting smaller)
                            !hasTextBeenRemoved && afterNonAutocompleteText.isNotEmpty()) ||
                            // Add autocomplete if valid text has been added
                            (hasTextBeenAdded && !afterNonAutocompleteText.contains(" "))
                    (afterNonAutocompleteText.contains(beforeChangedTextNonAutocomplete) ||
                            beforeChangedTextNonAutocomplete.isEmpty()) &&
                            afterNonAutocompleteText.length > beforeChangedTextNonAutocomplete.length

            var shouldAddAutocomplete: Boolean = hasTextBeenAdded || (!afterTextIsSearch && !hasBackspaceBeenPressed)

            autoCompletePrefixLength = afterNonAutocompleteText.length

+15 −4
Original line number Diff line number Diff line
@@ -272,10 +272,6 @@ class InlineAutocompleteEditTextTest {
        et.setText("tex")
        assertEquals(1, invokedCounter)

        // Simulate removing a block of text.  We don't expect autocomplete to have been called
        et.setText("t")
        assertEquals(1, invokedCounter)

        // Presence of a space is counted as a 'search query', we don't autocomplete those.
        et.setText("search term")
        assertEquals(1, invokedCounter)
@@ -404,4 +400,19 @@ class InlineAutocompleteEditTextTest {
        et.setText("g")
        assertEquals("google.com", "${et.text}")
    }

    @Test
    fun `GIVEN field with selected text 'google ' WHEN text 'g' added THEN autocomplete to google`() {
        val et = InlineAutocompleteEditText(testContext, attributes)
        et.setText("https://www.google.com/")
        et.selectAll()
        et.onAttachedToWindow()
        et.autocompleteResult = AutocompleteResult(
                text = "google.com",
                source = "test-source",
                totalItems = 100)

        et.setText("g")
        assertEquals("google.com", "${et.text}")
    }
}