Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefixEnabled property to allow the prefix to be disabled #415

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 57 additions & 44 deletions library/src/main/java/com/tokenautocomplete/TokenCompleteTextView.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,22 @@
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import androidx.annotation.NonNull;
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;
import android.view.KeyEvent;
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -250,6 +235,7 @@ public void setTokenListener(TokenListener<T> 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.
*/
Expand All @@ -259,13 +245,24 @@ 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.
*/
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.
Expand Down Expand Up @@ -304,7 +301,7 @@ public void setPrefix(CharSequence p) {
* 'olive', 'purple', 'silver', 'teal'.</p>
*
* @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) {
Expand All @@ -319,12 +316,12 @@ public void setPrefix(CharSequence prefix, int color) {
* @return List of tokens
*/
public List<T> getObjects() {
ArrayList<T>objects = new ArrayList<>();
ArrayList<T> 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;
Expand Down Expand Up @@ -403,8 +400,9 @@ public void setTokenLimit(int tokenLimit) {

/**
* Correctly build accessibility string for token contents
*
* <p>
* This seems to be a hidden API, but there doesn't seem to be another reasonable way
*
* @return custom string for accessibility
*/
@SuppressWarnings("unused")
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -513,7 +511,7 @@ private Range getCurrentCandidateTokenRange() {

List<Range> tokenRanges = tokenizer.findTokenRanges(editable, candidateStringStart, candidateStringEnd);

for (Range range: tokenRanges) {
for (Range range : tokenRanges) {
if (range.start <= cursorEndPosition && cursorEndPosition <= range.end) {
return range;
}
Expand All @@ -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()}
*/
Expand All @@ -548,7 +547,7 @@ protected float maxTextWidth() {

@Override
public int getMaxViewSpanWidth() {
return (int)maxTextWidth();
return (int) maxTextWidth();
}

public void redrawTokens() {
Expand Down Expand Up @@ -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
ArrayList<Editable>texts = new ArrayList<>();
ArrayList<Editable> texts = new ArrayList<>();
//If there is hidden content, it's important that we update it first
if (hiddenContent != null) {
texts.add(hiddenContent);
Expand All @@ -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)) {
Expand Down Expand Up @@ -989,7 +988,7 @@ public void clearAsync() {
post(new Runnable() {
@Override
public void run() {
for (T object: getObjects()) {
for (T object : getObjects()) {
removeObjectSync(object);
}
}
Expand All @@ -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();

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1216,7 +1217,9 @@ public void onClick() {

public interface TokenListener<T> {
void onTokenAdded(T token);

void onTokenRemoved(T token);

void onTokenIgnored(T token);
}

Expand Down Expand Up @@ -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
Expand All @@ -1352,7 +1355,10 @@ public Parcelable onSaveInstanceState() {
savingState = false;
SavedState state = new SavedState(superState);

state.prefix = prefix;
state.prefixEnabled = prefixEnabled;
if (prefixEnabled) {
state.prefix = prefix;
}
state.allowCollapse = allowCollapse;
state.performBestGuess = performBestGuess;
state.preventFreeFormText = preventFreeFormText;
Expand Down Expand Up @@ -1392,8 +1398,11 @@ public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(ss.getSuperState());

internalEditInProgress = true;
setText(ss.prefix);
prefix = ss.prefix;
prefixEnabled = ss.prefixEnabled;
if (prefixEnabled) {
prefix = ss.prefix;
setText(prefix);
}
internalEditInProgress = false;
updateHint();
allowCollapse = ss.allowCollapse;
Expand All @@ -1407,11 +1416,11 @@ public void onRestoreInstanceState(Parcelable state) {
if (SavedState.SERIALIZABLE_PLACEHOLDER.equals(ss.parcelableClassName)) {
objects = convertSerializableObjectsToTypedObjects(ss.baseObjects);
} else {
objects = (List<T>)ss.baseObjects;
objects = (List<T>) 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);
}

Expand All @@ -1434,6 +1443,7 @@ private static class SavedState extends BaseSavedState {
static final String SERIALIZABLE_PLACEHOLDER = "Serializable";

CharSequence prefix;
boolean prefixEnabled;
boolean allowCollapse;
boolean performBestGuess;
boolean preventFreeFormText;
Expand All @@ -1447,13 +1457,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();
Expand Down Expand Up @@ -1481,13 +1492,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);
Expand Down Expand Up @@ -1519,6 +1531,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
*/
Expand Down