diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a26acecc..19fe11618 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -546,8 +546,11 @@ android:launchMode="singleTask" android:taskAffinity="" android:theme="@style/TranslucentDialog"/> + + + getFilters() { List filters = newArrayList(transform(filterDao.getFilters(), this::load)); - Collections.sort(filters, new AlphanumComparator()); + Collections.sort(filters, new AlphanumComparator<>(AlphanumComparator.FILTER)); return filters; } diff --git a/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java b/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java index 074ed8253..26835379c 100644 --- a/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java +++ b/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java @@ -56,7 +56,7 @@ public class GtasksFilterExposer { } } for (Map.Entry> entry : filters.entrySet()) { - Collections.sort(entry.getValue(), new AlphanumComparator()); + Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER)); } return filters; } diff --git a/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java b/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java index 1bc7b04ba..364a22185 100644 --- a/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java +++ b/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java @@ -46,7 +46,7 @@ public class TagFilterExposer { public List getFilters() { List tags = newArrayList(transform(tagDataDao.getTagFilters(now()), TagFilters::toTagFilter)); - Collections.sort(tags, new AlphanumComparator()); + Collections.sort(tags, new AlphanumComparator<>(AlphanumComparator.FILTER)); return tags; } diff --git a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java index e1beb3ed3..a54351419 100644 --- a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java +++ b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java @@ -6,54 +6,33 @@ package com.todoroo.astrid.tags; +import static android.app.Activity.RESULT_OK; import static com.google.common.collect.Lists.transform; import static com.google.common.collect.Sets.newHashSet; -import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybeanMR1; -import android.annotation.SuppressLint; -import android.graphics.drawable.Drawable; +import android.content.Intent; import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.CheckedTextView; -import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.drawable.DrawableCompat; import butterknife.BindView; import butterknife.OnClick; import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; -import com.google.common.base.Strings; -import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.utility.Flags; import java.util.ArrayList; -import java.util.List; import java.util.Set; import javax.inject.Inject; import org.tasks.R; -import org.tasks.billing.Inventory; import org.tasks.data.TagDao; import org.tasks.data.TagData; import org.tasks.data.TagDataDao; -import org.tasks.dialogs.DialogBuilder; import org.tasks.injection.FragmentComponent; -import org.tasks.themes.CustomIcons; -import org.tasks.themes.ThemeCache; -import org.tasks.themes.ThemeColor; +import org.tasks.tags.TagPickerActivity; import org.tasks.ui.ChipProvider; import org.tasks.ui.TaskEditControlFragment; @@ -66,9 +45,9 @@ public final class TagsControlSet extends TaskEditControlFragment { public static final int TAG = R.string.TEA_ctrl_lists_pref; - private static final String EXTRA_NEW_TAGS = "extra_new_tags"; private static final String EXTRA_ORIGINAL_TAGS = "extra_original_tags"; private static final String EXTRA_SELECTED_TAGS = "extra_selected_tags"; + private static final int REQUEST_TAG_PICKER_ACTIVITY = 10582; private final Ordering orderByName = new Ordering() { @Override @@ -78,20 +57,13 @@ public final class TagsControlSet extends TaskEditControlFragment { }; @Inject TagDao tagDao; @Inject TagDataDao tagDataDao; - @Inject DialogBuilder dialogBuilder; - @Inject ThemeCache themeCache; @Inject ChipProvider chipProvider; - @Inject Inventory inventory; @BindView(R.id.no_tags) TextView tagsDisplay; @BindView(R.id.chip_group) ChipGroup chipGroup; - private LinearLayout newTagLayout; - private ListView tagListView; - private View dialogView; - private AlertDialog dialog; - private List allTags; + private ArrayList originalTags; private ArrayList selectedTags; @@ -100,11 +72,9 @@ public final class TagsControlSet extends TaskEditControlFragment { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); - ArrayList newTags; if (savedInstanceState != null) { selectedTags = savedInstanceState.getParcelableArrayList(EXTRA_SELECTED_TAGS); originalTags = savedInstanceState.getParcelableArrayList(EXTRA_ORIGINAL_TAGS); - newTags = savedInstanceState.getStringArrayList(EXTRA_NEW_TAGS); } else { originalTags = new ArrayList<>( @@ -112,48 +82,6 @@ public final class TagsControlSet extends TaskEditControlFragment { ? transform(task.getTags(), tagDataDao::getTagByName) : tagDataDao.getTagDataForTask(task.getId())); selectedTags = new ArrayList<>(originalTags); - newTags = new ArrayList<>(); - } - allTags = tagDataDao.tagDataOrderedByName(); - dialogView = inflater.inflate(R.layout.control_set_tag_list, null); - newTagLayout = dialogView.findViewById(R.id.newTags); - tagListView = dialogView.findViewById(R.id.existingTags); - tagListView.setAdapter( - new ArrayAdapter( - getActivity(), R.layout.simple_list_item_multiple_choice_themed, allTags) { - @NonNull - @SuppressLint("NewApi") - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - CheckedTextView view = (CheckedTextView) super.getView(position, convertView, parent); - TagData tagData = allTags.get(position); - ThemeColor themeColor = - themeCache.getThemeColor(tagData.getColor() >= 0 ? tagData.getColor() : 19); - view.setText(tagData.getName()); - Integer icon = null; - if (tagData.getIcon() < 1000 || inventory.hasPro()) { - icon = CustomIcons.getIconResId(tagData.getIcon()); - } - if (icon == null) { - icon = R.drawable.ic_outline_label_24px; - } - Drawable original = ContextCompat.getDrawable(getContext(), icon); - Drawable wrapped = DrawableCompat.wrap(original.mutate()); - DrawableCompat.setTint(wrapped, themeColor.getPrimaryColor()); - if (atLeastJellybeanMR1()) { - view.setCompoundDrawablesRelativeWithIntrinsicBounds(wrapped, null, null, null); - } else { - view.setCompoundDrawablesWithIntrinsicBounds(wrapped, null, null, null); - } - return view; - } - }); - for (String newTag : newTags) { - addTag(newTag); - } - addTag(""); - for (TagData tag : selectedTags) { - setTagSelected(tag, true); } refreshDisplayView(); return view; @@ -165,7 +93,6 @@ public final class TagsControlSet extends TaskEditControlFragment { outState.putParcelableArrayList(EXTRA_SELECTED_TAGS, selectedTags); outState.putParcelableArrayList(EXTRA_ORIGINAL_TAGS, originalTags); - outState.putStringArrayList(EXTRA_NEW_TAGS, getNewTags()); } @Override @@ -175,159 +102,16 @@ public final class TagsControlSet extends TaskEditControlFragment { @Override public void apply(Task task) { - if (synchronizeTags(task)) { + if (tagDao.applyTags(task, tagDataDao, selectedTags)) { task.setModificationDate(DateUtilities.now()); } } @OnClick(R.id.tag_row) void onClickRow() { - if (dialog == null) { - dialog = buildDialog(); - } - dialog.show(); - } - - private AlertDialog buildDialog() { - return dialogBuilder - .newDialog() - .setView(dialogView) - .setOnDismissListener(dialogInterface -> refreshDisplayView()) - .create(); - } - - private void setTagSelected(TagData tag, boolean selected) { - int index = allTags.indexOf(tag); - if (index >= 0) { - tagListView.setItemChecked(index, selected); - } - } - - private boolean notSelected(List selected, final String name) { - return !Iterables.any(selected, input -> name.equalsIgnoreCase(input.getName())); - } - - private ArrayList getSelectedTags() { - ArrayList tags = new ArrayList<>(); - for (int i = 0; i < tagListView.getAdapter().getCount(); i++) { - if (tagListView.isItemChecked(i)) { - tags.add(allTags.get(i)); - } - } - for (int i = newTagLayout.getChildCount() - 1; i >= 0; i--) { - TextView tagName = newTagLayout.getChildAt(i).findViewById(R.id.text1); - final String text = tagName.getText().toString(); - if (Strings.isNullOrEmpty(text)) { - continue; - } - TagData tagByName = tagDataDao.getTagByName(text); - if (tagByName != null) { - if (notSelected(tags, text)) { - setTagSelected(tagByName, true); - tags.add(tagByName); - } - newTagLayout.removeViewAt(i); - } else if (notSelected(tags, text)) { - TagData newTag = new TagData(); - newTag.setName(text); - tags.add(newTag); - } - } - return tags; - } - - private ArrayList getNewTags() { - ArrayList tags = new ArrayList<>(); - for (int i = newTagLayout.getChildCount() - 1; i >= 0; i--) { - TextView textView = newTagLayout.getChildAt(i).findViewById(R.id.text1); - String tagName = textView.getText().toString(); - if (Strings.isNullOrEmpty(tagName)) { - continue; - } - tags.add(tagName); - } - return tags; - } - - /** Adds a tag to the tag field */ - private void addTag(String tagName) { - LayoutInflater inflater = getActivity().getLayoutInflater(); - - // check if already exists - TextView lastText; - for (int i = 0; i < newTagLayout.getChildCount(); i++) { - View view = newTagLayout.getChildAt(i); - lastText = view.findViewById(R.id.text1); - if (lastText.getText().equals(tagName)) { - return; - } - } - - final View tagItem; - tagItem = inflater.inflate(R.layout.tag_edit_row, null); - newTagLayout.addView(tagItem); - if (tagName == null) { - tagName = ""; // $NON-NLS-1$ - } - - final AutoCompleteTextView textView = tagItem.findViewById(R.id.text1); - textView.setText(tagName); - - textView.addTextChangedListener( - new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - // - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (count > 0 && newTagLayout.getChildAt(newTagLayout.getChildCount() - 1) == tagItem) { - addTag(""); // $NON-NLS-1$ - } - } - }); - - textView.setOnEditorActionListener( - (arg0, actionId, arg2) -> { - if (actionId != EditorInfo.IME_NULL) { - return false; - } - if (getLastTextView().getText().length() != 0) { - addTag(""); // $NON-NLS-1$ - } - return true; - }); - - tagItem - .findViewById(R.id.button1) - .setOnClickListener( - v -> { - TextView lastView = getLastTextView(); - if (lastView == textView && textView.getText().length() == 0) { - return; - } - - if (newTagLayout.getChildCount() > 1) { - newTagLayout.removeView(tagItem); - } else { - textView.setText(""); // $NON-NLS-1$ - } - }); - } - - /** Get tags container last text view. might be null */ - private TextView getLastTextView() { - if (newTagLayout.getChildCount() == 0) { - return null; - } - View lastItem = newTagLayout.getChildAt(newTagLayout.getChildCount() - 1); - return (TextView) lastItem.findViewById(R.id.text1); + Intent intent = new Intent(getContext(), TagPickerActivity.class); + intent.putParcelableArrayListExtra(TagPickerActivity.EXTRA_TAGS, selectedTags); + startActivityForResult(intent, REQUEST_TAG_PICKER_ACTIVITY); } @Override @@ -348,7 +132,6 @@ public final class TagsControlSet extends TaskEditControlFragment { } private void refreshDisplayView() { - selectedTags = getSelectedTags(); if (selectedTags.isEmpty()) { chipGroup.setVisibility(View.GONE); tagsDisplay.setVisibility(View.VISIBLE); @@ -362,7 +145,7 @@ public final class TagsControlSet extends TaskEditControlFragment { chip.setOnClickListener(view -> onClickRow()); chip.setOnCloseIconClickListener( view -> { - setTagSelected(tagData, false); + selectedTags.remove(tagData); refreshDisplayView(); }); chipGroup.addView(chip); @@ -370,13 +153,16 @@ public final class TagsControlSet extends TaskEditControlFragment { } } - private boolean synchronizeTags(Task task) { - for (TagData tagData : selectedTags) { - if (Task.NO_UUID.equals(tagData.getRemoteId())) { - tagDataDao.createNew(tagData); + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (requestCode == REQUEST_TAG_PICKER_ACTIVITY) { + if (resultCode == RESULT_OK && data != null) { + selectedTags = data.getParcelableArrayListExtra(TagPickerActivity.EXTRA_TAGS); + refreshDisplayView(); } + } else { + super.onActivityResult(requestCode, resultCode, data); } - return tagDao.applyTags(task, tagDataDao, selectedTags); } @Override diff --git a/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java b/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java index cd1b1e0d2..ede93c927 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java +++ b/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java @@ -40,7 +40,7 @@ public class CaldavFilterExposer { } } for (Map.Entry> entry : filters.entrySet()) { - Collections.sort(entry.getValue(), new AlphanumComparator()); + Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER)); } return filters; } diff --git a/app/src/main/java/org/tasks/data/TagDataDao.java b/app/src/main/java/org/tasks/data/TagDataDao.java index 591ef629e..142738278 100644 --- a/app/src/main/java/org/tasks/data/TagDataDao.java +++ b/app/src/main/java/org/tasks/data/TagDataDao.java @@ -9,7 +9,10 @@ import androidx.room.Transaction; import androidx.room.Update; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.helper.UUIDHelper; +import io.reactivex.Single; +import java.util.Collections; import java.util.List; +import org.tasks.filters.AlphanumComparator; import org.tasks.filters.TagFilters; @Dao @@ -30,6 +33,15 @@ public abstract class TagDataDao { return tagData != null ? tagData.getName() : tag; } + public List searchTags(String query) { + List results = searchTagsInternal("%" + query + "%"); + Collections.sort(results, new AlphanumComparator<>(TagData::getName)); + return results; + } + + @Query("SELECT * FROM tagdata WHERE name LIKE :query AND name NOT NULL AND name != ''") + protected abstract List searchTagsInternal(String query); + @Query("SELECT * FROM tagdata") public abstract List getAll(); diff --git a/app/src/main/java/org/tasks/filters/AlphanumComparator.java b/app/src/main/java/org/tasks/filters/AlphanumComparator.java index d84f41150..a6c333f36 100644 --- a/app/src/main/java/org/tasks/filters/AlphanumComparator.java +++ b/app/src/main/java/org/tasks/filters/AlphanumComparator.java @@ -30,6 +30,7 @@ package org.tasks.filters; * USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import androidx.arch.core.util.Function; import com.todoroo.astrid.api.Filter; import java.util.Comparator; @@ -40,11 +41,20 @@ import java.util.Comparator; *

To use this class: Use the static "sort" method from the java.util.Collections class: * Collections.sort(your list, new AlphanumComparator()); */ -public class AlphanumComparator implements Comparator { +public class AlphanumComparator implements Comparator { + + public static Function FILTER = f -> f.listingTitle; + + private final Function getTitle; + private boolean isDigit(char ch) { return ((ch >= 48) && (ch <= 57)); } + public AlphanumComparator(Function getTitle) { + this.getTitle = getTitle; + } + /** Length of string is passed in for improved efficiency (only need to calculate it once) * */ private String getChunk(String s, int slength, int marker) { StringBuilder chunk = new StringBuilder(); @@ -69,9 +79,9 @@ public class AlphanumComparator implements Comparator { return chunk.toString(); } - public int compare(Filter f1, Filter f2) { - String s1 = f1.listingTitle; - String s2 = f2.listingTitle; + public int compare(T t1, T t2) { + String s1 = getTitle.apply(t1); + String s2 = getTitle.apply(t2); if ((s1 == null) || (s2 == null)) { return 0; } diff --git a/app/src/main/java/org/tasks/filters/TagFilters.java b/app/src/main/java/org/tasks/filters/TagFilters.java index e17b11754..66c9f65d9 100644 --- a/app/src/main/java/org/tasks/filters/TagFilters.java +++ b/app/src/main/java/org/tasks/filters/TagFilters.java @@ -14,6 +14,10 @@ public class TagFilters { return filter; } + public String getName() { + return tagData.getName(); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/app/src/main/java/org/tasks/injection/ActivityComponent.java b/app/src/main/java/org/tasks/injection/ActivityComponent.java index 158328ff7..bb7bf52e5 100644 --- a/app/src/main/java/org/tasks/injection/ActivityComponent.java +++ b/app/src/main/java/org/tasks/injection/ActivityComponent.java @@ -19,6 +19,7 @@ import org.tasks.activities.DatePickerActivity; import org.tasks.activities.FilterSelectionActivity; import org.tasks.activities.FilterSettingsActivity; import org.tasks.activities.GoogleTaskListSettingsActivity; +import org.tasks.tags.TagPickerActivity; import org.tasks.activities.TagSettingsActivity; import org.tasks.activities.TimePickerActivity; import org.tasks.billing.PurchaseActivity; @@ -39,6 +40,7 @@ import org.tasks.preferences.MiscellaneousPreferences; import org.tasks.reminders.NotificationActivity; import org.tasks.reminders.SnoozeActivity; import org.tasks.sync.SynchronizationPreferences; +import org.tasks.tags.TagPickerViewModel; import org.tasks.themes.Theme; import org.tasks.ui.TaskListViewModel; import org.tasks.voice.VoiceCommandActivity; @@ -143,4 +145,8 @@ public interface ActivityComponent { void inject(LocationPickerActivity locationPickerActivity); void inject(AttributionActivity attributionActivity); + + void inject(TagPickerActivity tagPickerActivity); + + void inject(TagPickerViewModel viewModel); } diff --git a/app/src/main/java/org/tasks/tags/TagDiffCallback.java b/app/src/main/java/org/tasks/tags/TagDiffCallback.java new file mode 100644 index 000000000..11085c588 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagDiffCallback.java @@ -0,0 +1,19 @@ +package org.tasks.tags; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import com.google.common.base.Objects; +import org.tasks.data.TagData; + +public class TagDiffCallback extends DiffUtil.ItemCallback { + + @Override + public boolean areItemsTheSame(@NonNull TagData oldItem, @NonNull TagData newItem) { + return Objects.equal(oldItem.getId(), newItem.getId()); + } + + @Override + public boolean areContentsTheSame(@NonNull TagData oldItem, @NonNull TagData newItem) { + return oldItem.equals(newItem); + } +} diff --git a/app/src/main/java/org/tasks/tags/TagPickerActivity.java b/app/src/main/java/org/tasks/tags/TagPickerActivity.java new file mode 100644 index 000000000..ae7781a95 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagPickerActivity.java @@ -0,0 +1,115 @@ +package org.tasks.tags; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.EditText; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnTextChanged; +import com.google.common.base.Strings; +import java.util.ArrayList; +import javax.inject.Inject; +import org.tasks.R; +import org.tasks.billing.Inventory; +import org.tasks.data.TagData; +import org.tasks.injection.ActivityComponent; +import org.tasks.injection.ThemedInjectingAppCompatActivity; +import org.tasks.themes.Theme; +import org.tasks.themes.ThemeCache; +import org.tasks.themes.ThemeColor; +import org.tasks.ui.MenuColorizer; + +public class TagPickerActivity extends ThemedInjectingAppCompatActivity { + + public static final String EXTRA_TAGS = "extra_tags"; + + @BindView(R.id.toolbar) + Toolbar toolbar; + + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + + @BindView(R.id.search_input) + EditText editText; + + @Inject Theme theme; + @Inject ThemeCache themeCache; + @Inject Inventory inventory; + + private TagPickerViewModel viewModel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + ArrayList tags = getIntent().getParcelableArrayListExtra(EXTRA_TAGS); + viewModel.setTags(tags); + } + + setContentView(R.layout.activity_tag_picker); + + ButterKnife.bind(this); + + toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px); + toolbar.setNavigationOnClickListener(v -> onBackPressed()); + + MenuColorizer.colorToolbar(this, toolbar); + ThemeColor themeColor = theme.getThemeColor(); + themeColor.applyToStatusBarIcons(this); + themeColor.applyToNavigationBar(this); + + TagRecyclerAdapter recyclerAdapter = + new TagRecyclerAdapter(this, viewModel, themeCache, inventory, this::onToggle); + + recyclerView.setAdapter(recyclerAdapter); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + viewModel.observe(this, recyclerAdapter::submitList); + editText.setText(viewModel.getText()); + } + + private Void onToggle(TagData tagData, Boolean checked) { + boolean newTag = tagData.getId() == null; + + viewModel.toggle(tagData, checked); + + if (newTag) { + clear(); + } + + return null; + } + + @OnTextChanged(R.id.search_input) + void onSearch(CharSequence text) { + viewModel.search(text.toString()); + } + + @Override + public void onBackPressed() { + if (Strings.isNullOrEmpty(viewModel.getText())) { + Intent data = new Intent(); + data.putParcelableArrayListExtra(EXTRA_TAGS, viewModel.getTags()); + setResult(RESULT_OK, data); + finish(); + } else { + clear(); + } + } + + private void clear() { + editText.setText(""); + } + + @Override + public void inject(ActivityComponent component) { + component.inject(this); + viewModel = ViewModelProviders.of(this).get(TagPickerViewModel.class); + component.inject(viewModel); + } +} diff --git a/app/src/main/java/org/tasks/tags/TagPickerViewHolder.java b/app/src/main/java/org/tasks/tags/TagPickerViewHolder.java new file mode 100644 index 000000000..3c448f9d3 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagPickerViewHolder.java @@ -0,0 +1,83 @@ +package org.tasks.tags; + +import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybeanMR1; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnCheckedChanged; +import butterknife.OnClick; +import kotlin.jvm.functions.Function2; +import org.tasks.R; +import org.tasks.data.TagData; + +public class TagPickerViewHolder extends RecyclerView.ViewHolder { + + private final Context context; + private final Function2 callback; + + @BindView(R.id.text) + TextView text; + + @BindView(R.id.checkbox) + CheckBox checkBox; + + private TagData tagData; + + TagPickerViewHolder( + Context context, @NonNull View view, Function2 callback) { + super(view); + this.callback = callback; + + ButterKnife.bind(this, view); + + this.context = context; + } + + @OnClick(R.id.tag_row) + void onClickRow() { + if (tagData.getId() == null) { + callback.invoke(tagData, true); + } else { + checkBox.toggle(); + } + } + + @OnCheckedChanged(R.id.checkbox) + void onCheckedChanged() { + callback.invoke(tagData, checkBox.isChecked()); + } + + public void bind(@NonNull TagData tagData, int color, @Nullable Integer icon, boolean checked) { + this.tagData = tagData; + if (tagData.getId() == null) { + text.setText(context.getString(R.string.create_new_tag, tagData.getName())); + icon = R.drawable.ic_outline_add_24px; + checkBox.setVisibility(View.GONE); + } else { + text.setText(tagData.getName()); + checkBox.setChecked(checked); + checkBox.setVisibility(View.VISIBLE); + if (icon == null) { + icon = R.drawable.ic_outline_label_24px; + } + } + Drawable original = ContextCompat.getDrawable(context, icon); + Drawable wrapped = DrawableCompat.wrap(original.mutate()); + DrawableCompat.setTint(wrapped, color); + if (atLeastJellybeanMR1()) { + text.setCompoundDrawablesRelativeWithIntrinsicBounds(wrapped, null, null, null); + } else { + text.setCompoundDrawablesWithIntrinsicBounds(wrapped, null, null, null); + } + } +} diff --git a/app/src/main/java/org/tasks/tags/TagPickerViewModel.java b/app/src/main/java/org/tasks/tags/TagPickerViewModel.java new file mode 100644 index 000000000..8ab182d89 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagPickerViewModel.java @@ -0,0 +1,93 @@ +package org.tasks.tags; + +import static com.google.common.collect.Iterables.any; +import static com.google.common.collect.Lists.newArrayList; + +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModel; +import com.google.common.base.Strings; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.inject.Inject; +import org.tasks.data.TagData; +import org.tasks.data.TagDataDao; + +public class TagPickerViewModel extends ViewModel { + + private final MutableLiveData> tags = new MutableLiveData<>(); + private final CompositeDisposable disposables = new CompositeDisposable(); + + @Inject TagDataDao tagDataDao; + + private String text; + private Set selected = new HashSet<>(); + + public void observe(LifecycleOwner owner, Observer> observer) { + tags.observe(owner, observer); + } + + public void setTags(List tags) { + selected.addAll(tags); + } + + public ArrayList getTags() { + return newArrayList(selected); + } + + public String getText() { + return text; + } + + public void clear() { + search(""); + } + + public void search(String text) { + if (!text.equalsIgnoreCase(this.text)) { + disposables.add( + Single.fromCallable(() -> tagDataDao.searchTags(text)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::onUpdate)); + } + this.text = text; + } + + private void onUpdate(List results) { + if (!Strings.isNullOrEmpty(text) && !any(results, t -> text.equalsIgnoreCase(t.getName()))) { + results.add(0, new TagData(text)); + } + tags.setValue(results); + } + + @Override + protected void onCleared() { + super.onCleared(); + disposables.dispose(); + } + + public boolean isChecked(TagData tagData) { + return selected.contains(tagData); + } + + void toggle(TagData tagData, boolean checked) { + if (tagData.getId() == null) { + tagData = new TagData(tagData.getName()); + tagDataDao.createNew(tagData); + } + + if (checked) { + selected.add(tagData); + } else { + selected.remove(tagData); + } + } +} diff --git a/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.java b/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.java new file mode 100644 index 000000000..73a0ec581 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.java @@ -0,0 +1,76 @@ +package org.tasks.tags; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.RecyclerView; +import java.util.List; +import kotlin.jvm.functions.Function2; +import org.tasks.R; +import org.tasks.billing.Inventory; +import org.tasks.data.TagData; +import org.tasks.themes.CustomIcons; +import org.tasks.themes.ThemeCache; + +public class TagRecyclerAdapter extends RecyclerView.Adapter { + + private final AsyncListDiffer differ; + private final Context context; + private final TagPickerViewModel viewModel; + private final ThemeCache themeCache; + private final Inventory inventory; + private final Function2 callback; + + TagRecyclerAdapter( + Context context, + TagPickerViewModel viewModel, + ThemeCache themeCache, + Inventory inventory, + Function2 callback) { + this.context = context; + this.viewModel = viewModel; + this.themeCache = themeCache; + this.inventory = inventory; + this.callback = callback; + differ = new AsyncListDiffer<>(this, new TagDiffCallback()); + } + + @NonNull + @Override + public TagPickerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(context).inflate(R.layout.row_tag_picker, parent, false); + return new TagPickerViewHolder(context, view, callback); + } + + @Override + public void onBindViewHolder(@NonNull TagPickerViewHolder holder, int position) { + TagData tagData = differ.getCurrentList().get(position); + boolean checked = viewModel.isChecked(tagData); + holder.bind(tagData, getColor(tagData), getIcon(tagData), checked); + } + + @Override + public int getItemCount() { + return differ.getCurrentList().size(); + } + + private int getColor(TagData tagData) { + return themeCache + .getThemeColor(tagData.getColor() >= 0 ? tagData.getColor() : 19) + .getPrimaryColor(); + } + + private @Nullable Integer getIcon(TagData tagData) { + return tagData.getIcon() < 1000 || inventory.hasPro() + ? CustomIcons.getIconResId(tagData.getIcon()) + : null; + } + + public void submitList(List tagData) { + differ.submitList(tagData); + } +} diff --git a/app/src/main/java/org/tasks/ui/SubtaskControlSet.java b/app/src/main/java/org/tasks/ui/SubtaskControlSet.java index 29c8dab03..ab5642b59 100644 --- a/app/src/main/java/org/tasks/ui/SubtaskControlSet.java +++ b/app/src/main/java/org/tasks/ui/SubtaskControlSet.java @@ -15,7 +15,6 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.LinearLayout; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.ViewModelProviders; @@ -60,9 +59,6 @@ public class SubtaskControlSet extends TaskEditControlFragment implements Callba @BindView(R.id.recycler_view) RecyclerView recyclerView; - @BindView(R.id.add_subtask) - TextView addSubtask; - @BindView(R.id.new_subtasks) LinearLayout newSubtaskContainer; diff --git a/app/src/main/res/layout/activity_tag_picker.xml b/app/src/main/res/layout/activity_tag_picker.xml new file mode 100644 index 000000000..d0dfd8fd9 --- /dev/null +++ b/app/src/main/res/layout/activity_tag_picker.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/control_set_tag_list.xml b/app/src/main/res/layout/control_set_tag_list.xml deleted file mode 100644 index 8da7d282d..000000000 --- a/app/src/main/res/layout/control_set_tag_list.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/layout/row_tag_picker.xml b/app/src/main/res/layout/row_tag_picker.xml new file mode 100644 index 000000000..79fdc4743 --- /dev/null +++ b/app/src/main/res/layout/row_tag_picker.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/app/src/main/res/layout/simple_list_item_multiple_choice_themed.xml b/app/src/main/res/layout/simple_list_item_multiple_choice_themed.xml deleted file mode 100644 index f0c19bc40..000000000 --- a/app/src/main/res/layout/simple_list_item_multiple_choice_themed.xml +++ /dev/null @@ -1,18 +0,0 @@ - - diff --git a/app/src/main/res/layout/tag_edit_row.xml b/app/src/main/res/layout/tag_edit_row.xml deleted file mode 100644 index 4ea7d7172..000000000 --- a/app/src/main/res/layout/tag_edit_row.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index e98b1dd7a..a726ffda4 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -16,6 +16,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b6a204942..77a2e7460 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -563,4 +563,6 @@ File %1$s contained %2$s.\n\n Enter title Show subtasks Displaying subtasks will degrade app performance + Enter tag name + Create \"%s\" diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c1a28a4b8..d1a6ea29a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -28,12 +28,14 @@ false @style/WhiteToolbarOverlay @color/white_100 + @color/white_70