Add ItemTouchHelperCallback and ActionModeProvider

pull/699/head
Alex Baker 8 years ago
parent 4699e29bb8
commit c96e518885

@ -45,7 +45,6 @@ import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.gtasks.GtasksSubtaskListFragment;
import com.todoroo.astrid.service.TaskCreator;
import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.service.TaskDuplicator;
import com.todoroo.astrid.service.TaskMover;
import com.todoroo.astrid.timers.TimerPlugin;
import java.util.List;
@ -64,6 +63,7 @@ import org.tasks.injection.InjectingFragment;
import org.tasks.preferences.Device;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import org.tasks.tasklist.ActionModeProvider;
import org.tasks.tasklist.TaskListRecyclerAdapter;
import org.tasks.tasklist.ViewHolderFactory;
import org.tasks.ui.CheckBoxes;
@ -93,7 +93,6 @@ public class TaskListFragment extends InjectingFragment
protected Filter filter;
@Inject SyncAdapters syncAdapters;
@Inject TaskDeleter taskDeleter;
@Inject TaskDuplicator taskDuplicator;
@Inject @ForActivity Context context;
@Inject Preferences preferences;
@Inject GtasksPreferenceService gtasksPreferenceService;
@ -105,6 +104,7 @@ public class TaskListFragment extends InjectingFragment
@Inject LocalBroadcastManager localBroadcastManager;
@Inject Device device;
@Inject TaskMover taskMover;
@Inject ActionModeProvider actionModeProvider;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@ -447,15 +447,11 @@ public class TaskListFragment extends InjectingFragment
taskAdapter = createTaskAdapter();
recyclerAdapter =
new TaskListRecyclerAdapter(
getActivity(),
taskAdapter,
viewHolderFactory,
this,
taskDeleter,
taskDuplicator,
tracker,
dialogBuilder);
taskAdapter.setHelper(recyclerAdapter.getHelper());
actionModeProvider);
taskAdapter.setHelper(recyclerAdapter.getAsyncPagedListDiffer());
}
protected Property<?>[] taskProperties() {

@ -0,0 +1,132 @@
package org.tasks.tasklist;
import static org.tasks.activities.RemoteListSupportPicker.newRemoteListSupportPicker;
import android.content.Context;
import android.support.v7.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.service.TaskDuplicator;
import java.util.List;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ForActivity;
import org.tasks.ui.MenuColorizer;
public class ActionModeProvider {
private static final String FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker";
private final Context context;
private final DialogBuilder dialogBuilder;
private final TaskDeleter taskDeleter;
private final TaskDuplicator taskDuplicator;
private final Tracker tracker;
@Inject
public ActionModeProvider(
@ForActivity Context context,
DialogBuilder dialogBuilder,
TaskDeleter taskDeleter,
TaskDuplicator taskDuplicator,
Tracker tracker) {
this.context = context;
this.dialogBuilder = dialogBuilder;
this.taskDeleter = taskDeleter;
this.taskDuplicator = taskDuplicator;
this.tracker = tracker;
}
public ActionMode startActionMode(
TaskAdapter adapter,
TaskListFragment taskList,
TaskListRecyclerAdapter taskListRecyclerAdapter) {
return ((TaskListActivity) context)
.startSupportActionMode(
new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.menu_multi_select, menu);
MenuColorizer.colorMenu(context, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.move_tasks:
newRemoteListSupportPicker(null, taskList, TaskListFragment.REQUEST_MOVE_TASKS)
.show(taskList.getFragmentManager(), FRAG_TAG_REMOTE_LIST_PICKER);
return true;
case R.id.delete:
dialogBuilder
.newMessageDialog(R.string.delete_selected_tasks)
.setPositiveButton(
android.R.string.ok, (dialogInterface, i) -> deleteSelectedItems())
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
case R.id.copy_tasks:
dialogBuilder
.newMessageDialog(R.string.copy_selected_tasks)
.setPositiveButton(
android.R.string.ok, ((dialogInterface, i) -> copySelectedItems()))
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
adapter.clearSelections();
taskListRecyclerAdapter.onDestroyActionMode();
}
private void deleteSelectedItems() {
tracker.reportEvent(Tracking.Events.MULTISELECT_DELETE);
List<Long> tasks = adapter.getSelected();
taskListRecyclerAdapter.finishActionMode();
List<Task> result = taskDeleter.markDeleted(tasks);
taskList.onTaskDelete(result);
taskList
.makeSnackbar(
context.getString(
R.string.delete_multiple_tasks_confirmation,
Integer.toString(result.size())))
.show();
}
private void copySelectedItems() {
tracker.reportEvent(Tracking.Events.MULTISELECT_CLONE);
List<Long> tasks = adapter.getSelected();
taskListRecyclerAdapter.finishActionMode();
List<Task> duplicates = taskDuplicator.duplicate(tasks);
taskList.onTaskCreated(duplicates);
taskList
.makeSnackbar(
context.getString(
R.string.copy_multiple_tasks_confirmation,
Integer.toString(duplicates.size())))
.show();
}
});
}
}

