Fix action mode memory leak

gtask_related_email
Alex Baker 5 years ago
parent 48a218e63d
commit 72101b2ebb

@ -9,6 +9,7 @@ package com.todoroo.astrid.activity;
import static android.app.Activity.RESULT_OK;
import static androidx.core.content.ContextCompat.getColor;
import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread;
import static org.tasks.activities.RemoteListSupportPicker.newRemoteListSupportPicker;
import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_CALENDAR;
import static org.tasks.ui.CheckBoxes.getPriorityColor;
@ -20,12 +21,16 @@ import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MenuItem.OnActionExpandListener;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.view.ActionMode.Callback;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.SearchView.OnQueryTextListener;
import androidx.appcompat.widget.Toolbar;
@ -54,8 +59,10 @@ import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
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 com.todoroo.astrid.utility.Flags;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
@ -83,7 +90,6 @@ import org.tasks.intents.TaskIntents;
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.MenuColorizer;
@ -94,21 +100,23 @@ public final class TaskListFragment extends InjectingFragment
implements OnRefreshListener,
OnMenuItemClickListener,
OnActionExpandListener,
OnQueryTextListener {
OnQueryTextListener,
Callback {
public static final String TAGS_METADATA_JOIN = "for_tags"; // $NON-NLS-1$
public static final String GTASK_METADATA_JOIN = "googletask"; // $NON-NLS-1$
public static final String CALDAV_METADATA_JOIN = "for_caldav"; // $NON-NLS-1$
public static final String ACTION_RELOAD = "action_reload";
public static final String ACTION_DELETED = "action_deleted";
public static final int REQUEST_MOVE_TASKS = 10103;
private static final String EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids";
private static final String EXTRA_SEARCH = "extra_search";
private static final int VOICE_RECOGNITION_REQUEST_CODE = 1234;
private static final String EXTRA_FILTER = "extra_filter";
private static final String FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker";
private static final String FRAG_TAG_SORT_DIALOG = "frag_tag_sort_dialog";
private static final int REQUEST_CALDAV_SETTINGS = 10101;
private static final int REQUEST_GTASK_SETTINGS = 10102;
private static final int REQUEST_MOVE_TASKS = 10103;
private static final int REQUEST_FILTER_SETTINGS = 10104;
private static final int REQUEST_TAG_SETTINGS = 10105;
@ -127,10 +135,10 @@ public final class TaskListFragment extends InjectingFragment
@Inject LocalBroadcastManager localBroadcastManager;
@Inject Device device;
@Inject TaskMover taskMover;
@Inject ActionModeProvider actionModeProvider;
@Inject Toaster toaster;
@Inject TaskAdapterProvider taskAdapterProvider;
@Inject TaskDao taskDao;
@Inject TaskDuplicator taskDuplicator;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@ -155,6 +163,7 @@ public final class TaskListFragment extends InjectingFragment
private Disposable searchDisposable;
private MenuItem search;
private String searchQuery;
private ActionMode mode = null;
private TaskListFragmentCallbackHandler callbacks;
@ -205,7 +214,7 @@ public final class TaskListFragment extends InjectingFragment
long[] longArray = savedInstanceState.getLongArray(EXTRA_SELECTED_TASK_IDS);
if (longArray != null && longArray.length > 0) {
taskAdapter.setSelected(longArray);
recyclerAdapter.startActionMode();
startActionMode();
}
}
}
@ -260,7 +269,6 @@ public final class TaskListFragment extends InjectingFragment
recyclerView,
viewHolderFactory,
this,
actionModeProvider,
taskListViewModel.getValue(),
taskDao);
taskAdapter.setHelper(recyclerAdapter);
@ -547,7 +555,7 @@ public final class TaskListFragment extends InjectingFragment
taskMover.move(
taskAdapter.getSelected(),
data.getParcelableExtra(RemoteListSupportPicker.EXTRA_SELECTED_FILTER));
recyclerAdapter.finishActionMode();
finishActionMode();
}
break;
case REQUEST_FILTER_SETTINGS:
@ -620,6 +628,61 @@ public final class TaskListFragment extends InjectingFragment
localBroadcastManager.broadcastRefresh();
}
@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:
Filter singleFilter = taskMover.getSingleFilter(taskAdapter.getSelected());
(singleFilter == null
? newRemoteListSupportPicker(this, REQUEST_MOVE_TASKS)
: newRemoteListSupportPicker(singleFilter, this, REQUEST_MOVE_TASKS))
.show(getFragmentManager(), FRAG_TAG_REMOTE_LIST_PICKER);
return true;
case R.id.delete:
dialogBuilder
.newDialog(R.string.delete_selected_tasks)
.setPositiveButton(
android.R.string.ok,
(dialogInterface, i) -> deleteSelectedItems(taskAdapter.getSelected()))
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
case R.id.copy_tasks:
dialogBuilder
.newDialog(R.string.copy_selected_tasks)
.setPositiveButton(
android.R.string.ok,
((dialogInterface, i) -> copySelectedItems(taskAdapter.getSelected())))
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
this.mode = null;
if (taskAdapter.getNumSelected() > 0) {
taskAdapter.clearSelections();
recyclerAdapter.notifyDataSetChanged();
}
}
public interface TaskListFragmentCallbackHandler {
void onTaskListItemClicked(Task task);
@ -632,4 +695,53 @@ public final class TaskListFragment extends InjectingFragment
refresh();
}
}
public boolean isActionModeActive() {
return mode != null;
}
public void startActionMode() {
if (mode == null) {
mode = ((AppCompatActivity) getActivity()).startSupportActionMode(this);
updateModeTitle();
if (taskAdapter.supportsParentingOrManualSort()) {
Flags.set(Flags.TLFP_NO_INTERCEPT_TOUCH);
}
}
}
public void finishActionMode() {
if (mode != null) {
mode.finish();
}
}
public void updateModeTitle() {
if (mode != null) {
int count = Math.max(1, taskAdapter.getNumSelected());
mode.setTitle(Integer.toString(count));
}
}
private void deleteSelectedItems(List<Long> tasks) {
finishActionMode();
List<Task> result = taskDeleter.markDeleted(tasks);
for (Task task : result) {
onTaskDelete(task);
}
makeSnackbar(
context.getString(
R.string.delete_multiple_tasks_confirmation, Integer.toString(result.size())))
.show();
}
private void copySelectedItems(List<Long> tasks) {
finishActionMode();
List<Task> duplicates = taskDuplicator.duplicate(tasks);
onTaskCreated(duplicates);
makeSnackbar(
context.getString(
R.string.copy_multiple_tasks_confirmation, Integer.toString(duplicates.size())))
.show();
}
}

