Convert tag picker to Kotlin

pull/1061/head
Alex Baker 4 years ago
parent 811c9497e1
commit ef432e296c

@ -30,9 +30,7 @@ abstract class TagDataDao {
}
suspend fun searchTags(query: String): List<TagData> {
return searchTagsInternal("%$query%")
.sortedWith(AlphanumComparator(TagData::name))
.toMutableList()
return searchTagsInternal("%$query%").sort()
}
@Query("SELECT * FROM tagdata WHERE name LIKE :query AND name NOT NULL AND name != ''")
@ -161,4 +159,24 @@ abstract class TagDataDao {
} else {
getTagDataForTask(task.id)
})
companion object {
private val COMPARATOR = Comparator<TagData> { f1, f2 ->
when {
f1.order == NO_ORDER && f2.order == NO_ORDER -> f1.id!!.compareTo(f2.id!!)
f1.order == NO_ORDER -> 1
f2.order == NO_ORDER -> -1
f1.order < f2.order -> -1
f1.order > f2.order -> 1
else -> AlphanumComparator.TAGDATA.compare(f1, f2)
}
}
private fun List<TagData>.sort(): List<TagData> =
if (all { it.order == NO_ORDER }) {
sortedWith(AlphanumComparator.TAGDATA)
} else {
sortedWith(COMPARATOR)
}
}
}

