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