@ -1,147 +0,0 @@
package org.tasks.tasklist;
import static com.todoroo.astrid.activity.TaskListFragment.REQUEST_MOVE_TASKS;
import static org.tasks.activities.RemoteListSupportPicker.newRemoteListSupportPicker;
import android.content.Context;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.appcompat.view.ActionMode;
import com.todoroo.astrid.activity.MainActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.service.TaskDuplicator;
import com.todoroo.astrid.service.TaskMover;
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 TaskMover taskMover;
private final Tracker tracker;
@Inject
public ActionModeProvider(
@ForActivity Context context,
DialogBuilder dialogBuilder,
TaskDeleter taskDeleter,
TaskDuplicator taskDuplicator,
TaskMover taskMover,
Tracker tracker) {
this.context = context;
this.dialogBuilder = dialogBuilder;
this.taskDeleter = taskDeleter;
this.taskDuplicator = taskDuplicator;
this.taskMover = taskMover;
this.tracker = tracker;
}
ActionMode startActionMode(
TaskAdapter adapter,
TaskListFragment taskList,
TaskListRecyclerAdapter taskListRecyclerAdapter) {
return ((MainActivity) 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:
Filter singleFilter = taskMover.getSingleFilter(adapter.getSelected());
(singleFilter == null
? newRemoteListSupportPicker(taskList, REQUEST_MOVE_TASKS)
: newRemoteListSupportPicker(
singleFilter, taskList, REQUEST_MOVE_TASKS))
.show(taskList.getFragmentManager(), FRAG_TAG_REMOTE_LIST_PICKER);
return true;
case R.id.delete:
dialogBuilder
.newDialog(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
.newDialog(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) {
taskListRecyclerAdapter.onDestroyActionMode();
if (adapter.getNumSelected() > 0) {
adapter.clearSelections();
taskListRecyclerAdapter.notifyDataSetChanged();
}
}
private void deleteSelectedItems() {
tracker.reportEvent(Tracking.Events.MULTISELECT_DELETE);
List<Long> tasks = adapter.getSelected();
taskListRecyclerAdapter.finishActionMode();
List<Task> result = taskDeleter.markDeleted(tasks);
for (Task task : result) {
taskList.onTaskDelete(task);
}
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();
}
});
}
}

