From 0f55cacf103476194323aa8cacd2fb8448e9082f Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 22 May 2020 16:11:03 -0500 Subject: [PATCH] Convert task list recycler adapters to Kotlin --- .../CaldavManualSortTaskAdapterTest.kt | 2 +- .../astrid/adapter/CaldavTaskAdapterTest.kt | 2 +- .../GoogleTaskManualSortAdapterTest.kt | 2 +- .../astrid/activity/TaskListFragment.kt | 2 +- .../astrid/adapter/GoogleTaskAdapter.kt | 2 +- .../com/todoroo/astrid/adapter/TaskAdapter.kt | 2 +- .../astrid/adapter/TaskAdapterDataSource.kt | 2 +- .../java/org/tasks/tasklist/DiffCallback.java | 41 --- .../java/org/tasks/tasklist/DiffCallback.kt | 22 ++ .../tasklist/DragAndDropRecyclerAdapter.java | 271 ------------------ .../tasklist/DragAndDropRecyclerAdapter.kt | 219 ++++++++++++++ .../tasklist/PagedListRecyclerAdapter.java | 63 ---- .../tasklist/PagedListRecyclerAdapter.kt | 42 +++ .../tasklist/TaskListRecyclerAdapter.java | 143 --------- .../tasks/tasklist/TaskListRecyclerAdapter.kt | 107 +++++++ 15 files changed, 397 insertions(+), 525 deletions(-) delete mode 100644 app/src/main/java/org/tasks/tasklist/DiffCallback.java create mode 100644 app/src/main/java/org/tasks/tasklist/DiffCallback.kt delete mode 100644 app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.java create mode 100644 app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt delete mode 100644 app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.java create mode 100644 app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt delete mode 100644 app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java create mode 100644 app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt diff --git a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt index 8cf52e8f8..7b52a47be 100644 --- a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt +++ b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt @@ -41,7 +41,7 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() { private val dataSource = object : TaskAdapterDataSource { override fun getItem(position: Int) = tasks[position] - override val itemCount get() = tasks.size + override fun getItemCount() = tasks.size } @Before diff --git a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt index 54b6f9ae6..881b6a784 100644 --- a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt +++ b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt @@ -36,7 +36,7 @@ class CaldavTaskAdapterTest : InjectingTestCase() { adapter.setDataSource(object : TaskAdapterDataSource { override fun getItem(position: Int) = tasks[position] - override val itemCount get() = tasks.size + override fun getItemCount() = tasks.size }) } diff --git a/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt b/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt index 5582df75a..1a80d103a 100644 --- a/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt +++ b/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt @@ -40,7 +40,7 @@ class GoogleTaskManualSortAdapterTest : InjectingTestCase() { private val dataSource = object : TaskAdapterDataSource { override fun getItem(position: Int) = tasks[position] - override val itemCount get() = tasks.size + override fun getItemCount() = tasks.size } @Test diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index 4b0c94789..bdd7fcdd5 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -237,7 +237,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI } else if (recyclerAdapter !is DragAndDropRecyclerAdapter) { setAdapter( DragAndDropRecyclerAdapter( - taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao)) + taskAdapter, recyclerView, viewHolderFactory, this, tasks as MutableList, taskDao)) return } recyclerAdapter!!.submitList(tasks) diff --git a/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.kt b/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.kt index ee746ac33..fe301e8a3 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.kt @@ -34,7 +34,6 @@ open class GoogleTaskAdapter internal constructor(private val taskDao: TaskDao, override fun moved(from: Int, to: Int, indent: Int) { val task = getTask(from) val googleTask = task.googleTask - val previous = if (to > 0) getTask(to - 1) else null if (indent == 0) { if (googleTask.indent == 0) { return @@ -42,6 +41,7 @@ open class GoogleTaskAdapter internal constructor(private val taskDao: TaskDao, googleTaskDao.move( googleTask, 0, if (newTasksOnTop) 0 else googleTaskDao.getBottom(googleTask.listId, 0)) } else { + val previous = if (to > 0) getTask(to - 1) else null val newParent = if (previous!!.hasParent()) previous.parent else previous.id if (googleTask.parent == newParent) { return diff --git a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt index 320b66048..abbeb6b77 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt @@ -14,7 +14,7 @@ open class TaskAdapter { private lateinit var dataSource: TaskAdapterDataSource val count: Int - get() = dataSource.itemCount + get() = dataSource.getItemCount() fun setDataSource(dataSource: TaskAdapterDataSource) { this.dataSource = dataSource diff --git a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt index 09da1807d..0cea3577a 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt @@ -5,5 +5,5 @@ import org.tasks.data.TaskContainer interface TaskAdapterDataSource { fun getItem(position: Int): TaskContainer - val itemCount: Int + fun getItemCount(): Int } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/DiffCallback.java b/app/src/main/java/org/tasks/tasklist/DiffCallback.java deleted file mode 100644 index e5a4f68ec..000000000 --- a/app/src/main/java/org/tasks/tasklist/DiffCallback.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.tasks.tasklist; - -import androidx.recyclerview.widget.DiffUtil; -import com.todoroo.astrid.adapter.TaskAdapter; -import java.util.List; -import org.tasks.data.TaskContainer; - -class DiffCallback extends DiffUtil.Callback { - - private final List oldList; - private final List newList; - @Deprecated private final TaskAdapter adapter; - - DiffCallback(List oldList, List newList, TaskAdapter adapter) { - this.oldList = oldList; - this.newList = newList; - this.adapter = adapter; - } - - @Override - public int getOldListSize() { - return oldList.size(); - } - - @Override - public int getNewListSize() { - return newList.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId(); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - TaskContainer oldItem = oldList.get(oldItemPosition); - TaskContainer newItem = newList.get(newItemPosition); - return oldItem.equals(newItem) && oldItem.getIndent() == adapter.getIndent(newItem); - } -} diff --git a/app/src/main/java/org/tasks/tasklist/DiffCallback.kt b/app/src/main/java/org/tasks/tasklist/DiffCallback.kt new file mode 100644 index 000000000..1b686c58a --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/DiffCallback.kt @@ -0,0 +1,22 @@ +package org.tasks.tasklist + +import androidx.recyclerview.widget.DiffUtil +import com.todoroo.astrid.adapter.TaskAdapter +import org.tasks.data.TaskContainer + +internal class DiffCallback(private val old: List, private val new: List, @Deprecated("") private val adapter: TaskAdapter) : DiffUtil.Callback() { + + override fun getOldListSize() = old.size + + override fun getNewListSize() = new.size + + override fun areItemsTheSame(oldPosition: Int, newPosition: Int): Boolean { + return old[oldPosition].id == new[newPosition].id + } + + override fun areContentsTheSame(oldPosition: Int, newPosition: Int): Boolean { + val oldItem = old[oldPosition] + val newItem = new[newPosition] + return oldItem == newItem && oldItem.getIndent() == adapter.getIndent(newItem) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.java b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.java deleted file mode 100644 index 24dba1373..000000000 --- a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.java +++ /dev/null @@ -1,271 +0,0 @@ -package org.tasks.tasklist; - -import static androidx.recyclerview.widget.ItemTouchHelper.DOWN; -import static androidx.recyclerview.widget.ItemTouchHelper.LEFT; -import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT; -import static androidx.recyclerview.widget.ItemTouchHelper.UP; -import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread; -import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread; - -import android.graphics.Canvas; -import android.os.Parcelable; -import androidx.annotation.NonNull; -import androidx.core.util.Pair; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.DiffUtil.DiffResult; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.ListUpdateCallback; -import androidx.recyclerview.widget.RecyclerView; -import com.todoroo.astrid.activity.TaskListFragment; -import com.todoroo.astrid.adapter.TaskAdapter; -import com.todoroo.astrid.dao.TaskDao; -import com.todoroo.astrid.utility.Flags; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.subjects.PublishSubject; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import org.tasks.data.TaskContainer; - -public class DragAndDropRecyclerAdapter extends TaskListRecyclerAdapter { - - private static final int LONG_LIST_SIZE = 500; - - private final TaskAdapter adapter; - private final TaskListFragment taskList; - private final RecyclerView recyclerView; - private List list; - private final PublishSubject> publishSubject = PublishSubject.create(); - private final CompositeDisposable disposables = new CompositeDisposable(); - private final Queue, DiffResult>> updates = new LinkedList<>(); - private boolean dragging; - - public DragAndDropRecyclerAdapter( - TaskAdapter adapter, - RecyclerView recyclerView, - ViewHolderFactory viewHolderFactory, - TaskListFragment taskList, - List list, - TaskDao taskDao) { - super(adapter, viewHolderFactory, taskList, taskDao); - - this.adapter = adapter; - this.recyclerView = recyclerView; - this.taskList = taskList; - this.list = list; - new ItemTouchHelper(new ItemTouchHelperCallback()).attachToRecyclerView(recyclerView); - Pair, DiffResult> initial = Pair.create(list, null); - disposables.add( - publishSubject - .observeOn(Schedulers.computation()) - .scan(initial, this::calculateDiff) - .skip(1) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::applyDiff)); - } - - @Override - protected boolean dragAndDropEnabled() { - return adapter.supportsParentingOrManualSort(); - } - - @Override - public TaskContainer getItem(int position) { - return list.get(position); - } - - @Override - public void submitList(List list) { - publishSubject.onNext(list); - } - - private Pair, DiffResult> calculateDiff( - Pair, DiffResult> last, List next) { - assertNotMainThread(); - - DiffCallback cb = new DiffCallback(last.first, next, adapter); - DiffResult result = DiffUtil.calculateDiff(cb, next.size() < LONG_LIST_SIZE); - - return Pair.create(next, result); - } - - private void applyDiff(Pair, DiffResult> update) { - assertMainThread(); - - updates.add(update); - - if (!dragging) { - drainQueue(); - } - } - - private void drainQueue() { - assertMainThread(); - - Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState(); - - Pair, DiffResult> update = updates.poll(); - while (update != null) { - list = update.first; - update.second.dispatchUpdatesTo((ListUpdateCallback) this); - update = updates.poll(); - } - - recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState); - } - - @Override - public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { - disposables.dispose(); - } - - @Override - public int getItemCount() { - return list.size(); - } - - private class ItemTouchHelperCallback extends ItemTouchHelper.Callback { - private int from = -1; - private int to = -1; - - @Override - public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { - super.onSelectedChanged(viewHolder, actionState); - if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { - taskList.startActionMode(); - ((ViewHolder) viewHolder).setMoving(true); - dragging = true; - int position = viewHolder.getAdapterPosition(); - updateIndents((ViewHolder) viewHolder, position, position); - } - } - - @Override - public int getMovementFlags( - @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { - return adapter.supportsParentingOrManualSort() && adapter.getNumSelected() == 0 - ? makeMovementFlags(UP | DOWN | LEFT | RIGHT, 0) - : makeMovementFlags(0, 0); - } - - @Override - public boolean onMove( - @NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder src, - @NonNull RecyclerView.ViewHolder target) { - taskList.finishActionMode(); - int fromPosition = src.getAdapterPosition(); - int toPosition = target.getAdapterPosition(); - ViewHolder source = (ViewHolder) src; - if (!adapter.canMove(source.task, fromPosition, ((ViewHolder) target).task, toPosition)) { - return false; - } - if (from == -1) { - source.setSelected(false); - from = fromPosition; - } - to = toPosition; - notifyItemMoved(fromPosition, toPosition); - updateIndents(source, from, to); - return true; - } - - private void updateIndents(ViewHolder source, int from, int to) { - TaskContainer task = source.task; - source.setMinIndent( - to == 0 || to == getItemCount() - 1 - ? 0 - : adapter.minIndent(from <= to ? to + 1 : to, task)); - source.setMaxIndent(to == 0 ? 0 : adapter.maxIndent(from >= to ? to - 1 : to, task)); - } - - @Override - public void onChildDraw( - @NonNull Canvas c, - @NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder, - float dX, - float dY, - int actionState, - boolean isCurrentlyActive) { - ViewHolder vh = (ViewHolder) viewHolder; - TaskContainer task = vh.task; - float shiftSize = vh.getShiftSize(); - if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { - int currentIndent = ((ViewHolder) viewHolder).getIndent(); - int maxIndent = vh.getMaxIndent(); - int minIndent = vh.getMinIndent(); - if (isCurrentlyActive) { - float dxAdjusted; - if (dX > 0) { - dxAdjusted = Math.min(dX, (maxIndent - currentIndent) * shiftSize); - } else { - dxAdjusted = Math.max((currentIndent - minIndent) * -shiftSize, dX); - } - - int targetIndent = currentIndent + Float.valueOf(dxAdjusted / shiftSize).intValue(); - - if (targetIndent != task.getIndent()) { - if (from == -1) { - taskList.finishActionMode(); - vh.setSelected(false); - } - } - if (targetIndent < minIndent) { - task.setTargetIndent(minIndent); - } else - task.setTargetIndent(Math.min(targetIndent, maxIndent)); - } - - dX = (task.getTargetIndent() - task.getIndent()) * shiftSize; - } - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - } - - @Override - public void clearView( - @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { - super.clearView(recyclerView, viewHolder); - ViewHolder vh = (ViewHolder) viewHolder; - vh.setMoving(false); - dragging = false; - drainQueue(); - if (taskList.isActionModeActive()) { - toggle(vh); - } else { - TaskContainer task = vh.task; - int targetIndent = task.getTargetIndent(); - if (from >= 0 && from != to) { - if (from < to) { - to++; - } - vh.task.setIndent(targetIndent); - vh.setIndent(targetIndent); - moved(from, to, targetIndent); - } else if (task.getIndent() != targetIndent) { - int position = vh.getAdapterPosition(); - vh.task.setIndent(targetIndent); - vh.setIndent(targetIndent); - moved(position, position, targetIndent); - } - } - from = -1; - to = -1; - Flags.clear(Flags.TLFP_NO_INTERCEPT_TOUCH); - } - - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - throw new UnsupportedOperationException(); - } - - private void moved(int from, int to, int indent) { - adapter.moved(from, to, indent); - TaskContainer task = list.remove(from); - list.add(from < to ? to - 1 : to, task); - taskList.loadTaskListContent(); - } - } -} diff --git a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt new file mode 100644 index 000000000..0b384c167 --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt @@ -0,0 +1,219 @@ +package org.tasks.tasklist + +import android.graphics.Canvas +import androidx.core.util.Pair +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.ListUpdateCallback +import androidx.recyclerview.widget.RecyclerView +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.astrid.activity.TaskListFragment +import com.todoroo.astrid.adapter.TaskAdapter +import com.todoroo.astrid.dao.TaskDao +import com.todoroo.astrid.utility.Flags +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.PublishSubject +import org.tasks.data.TaskContainer +import java.util.* +import kotlin.math.max +import kotlin.math.min + +class DragAndDropRecyclerAdapter( + private val adapter: TaskAdapter, + private val recyclerView: RecyclerView, + viewHolderFactory: ViewHolderFactory, + private val taskList: TaskListFragment, + private var list: MutableList, + taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) { + private val publishSubject = PublishSubject.create>() + private val disposables = CompositeDisposable() + private val updates: Queue, DiffUtil.DiffResult>> = LinkedList() + private var dragging = false + override fun dragAndDropEnabled() = adapter.supportsParentingOrManualSort() + + override fun getItem(position: Int) = list[position] + + override fun submitList(list: List) = publishSubject.onNext(list) + + private fun calculateDiff( + last: Pair, DiffUtil.DiffResult>, next: MutableList): Pair, DiffUtil.DiffResult> { + AndroidUtilities.assertNotMainThread() + val cb = DiffCallback(last.first, next, adapter) + val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE) + return Pair.create(next, result) + } + + private fun applyDiff(update: Pair, DiffUtil.DiffResult>) { + AndroidUtilities.assertMainThread() + updates.add(update) + if (!dragging) { + drainQueue() + } + } + + private fun drainQueue() { + AndroidUtilities.assertMainThread() + val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState() + var update = updates.poll() + while (update != null) { + list = update.first!! + update.second!!.dispatchUpdatesTo((this as ListUpdateCallback)) + update = updates.poll() + } + recyclerView.layoutManager!!.onRestoreInstanceState(recyclerViewState) + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) = disposables.dispose() + + override fun getItemCount(): Int { + return list.size + } + + private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() { + private var from = -1 + private var to = -1 + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + taskList.startActionMode() + (viewHolder as ViewHolder?)!!.isMoving = true + dragging = true + val position = viewHolder!!.adapterPosition + updateIndents(viewHolder as ViewHolder?, position, position) + } + } + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) = if (adapter.supportsParentingOrManualSort() && adapter.numSelected == 0) { + makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0) + } else { + makeMovementFlags(0, 0) + } + + override fun onMove( + recyclerView: RecyclerView, + src: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder): Boolean { + taskList.finishActionMode() + val fromPosition = src.adapterPosition + val toPosition = target.adapterPosition + val source = src as ViewHolder + if (!adapter.canMove(source.task, fromPosition, (target as ViewHolder).task, toPosition)) { + return false + } + if (from == -1) { + source.setSelected(false) + from = fromPosition + } + to = toPosition + notifyItemMoved(fromPosition, toPosition) + updateIndents(source, from, to) + return true + } + + private fun updateIndents(source: ViewHolder?, from: Int, to: Int) { + val task = source!!.task + source.minIndent = if (to == 0 || to == itemCount - 1) 0 else adapter.minIndent(if (from <= to) to + 1 else to, task) + source.maxIndent = if (to == 0) 0 else adapter.maxIndent(if (from >= to) to - 1 else to, task) + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dXOrig: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean) { + var dX = dXOrig + val vh = viewHolder as ViewHolder + val task = vh.task + val shiftSize = vh.shiftSize + if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + val currentIndent = viewHolder.indent + val maxIndent = vh.maxIndent + val minIndent = vh.minIndent + if (isCurrentlyActive) { + val dxAdjusted: Float = if (dX > 0) { + min(dX, (maxIndent - currentIndent) * shiftSize) + } else { + max((currentIndent - minIndent) * -shiftSize, dX) + } + val targetIndent = currentIndent + java.lang.Float.valueOf(dxAdjusted / shiftSize).toInt() + if (targetIndent != task.getIndent()) { + if (from == -1) { + taskList.finishActionMode() + vh.setSelected(false) + } + } + if (targetIndent < minIndent) { + task.targetIndent = minIndent + } else task.targetIndent = Math.min(targetIndent, maxIndent) + } + dX = (task.targetIndent - task.getIndent()) * shiftSize + } + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + } + + override fun clearView( + recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + val vh = viewHolder as ViewHolder + vh.isMoving = false + dragging = false + drainQueue() + if (taskList.isActionModeActive) { + toggle(vh) + } else { + val task = vh.task + val targetIndent = task.targetIndent + if (from >= 0 && from != to) { + if (from < to) { + to++ + } + vh.task.setIndent(targetIndent) + vh.indent = targetIndent + moved(from, to, targetIndent) + } else if (task.getIndent() != targetIndent) { + val position = vh.adapterPosition + vh.task.setIndent(targetIndent) + vh.indent = targetIndent + moved(position, position, targetIndent) + } + } + from = -1 + to = -1 + Flags.clear(Flags.TLFP_NO_INTERCEPT_TOUCH) + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + throw UnsupportedOperationException() + } + + private fun moved(from: Int, to: Int, indent: Int) { + adapter.moved(from, to, indent) + val task: TaskContainer = list.removeAt(from) + list.add(if (from < to) to - 1 else to, task) + taskList.loadTaskListContent() + } + } + + companion object { + private const val LONG_LIST_SIZE = 500 + } + + init { + ItemTouchHelper(ItemTouchHelperCallback()).attachToRecyclerView(recyclerView) + val initial = Pair.create, DiffUtil.DiffResult>(list, null) + disposables.add( + publishSubject + .observeOn(Schedulers.computation()) + .scan(initial, { last: Pair, DiffUtil.DiffResult>, next: List -> + calculateDiff(last, next.toMutableList()) + }) + .skip(1) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { update: Pair, DiffUtil.DiffResult> -> applyDiff(update) }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.java b/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.java deleted file mode 100644 index c96f7b8c1..000000000 --- a/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.tasks.tasklist; - -import android.os.Parcelable; -import androidx.paging.AsyncPagedListDiffer; -import androidx.paging.PagedList; -import androidx.recyclerview.widget.AsyncDifferConfig; -import androidx.recyclerview.widget.RecyclerView; -import com.todoroo.astrid.activity.TaskListFragment; -import com.todoroo.astrid.adapter.TaskAdapter; -import com.todoroo.astrid.dao.TaskDao; -import java.util.List; -import org.tasks.data.TaskContainer; - -public class PagedListRecyclerAdapter extends TaskListRecyclerAdapter { - - private final RecyclerView recyclerView; - private final AsyncPagedListDiffer differ; - - public PagedListRecyclerAdapter( - TaskAdapter adapter, - RecyclerView recyclerView, - ViewHolderFactory viewHolderFactory, - TaskListFragment taskList, - List list, - TaskDao taskDao) { - super(adapter, viewHolderFactory, taskList, taskDao); - this.recyclerView = recyclerView; - differ = - new AsyncPagedListDiffer<>( - this, new AsyncDifferConfig.Builder<>(new ItemCallback()).build()); - if (list instanceof PagedList) { - differ.submitList((PagedList) list); - } - } - - @Override - public TaskContainer getItem(int position) { - return differ.getItem(position); - } - - public void submitList(List list) { - differ.submitList((PagedList) list); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState(); - - super.onMoved(fromPosition, toPosition); - - recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState); - } - - @Override - protected boolean dragAndDropEnabled() { - return false; - } - - @Override - public int getItemCount() { - return differ.getItemCount(); - } -} diff --git a/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt new file mode 100644 index 000000000..4475de877 --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt @@ -0,0 +1,42 @@ +package org.tasks.tasklist + +import androidx.paging.AsyncPagedListDiffer +import androidx.paging.PagedList +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.RecyclerView +import com.todoroo.astrid.activity.TaskListFragment +import com.todoroo.astrid.adapter.TaskAdapter +import com.todoroo.astrid.dao.TaskDao +import org.tasks.data.TaskContainer + +class PagedListRecyclerAdapter( + adapter: TaskAdapter, + private val recyclerView: RecyclerView, + viewHolderFactory: ViewHolderFactory, + taskList: TaskListFragment, + list: List, + taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) { + + private val differ: AsyncPagedListDiffer = + AsyncPagedListDiffer(this, AsyncDifferConfig.Builder(ItemCallback()).build()) + + override fun getItem(position: Int) = differ.getItem(position)!! + + override fun submitList(list: List) = differ.submitList(list as PagedList) + + override fun onMoved(fromPosition: Int, toPosition: Int) { + val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState() + super.onMoved(fromPosition, toPosition) + recyclerView.layoutManager!!.onRestoreInstanceState(recyclerViewState) + } + + override fun dragAndDropEnabled() = false + + override fun getItemCount() = differ.itemCount + + init { + if (list is PagedList<*>) { + differ.submitList(list as PagedList?) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java deleted file mode 100644 index 78499a27e..000000000 --- a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.tasks.tasklist; - -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.ListUpdateCallback; -import androidx.recyclerview.widget.RecyclerView; - -import com.todoroo.astrid.activity.TaskListFragment; -import com.todoroo.astrid.adapter.TaskAdapter; -import com.todoroo.astrid.adapter.TaskAdapterDataSource; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.dao.TaskDao; - -import org.tasks.data.TaskContainer; -import org.tasks.intents.TaskIntents; -import org.tasks.tasklist.ViewHolder.ViewHolderCallbacks; - -import java.util.List; - -public abstract class TaskListRecyclerAdapter extends RecyclerView.Adapter - implements ViewHolderCallbacks, ListUpdateCallback, TaskAdapterDataSource { - - private final TaskAdapter adapter; - private final TaskListFragment taskList; - private final ViewHolderFactory viewHolderFactory; - private final TaskDao taskDao; - - TaskListRecyclerAdapter( - TaskAdapter adapter, - ViewHolderFactory viewHolderFactory, - TaskListFragment taskList, - TaskDao taskDao) { - this.adapter = adapter; - this.viewHolderFactory = viewHolderFactory; - this.taskList = taskList; - this.taskDao = taskDao; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return viewHolderFactory.newViewHolder(parent, this); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - TaskContainer task = getItem(position); - if (task != null) { - holder.bindView(task, taskList.getFilter()); - holder.setMoving(false); - int indent = adapter.getIndent(task); - task.setIndent(indent); - holder.setIndent(indent); - holder.setSelected(adapter.isSelected(task)); - } - } - - @Override - public void onCompletedTask(TaskContainer task, boolean newState) { - adapter.onCompletedTask(task, newState); - taskList.loadTaskListContent(); - } - - @Override - public void onClick(ViewHolder viewHolder) { - if (taskList.isActionModeActive()) { - toggle(viewHolder); - } else { - taskList.onTaskListItemClicked(viewHolder.task.getTask()); - } - } - - @Override - public void onClick(Filter filter) { - if (!taskList.isActionModeActive()) { - FragmentActivity context = taskList.getActivity(); - if (context != null) { - context.startActivity(TaskIntents.getTaskListIntent(context, filter)); - } - } - } - - @Override - public boolean onLongPress(ViewHolder viewHolder) { - if (!dragAndDropEnabled()) { - taskList.startActionMode(); - } - if (taskList.isActionModeActive() && !viewHolder.isMoving()) { - toggle(viewHolder); - } - return true; - } - - @Override - public void onChangeDueDate(TaskContainer task) { - taskList.showDateTimePicker(task); - } - - protected abstract boolean dragAndDropEnabled(); - - @Override - public void toggleSubtasks(TaskContainer task, boolean collapsed) { - taskDao.setCollapsed(task.getId(), collapsed); - taskList.broadcastRefresh(); - } - - void toggle(ViewHolder viewHolder) { - adapter.toggleSelection(viewHolder.task); - notifyItemChanged(viewHolder.getAdapterPosition()); - if (adapter.getSelected().isEmpty()) { - taskList.finishActionMode(); - } else { - taskList.updateModeTitle(); - } - } - - public abstract TaskContainer getItem(int position); - - public abstract void submitList(List list); - - @Override - public void onInserted(int position, int count) { - notifyItemRangeInserted(position, count); - } - - @Override - public void onRemoved(int position, int count) { - notifyItemRangeRemoved(position, count); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - notifyItemMoved(fromPosition, toPosition); - } - - @Override - public void onChanged(int position, int count, @Nullable Object payload) { - notifyItemRangeChanged(position, count, payload); - } -} diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt new file mode 100644 index 000000000..c26a8ed0e --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt @@ -0,0 +1,107 @@ +package org.tasks.tasklist + +import android.view.ViewGroup +import androidx.recyclerview.widget.ListUpdateCallback +import androidx.recyclerview.widget.RecyclerView +import com.todoroo.astrid.activity.TaskListFragment +import com.todoroo.astrid.adapter.TaskAdapter +import com.todoroo.astrid.adapter.TaskAdapterDataSource +import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.dao.TaskDao +import org.tasks.data.TaskContainer +import org.tasks.intents.TaskIntents +import org.tasks.tasklist.ViewHolder.ViewHolderCallbacks + +abstract class TaskListRecyclerAdapter internal constructor( + private val adapter: TaskAdapter, + private val viewHolderFactory: ViewHolderFactory, + private val taskList: TaskListFragment, + private val taskDao: TaskDao) : RecyclerView.Adapter(), ViewHolderCallbacks, ListUpdateCallback, TaskAdapterDataSource { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return viewHolderFactory.newViewHolder(parent, this) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val task = getItem(position) + if (task != null) { + holder.bindView(task, taskList.getFilter()) + holder.isMoving = false + val indent = adapter.getIndent(task) + task.setIndent(indent) + holder.indent = indent + holder.setSelected(adapter.isSelected(task)) + } + } + + override fun onCompletedTask(task: TaskContainer, newState: Boolean) { + adapter.onCompletedTask(task, newState) + taskList.loadTaskListContent() + } + + override fun onClick(viewHolder: ViewHolder) { + if (taskList.isActionModeActive) { + toggle(viewHolder) + } else { + taskList.onTaskListItemClicked(viewHolder.task.getTask()) + } + } + + override fun onClick(filter: Filter) { + if (!taskList.isActionModeActive) { + val context = taskList.activity + context?.startActivity(TaskIntents.getTaskListIntent(context, filter)) + } + } + + override fun onLongPress(viewHolder: ViewHolder): Boolean { + if (!dragAndDropEnabled()) { + taskList.startActionMode() + } + if (taskList.isActionModeActive && !viewHolder.isMoving) { + toggle(viewHolder) + } + return true + } + + override fun onChangeDueDate(task: TaskContainer) { + taskList.showDateTimePicker(task) + } + + override fun toggleSubtasks(task: TaskContainer, collapsed: Boolean) { + taskDao.setCollapsed(task.id, collapsed) + taskList.broadcastRefresh() + } + + fun toggle(viewHolder: ViewHolder) { + adapter.toggleSelection(viewHolder.task) + notifyItemChanged(viewHolder.adapterPosition) + if (adapter.getSelected().isEmpty()) { + taskList.finishActionMode() + } else { + taskList.updateModeTitle() + } + } + + protected abstract fun dragAndDropEnabled(): Boolean + + abstract override fun getItem(position: Int): TaskContainer + + abstract fun submitList(list: List) + + override fun onInserted(position: Int, count: Int) { + notifyItemRangeInserted(position, count) + } + + override fun onRemoved(position: Int, count: Int) { + notifyItemRangeRemoved(position, count) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + notifyItemMoved(fromPosition, toPosition) + } + + override fun onChanged(position: Int, count: Int, payload: Any?) { + notifyItemRangeChanged(position, count, payload) + } +} \ No newline at end of file