@ -0,0 +1,127 @@
package org.tasks.tasklist;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.utility.Flags;
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final TaskAdapter adapter;
private final TaskListRecyclerAdapter recyclerAdapter;
private final TaskListFragment taskList;
private int from = -1;
private int to = -1;
private boolean dragging;
ItemTouchHelperCallback(
TaskAdapter adapter, TaskListRecyclerAdapter recyclerAdapter, TaskListFragment taskList) {
this.adapter = adapter;
this.recyclerAdapter = recyclerAdapter;
this.taskList = taskList;
}
public boolean isDragging() {
return dragging;
}
public void setDragging(boolean dragging) {
this.dragging = dragging;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
((ViewHolder) viewHolder).setMoving(true);
}
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (!adapter.isManuallySorted()) {
return makeMovementFlags(0, 0);
}
ViewHolder vh = (ViewHolder) viewHolder;
if (!recyclerAdapter.isActionModeActive()) {
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 makeMovementFlags(0, indentFlags);
}
if (dragging) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
return makeMovementFlags(0, 0);
}
@Override
public boolean onMove(
RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
recyclerAdapter.finishActionMode();
int fromPosition = source.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (from == -1) {
((ViewHolder) source).setSelected(false);
from = fromPosition;
}
to = toPosition;
recyclerAdapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
return .2f;
}
@Override
public void onChildDraw(
Canvas c,
RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
float dX,
float dY,
int actionState,
boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float shiftSize = ((ViewHolder) viewHolder).getShiftSize();
dX = Math.max(-shiftSize, Math.min(shiftSize, dX));
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
ViewHolder vh = (ViewHolder) viewHolder;
if (dragging) {
vh.setMoving(false);
dragging = false;
if (from != -1) {
if (from >= 0 && from != to) {
if (from < to) {
to++;
}
adapter.moved(from, to);
taskList.loadTaskListContent(false);
}
}
}
from = -1;
to = -1;
Flags.checkAndClear(Flags.TLFP_NO_INTERCEPT_TOUCH);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapter.indented(viewHolder.getAdapterPosition(), direction == ItemTouchHelper.RIGHT ? 1 : -1);
taskList.loadTaskListContent(false);
}
}

