diff --git a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java index 54dda8d57..44e99e572 100644 --- a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java +++ b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java @@ -74,8 +74,8 @@ public View onCreateInputView() { */ @Override public void onStartInput(EditorInfo inputField, boolean restarting) { + // Logger.d("T9.onStartInput", "inputType: " + inputField.inputType + " fieldId: " + inputField.fieldId + " fieldName: " + inputField.fieldName + " packageName: " + inputField.packageName + " privateImeOptions: " + inputField.privateImeOptions + " imeOptions: " + inputField.imeOptions + " extras: " + inputField.extras); currentInputConnection = getCurrentInputConnection(); - // Logger.d("T9.onStartInput", "inputType: " + inputField.inputType + " fieldId: " + inputField.fieldId + " fieldName: " + inputField.fieldName + " packageName: " + inputField.packageName); onStart(inputField); } diff --git a/src/io/github/sspanak/tt9/ime/TraditionalT9.java b/src/io/github/sspanak/tt9/ime/TraditionalT9.java index e662759e6..3d0a59bf0 100644 --- a/src/io/github/sspanak/tt9/ime/TraditionalT9.java +++ b/src/io/github/sspanak/tt9/ime/TraditionalT9.java @@ -18,6 +18,7 @@ import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.db.DictionaryDb; +import io.github.sspanak.tt9.ime.helpers.AppHacks; import io.github.sspanak.tt9.ime.helpers.InputModeValidator; import io.github.sspanak.tt9.ime.helpers.InputType; import io.github.sspanak.tt9.ime.helpers.TextField; @@ -35,6 +36,7 @@ public class TraditionalT9 extends KeyPadHandler { // internal settings/data private boolean isActive = false; + @NonNull private AppHacks appHacks = new AppHacks(null, null); @NonNull private TextField textField = new TextField(null, null); @NonNull private InputType inputType = new InputType(null, null); @NonNull private final Handler autoAcceptHandler = new Handler(Looper.getMainLooper()); @@ -202,6 +204,7 @@ private void initUi() { protected void onStart(EditorInfo input) { inputType = new InputType(currentInputConnection, input); textField = new TextField(currentInputConnection, input); + appHacks = new AppHacks(input, textField); if (!inputType.isValid() || inputType.isLimited()) { // When the input is invalid or simple, let Android handle it. @@ -242,6 +245,10 @@ public boolean onBack() { public boolean onBackspace() { + if (appHacks.onBackspace(mInputMode)) { + return true; + } + // 1. Dialer fields seem to handle backspace on their own and we must ignore it, // otherwise, keyDown race condition occur for all keys. // 2. Allow the assigned key to function normally, when there is no text (e.g. "Back" navigates back) @@ -296,7 +303,7 @@ protected boolean onNumber(int key, boolean hold, int repeat) { } if (mInputMode.shouldSelectNextSuggestion() && !isSuggestionViewHidden()) { - nextSuggestion(); + onKeyScrollSuggestion(false, false); scheduleAutoAccept(mInputMode.getAutoAcceptTimeout()); } else { getSuggestions(); @@ -430,10 +437,9 @@ public boolean onKeyScrollSuggestion(boolean validateOnly, boolean backward) { } cancelAutoAccept(); - if (backward) previousSuggestion(); - else nextSuggestion(); + suggestionBar.scrollToSuggestion(backward ? -1 : 1); mInputMode.setWordStem(suggestionBar.getCurrentSuggestion(), true); - textField.setComposingTextWithHighlightedStem(suggestionBar.getCurrentSuggestion(), mInputMode); + setComposingTextWithHighlightedStem(suggestionBar.getCurrentSuggestion()); return true; } @@ -499,12 +505,18 @@ public boolean onKeyShowSettings(boolean validateOnly) { @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { + // Logger.d("onUpdateSelection", "oldSelStart: " + oldSelStart + " oldSelEnd: " + oldSelEnd + " newSelStart: " + newSelStart + " oldSelEnd: " + oldSelEnd + " candidatesStart: " + candidatesStart + " candidatesEnd: " + candidatesEnd); + super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); // If the cursor moves while composing a word (usually, because the user has touched the screen outside the word), we must // end typing end accept the word. Otherwise, the cursor would jump back at the end of the word, after the next key press. // This is confusing from user perspective, so we want to avoid it. - if (!suggestionBar.isEmpty() && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd)) { + if ( + candidatesStart != -1 && candidatesEnd != -1 + && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd) + && !suggestionBar.isEmpty() + ) { acceptIncompleteSuggestion(); } } @@ -515,18 +527,6 @@ private boolean isSuggestionViewHidden() { } - private void previousSuggestion() { - suggestionBar.scrollToSuggestion(-1); - textField.setComposingTextWithHighlightedStem(suggestionBar.getCurrentSuggestion(), mInputMode); - } - - - private void nextSuggestion() { - suggestionBar.scrollToSuggestion(1); - textField.setComposingTextWithHighlightedStem(suggestionBar.getCurrentSuggestion(), mInputMode); - } - - private boolean scheduleAutoAccept(int delay) { cancelAutoAccept(); @@ -630,7 +630,7 @@ private void handleSuggestions() { // for a more intuitive experience. String word = suggestionBar.getCurrentSuggestion(); word = word.substring(0, Math.min(mInputMode.getSequenceLength(), word.length())); - textField.setComposingTextWithHighlightedStem(word, mInputMode); + setComposingTextWithHighlightedStem(word); } @@ -671,6 +671,15 @@ private void refreshComposingText() { } + private void setComposingTextWithHighlightedStem(@NonNull String word) { + if (appHacks.setComposingTextWithHighlightedStem(word)) { + Logger.w("highlightComposingText", "Defective text field detected! Text highlighting disabled."); + } else { + textField.setComposingTextWithHighlightedStem(word, mInputMode); + } + } + + private void nextInputMode() { if (mInputMode.isPassthrough()) { return; diff --git a/src/io/github/sspanak/tt9/ime/helpers/AppHacks.java b/src/io/github/sspanak/tt9/ime/helpers/AppHacks.java new file mode 100644 index 000000000..d6ee6b99a --- /dev/null +++ b/src/io/github/sspanak/tt9/ime/helpers/AppHacks.java @@ -0,0 +1,42 @@ +package io.github.sspanak.tt9.ime.helpers; + +import android.view.inputmethod.EditorInfo; + +import androidx.annotation.NonNull; + +import io.github.sspanak.tt9.ime.modes.InputMode; + +public class AppHacks { + private final EditorInfo editorInfo; + private final TextField textField; + + + public AppHacks(EditorInfo inputField, TextField textField) { + this.editorInfo = inputField; + this.textField = textField; + } + + + private boolean isKindleInvertedTextField() { + return editorInfo != null && editorInfo.inputType == 1 && editorInfo.packageName.contains("com.amazon.kindle"); + } + + + public boolean setComposingTextWithHighlightedStem(@NonNull String word) { + if (isKindleInvertedTextField()) { + textField.setComposingText(word); + return true; + } + + return false; + } + + + public boolean onBackspace(InputMode inputMode) { + if (isKindleInvertedTextField()) { + inputMode.clearWordStem(); + } + + return false; + } +} diff --git a/src/io/github/sspanak/tt9/ime/helpers/TextField.java b/src/io/github/sspanak/tt9/ime/helpers/TextField.java index fc7e2bf44..a3af0fe82 100644 --- a/src/io/github/sspanak/tt9/ime/helpers/TextField.java +++ b/src/io/github/sspanak/tt9/ime/helpers/TextField.java @@ -216,6 +216,17 @@ public void deletePrecedingSpace(String word) { } + /** + * deletePrecedingCharacters + * Deletes N characters before the cursor or does nothing on invalid input. + */ + public void deletePrecedingCharacters(int numberOfChars) { + if (numberOfChars > 0 && connection != null) { + connection.deleteSurroundingText(numberOfChars, 0); + } + } + + /** * setText * A fail-safe setter that appends text to the field, ignoring NULL input. @@ -274,13 +285,13 @@ public void finishComposingText() { * the text will be in bold and italic. */ private CharSequence highlightText(CharSequence word, int start, int end, boolean highlightMore) { - if (end < start || start < 0) { + if (end <= start || start < 0) { Logger.w("tt9.util.highlightComposingText", "Cannot highlight invalid composing text range: [" + start + ", " + end + "]"); return word; } - // nothing to highlight in an empty word - if (word == null || word.length() == 0) { + // nothing to highlight in an empty word or if the target is beyond the last letter + if (word == null || word.length() == 0 || word.length() <= start) { return word; } diff --git a/src/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java b/src/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java index 34b7a8df9..5794e715c 100644 --- a/src/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java +++ b/src/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java @@ -7,6 +7,7 @@ import android.os.Handler; import android.view.View; +import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.DividerItemDecoration; @@ -106,6 +107,7 @@ public String getCurrentSuggestion() { } + @NonNull public String getSuggestion(int id) { if (id < 0 || id >= suggestions.size()) { return "";