@ -1,166 +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 android.graphics.Canvas;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.utility.Flags;
import org.tasks.data.TaskContainer;
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final TaskAdapter adapter;
private final TaskListRecyclerAdapter recyclerAdapter;
private final Runnable onClear;
private int from = -1;
private int to = -1;
private boolean dragging;
ItemTouchHelperCallback(
TaskAdapter adapter, TaskListRecyclerAdapter recyclerAdapter, Runnable onClear) {
this.adapter = adapter;
this.recyclerAdapter = recyclerAdapter;
this.onClear = onClear;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
recyclerAdapter.startActionMode();
((ViewHolder) viewHolder).setMoving(true);
dragging = true;
int position = viewHolder.getAdapterPosition();
updateIndents((ViewHolder) viewHolder, position, position);
}
}
@Override
public int getMovementFlags(RecyclerView recyclerView, 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) {
recyclerAdapter.finishActionMode();
int fromPosition = src.getAdapterPosition();
int toPosition = target.getAdapterPosition();
ViewHolder source = (ViewHolder) src;
if (!adapter.canMove(source, (ViewHolder) target)) {
return false;
}
if (from == -1) {
source.setSelected(false);
from = fromPosition;
}
to = toPosition;
recyclerAdapter.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 == recyclerAdapter.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(
Canvas c,
RecyclerView recyclerView,
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) {
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);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
ViewHolder vh = (ViewHolder) viewHolder;
vh.setMoving(false);
dragging = false;
onClear.run();
if (recyclerAdapter.isActionModeActive()) {
recyclerAdapter.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);
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;
to = -1;
Flags.checkAndClear(Flags.TLFP_NO_INTERCEPT_TOUCH);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
throw new UnsupportedOperationException();
}
boolean isDragging() {
return dragging;
}
}