@ -1,130 +1,48 @@
package org.tasks.tasklist;
import static org.tasks.activities.RemoteListSupportPicker.newRemoteListSupportPicker;
import android.app.Activity;
import android.arch.paging.AsyncPagedListDiffer;
import android.arch.paging.PagedList;
import android.graphics.Canvas;
import android.os.Bundle;
import android.support.v7.recyclerview.extensions.AsyncDifferConfig;
import android.support.v7.util.ListUpdateCallback;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.ViewGroup;
import com.google.common.primitives.Longs;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.service.TaskDuplicator;
import com.todoroo.astrid.utility.Flags;
import java.util.List;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.ui.MenuColorizer;
public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
implements ViewHolder.ViewHolderCallbacks, ListUpdateCallback {
private static final String EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids";
private static final String FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker";
private final Activity activity;
private final TaskAdapter adapter;
private final ViewHolderFactory viewHolderFactory;
private final TaskListFragment taskList;
private final TaskDeleter taskDeleter;
private final TaskDuplicator taskDuplicator;
private final Tracker tracker;
private final DialogBuilder dialogBuilder;
private final ItemTouchHelper itemTouchHelper;
private final AsyncPagedListDiffer<Task> adapterHelper;
private final ActionModeProvider actionModeProvider;
private final AsyncPagedListDiffer<Task> asyncPagedListDiffer;
private final ItemTouchHelperCallback itemTouchHelperCallback;
private ActionMode mode = null;
private boolean dragging;
private final ActionMode.Callback actionModeCallback =
new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = actionMode.getMenuInflater();
inflater.inflate(R.menu.menu_multi_select, menu);
MenuColorizer.colorMenu(activity, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.move_tasks:
newRemoteListSupportPicker(null, taskList, TaskListFragment.REQUEST_MOVE_TASKS)
.show(taskList.getFragmentManager(), FRAG_TAG_REMOTE_LIST_PICKER);
return true;
case R.id.delete:
dialogBuilder
.newMessageDialog(R.string.delete_selected_tasks)
.setPositiveButton(
android.R.string.ok, (dialogInterface, i) -> deleteSelectedItems())
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
case R.id.copy_tasks:
dialogBuilder
.newMessageDialog(R.string.copy_selected_tasks)
.setPositiveButton(
android.R.string.ok, ((dialogInterface, i) -> copySelectedItems()))
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
adapter.clearSelections();
TaskListRecyclerAdapter.this.mode = null;
if (!dragging) {
notifyDataSetChanged();
}
}
};
private boolean animate;
private RecyclerView recyclerView;
public TaskListRecyclerAdapter(
Activity activity,
TaskAdapter adapter,
ViewHolderFactory viewHolderFactory,
TaskListFragment taskList,
TaskDeleter taskDeleter,
TaskDuplicator taskDuplicator,
Tracker tracker,
DialogBuilder dialogBuilder) {
this.activity = activity;
ActionModeProvider actionModeProvider) {
this.adapter = adapter;
this.viewHolderFactory = viewHolderFactory;
this.taskList = taskList;
this.taskDeleter = taskDeleter;
this.taskDuplicator = taskDuplicator;
this.tracker = tracker;
this.dialogBuilder = dialogBuilder;
itemTouchHelper = new ItemTouchHelper(new ItemTouchHelperCallback());
adapterHelper =
this.actionModeProvider = actionModeProvider;
itemTouchHelperCallback = new ItemTouchHelperCallback(adapter, this, taskList);
asyncPagedListDiffer =
new AsyncPagedListDiffer<>(
this, new AsyncDifferConfig.Builder<>(new DiffCallback(adapter)).build());
}
@ -132,7 +50,8 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
public void applyToRecyclerView(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
recyclerView.setAdapter(this);
itemTouchHelper.attachToRecyclerView(recyclerView);
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerView);
}
public Bundle getSaveState() {
@ -145,7 +64,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
public void restoreSaveState(Bundle savedState) {
long[] longArray = savedState.getLongArray(EXTRA_SELECTED_TASK_IDS);
if (longArray != null && longArray.length > 0) {
mode = ((TaskListActivity) activity).startSupportActionMode(actionModeCallback);
mode = actionModeProvider.startActionMode(adapter, taskList, this);
adapter.setSelected(longArray);
updateModeTitle();
@ -154,15 +73,12 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewGroup view =
(ViewGroup)
LayoutInflater.from(activity).inflate(R.layout.task_adapter_row_simple, parent, false);
return viewHolderFactory.newViewHolder(view, this);
return viewHolderFactory.newViewHolder(parent, this);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Task task = adapterHelper.getItem(position);
Task task = asyncPagedListDiffer.getItem(position);
if (task != null) {
holder.bindView(task);
holder.setMoving(false);
@ -175,7 +91,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
@Override
public int getItemCount() {
return adapterHelper.getItemCount();
return asyncPagedListDiffer.getItemCount();
}
@Override
@ -202,50 +118,24 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
adapter.toggleSelection(viewHolder.task);
notifyItemChanged(viewHolder.getAdapterPosition());
if (adapter.getSelected().isEmpty()) {
dragging = false;
itemTouchHelperCallback.setDragging(false);
finishActionMode();
} else {
if (mode == null) {
mode = ((TaskListActivity) activity).startSupportActionMode(actionModeCallback);
mode = actionModeProvider.startActionMode(adapter, taskList, this);
if (adapter.isManuallySorted()) {
dragging = true;
itemTouchHelperCallback.setDragging(true);
Flags.set(Flags.TLFP_NO_INTERCEPT_TOUCH);
} else {
dragging = false;
itemTouchHelperCallback.setDragging(false);
}
} else {
dragging = false;
itemTouchHelperCallback.setDragging(false);
}
updateModeTitle();
}
}
private void deleteSelectedItems() {
tracker.reportEvent(Tracking.Events.MULTISELECT_DELETE);
List<Long> tasks = adapter.getSelected();
finishActionMode();
List<Task> result = taskDeleter.markDeleted(tasks);
taskList.onTaskDelete(result);
taskList
.makeSnackbar(
activity.getString(
R.string.delete_multiple_tasks_confirmation, Integer.toString(result.size())))
.show();
}
private void copySelectedItems() {
tracker.reportEvent(Tracking.Events.MULTISELECT_CLONE);
List<Long> tasks = adapter.getSelected();
finishActionMode();
List<Task> duplicates = taskDuplicator.duplicate(tasks);
taskList.onTaskCreated(duplicates);
taskList
.makeSnackbar(
activity.getString(
R.string.copy_multiple_tasks_confirmation, Integer.toString(duplicates.size())))
.show();
}
private void updateModeTitle() {
if (mode != null) {
mode.setTitle(Integer.toString(adapter.getSelected().size()));
@ -289,116 +179,25 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
}
public void setList(PagedList<Task> list) {
adapterHelper.submitList(list);
asyncPagedListDiffer.submitList(list);
}
public void setAnimate(boolean animate) {
this.animate = animate;
}
public AsyncPagedListDiffer<Task> getHelper() {
return adapterHelper;
public AsyncPagedListDiffer<Task> getAsyncPagedListDiffer() {
return asyncPagedListDiffer;
}
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) {
((ViewHolder) viewHolder).setMoving(true);
}
public boolean isActionModeActive() {
return mode != null;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (!adapter.isManuallySorted()) {
return makeMovementFlags(0, 0);
}
ViewHolder vh = (ViewHolder) viewHolder;
if (mode == null) {
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 makeMovementFlags(0, indentFlags);
}
if (dragging) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
return makeMovementFlags(0, 0);
}
@Override
public boolean onMove(
RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
finishActionMode();
int fromPosition = source.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (from == -1) {
((ViewHolder) source).setSelected(false);
from = fromPosition;
}
to = toPosition;
notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
return .2f;
}
@Override
public void onChildDraw(
Canvas c,
RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
float dX,
float dY,
int actionState,
boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float shiftSize = ((ViewHolder) viewHolder).getShiftSize();
dX = Math.max(-shiftSize, Math.min(shiftSize, dX));
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
ViewHolder vh = (ViewHolder) viewHolder;
if (dragging) {
vh.setMoving(false);
dragging = false;
if (from != -1) {
if (from >= 0 && from != to) {
if (from < to) {
to++;
}
adapter.moved(from, to);
taskList.loadTaskListContent(false);
}
}
}
from = -1;
to = -1;
Flags.checkAndClear(Flags.TLFP_NO_INTERCEPT_TOUCH);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
adapter.indented(
viewHolder.getAdapterPosition(), direction == ItemTouchHelper.RIGHT ? 1 : -1);
taskList.loadTaskListContent(false);
void onDestroyActionMode() {
mode = null;
if (!itemTouchHelperCallback.isDragging()) {
notifyDataSetChanged();
}
}
}

@ -7,6 +7,7 @@ import static org.tasks.preferences.ResourceResolver.getResourceId;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.todoroo.astrid.dao.TaskDao;
import javax.inject.Inject;
@ -57,10 +58,11 @@ public class ViewHolderFactory {
rowPadding = convertDpToPixels(metrics, preferences.getInt(R.string.p_rowPadding, 16));
}
ViewHolder newViewHolder(ViewGroup viewGroup, ViewHolder.ViewHolderCallbacks callbacks) {
ViewHolder newViewHolder(ViewGroup parent, ViewHolder.ViewHolderCallbacks callbacks) {
return new ViewHolder(
context,
viewGroup,
(ViewGroup)
LayoutInflater.from(context).inflate(R.layout.task_adapter_row_simple, parent, false),
showFullTaskTitle,
fontSize,
checkBoxes,

Loading…
Cancel
Save