mirror of https://github.com/tasks/tasks
Convert task list recycler adapters to Kotlin
parent
34119391fc
commit
0f55cacf10
@ -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<TaskContainer> oldList;
|
|
||||||
private final List<TaskContainer> newList;
|
|
||||||
@Deprecated private final TaskAdapter adapter;
|
|
||||||
|
|
||||||
DiffCallback(List<TaskContainer> oldList, List<TaskContainer> 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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<TaskContainer>, private val new: List<TaskContainer>, @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)
|
||||||
|
}
|
||||||
|
}
|
@ -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<TaskContainer> list;
|
|
||||||
private final PublishSubject<List<TaskContainer>> publishSubject = PublishSubject.create();
|
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
|
||||||
private final Queue<Pair<List<TaskContainer>, DiffResult>> updates = new LinkedList<>();
|
|
||||||
private boolean dragging;
|
|
||||||
|
|
||||||
public DragAndDropRecyclerAdapter(
|
|
||||||
TaskAdapter adapter,
|
|
||||||
RecyclerView recyclerView,
|
|
||||||
ViewHolderFactory viewHolderFactory,
|
|
||||||
TaskListFragment taskList,
|
|
||||||
List<TaskContainer> 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<List<TaskContainer>, 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<TaskContainer> list) {
|
|
||||||
publishSubject.onNext(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pair<List<TaskContainer>, DiffResult> calculateDiff(
|
|
||||||
Pair<List<TaskContainer>, DiffResult> last, List<TaskContainer> 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<List<TaskContainer>, DiffResult> update) {
|
|
||||||
assertMainThread();
|
|
||||||
|
|
||||||
updates.add(update);
|
|
||||||
|
|
||||||
if (!dragging) {
|
|
||||||
drainQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drainQueue() {
|
|
||||||
assertMainThread();
|
|
||||||
|
|
||||||
Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
|
|
||||||
|
|
||||||
Pair<List<TaskContainer>, 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<TaskContainer>,
|
||||||
|
taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) {
|
||||||
|
private val publishSubject = PublishSubject.create<List<TaskContainer>>()
|
||||||
|
private val disposables = CompositeDisposable()
|
||||||
|
private val updates: Queue<Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>> = LinkedList()
|
||||||
|
private var dragging = false
|
||||||
|
override fun dragAndDropEnabled() = adapter.supportsParentingOrManualSort()
|
||||||
|
|
||||||
|
override fun getItem(position: Int) = list[position]
|
||||||
|
|
||||||
|
override fun submitList(list: List<TaskContainer>) = publishSubject.onNext(list)
|
||||||
|
|
||||||
|
private fun calculateDiff(
|
||||||
|
last: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>, next: MutableList<TaskContainer>): Pair<MutableList<TaskContainer>, 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<MutableList<TaskContainer>, 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<MutableList<TaskContainer>, DiffUtil.DiffResult>(list, null)
|
||||||
|
disposables.add(
|
||||||
|
publishSubject
|
||||||
|
.observeOn(Schedulers.computation())
|
||||||
|
.scan(initial, { last: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>, next: List<TaskContainer> ->
|
||||||
|
calculateDiff(last, next.toMutableList())
|
||||||
|
})
|
||||||
|
.skip(1)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { update: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult> -> applyDiff(update) })
|
||||||
|
}
|
||||||
|
}
|
@ -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<TaskContainer> differ;
|
|
||||||
|
|
||||||
public PagedListRecyclerAdapter(
|
|
||||||
TaskAdapter adapter,
|
|
||||||
RecyclerView recyclerView,
|
|
||||||
ViewHolderFactory viewHolderFactory,
|
|
||||||
TaskListFragment taskList,
|
|
||||||
List<TaskContainer> 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<TaskContainer>) list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TaskContainer getItem(int position) {
|
|
||||||
return differ.getItem(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void submitList(List<TaskContainer> list) {
|
|
||||||
differ.submitList((PagedList<TaskContainer>) 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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<TaskContainer>,
|
||||||
|
taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) {
|
||||||
|
|
||||||
|
private val differ: AsyncPagedListDiffer<TaskContainer> =
|
||||||
|
AsyncPagedListDiffer(this, AsyncDifferConfig.Builder(ItemCallback()).build())
|
||||||
|
|
||||||
|
override fun getItem(position: Int) = differ.getItem(position)!!
|
||||||
|
|
||||||
|
override fun submitList(list: List<TaskContainer>) = differ.submitList(list as PagedList<TaskContainer>)
|
||||||
|
|
||||||
|
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<TaskContainer>?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<ViewHolder>
|
|
||||||
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<TaskContainer> 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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<ViewHolder>(), 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<TaskContainer>)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue