New tag picker activity

gtask_related_email
Alex Baker 6 years ago
parent 1e33549d44
commit 4e29043390

@ -546,8 +546,11 @@
android:launchMode="singleTask"
android:taskAffinity=""
android:theme="@style/TranslucentDialog"/>
<activity android:name=".preferences.AttributionActivity"/>
<activity android:name=".tags.TagPickerActivity" />
<!-- launcher icons -->
<activity-alias

@ -28,7 +28,7 @@ public final class CustomFilterExposer {
public List<Filter> getFilters() {
List<Filter> filters = newArrayList(transform(filterDao.getFilters(), this::load));
Collections.sort(filters, new AlphanumComparator());
Collections.sort(filters, new AlphanumComparator<>(AlphanumComparator.FILTER));
return filters;
}

@ -56,7 +56,7 @@ public class GtasksFilterExposer {
}
}
for (Map.Entry<GoogleTaskAccount, List<Filter>> entry : filters.entrySet()) {
Collections.sort(entry.getValue(), new AlphanumComparator());
Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER));
}
return filters;
}

@ -46,7 +46,7 @@ public class TagFilterExposer {
public List<Filter> getFilters() {
List<Filter> tags =
newArrayList(transform(tagDataDao.getTagFilters(now()), TagFilters::toTagFilter));
Collections.sort(tags, new AlphanumComparator());
Collections.sort(tags, new AlphanumComparator<>(AlphanumComparator.FILTER));
return tags;
}

@ -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<TagData> orderByName =
new Ordering<TagData>() {
@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<TagData> allTags;
private ArrayList<TagData> originalTags;
private ArrayList<TagData> 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<String> 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<TagData>(
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<TagData> selected, final String name) {
return !Iterables.any(selected, input -> 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 = 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<String> getNewTags() {
ArrayList<String> 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

@ -40,7 +40,7 @@ public class CaldavFilterExposer {
}
}
for (Map.Entry<CaldavAccount, List<Filter>> entry : filters.entrySet()) {
Collections.sort(entry.getValue(), new AlphanumComparator());
Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER));
}
return filters;
}

@ -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<TagData> searchTags(String query) {
List<TagData> 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<TagData> searchTagsInternal(String query);
@Query("SELECT * FROM tagdata")
public abstract List<TagData> getAll();

@ -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;
* <p>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<Filter> {
public class AlphanumComparator<T> implements Comparator<T> {
public static Function<Filter, String> FILTER = f -> f.listingTitle;
private final Function<T, String> getTitle;
private boolean isDigit(char ch) {
return ((ch >= 48) && (ch <= 57));
}
public AlphanumComparator(Function<T, String> 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<Filter> {
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;
}

@ -14,6 +14,10 @@ public class TagFilters {
return filter;
}
public String getName() {
return tagData.getName();
}
@Override
public boolean equals(Object o) {
if (this == o) {

@ -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);
}

@ -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<TagData> {
@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);
}
}

@ -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<TagData> 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);
}
}

@ -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<TagData, Boolean, Void> callback;
@BindView(R.id.text)
TextView text;
@BindView(R.id.checkbox)
CheckBox checkBox;
private TagData tagData;
TagPickerViewHolder(
Context context, @NonNull View view, Function2<TagData, Boolean, Void> 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);
}
}
}

@ -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<List<TagData>> tags = new MutableLiveData<>();
private final CompositeDisposable disposables = new CompositeDisposable();
@Inject TagDataDao tagDataDao;
private String text;
private Set<TagData> selected = new HashSet<>();
public void observe(LifecycleOwner owner, Observer<List<TagData>> observer) {
tags.observe(owner, observer);
}
public void setTags(List<TagData> tags) {
selected.addAll(tags);
}
public ArrayList<TagData> 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<TagData> 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);
}
}
}