@ -1,6 +1,7 @@
package org.tasks.filters
import com.todoroo.astrid.api.Filter
import org.tasks.data.TagData
import java.util.*
/*
@ -111,5 +112,6 @@ class AlphanumComparator<T>(private val getTitle: (T) -> String?) : Comparator<T
companion object {
@JvmField
val FILTER = AlphanumComparator(Filter::listingTitle)
val TAGDATA = AlphanumComparator(TagData::name)
}
}

@ -1,118 +0,0 @@
package org.tasks.tags;
import static org.tasks.Strings.isNullOrEmpty;
import android.content.Intent;
import android.os.Bundle;
import android.widget.EditText;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnTextChanged;
import dagger.hilt.android.AndroidEntryPoint;
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.ThemedInjectingAppCompatActivity;
import org.tasks.tags.CheckBoxTriStates.State;
import org.tasks.themes.ColorProvider;
import org.tasks.themes.Theme;
import org.tasks.themes.ThemeColor;
@AndroidEntryPoint
public class TagPickerActivity extends ThemedInjectingAppCompatActivity {
public static final String EXTRA_SELECTED = "extra_tags";
public static final String EXTRA_PARTIALLY_SELECTED = "extra_partial";
public static final String EXTRA_TASKS = "extra_tasks";
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.recycler_view)
RecyclerView recyclerView;
@BindView(R.id.search_input)
EditText editText;
@Inject Theme theme;
@Inject Inventory inventory;
@Inject ColorProvider colorProvider;
private TagPickerViewModel viewModel;
private ArrayList<Long> taskIds;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(TagPickerViewModel.class);
Intent intent = getIntent();
taskIds = (ArrayList<Long>) intent.getSerializableExtra(EXTRA_TASKS);
if (savedInstanceState == null) {
viewModel.setSelected(
intent.getParcelableArrayListExtra(EXTRA_SELECTED),
intent.getParcelableArrayListExtra(EXTRA_PARTIALLY_SELECTED));
}
setContentView(R.layout.activity_tag_picker);
ButterKnife.bind(this);
toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px);
toolbar.setNavigationOnClickListener(v -> onBackPressed());
ThemeColor themeColor = theme.getThemeColor();
themeColor.applyToStatusBarIcons(this);
themeColor.applyToNavigationBar(this);
themeColor.apply(toolbar);
TagRecyclerAdapter recyclerAdapter =
new TagRecyclerAdapter(this, viewModel, inventory, colorProvider, this::onToggle);
recyclerView.setAdapter(recyclerAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
viewModel.observe(this, recyclerAdapter::submitList);
editText.setText(viewModel.getText());
}
private State onToggle(TagData tagData, Boolean checked) {
boolean newTag = tagData.getId() == null;
State state = viewModel.toggle(tagData, checked);
if (newTag) {
clear();
}
return state;
}
@OnTextChanged(R.id.search_input)
void onSearch(CharSequence text) {
viewModel.search(text.toString());
}
@Override
public void onBackPressed() {
if (isNullOrEmpty(viewModel.getText())) {
Intent data = new Intent();
data.putExtra(EXTRA_TASKS, taskIds);
data.putParcelableArrayListExtra(EXTRA_PARTIALLY_SELECTED, viewModel.getPartiallySelected());
data.putParcelableArrayListExtra(EXTRA_SELECTED, viewModel.getSelected());
setResult(RESULT_OK, data);
finish();
} else {
clear();
}
}
private void clear() {
editText.setText("");
}
}

@ -0,0 +1,107 @@
package org.tasks.tags
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.EditText
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.billing.Inventory
import org.tasks.data.TagData
import org.tasks.injection.ThemedInjectingAppCompatActivity
import org.tasks.themes.ColorProvider
import org.tasks.themes.Theme
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
class TagPickerActivity : ThemedInjectingAppCompatActivity() {
@BindView(R.id.toolbar)
lateinit var toolbar: Toolbar
@BindView(R.id.recycler_view)
lateinit var recyclerView: RecyclerView
@BindView(R.id.search_input)
lateinit var editText: EditText
@Inject lateinit var theme: Theme
@Inject lateinit var inventory: Inventory
@Inject lateinit var colorProvider: ColorProvider
private val viewModel: TagPickerViewModel by viewModels()
private var taskIds: ArrayList<Long>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = intent
taskIds = intent.getSerializableExtra(EXTRA_TASKS) as ArrayList<Long>?
if (savedInstanceState == null) {
viewModel.setSelected(
intent.getParcelableArrayListExtra(EXTRA_SELECTED),
intent.getParcelableArrayListExtra(EXTRA_PARTIALLY_SELECTED))
}
setContentView(R.layout.activity_tag_picker)
ButterKnife.bind(this)
toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px)
toolbar.setNavigationOnClickListener { onBackPressed() }
val themeColor = theme.themeColor
themeColor.applyToStatusBarIcons(this)
themeColor.applyToNavigationBar(this)
themeColor.apply(toolbar)
val recyclerAdapter = TagRecyclerAdapter(this, viewModel, inventory, colorProvider) { tagData, vh ->
onToggle(tagData, vh)
}
recyclerView.adapter = recyclerAdapter
(recyclerView.itemAnimator as DefaultItemAnimator?)!!.supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(this)
viewModel.observe(this) { recyclerAdapter.submitList(it) }
editText.setText(viewModel.text)
}
private fun onToggle(tagData: TagData, vh: TagPickerViewHolder) = lifecycleScope.launch {
val newTag = tagData.id == null
val newState = viewModel.toggle(tagData, vh.isChecked || newTag)
vh.updateCheckbox(newState)
if (newTag) {
clear()
}
}
@OnTextChanged(R.id.search_input)
fun onSearch(text: CharSequence) {
viewModel.search(text.toString())
}
override fun onBackPressed() {
if (isNullOrEmpty(viewModel.text)) {
val data = Intent()
data.putExtra(EXTRA_TASKS, taskIds)
data.putParcelableArrayListExtra(EXTRA_PARTIALLY_SELECTED, viewModel.getPartiallySelected())
data.putParcelableArrayListExtra(EXTRA_SELECTED, viewModel.getSelected())
setResult(Activity.RESULT_OK, data)
finish()
} else {
clear()
}
}
private fun clear() {
editText.setText("")
}
companion object {
const val EXTRA_SELECTED = "extra_tags"
const val EXTRA_PARTIALLY_SELECTED = "extra_partial"
const val EXTRA_TASKS = "extra_tasks"
}
}

@ -1,83 +0,0 @@
package org.tasks.tags;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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;
import org.tasks.tags.CheckBoxTriStates.State;
import org.tasks.themes.DrawableUtil;
public class TagPickerViewHolder extends RecyclerView.ViewHolder {
private final Context context;
private final Function2<TagData, Boolean, State> callback;
@BindView(R.id.text)
TextView text;
@BindView(R.id.checkbox)
CheckBoxTriStates checkBox;
private TagData tagData;
TagPickerViewHolder(
Context context, @NonNull View view, Function2<TagData, Boolean, State> 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() {
State newState = callback.invoke(tagData, checkBox.isChecked());
updateCheckbox(newState);
}
public void bind(
@NonNull TagData tagData, int color, @Nullable Integer icon, State state) {
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());
if (state == State.CHECKED) {
checkBox.setChecked(true);
} else {
updateCheckbox(state);
}
if (icon == null) {
icon = R.drawable.ic_outline_label_24px;
}
}
DrawableUtil.setLeftDrawable(context, text, icon);
DrawableUtil.setTint(DrawableUtil.getLeftDrawable(text), color);
}
private void updateCheckbox(State state) {
checkBox.setState(state, false);
checkBox.setVisibility(View.VISIBLE);
}
}

@ -0,0 +1,78 @@
package org.tasks.tags
import android.content.Context
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnCheckedChanged
import butterknife.OnClick
import org.tasks.R
import org.tasks.data.TagData
import org.tasks.tags.CheckBoxTriStates
import org.tasks.themes.DrawableUtil
class TagPickerViewHolder internal constructor(
private val context: Context,
view: View,
private val callback: (TagData, TagPickerViewHolder) -> Unit
) : RecyclerView.ViewHolder(view) {
val isChecked: Boolean
get() = checkBox.isChecked
@BindView(R.id.text)
lateinit var text: TextView
@BindView(R.id.checkbox)
lateinit var checkBox: CheckBoxTriStates
private var tagData: TagData? = null
@OnClick(R.id.tag_row)
fun onClickRow() {
if (tagData!!.id == null) {
callback.invoke(tagData!!, this)
} else {
checkBox.toggle()
}
}
@OnCheckedChanged(R.id.checkbox)
fun onCheckedChanged() {
callback.invoke(tagData!!, this)
}
fun bind(
tagData: TagData, color: Int, icon: Int?, state: CheckBoxTriStates.State) {
var icon = icon
this.tagData = tagData
if (tagData.id == null) {
text.text = context.getString(R.string.create_new_tag, tagData.name)
icon = R.drawable.ic_outline_add_24px
checkBox.visibility = View.GONE
} else {
text.text = tagData.name
if (state == CheckBoxTriStates.State.CHECKED) {
checkBox.isChecked = true
} else {
updateCheckbox(state)
}
if (icon == null) {
icon = R.drawable.ic_outline_label_24px
}
}
DrawableUtil.setLeftDrawable(context, text, icon)
DrawableUtil.setTint(DrawableUtil.getLeftDrawable(text), color)
}
fun updateCheckbox(state: CheckBoxTriStates.State) {
checkBox.setState(state, false)
checkBox.visibility = View.VISIBLE
}
init {
ButterKnife.bind(this, view)
}
}

@ -2,7 +2,6 @@ package org.tasks.tags
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.*
import androidx.lifecycle.Observer
import kotlinx.coroutines.launch
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.TagData
@ -20,7 +19,7 @@ class TagPickerViewModel @ViewModelInject constructor(
var text: String? = null
private set
fun observe(owner: LifecycleOwner, observer: Observer<List<TagData>>) =
fun observe(owner: LifecycleOwner, observer: (List<TagData>) -> Unit) =
tags.observe(owner, observer)
fun setSelected(selected: List<TagData>, partiallySelected: List<TagData>?) {
@ -58,13 +57,11 @@ class TagPickerViewModel @ViewModelInject constructor(
return if (selected.contains(tagData)) State.CHECKED else State.UNCHECKED
}
fun toggle(tagData: TagData, checked: Boolean): State {
suspend fun toggle(tagData: TagData, checked: Boolean): State {
var tagData = tagData
if (tagData.id == null) {
tagData = TagData(tagData.name)
viewModelScope.launch {
tagDataDao.createNew(tagData)
}
tagDataDao.createNew(tagData)
}
partiallySelected.remove(tagData)
return if (checked) {

@ -1,81 +0,0 @@
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.tags.CheckBoxTriStates.State;
import org.tasks.themes.ColorProvider;
import org.tasks.themes.CustomIcons;
import org.tasks.themes.ThemeColor;
class TagRecyclerAdapter extends RecyclerView.Adapter<TagPickerViewHolder> {
private final AsyncListDiffer<TagData> differ;
private final Context context;
private final TagPickerViewModel viewModel;
private final Inventory inventory;
private final ColorProvider colorProvider;
private final Function2<TagData, Boolean, State> callback;
TagRecyclerAdapter(
Context context,
TagPickerViewModel viewModel,
Inventory inventory,
ColorProvider colorProvider,
Function2<TagData, Boolean, State> callback) {
this.context = context;
this.viewModel = viewModel;
this.inventory = inventory;
this.colorProvider = colorProvider;
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);
holder.bind(tagData, getColor(tagData), getIcon(tagData), viewModel.getState(tagData));
}
@Override
public int getItemCount() {
return differ.getCurrentList().size();
}
private int getColor(TagData tagData) {
if (tagData.getColor() != 0) {
ThemeColor themeColor = colorProvider.getThemeColor(tagData.getColor(), true);
if (inventory.purchasedThemes() || themeColor.isFree()) {
return themeColor.getPrimaryColor();
}
}
return context.getColor(R.color.icon_tint_with_alpha);
}
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);
}
}

@ -0,0 +1,55 @@
package org.tasks.tags
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView
import org.tasks.R
import org.tasks.billing.Inventory
import org.tasks.data.TagData
import org.tasks.themes.ColorProvider
import org.tasks.themes.CustomIcons.getIconResId
internal class TagRecyclerAdapter(
private val context: Context,
private val viewModel: TagPickerViewModel,
private val inventory: Inventory,
private val colorProvider: ColorProvider,
private val callback: (TagData, TagPickerViewHolder) -> Unit
) : RecyclerView.Adapter<TagPickerViewHolder>() {
private val differ: AsyncListDiffer<TagData> = AsyncListDiffer(this, TagDiffCallback())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagPickerViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.row_tag_picker, parent, false)
return TagPickerViewHolder(context, view, callback)
}
override fun onBindViewHolder(holder: TagPickerViewHolder, position: Int) {
val tagData = differ.currentList[position]
holder.bind(tagData, getColor(tagData), getIcon(tagData), viewModel.getState(tagData))
}
override fun getItemCount(): Int {
return differ.currentList.size
}
private fun getColor(tagData: TagData): Int {
if (tagData.getColor() != 0) {
val themeColor = colorProvider.getThemeColor(tagData.getColor()!!, true)
if (inventory.purchasedThemes() || themeColor.isFree) {
return themeColor.primaryColor
}
}
return context.getColor(R.color.icon_tint_with_alpha)
}
private fun getIcon(tagData: TagData): Int? {
return if (tagData.getIcon()!! < 1000 || inventory.hasPro()) getIconResId(tagData.getIcon()!!) else null
}
fun submitList(tagData: List<TagData>?) {
differ.submitList(tagData)
}
}
Loading…
Cancel
Save