Fix tag picker issues

* Fix NPE when there are duplicate tags
* New tags survive rotation
pull/437/head
Alex Baker 8 years ago
parent cf5676976c
commit 51152c5522

@ -7,6 +7,8 @@ package com.todoroo.astrid.tags;
import android.text.TextUtils;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.todoroo.andlib.data.Callback;
import com.todoroo.andlib.data.Property.CountProperty;
import com.todoroo.andlib.sql.Criterion;
@ -27,6 +29,9 @@ import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
/**
* Provides operations for working with tags
*
@ -82,6 +87,34 @@ public final class TagService {
return tagDataDao.getByUuid(uuid, TagData.PROPERTIES);
}
public List<TagData> getTagDataForTask(String uuid) {
List<Metadata> tags = metadataDao.toList(Query.select(TaskToTagMetadata.TAG_UUID).where(
Criterion.and(
MetadataCriteria.withKey(TaskToTagMetadata.KEY),
Metadata.DELETION_DATE.eq(0),
TaskToTagMetadata.TASK_UUID.eq(uuid))));
return newArrayList(Iterables.transform(tags, new Function<Metadata, TagData>() {
@Override
public TagData apply(Metadata metadata) {
return tagFromUUID(metadata.getValue(TaskToTagMetadata.TAG_UUID));
}
}));
}
public ArrayList<TagData> getTagDataForTask(long taskId) {
List<Metadata> tags = metadataDao.toList(Query.select(TaskToTagMetadata.TAG_UUID).where(
Criterion.and(
MetadataCriteria.withKey(TaskToTagMetadata.KEY),
Metadata.DELETION_DATE.eq(0),
MetadataCriteria.byTask(taskId))));
return newArrayList(transform(tags, new Function<Metadata, TagData>() {
@Override
public TagData apply(Metadata metadata) {
return tagFromUUID(metadata.getValue(TaskToTagMetadata.TAG_UUID));
}
}));
}
public ArrayList<String> getTagNames(long taskId) {
Query query = Query.select(TaskToTagMetadata.TAG_NAME, TaskToTagMetadata.TAG_UUID).where(
Criterion.and(

@ -35,17 +35,16 @@ import android.widget.TextView.OnEditorActionListener;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.RemoteModel;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.utility.Flags;
@ -58,7 +57,6 @@ import org.tasks.themes.ThemeColor;
import org.tasks.ui.TaskEditControlFragment;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@ -68,7 +66,6 @@ import butterknife.BindView;
import butterknife.OnClick;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.filter;
@ -86,7 +83,8 @@ public final class TagsControlSet extends TaskEditControlFragment {
private static final char NO_BREAK_SPACE = '\u00a0';
private static final char HAIR_SPACE = '\u200a';
private static final String EXTRA_TAGS = "extra_tags";
private static final String EXTRA_NEW_TAGS = "extra_new_tags";
private static final String EXTRA_SELECTED_TAGS = "extra_selected_tags";
@Inject MetadataDao metadataDao;
@Inject TagDataDao tagDataDao;
@ -97,12 +95,12 @@ public final class TagsControlSet extends TaskEditControlFragment {
@BindView(R.id.display_row_edit) TextView tagsDisplay;
private long taskId;
private ArrayList<String> allTagNames;
private LinearLayout newTags;
private ListView selectedTags;
private LinearLayout newTagLayout;
private ListView tagListView;
private View dialogView;
private AlertDialog dialog;
private ArrayList<String> tagList;
private List<TagData> allTags;
private ArrayList<TagData> selectedTags;
private final Ordering<TagData> orderByName = new Ordering<TagData>() {
@Override
@ -131,7 +129,6 @@ public final class TagsControlSet extends TaskEditControlFragment {
}
private CharSequence buildTagString() {
Set<TagData> selectedTags = getSelectedTags(false);
List<TagData> sortedTagData = orderByName.sortedCopy(selectedTags);
List<SpannableString> tagStrings = Lists.transform(sortedTagData, tagToString(Float.MAX_VALUE));
SpannableStringBuilder builder = new SpannableStringBuilder();
@ -148,22 +145,19 @@ public final class TagsControlSet extends TaskEditControlFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
ArrayList<String> newTags;
if (savedInstanceState != null) {
tagList = savedInstanceState.getStringArrayList(EXTRA_TAGS);
selectedTags = savedInstanceState.getParcelableArrayList(EXTRA_SELECTED_TAGS);
newTags = savedInstanceState.getStringArrayList(EXTRA_NEW_TAGS);
} else {
tagList = tagService.getTagNames(taskId);
selectedTags = tagService.getTagDataForTask(taskId);
newTags = newArrayList();
}
final List<TagData> allTags = tagService.getTagList();
allTagNames = newArrayList(ImmutableSet.copyOf(transform(allTags, new Function<TagData, String>() {
@Override
public String apply(TagData tagData) {
return tagData.getName();
}
})));
allTags = tagService.getTagList();
dialogView = inflater.inflate(R.layout.control_set_tag_list, null);
newTags = (LinearLayout) dialogView.findViewById(R.id.newTags);
selectedTags = (ListView) dialogView.findViewById(R.id.existingTags);
ArrayAdapter<TagData> adapter = new ArrayAdapter<TagData>(getActivity(), R.layout.simple_list_item_multiple_choice_themed, allTags) {
newTagLayout = (LinearLayout) dialogView.findViewById(R.id.newTags);
tagListView = (ListView) dialogView.findViewById(R.id.existingTags);
tagListView.setAdapter(new ArrayAdapter<TagData>(getActivity(), R.layout.simple_list_item_multiple_choice_themed, allTags) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
CheckedTextView view = (CheckedTextView) super.getView(position, convertView, parent);
@ -176,10 +170,12 @@ public final class TagsControlSet extends TaskEditControlFragment {
view.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
return view;
}
};
selectedTags.setAdapter(adapter);
});
for (String newTag : newTags) {
addTag(newTag);
}
addTag("");
for (String tag : tagList) {
for (TagData tag : selectedTags) {
setTagSelected(tag);
}
refreshDisplayView();
@ -190,7 +186,8 @@ public final class TagsControlSet extends TaskEditControlFragment {
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putStringArrayList(EXTRA_TAGS, tagList);
outState.putParcelableArrayList(EXTRA_SELECTED_TAGS, selectedTags);
outState.putStringArrayList(EXTRA_NEW_TAGS, getNewTags());
}
@Override
@ -225,51 +222,68 @@ public final class TagsControlSet extends TaskEditControlFragment {
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
tagList = getTagList();
refreshDisplayView();
}
})
.create();
}
private void setTagSelected(String tag) {
int index = allTagNames.indexOf(tag);
private void setTagSelected(TagData tag) {
int index = allTags.indexOf(tag);
if (index >= 0) {
selectedTags.setItemChecked(index, true);
} else {
allTagNames.add(tag);
((ArrayAdapter<String>)selectedTags.getAdapter()).notifyDataSetChanged();
tagListView.setItemChecked(index, true);
}
}
private ArrayList<String> getTagList() {
Set<String> tags = new LinkedHashSet<>();
for(int i = 0; i < selectedTags.getAdapter().getCount(); i++) {
if (selectedTags.isItemChecked(i)) {
tags.add(allTagNames.get(i));
private boolean isSelected(List<TagData> selected, final String name) {
return Iterables.any(selected, new Predicate<TagData>() {
@Override
public boolean apply(TagData input) {
return name.equalsIgnoreCase(input.getName());
}
});
}
private ArrayList<TagData> getSelectedTags() {
ArrayList<TagData> tags = new ArrayList<>();
for(int i = 0; i < tagListView.getAdapter().getCount(); i++) {
if (tagListView.isItemChecked(i)) {
tags.add(allTags.get(i));
}
}
for (int i = newTags.getChildCount() - 1 ; i >= 0 ; i--) {
TextView tagName = (TextView) newTags.getChildAt(i).findViewById(R.id.text1);
for (int i = newTagLayout.getChildCount() - 1; i >= 0 ; i--) {
TextView tagName = (TextView) newTagLayout.getChildAt(i).findViewById(R.id.text1);
final String text = tagName.getText().toString();
if (Strings.isNullOrEmpty(text)) {
continue;
}
TagData tagByName = tagDataDao.getTagByName(text, TagData.PROPERTIES);
if (tagByName != null) {
setTagSelected(tagByName.getName());
tags.add(tagByName.getName());
newTags.removeViewAt(i);
} else if (!Iterables.any(tags, new Predicate<String>() {
@Override
public boolean apply(String input) {
return text.equalsIgnoreCase(input);
if (!isSelected(tags, text)) {
setTagSelected(tagByName);
tags.add(tagByName);
}
})) {
tags.add(text);
newTagLayout.removeViewAt(i);
} else if (!isSelected(tags, text)) {
TagData newTag = new TagData();
newTag.setName(text);
tags.add(newTag);
}
}
return tags;
}
private ArrayList<String> getNewTags() {
ArrayList<String> tags = new ArrayList<>();
for (int i = newTagLayout.getChildCount() - 1 ; i >= 0 ; i--) {
TextView textView = (TextView) newTagLayout.getChildAt(i).findViewById(R.id.text1);
String tagName = textView.getText().toString();
if (Strings.isNullOrEmpty(tagName)) {
continue;
}
tags.add(tagName);
}
return newArrayList(tags);
return tags;
}
/** Adds a tag to the tag field */
@ -278,8 +292,8 @@ public final class TagsControlSet extends TaskEditControlFragment {
// check if already exists
TextView lastText;
for(int i = 0; i < newTags.getChildCount(); i++) {
View view = newTags.getChildAt(i);
for(int i = 0; i < newTagLayout.getChildCount(); i++) {
View view = newTagLayout.getChildAt(i);
lastText = (TextView) view.findViewById(R.id.text1);
if(lastText.getText().equals(tagName)) {
return;
@ -288,7 +302,7 @@ public final class TagsControlSet extends TaskEditControlFragment {
final View tagItem;
tagItem = inflater.inflate(R.layout.tag_edit_row, null);
newTags.addView(tagItem);
newTagLayout.addView(tagItem);
if(tagName == null) {
tagName = ""; //$NON-NLS-1$
}
@ -310,7 +324,7 @@ public final class TagsControlSet extends TaskEditControlFragment {
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if(count > 0 && newTags.getChildAt(newTags.getChildCount()-1) ==
if(count > 0 && newTagLayout.getChildAt(newTagLayout.getChildCount()-1) ==
tagItem) {
addTag(""); //$NON-NLS-1$
}
@ -338,8 +352,8 @@ public final class TagsControlSet extends TaskEditControlFragment {
return;
}
if(newTags.getChildCount() > 1) {
newTags.removeView(tagItem);
if(newTagLayout.getChildCount() > 1) {
newTagLayout.removeView(tagItem);
} else {
textView.setText(""); //$NON-NLS-1$
}
@ -351,10 +365,10 @@ public final class TagsControlSet extends TaskEditControlFragment {
* Get tags container last text view. might be null
*/
private TextView getLastTextView() {
if(newTags.getChildCount() == 0) {
if(newTagLayout.getChildCount() == 0) {
return null;
}
View lastItem = newTags.getChildAt(newTags.getChildCount()-1);
View lastItem = newTagLayout.getChildAt(newTagLayout.getChildCount()-1);
return (TextView) lastItem.findViewById(R.id.text1);
}
@ -370,10 +384,13 @@ public final class TagsControlSet extends TaskEditControlFragment {
@Override
public boolean hasChanges(Task original) {
return !getExistingTags(original.getUUID()).equals(getSelectedTags(false));
Set<TagData> existingSet = newHashSet(tagService.getTagDataForTask(original.getUUID()));
Set<TagData> selectedSet = newHashSet(selectedTags);
return !existingSet.equals(selectedSet);
}
protected void refreshDisplayView() {
selectedTags = getSelectedTags();
CharSequence tagString = buildTagString();
if (TextUtils.isEmpty(tagString)) {
tagsDisplay.setText(R.string.tag_FEx_untagged);
@ -382,46 +399,19 @@ public final class TagsControlSet extends TaskEditControlFragment {
}
}
private Set<TagData> getSelectedTags(final boolean createMissingTags) {
return newHashSet(transform(tagList, new Function<String, TagData>() {
@Override
public TagData apply(String tagName) {
TagData tagData = tagDataDao.getTagByName(tagName, TagData.PROPERTIES);
if (tagData == null) {
// create missing tags
tagData = new TagData();
tagData.setName(tagName);
if (createMissingTags) {
tagDataDao.persist(tagData);
}
}
return tagData;
}
}));
}
private Set<TagData> getExistingTags(String taskUuid) {
Query query = Query.select(Metadata.PROPERTIES).where(
Criterion.and(
TaskToTagMetadata.TASK_UUID.eq(taskUuid),
Metadata.DELETION_DATE.eq(0))
);
return newHashSet(transform(metadataDao.toList(query), new Function<Metadata, TagData>() {
@Override
public TagData apply(Metadata metadata) {
return tagDataDao.getByUuid(metadata.getValue(TaskToTagMetadata.TAG_UUID));
}
}));
}
/**
* Save the given array of tags into the database
*/
private boolean synchronizeTags(long taskId, String taskUuid) {
Set<TagData> existingTags = getExistingTags(taskUuid);
Set<TagData> selectedTags = getSelectedTags(true);
Sets.SetView<TagData> added = difference(selectedTags, existingTags);
Sets.SetView<TagData> removed = difference(existingTags, selectedTags);
for (TagData tagData : selectedTags) {
if (RemoteModel.NO_UUID.equals(tagData.getUuid())) {
tagDataDao.persist(tagData);
}
}
Set<TagData> existingHash = newHashSet(tagService.getTagDataForTask(taskUuid));
Set<TagData> selectedHash = newHashSet(selectedTags);
Sets.SetView<TagData> added = difference(selectedHash, existingHash);
Sets.SetView<TagData> removed = difference(existingHash, selectedHash);
deleteLinks(taskId, taskUuid, filter(removed, notNull()));
for (TagData tagData : added) {
Metadata newLink = TaskToTagMetadata.newTagMetadata(taskId, taskUuid, tagData.getName(), tagData.getUuid());

Loading…
Cancel
Save