@ -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<TagPickerViewHolder> {
private final AsyncListDiffer<TagData> differ;
private final Context context;
private final TagPickerViewModel viewModel;
private final ThemeCache themeCache;
private final Inventory inventory;
private final Function2<TagData, Boolean, Void> callback;
TagRecyclerAdapter(
Context context,
TagPickerViewModel viewModel,
ThemeCache themeCache,
Inventory inventory,
Function2<TagData, Boolean, Void> 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> tagData) {
differ.submitList(tagData);
}
}

@ -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;

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="@dimen/elevation_toolbar"
android:theme="?attr/overlay_theme"
app:popupTheme="@style/popup_overlay"
app:titleTextColor="?attr/colorOnPrimary"
app:toolbarStyle="@style/Widget.MaterialComponents.Toolbar"
tools:ignore="UnusedAttribute">
<EditText
android:id="@+id/search_input"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:hint="@string/enter_tag_name"
android:imeOptions="flagNoExtractUi"
android:textColor="?attr/colorOnPrimary"
android:textColorHint="?attr/colorOnPrimaryHint" />
</androidx.appcompat.widget.Toolbar>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:elevation="@dimen/elevation_task_list" />
</LinearLayout>

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
** Copyright (c) 2012 Todoroo Inc
**
** See the file "LICENSE" for the full license governing this code.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/newTags"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first"
android:paddingRight="@dimen/keyline_first"
android:orientation="vertical"/>
<View
style="@style/horizontal_divider"
android:padding="5dip"/>
<ListView
android:id="@+id/existingTags"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="100"
android:choiceMode="multipleChoice"
android:divider="@null"
android:dividerHeight="0dp"/>
</LinearLayout>

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tag_row"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:clickable="true"
android:focusable="true"
android:layout_centerVertical="true"
android:padding="12dp" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_toStartOf="@id/checkbox"
android:layout_toLeftOf="@id/checkbox"
android:drawablePadding="@dimen/keyline_second"
android:textColor="@color/text_primary"
android:layout_centerVertical="true"
android:paddingStart="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:paddingRight="@dimen/keyline_first"
android:textSize="@dimen/task_edit_text_size" />
</RelativeLayout>

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
** Copyright (c) 2012 Todoroo Inc
**
** See the file "LICENSE" for the full license governing this code.
-->
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first"
android:paddingRight="@dimen/keyline_first"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:drawableLeft="@drawable/ic_outline_label_24px"
android:drawablePadding="@dimen/keyline_second"
android:drawableStart="@drawable/ic_outline_label_24px"
android:gravity="center_vertical"/>

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<AutoCompleteTextView
android:id="@+id/text1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:hint="@string/new_tag"
android:inputType="textCapSentences"/>
<ImageView
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/remove"
android:src="@drawable/ic_outline_clear_24px"
android:tint="@color/icon_tint"/>
</LinearLayout>

@ -16,6 +16,7 @@
<attr format="reference" name="weekdayButtonBackground"/>
<attr format="reference" name="weekdayButtonText"/>
<attr format="integer" name="horizontal_divider"/>
<attr format="color" name="colorOnPrimaryHint"/>
<declare-styleable name="TimePreference">
<attr format="string" name="summary"/>

@ -563,4 +563,6 @@ File %1$s contained %2$s.\n\n
<string name="enter_title_hint">Enter title</string>
<string name="show_subtasks">Show subtasks</string>
<string name="show_subtasks_summary">Displaying subtasks will degrade app performance</string>
<string name="enter_tag_name">Enter tag name</string>
<string name="create_new_tag">Create \"%s\"</string>
</resources>

@ -28,12 +28,14 @@
<item name="dark_status_bar">false</item>
<item name="overlay_theme">@style/WhiteToolbarOverlay</item>
<item name="colorOnPrimary">@color/white_100</item>
<item name="colorOnPrimaryHint">@color/white_70</item>
</style>
<style name="BlackToolbarTheme">
<item name="dark_status_bar">true</item>
<item name="overlay_theme">@style/BlackToolbarOverlay</item>
<item name="colorOnPrimary">@color/black_87</item>
<item name="colorOnPrimaryHint">@color/black_54</item>
</style>
<style name="WhiteTint">

Loading…
Cancel
Save