Google Tasks' style drag and drop

pull/820/head
Alex Baker 6 years ago
parent abf15a3635
commit 787060c35b

@ -43,7 +43,6 @@ public final class AstridTaskAdapter extends TaskAdapter {
return true; return true;
} }
@Override
public boolean canIndent(int position, TaskContainer task) { public boolean canIndent(int position, TaskContainer task) {
String parentUuid = getItemUuid(position - 1); String parentUuid = getItemUuid(position - 1);
int parentIndent = updater.getIndentForTask(parentUuid); int parentIndent = updater.getIndentForTask(parentUuid);
@ -56,7 +55,7 @@ public final class AstridTaskAdapter extends TaskAdapter {
} }
@Override @Override
public void moved(int from, int to) { public void moved(int from, int to, int indent) {
String targetTaskId = getItemUuid(from); String targetTaskId = getItemUuid(from);
if (!Task.isValidUuid(targetTaskId)) { if (!Task.isValidUuid(targetTaskId)) {
return; // This can happen with gestures on empty parts of the list (e.g. extra space below return; // This can happen with gestures on empty parts of the list (e.g. extra space below
@ -76,7 +75,7 @@ public final class AstridTaskAdapter extends TaskAdapter {
} }
@Override @Override
public void indented(int which, int delta) { public void swiped(int which, int delta) {
String targetTaskId = getItemUuid(which); String targetTaskId = getItemUuid(which);
if (!Task.isValidUuid(targetTaskId)) { if (!Task.isValidUuid(targetTaskId)) {
return; // This can happen with gestures on empty parts of the list (e.g. extra space below return; // This can happen with gestures on empty parts of the list (e.g. extra space below

@ -23,7 +23,7 @@ public final class GoogleTaskAdapter extends TaskAdapter {
@Override @Override
public int getIndent(TaskContainer task) { public int getIndent(TaskContainer task) {
return task.getParent() > 0 ? 1 : 0; return task.getIndent();
} }
@Override @Override
@ -56,8 +56,13 @@ public final class GoogleTaskAdapter extends TaskAdapter {
} }
@Override @Override
public boolean canIndent(int position, TaskContainer task) { public int maxIndent(int position, TaskContainer task) {
return position > 0 && !task.hasChildren() && !task.hasParent(); return position == 0 || task.hasChildren() ? 0 : 1;
}
@Override
public int minIndent(int nextPosition, TaskContainer task) {
return task.hasChildren() || !getTask(nextPosition).hasParent() ? 0 : 1;
} }
@Override @Override
@ -66,51 +71,36 @@ public final class GoogleTaskAdapter extends TaskAdapter {
} }
@Override @Override
public void moved(int from, int to) { public void moved(int from, int to, int indent) {
TaskContainer task = getTask(from); TaskContainer task = getTask(from);
GoogleTask googleTask = task.getGoogleTask(); GoogleTask googleTask = task.getGoogleTask();
if (to == 0) { TaskContainer previous = to > 0 ? getTask(to - 1) : null;
if (previous == null) {
googleTaskDao.move(googleTask, 0, 0); googleTaskDao.move(googleTask, 0, 0);
} else if (to == getCount()) { } else if (to == getCount() || to <= from) {
TaskContainer previous = getTask(to - 1); if (indent == 0) {
if (googleTask.getParent() > 0 && googleTask.getParent() == previous.getParent()) { googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + 1);
googleTaskDao.move(googleTask, googleTask.getParent(), previous.getSecondarySort()); } else if (previous.hasParent()) {
googleTaskDao.move(googleTask, previous.getParent(), previous.getSecondarySort() + 1);
} else { } else {
googleTaskDao.move(googleTask, 0, previous.getPrimarySort());
}
} else if (from < to) {
TaskContainer previous = getTask(to - 1);
TaskContainer next = getTask(to);
if (previous.hasParent()) {
if (next.hasParent()) {
googleTaskDao.move(googleTask, next.getParent(), next.getSecondarySort());
} else if (task.getParent() == previous.getParent() || next.hasParent()) {
googleTaskDao.move(googleTask, previous.getParent(), previous.getSecondarySort());
} else {
googleTaskDao.move(googleTask, 0, previous.getPrimarySort());
}
} else if (previous.hasChildren()) {
googleTaskDao.move(googleTask, previous.getId(), 0); googleTaskDao.move(googleTask, previous.getId(), 0);
} else if (task.hasParent()) {
googleTaskDao.move(googleTask, 0, next.getPrimarySort());
} else {
googleTaskDao.move(googleTask, 0, previous.getPrimarySort());
} }
} else { } else {
TaskContainer previous = getTask(to - 1); if (indent == 0) {
TaskContainer next = getTask(to); googleTaskDao.move(
if (previous.hasParent()) { googleTask,
if (next.hasParent()) { 0,
googleTaskDao.move(googleTask, next.getParent(), next.getSecondarySort()); task.hasParent() ? previous.getPrimarySort() + 1 : previous.getPrimarySort());
} else if (task.getParent() == previous.getParent()) { } else if (previous.hasParent()) {
googleTaskDao.move(googleTask, previous.getParent(), previous.getSecondarySort()); googleTaskDao.move(
} else { googleTask,
googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + 1); previous.getParent(),
} task.getParent() == previous.getParent()
} else if (previous.hasChildren()) { ? previous.getSecondarySort()
googleTaskDao.move(googleTask, previous.getId(), 0); : previous.getSecondarySort() + 1);
} else { } else {
googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + 1); googleTaskDao.move(googleTask, previous.getId(), 0);
} }
} }
@ -125,24 +115,6 @@ public final class GoogleTaskAdapter extends TaskAdapter {
} }
@Override @Override
public void indented(int which, int delta) { public void swiped(int which, int delta) {
TaskContainer task = getTask(which);
TaskContainer previous;
GoogleTask current = task.getGoogleTask();
if (delta == -1) {
googleTaskDao.unindent(googleTaskDao.getByTaskId(task.getParent()), current);
} else {
previous = getTask(which - 1);
googleTaskDao.indent(googleTaskDao.getByTaskId(previous.getId()), current);
}
Task update = task.getTask();
update.setModificationDate(now());
update.putTransitory(SyncFlags.FORCE_SYNC, true);
taskDao.save(update);
if (BuildConfig.DEBUG) {
googleTaskDao.validateSorting(task.getGoogleTaskList());
}
} }
} }

@ -60,8 +60,12 @@ public class TaskAdapter {
return false; return false;
} }
public boolean canIndent(int position, TaskContainer task) { public int maxIndent(int position, TaskContainer task) {
return false; return 0;
}
public int minIndent(int nextPosition, TaskContainer task) {
return 0;
} }
public boolean isSelected(TaskContainer task) { public boolean isSelected(TaskContainer task) {
@ -81,9 +85,9 @@ public class TaskAdapter {
return false; return false;
} }
public void moved(int from, int to) {} public void moved(int from, int to, int indent) {}
public void indented(int position, int delta) {} public void swiped(int position, int delta) {}
public TaskContainer getTask(int position) { public TaskContainer getTask(int position) {
return helper.getItem(position); return helper.getItem(position);

@ -43,32 +43,6 @@ public abstract class GoogleTaskDao {
"UPDATE google_tasks SET gt_order = gt_order - 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order >= :position") "UPDATE google_tasks SET gt_order = gt_order - 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order >= :position")
abstract void shiftUp(String listId, long parent, long position); abstract void shiftUp(String listId, long parent, long position);
@Transaction
public void unindent(GoogleTask parent, GoogleTask task) {
String list = parent.getListId();
shiftUp(list, task.getParent(), task.getOrder());
long newPosition = parent.getOrder() + 1;
shiftDown(list, 0, newPosition);
task.setParent(0);
task.setOrder(newPosition);
task.setMoved(true);
update(task);
}
@Transaction
public void indent(GoogleTask previous, GoogleTask task) {
shiftUp(previous.getListId(), 0, task.getOrder());
if (previous.getParent() == 0) {
task.setParent(previous.getTask());
task.setOrder(0);
} else {
task.setParent(previous.getParent());
task.setOrder(previous.getOrder() + 1);
}
task.setMoved(true);
update(task);
}
@Transaction @Transaction
public void move(GoogleTask task, long newParent, long newPosition) { public void move(GoogleTask task, long newParent, long newPosition) {
long previousParent = task.getParent(); long previousParent = task.getParent();

@ -13,6 +13,7 @@ public class TaskContainer {
public long primarySort; public long primarySort;
public long secondarySort; public long secondarySort;
@Deprecated public int indent; @Deprecated public int indent;
private int targetIndent;
public String getTagsString() { public String getTagsString() {
return tags; return tags;
@ -83,11 +84,15 @@ public class TaskContainer {
} }
public int getIndent() { public int getIndent() {
if (googletask != null) {
return getParent() > 0 ? 1 : 0;
}
return indent; return indent;
} }
public void setIndent(int indent) { public void setIndent(int indent) {
this.indent = indent; this.indent = indent;
targetIndent = indent;
} }
@Override @Override
@ -113,6 +118,12 @@ public class TaskContainer {
if (secondarySort != that.secondarySort) { if (secondarySort != that.secondarySort) {
return false; return false;
} }
if (indent != that.indent) {
return false;
}
if (targetIndent != that.targetIndent) {
return false;
}
if (task != null ? !task.equals(that.task) : that.task != null) { if (task != null ? !task.equals(that.task) : that.task != null) {
return false; return false;
} }
@ -135,6 +146,8 @@ public class TaskContainer {
result = 31 * result + siblings; result = 31 * result + siblings;
result = 31 * result + (int) (primarySort ^ (primarySort >>> 32)); result = 31 * result + (int) (primarySort ^ (primarySort >>> 32));
result = 31 * result + (int) (secondarySort ^ (secondarySort >>> 32)); result = 31 * result + (int) (secondarySort ^ (secondarySort >>> 32));
result = 31 * result + indent;
result = 31 * result + targetIndent;
return result; return result;
} }
@ -161,6 +174,8 @@ public class TaskContainer {
+ secondarySort + secondarySort
+ ", indent=" + ", indent="
+ indent + indent
+ ", targetIndent="
+ targetIndent
+ '}'; + '}';
} }
@ -191,4 +206,12 @@ public class TaskContainer {
public GoogleTask getGoogleTask() { public GoogleTask getGoogleTask() {
return googletask; return googletask;
} }
public void setTargetIndent(int indent) {
targetIndent = indent;
}
public int getTargetIndent() {
return targetIndent;
}
} }

@ -1,6 +1,8 @@
package org.tasks.tasklist; package org.tasks.tasklist;
import static androidx.recyclerview.widget.ItemTouchHelper.DOWN; 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 androidx.recyclerview.widget.ItemTouchHelper.UP;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -10,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.utility.Flags; import com.todoroo.astrid.utility.Flags;
import org.tasks.data.TaskContainer;
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback { public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final TaskAdapter adapter; private final TaskAdapter adapter;
@ -19,6 +22,7 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
private int from = -1; private int from = -1;
private int to = -1; private int to = -1;
private boolean dragging; private boolean dragging;
private boolean swiping;
ItemTouchHelperCallback( ItemTouchHelperCallback(
TaskAdapter adapter, TaskAdapter adapter,
@ -38,51 +42,50 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
recyclerAdapter.startActionMode(); recyclerAdapter.startActionMode();
((ViewHolder) viewHolder).setMoving(true); ((ViewHolder) viewHolder).setMoving(true);
dragging = true; dragging = true;
int position = viewHolder.getAdapterPosition();
updateIndents((ViewHolder) viewHolder, position, position);
} else if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
((ViewHolder) viewHolder).setSwiping(true);
swiping = true;
} }
} }
@Override @Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return adapter.isManuallySorted() && adapter.getNumSelected() == 0 return adapter.isManuallySorted() && adapter.getNumSelected() == 0
? makeMovementFlags(UP | DOWN, getSwipeFlags((ViewHolder) viewHolder)) ? makeMovementFlags(UP | DOWN | LEFT | RIGHT, 0)
: makeMovementFlags(0, 0); : makeMovementFlags(0, 0);
} }
private int getSwipeFlags(ViewHolder vh) {
int indentFlags = 0;
if (vh.isIndented()) {
indentFlags |= ItemTouchHelper.LEFT;
}
int position = vh.getAdapterPosition();
if (position > 0 && adapter.canIndent(position, vh.task)) {
indentFlags |= ItemTouchHelper.RIGHT;
}
return indentFlags;
}
@Override @Override
public boolean onMove( public boolean onMove(
@NonNull RecyclerView recyclerView, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder source, @NonNull RecyclerView.ViewHolder src,
@NonNull RecyclerView.ViewHolder target) { @NonNull RecyclerView.ViewHolder target) {
recyclerAdapter.finishActionMode(); recyclerAdapter.finishActionMode();
int fromPosition = source.getAdapterPosition(); int fromPosition = src.getAdapterPosition();
int toPosition = target.getAdapterPosition(); int toPosition = target.getAdapterPosition();
if (!adapter.canMove((ViewHolder) source, (ViewHolder) target)) { ViewHolder source = (ViewHolder) src;
if (!adapter.canMove(source, (ViewHolder) target)) {
return false; return false;
} }
if (from == -1) { if (from == -1) {
((ViewHolder) source).setSelected(false); source.setSelected(false);
from = fromPosition; from = fromPosition;
} }
to = toPosition; to = toPosition;
recyclerAdapter.notifyItemMoved(fromPosition, toPosition); recyclerAdapter.notifyItemMoved(fromPosition, toPosition);
updateIndents(source, from, to);
return true; return true;
} }
@Override private void updateIndents(ViewHolder source, int from, int to) {
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { TaskContainer task = source.task;
return .2f; source.setMinIndent(
to == 0 || to == recyclerAdapter.getItemCount() - 1
? 0
: adapter.minIndent(from <= to ? to + 1 : to, task));
source.setMaxIndent(adapter.maxIndent(to, task));
} }
@Override @Override
@ -94,9 +97,39 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
float dY, float dY,
int actionState, int actionState,
boolean isCurrentlyActive) { boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { ViewHolder vh = (ViewHolder) viewHolder;
float shiftSize = ((ViewHolder) viewHolder).getShiftSize(); TaskContainer task = vh.task;
dX = Math.max(-shiftSize, Math.min(shiftSize, dX)); 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) {
recyclerAdapter.finishActionMode();
vh.setSelected(false);
}
}
if (targetIndent < minIndent) {
task.setTargetIndent(minIndent);
} else if (targetIndent > maxIndent) {
task.setTargetIndent(maxIndent);
} else {
task.setTargetIndent(targetIndent);
}
}
dX = (task.getTargetIndent() - task.getIndent()) * shiftSize;
} }
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
} }
@ -106,17 +139,27 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
super.clearView(recyclerView, viewHolder); super.clearView(recyclerView, viewHolder);
ViewHolder vh = (ViewHolder) viewHolder; ViewHolder vh = (ViewHolder) viewHolder;
vh.setMoving(false); vh.setMoving(false);
onClear.run(); vh.setSwiping(false);
dragging = false; dragging = false;
swiping = false;
onClear.run();
if (recyclerAdapter.isActionModeActive()) { if (recyclerAdapter.isActionModeActive()) {
recyclerAdapter.toggle(vh); recyclerAdapter.toggle(vh);
} else { } else {
TaskContainer task = vh.task;
int targetIndent = task.getTargetIndent();
if (from >= 0 && from != to) { if (from >= 0 && from != to) {
if (from < to) { if (from < to) {
to++; to++;
} }
recyclerAdapter.moved(from, to); vh.task.setIndent(targetIndent);
taskList.loadTaskListContent(); vh.setIndent(targetIndent);
recyclerAdapter.moved(from, to, targetIndent);
} else if (task.getIndent() != targetIndent) {
int position = vh.getAdapterPosition();
vh.task.setIndent(targetIndent);
vh.setIndent(targetIndent);
recyclerAdapter.moved(position, position, targetIndent);
} }
} }
from = -1; from = -1;
@ -126,11 +169,20 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapter.indented(viewHolder.getAdapterPosition(), direction == ItemTouchHelper.RIGHT ? 1 : -1); ViewHolder vh = (ViewHolder) viewHolder;
vh.setSwiping(false);
swiping = false;
int delta = direction == ItemTouchHelper.RIGHT ? 1 : -1;
int position = viewHolder.getAdapterPosition();
recyclerAdapter.swiped(position, delta);
taskList.loadTaskListContent(); taskList.loadTaskListContent();
} }
public boolean isDragging() { boolean isDragging() {
return dragging; return dragging;
} }
boolean isSwiping() {
return swiping;
}
} }

