From ef432e296cfee6b0c7eb1cb539a0d3f879787924 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 31 Jul 2020 15:44:59 -0500 Subject: [PATCH] Convert tag picker to Kotlin --- .../main/java/org/tasks/data/TagDataDao.kt | 24 +++- .../org/tasks/filters/AlphanumComparator.kt | 2 + .../org/tasks/tags/TagPickerActivity.java | 118 ------------------ .../java/org/tasks/tags/TagPickerActivity.kt | 107 ++++++++++++++++ .../org/tasks/tags/TagPickerViewHolder.java | 83 ------------ .../org/tasks/tags/TagPickerViewHolder.kt | 78 ++++++++++++ .../java/org/tasks/tags/TagPickerViewModel.kt | 9 +- .../org/tasks/tags/TagRecyclerAdapter.java | 81 ------------ .../java/org/tasks/tags/TagRecyclerAdapter.kt | 55 ++++++++ 9 files changed, 266 insertions(+), 291 deletions(-) delete mode 100644 app/src/main/java/org/tasks/tags/TagPickerActivity.java create mode 100644 app/src/main/java/org/tasks/tags/TagPickerActivity.kt delete mode 100644 app/src/main/java/org/tasks/tags/TagPickerViewHolder.java create mode 100644 app/src/main/java/org/tasks/tags/TagPickerViewHolder.kt delete mode 100644 app/src/main/java/org/tasks/tags/TagRecyclerAdapter.java create mode 100644 app/src/main/java/org/tasks/tags/TagRecyclerAdapter.kt diff --git a/app/src/main/java/org/tasks/data/TagDataDao.kt b/app/src/main/java/org/tasks/data/TagDataDao.kt index 8ece20347..84bbf8b31 100644 --- a/app/src/main/java/org/tasks/data/TagDataDao.kt +++ b/app/src/main/java/org/tasks/data/TagDataDao.kt @@ -30,9 +30,7 @@ abstract class TagDataDao { } suspend fun searchTags(query: String): List { - 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 { 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.sort(): List = + if (all { it.order == NO_ORDER }) { + sortedWith(AlphanumComparator.TAGDATA) + } else { + sortedWith(COMPARATOR) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/filters/AlphanumComparator.kt b/app/src/main/java/org/tasks/filters/AlphanumComparator.kt index 797e994e2..0f797ce95 100644 --- a/app/src/main/java/org/tasks/filters/AlphanumComparator.kt +++ b/app/src/main/java/org/tasks/filters/AlphanumComparator.kt @@ -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(private val getTitle: (T) -> String?) : Comparator taskIds; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(TagPickerViewModel.class); - Intent intent = getIntent(); - taskIds = (ArrayList) 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(""); - } -} diff --git a/app/src/main/java/org/tasks/tags/TagPickerActivity.kt b/app/src/main/java/org/tasks/tags/TagPickerActivity.kt new file mode 100644 index 000000000..8a6277da0 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagPickerActivity.kt @@ -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? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val intent = intent + taskIds = intent.getSerializableExtra(EXTRA_TASKS) as ArrayList? + 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tags/TagPickerViewHolder.java b/app/src/main/java/org/tasks/tags/TagPickerViewHolder.java deleted file mode 100644 index f6ac45dc8..000000000 --- a/app/src/main/java/org/tasks/tags/TagPickerViewHolder.java +++ /dev/null @@ -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 callback; - - @BindView(R.id.text) - TextView text; - - @BindView(R.id.checkbox) - CheckBoxTriStates 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() { - 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); - } -} diff --git a/app/src/main/java/org/tasks/tags/TagPickerViewHolder.kt b/app/src/main/java/org/tasks/tags/TagPickerViewHolder.kt new file mode 100644 index 000000000..cae800dd6 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagPickerViewHolder.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tags/TagPickerViewModel.kt b/app/src/main/java/org/tasks/tags/TagPickerViewModel.kt index f4848be40..04841675c 100644 --- a/app/src/main/java/org/tasks/tags/TagPickerViewModel.kt +++ b/app/src/main/java/org/tasks/tags/TagPickerViewModel.kt @@ -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>) = + fun observe(owner: LifecycleOwner, observer: (List) -> Unit) = tags.observe(owner, observer) fun setSelected(selected: List, partiallySelected: List?) { @@ -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) { diff --git a/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.java b/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.java deleted file mode 100644 index 7b6c7353b..000000000 --- a/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.java +++ /dev/null @@ -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 { - - private final AsyncListDiffer differ; - private final Context context; - private final TagPickerViewModel viewModel; - private final Inventory inventory; - private final ColorProvider colorProvider; - private final Function2 callback; - - TagRecyclerAdapter( - Context context, - TagPickerViewModel viewModel, - Inventory inventory, - ColorProvider colorProvider, - Function2 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) { - differ.submitList(tagData); - } -} diff --git a/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.kt b/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.kt new file mode 100644 index 000000000..ef2554936 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagRecyclerAdapter.kt @@ -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() { + + private val differ: AsyncListDiffer = 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?) { + differ.submitList(tagData) + } +} \ No newline at end of file