From 505c9c747ded8f7d8fcdb9bb6979929822c92185 Mon Sep 17 00:00:00 2001 From: Scott Alexander-Bown Date: Tue, 23 Feb 2021 17:02:46 +0000 Subject: [PATCH 1/2] Add `prefixEnabled` property to allow the prefix to be disabled as it's causing the text to be cleared when called from OnRestoreState() --- .../TokenCompleteTextView.java | 100 ++++++++++-------- 1 file changed, 56 insertions(+), 44 deletions(-) diff --git a/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java b/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java index d486367..18100da 100644 --- a/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java +++ b/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java @@ -6,23 +6,7 @@ import android.graphics.Typeface; import android.os.Parcel; import android.os.Parcelable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.appcompat.widget.AppCompatAutoCompleteTextView; -import android.text.Editable; -import android.text.InputFilter; -import android.text.InputType; -import android.text.Layout; -import android.text.NoCopySpan; -import android.text.Selection; -import android.text.SpanWatcher; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.TextWatcher; +import android.text.*; import android.text.style.ForegroundColorSpan; import android.util.AttributeSet; import android.util.Log; @@ -30,15 +14,14 @@ import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputConnectionWrapper; -import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.*; import android.widget.Filter; import android.widget.ListView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.appcompat.widget.AppCompatAutoCompleteTextView; import java.io.Serializable; import java.lang.reflect.ParameterizedType; @@ -84,9 +67,11 @@ public boolean isSelectable() { private TokenSpanWatcher spanWatcher; private TokenTextWatcher textWatcher; private CountSpan countSpan; - private @Nullable SpannableStringBuilder hiddenContent; + private @Nullable + SpannableStringBuilder hiddenContent; private TokenClickStyle tokenClickStyle = TokenClickStyle.None; private CharSequence prefix = ""; + private boolean prefixEnabled = true; private boolean hintVisible = false; private Layout lastLayout = null; private boolean initialized = false; @@ -250,6 +235,7 @@ public void setTokenListener(TokenListener l) { /** * Override if you want to prevent a token from being added. Defaults to false. + * * @param token the token to check * @return true if the token should not be added, false if it's ok to add it. */ @@ -259,6 +245,7 @@ public boolean shouldIgnoreToken(@SuppressWarnings("unused") T token) { /** * Override if you want to prevent a token from being removed. Defaults to true. + * * @param token the token to check * @return false if the token should not be removed, true if it's ok to remove it. */ @@ -266,6 +253,16 @@ public boolean isTokenRemovable(@SuppressWarnings("unused") T token) { return true; } + /** + * There is an issue if onRestoreState of the objects is not desired, the setting of the prefix + * overrides any existing in `text`. This option allows callers to disable that feature. + * + * @param enabled whether to enable the prefix + */ + public void setPrefixEnabled(boolean enabled) { + this.prefixEnabled = enabled; + } + /** * A String of text that is shown before all the tokens inside the EditText * (Think "To: " in an email address field. I would advise against this: use a label and a hint. @@ -304,7 +301,7 @@ public void setPrefix(CharSequence p) { * 'olive', 'purple', 'silver', 'teal'.

* * @param prefix prefix - * @param color A single color value in the form 0xAARRGGBB. + * @param color A single color value in the form 0xAARRGGBB. */ @SuppressWarnings("SameParameterValue") public void setPrefix(CharSequence prefix, int color) { @@ -319,12 +316,12 @@ public void setPrefix(CharSequence prefix, int color) { * @return List of tokens */ public List getObjects() { - ArrayListobjects = new ArrayList<>(); + ArrayList objects = new ArrayList<>(); Editable text = getText(); if (hiddenContent != null) { text = hiddenContent; } - for (TokenImageSpan span: text.getSpans(0, text.length(), TokenImageSpan.class)) { + for (TokenImageSpan span : text.getSpans(0, text.length(), TokenImageSpan.class)) { objects.add(span.getToken()); } return objects; @@ -403,8 +400,9 @@ public void setTokenLimit(int tokenLimit) { /** * Correctly build accessibility string for token contents - * + *

* This seems to be a hidden API, but there doesn't seem to be another reasonable way + * * @return custom string for accessibility */ @SuppressWarnings("unused") @@ -466,7 +464,7 @@ public CharSequence getTextForAccessibility() { @SuppressWarnings("unused") public void clearCompletionText() { //Respect currentCompletionText in case hint is visible or if other checks are added. - if (currentCompletionText().length() == 0){ + if (currentCompletionText().length() == 0) { return; } @@ -513,7 +511,7 @@ private Range getCurrentCandidateTokenRange() { List tokenRanges = tokenizer.findTokenRanges(editable, candidateStringStart, candidateStringEnd); - for (Range range: tokenRanges) { + for (Range range : tokenRanges) { if (range.start <= cursorEndPosition && cursorEndPosition <= range.end) { return range; } @@ -524,6 +522,7 @@ private Range getCurrentCandidateTokenRange() { /** * Override if you need custom logic to provide a sting representation of a token + * * @param token the token to convert * @return the string representation of the token. Defaults to {@link Object#toString()} */ @@ -548,7 +547,7 @@ protected float maxTextWidth() { @Override public int getMaxViewSpanWidth() { - return (int)maxTextWidth(); + return (int) maxTextWidth(); } public void redrawTokens() { @@ -944,7 +943,7 @@ public void run() { public void removeObjectSync(T object) { //To make sure all the appropriate callbacks happen, we just want to piggyback on the //existing code that handles deleting spans when the text changes - ArrayListtexts = new ArrayList<>(); + ArrayList texts = new ArrayList<>(); //If there is hidden content, it's important that we update it first if (hiddenContent != null) { texts.add(hiddenContent); @@ -954,7 +953,7 @@ public void removeObjectSync(T object) { } // If the object is currently visible, remove it - for (Editable text: texts) { + for (Editable text : texts) { TokenImageSpan[] spans = text.getSpans(0, text.length(), TokenImageSpan.class); for (TokenImageSpan span : spans) { if (span.getToken().equals(object)) { @@ -989,7 +988,7 @@ public void clearAsync() { post(new Runnable() { @Override public void run() { - for (T object: getObjects()) { + for (T object : getObjects()) { removeObjectSync(object); } } @@ -1001,7 +1000,9 @@ public void run() { */ private void updateCountSpan() { //No count span with free form text - if (!preventFreeFormText) { return; } + if (!preventFreeFormText) { + return; + } Editable text = getText(); @@ -1074,7 +1075,7 @@ private void insertSpan(TokenImageSpan tokenSpan) { } } editable.insert(offset, ssb); - editable.insert(offset + ssb.length(), " "); + editable.insert(offset + ssb.length(), " "); editable.setSpan(tokenSpan, offset, offset + ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); internalEditInProgress = false; } else { @@ -1216,7 +1217,9 @@ public void onClick() { public interface TokenListener { void onTokenAdded(T token); + void onTokenRemoved(T token); + void onTokenIgnored(T token); } @@ -1336,7 +1339,7 @@ private Class reifyParameterizedTypeClass() { // always return the Type of this class. Because this class is parameterized, the cast is safe ParameterizedType superclass = (ParameterizedType) viewClass.getGenericSuperclass(); Type type = superclass.getActualTypeArguments()[0]; - return (Class)type; + return (Class) type; } @Override @@ -1352,7 +1355,9 @@ public Parcelable onSaveInstanceState() { savingState = false; SavedState state = new SavedState(superState); - state.prefix = prefix; + if (prefixEnabled) { + state.prefix = prefix; + } state.allowCollapse = allowCollapse; state.performBestGuess = performBestGuess; state.preventFreeFormText = preventFreeFormText; @@ -1392,8 +1397,11 @@ public void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(ss.getSuperState()); internalEditInProgress = true; - setText(ss.prefix); - prefix = ss.prefix; + prefixEnabled = ss.prefixEnabled; + if (prefixEnabled) { + setText(ss.prefix); + prefix = ss.prefix; + } internalEditInProgress = false; updateHint(); allowCollapse = ss.allowCollapse; @@ -1407,11 +1415,11 @@ public void onRestoreInstanceState(Parcelable state) { if (SavedState.SERIALIZABLE_PLACEHOLDER.equals(ss.parcelableClassName)) { objects = convertSerializableObjectsToTypedObjects(ss.baseObjects); } else { - objects = (List)ss.baseObjects; + objects = (List) ss.baseObjects; } //TODO: change this to keep object spans in the correct locations based on ranges. - for (T obj: objects) { + for (T obj : objects) { addObjectSync(obj); } @@ -1434,6 +1442,7 @@ private static class SavedState extends BaseSavedState { static final String SERIALIZABLE_PLACEHOLDER = "Serializable"; CharSequence prefix; + boolean prefixEnabled; boolean allowCollapse; boolean performBestGuess; boolean preventFreeFormText; @@ -1447,13 +1456,14 @@ private static class SavedState extends BaseSavedState { SavedState(Parcel in) { super(in); prefix = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + prefixEnabled = in.readInt() != 0; allowCollapse = in.readInt() != 0; performBestGuess = in.readInt() != 0; preventFreeFormText = in.readInt() != 0; tokenClickStyle = TokenClickStyle.values()[in.readInt()]; parcelableClassName = in.readString(); if (SERIALIZABLE_PLACEHOLDER.equals(parcelableClassName)) { - baseObjects = (ArrayList)in.readSerializable(); + baseObjects = (ArrayList) in.readSerializable(); } else { try { ClassLoader loader = Class.forName(parcelableClassName).getClassLoader(); @@ -1481,13 +1491,14 @@ private static class SavedState extends BaseSavedState { public void writeToParcel(@NonNull Parcel out, int flags) { super.writeToParcel(out, flags); TextUtils.writeToParcel(prefix, out, 0); + out.writeInt(prefixEnabled ? 1 : 0); out.writeInt(allowCollapse ? 1 : 0); out.writeInt(performBestGuess ? 1 : 0); out.writeInt(preventFreeFormText ? 1 : 0); out.writeInt(tokenClickStyle.ordinal()); if (SERIALIZABLE_PLACEHOLDER.equals(parcelableClassName)) { out.writeString(SERIALIZABLE_PLACEHOLDER); - out.writeSerializable((Serializable)baseObjects); + out.writeSerializable((Serializable) baseObjects); } else { out.writeString(parcelableClassName); out.writeList(baseObjects); @@ -1519,6 +1530,7 @@ public SavedState[] newArray(int size) { /** * Checks if selection can be deleted. This method is called from TokenInputConnection . + * * @param beforeLength the number of characters before the current selection end to check * @return true if there are no non-deletable pieces of the section */ From f30744c8fbe26fde42b0dfcd53de8912a6029e2a Mon Sep 17 00:00:00 2001 From: Scott Alexander-Bown Date: Wed, 24 Feb 2021 10:21:16 +0000 Subject: [PATCH 2/2] Add prefixEnabled to the SaveSave in onSaveInstanceState --- .../main/java/com/tokenautocomplete/TokenCompleteTextView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java b/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java index 18100da..d7ec6d3 100644 --- a/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java +++ b/library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java @@ -1355,6 +1355,7 @@ public Parcelable onSaveInstanceState() { savingState = false; SavedState state = new SavedState(superState); + state.prefixEnabled = prefixEnabled; if (prefixEnabled) { state.prefix = prefix; } @@ -1399,8 +1400,8 @@ public void onRestoreInstanceState(Parcelable state) { internalEditInProgress = true; prefixEnabled = ss.prefixEnabled; if (prefixEnabled) { - setText(ss.prefix); prefix = ss.prefix; + setText(prefix); } internalEditInProgress = false; updateHint();