@ -99,7 +99,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
updates.add(update); updates.add(update);
if (!itemTouchHelperCallback.isDragging()) { if (!itemTouchHelperCallback.isDragging() && !itemTouchHelperCallback.isSwiping()) {
drainQueue(); drainQueue();
} }
} }
@ -258,9 +258,13 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
notifyItemRangeChanged(position, count, payload); notifyItemRangeChanged(position, count, payload);
} }
void moved(int from, int to) { void moved(int from, int to, int indent) {
adapter.moved(from, to); adapter.moved(from, to, indent);
TaskContainer task = list.remove(from); TaskContainer task = list.remove(from);
list.add(from < to ? to - 1 : to, task); list.add(from < to ? to - 1 : to, task);
} }
void swiped(int position, int delta) {
adapter.swiped(position, delta);
}
} }

@ -83,6 +83,9 @@ public class ViewHolder extends RecyclerView.ViewHolder {
private boolean selected; private boolean selected;
private boolean moving; private boolean moving;
private boolean isGoogleTaskList; private boolean isGoogleTaskList;
private boolean swiping;
private int minIndent;
private int maxIndent;
ViewHolder( ViewHolder(
Activity context, Activity context,
@ -200,10 +203,6 @@ public class ViewHolder extends RecyclerView.ViewHolder {
return Math.round(indent * getShiftSize()); return Math.round(indent * getShiftSize());
} }
boolean isIndented() {
return indent > 0;
}
void bindView(TaskContainer task, boolean isGoogleTaskList) { void bindView(TaskContainer task, boolean isGoogleTaskList) {
this.task = task; this.task = task;
this.isGoogleTaskList = isGoogleTaskList; this.isGoogleTaskList = isGoogleTaskList;
@ -328,6 +327,40 @@ public class ViewHolder extends RecyclerView.ViewHolder {
setTaskAppearance(); setTaskAppearance();
} }
public int getIndent() {
return indent;
}
void setSwiping(boolean swiping) {
this.swiping = swiping;
}
boolean isSwiping() {
return swiping;
}
void setMinIndent(int minIndent) {
this.minIndent = minIndent;
if (task.getTargetIndent() < minIndent) {
task.setTargetIndent(minIndent);
}
}
void setMaxIndent(int maxIndent) {
this.maxIndent = maxIndent;
if (task.getTargetIndent() > maxIndent) {
task.setTargetIndent(maxIndent);
}
}
int getMinIndent() {
return minIndent;
}
int getMaxIndent() {
return maxIndent;
}
interface ViewHolderCallbacks { interface ViewHolderCallbacks {
void onCompletedTask(TaskContainer task, boolean newState); void onCompletedTask(TaskContainer task, boolean newState);

Loading…
Cancel
Save