@ -1,13 +1,17 @@
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.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.view.ActionMode;
import androidx.core.util.Pair;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.DiffUtil;
@ -33,9 +37,10 @@ import java.util.Objects;
import java.util.Queue;
import org.tasks.data.TaskContainer;
import org.tasks.intents.TaskIntents;
import org.tasks.tasklist.ViewHolder.ViewHolderCallbacks;
public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
implements ViewHolder.ViewHolderCallbacks, ListUpdateCallback {
implements ViewHolderCallbacks, ListUpdateCallback {
private static final int LONG_LIST_SIZE = 500;
@ -43,36 +48,31 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
private final TaskListFragment taskList;
private final RecyclerView recyclerView;
private final ViewHolderFactory viewHolderFactory;
private final ActionModeProvider actionModeProvider;
private final boolean isRemoteList;
private final ItemTouchHelperCallback itemTouchHelperCallback;
private final TaskDao taskDao;
private ActionMode mode = null;
private List<TaskContainer> list;
private PublishSubject<List<TaskContainer>> publishSubject = PublishSubject.create();
private CompositeDisposable disposables = new CompositeDisposable();
private Queue<Pair<List<TaskContainer>, DiffResult>> updates = new LinkedList<>();
private boolean dragging;
public TaskListRecyclerAdapter(
TaskAdapter adapter,
RecyclerView recyclerView,
ViewHolderFactory viewHolderFactory,
TaskListFragment taskList,
ActionModeProvider actionModeProvider,
List<TaskContainer> list,
TaskDao taskDao) {
this.adapter = adapter;
this.recyclerView = recyclerView;
this.viewHolderFactory = viewHolderFactory;
this.taskList = taskList;
this.actionModeProvider = actionModeProvider;
isRemoteList =
taskList.getFilter() instanceof GtasksFilter
|| taskList.getFilter() instanceof CaldavFilter;
this.list = list;
itemTouchHelperCallback = new ItemTouchHelperCallback(adapter, this, this::drainQueue);
this.taskDao = taskDao;
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerView);
new ItemTouchHelper(new ItemTouchHelperCallback()).attachToRecyclerView(recyclerView);
Pair<List<TaskContainer>, DiffResult> initial = Pair.create(list, null);
disposables.add(
publishSubject
@ -110,16 +110,16 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
@Override
public void onClick(ViewHolder viewHolder) {
if (mode == null) {
taskList.onTaskListItemClicked(viewHolder.task.getTask());
} else {
if (taskList.isActionModeActive()) {
toggle(viewHolder);
} else {
taskList.onTaskListItemClicked(viewHolder.task.getTask());
}
}
@Override
public void onClick(Filter filter) {
if (mode == null) {
if (!taskList.isActionModeActive()) {
FragmentActivity context = taskList.getActivity();
if (context != null) {
context.startActivity(TaskIntents.getTaskListIntent(context, filter));
@ -130,9 +130,9 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
@Override
public boolean onLongPress(ViewHolder viewHolder) {
if (!adapter.supportsParentingOrManualSort()) {
startActionMode();
taskList.startActionMode();
}
if (mode != null && !viewHolder.isMoving()) {
if (taskList.isActionModeActive() && !viewHolder.isMoving()) {
toggle(viewHolder);
}
return true;
@ -144,47 +144,16 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
taskList.broadcastRefresh();
}
public void startActionMode() {
if (mode == null) {
mode = actionModeProvider.startActionMode(adapter, taskList, this);
updateModeTitle();
if (adapter.supportsParentingOrManualSort()) {
Flags.set(Flags.TLFP_NO_INTERCEPT_TOUCH);
}
}
}
void toggle(ViewHolder viewHolder) {
private void toggle(ViewHolder viewHolder) {
adapter.toggleSelection(viewHolder.task);
notifyItemChanged(viewHolder.getAdapterPosition());
if (adapter.getSelected().isEmpty()) {
finishActionMode();
taskList.finishActionMode();
} else {
updateModeTitle();
}
}
private void updateModeTitle() {
if (mode != null) {
int count = Math.max(1, adapter.getNumSelected());
mode.setTitle(Integer.toString(count));
taskList.updateModeTitle();
}
}
public void finishActionMode() {
if (mode != null) {
mode.finish();
}
}
boolean isActionModeActive() {
return mode != null;
}
void onDestroyActionMode() {
mode = null;
}
public TaskContainer getItem(int position) {
return list.get(position);
}
@ -241,7 +210,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
updates.add(update);
if (!itemTouchHelperCallback.isDragging()) {
if (!dragging) {
drainQueue();
}
}
@ -271,10 +240,149 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
return list.size();
}
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();
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, (ViewHolder) target)) {
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 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);
}
@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.checkAndClear(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();
}
}
}

Loading…
Cancel
Save