Use paged list when not using drag and drop

gtask_related_email
Alex Baker 5 years ago
parent 21a1b9cfc7
commit 40a542b945

@ -144,6 +144,7 @@ dependencies {
annotationProcessor("androidx.room:room-compiler:${Versions.room}")
implementation("androidx.lifecycle:lifecycle-extensions:2.1.0")
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
implementation("androidx.paging:paging-runtime:2.1.1")
annotationProcessor("com.jakewharton:butterknife-compiler:${Versions.butterknife}")
implementation("com.jakewharton:butterknife:${Versions.butterknife}")

@ -726,3 +726,15 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.paging:paging-runtime:+
name: Android Paging-Runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.paging:paging-common:+
name: Android Paging-Common
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html

@ -45,7 +45,7 @@
"artifactId": {
"name": "coordinatorlayout",
"group": "androidx.coordinatorlayout",
"version": "1.1.0-beta01"
"version": "1.1.0-rc01"
}
},
{
@ -109,7 +109,7 @@
"artifactId": {
"name": "fragment",
"group": "androidx.fragment",
"version": "1.1.0-rc01"
"version": "1.1.0"
}
},
{
@ -125,7 +125,7 @@
"artifactId": {
"name": "vectordrawable-animated",
"group": "androidx.vectordrawable",
"version": "1.1.0-rc01"
"version": "1.1.0"
}
},
{
@ -925,7 +925,7 @@
"artifactId": {
"name": "recyclerview",
"group": "androidx.recyclerview",
"version": "1.1.0-beta01"
"version": "1.1.0-rc01"
}
},
{
@ -1053,7 +1053,7 @@
"artifactId": {
"name": "material",
"group": "com.google.android.material",
"version": "1.1.0-alpha10"
"version": "1.1.0-beta02"
}
},
{
@ -1101,7 +1101,7 @@
"artifactId": {
"name": "vectordrawable",
"group": "androidx.vectordrawable",
"version": "1.1.0-rc01"
"version": "1.1.0"
}
},
{
@ -1149,7 +1149,7 @@
"artifactId": {
"name": "appcompat",
"group": "androidx.appcompat",
"version": "1.1.0-rc01"
"version": "1.1.0"
}
},
{
@ -1293,7 +1293,7 @@
"artifactId": {
"name": "transition",
"group": "androidx.transition",
"version": "1.0.1"
"version": "1.2.0"
}
},
{
@ -1501,7 +1501,7 @@
"artifactId": {
"name": "kotlin-stdlib",
"group": "org.jetbrains.kotlin",
"version": "1.3.60"
"version": "1.3.61"
}
},
{
@ -1613,7 +1613,7 @@
"artifactId": {
"name": "kotlin-stdlib-common",
"group": "org.jetbrains.kotlin",
"version": "1.3.60"
"version": "1.3.61"
}
},
{
@ -1901,7 +1901,7 @@
"artifactId": {
"name": "appcompat-resources",
"group": "androidx.appcompat",
"version": "1.1.0-rc01"
"version": "1.1.0"
}
},
{
@ -1917,7 +1917,7 @@
"artifactId": {
"name": "viewpager2",
"group": "androidx.viewpager2",
"version": "1.0.0-beta03"
"version": "1.0.0-rc01"
}
},
{
@ -1933,7 +1933,7 @@
"artifactId": {
"name": "savedstate",
"group": "androidx.savedstate",
"version": "1.0.0-rc01"
"version": "1.0.0"
}
},
{
@ -1949,7 +1949,39 @@
"artifactId": {
"name": "activity",
"group": "androidx.activity",
"version": "1.0.0-rc01"
"version": "1.0.0"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright © Android Open Source Project. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://developer.android.com/topic/libraries/architecture/index.html",
"libraryName": "Android Paging-Runtime",
"artifactId": {
"name": "paging-runtime",
"group": "androidx.paging",
"version": "2.1.1"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright © Android Open Source Project. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://developer.android.com/topic/libraries/architecture/index.html",
"libraryName": "Android Paging-Common",
"artifactId": {
"name": "paging-common",
"group": "androidx.paging",
"version": "2.1.1"
}
}
]

@ -90,6 +90,8 @@ import org.tasks.intents.TaskIntents;
import org.tasks.preferences.Device;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import org.tasks.tasklist.DragAndDropRecyclerAdapter;
import org.tasks.tasklist.PagedListRecyclerAdapter;
import org.tasks.tasklist.TaskListRecyclerAdapter;
import org.tasks.tasklist.ViewHolderFactory;
import org.tasks.ui.MenuColorizer;
@ -259,18 +261,21 @@ public final class TaskListFragment extends InjectingFragment
searchQuery = savedInstanceState.getString(EXTRA_SEARCH);
}
boolean dragAndDrop = taskAdapter.supportsManualSorting() || preferences.showSubtasks();
taskListViewModel.setFilter(
searchQuery == null ? filter : createSearchFilter(searchQuery),
taskAdapter.supportsParentingOrManualSort());
searchQuery == null ? filter : createSearchFilter(searchQuery), dragAndDrop);
recyclerAdapter =
new TaskListRecyclerAdapter(
dragAndDrop
? new DragAndDropRecyclerAdapter(
taskAdapter,
recyclerView,
viewHolderFactory,
this,
taskListViewModel.getValue(),
taskDao);
taskDao)
: new PagedListRecyclerAdapter(
taskAdapter, viewHolderFactory, this, taskListViewModel.getValue(), taskDao);
taskAdapter.setHelper(recyclerAdapter);
((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
recyclerView.setLayoutManager(new LinearLayoutManager(context));

@ -14,6 +14,7 @@ import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
import static com.todoroo.andlib.utility.DateUtilities.now;
import static org.tasks.db.DbUtils.batch;
import androidx.paging.DataSource;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
@ -22,7 +23,6 @@ import androidx.room.Transaction;
import androidx.room.Update;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteDatabase;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field;
@ -33,6 +33,9 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.helper.UUIDHelper;
import java.util.List;
import org.tasks.BuildConfig;
import org.tasks.data.CaldavTask;
import org.tasks.data.GoogleTask;
import org.tasks.data.Tag;
import org.tasks.data.TaskContainer;
import org.tasks.jobs.WorkManager;
import timber.log.Timber;
@ -163,6 +166,10 @@ public abstract class TaskDao {
@Query("SELECT EXISTS(SELECT 1 FROM google_tasks WHERE gt_parent > 0 AND gt_deleted = 0)")
abstract boolean hasGoogleTaskSubtasks();
@RawQuery(observedEntities = {Task.class})
public abstract DataSource.Factory<Integer, TaskContainer> getTaskFactory(
SimpleSQLiteQuery query);
@Query("UPDATE tasks SET modified = datetime('now', 'localtime') WHERE _id in (:ids)")
public abstract void touch(List<Long> ids);

@ -0,0 +1,269 @@
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 PublishSubject<List<TaskContainer>> publishSubject = PublishSubject.create();
private CompositeDisposable disposables = new CompositeDisposable();
private 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
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, (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();
}
}
}

@ -0,0 +1,44 @@
package org.tasks.tasklist;
import androidx.paging.AsyncPagedListDiffer;
import androidx.paging.PagedList;
import androidx.recyclerview.widget.AsyncDifferConfig;
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 AsyncPagedListDiffer<TaskContainer> differ;
public PagedListRecyclerAdapter(
TaskAdapter adapter,
ViewHolderFactory viewHolderFactory,
TaskListFragment taskList,
List<TaskContainer> list,
TaskDao taskDao) {
super(adapter, viewHolderFactory, taskList, taskDao);
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 int getItemCount() {
return differ.getItemCount();
}
}

@ -1,22 +1,9 @@
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 android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.fragment.app.FragmentActivity;
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;
@ -25,60 +12,32 @@ import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter;
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;
import org.tasks.intents.TaskIntents;
import org.tasks.tasklist.ViewHolder.ViewHolderCallbacks;
public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
public abstract class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
implements ViewHolderCallbacks, ListUpdateCallback {
private static final int LONG_LIST_SIZE = 500;
private final TaskAdapter adapter;
private final TaskListFragment taskList;
private final RecyclerView recyclerView;
private final ViewHolderFactory viewHolderFactory;
private final boolean isRemoteList;
private final TaskDao taskDao;
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(
TaskListRecyclerAdapter(
TaskAdapter adapter,
RecyclerView recyclerView,
ViewHolderFactory viewHolderFactory,
TaskListFragment taskList,
List<TaskContainer> list,
TaskDao taskDao) {
this.adapter = adapter;
this.recyclerView = recyclerView;
this.viewHolderFactory = viewHolderFactory;
this.taskList = taskList;
isRemoteList =
taskList.getFilter() instanceof GtasksFilter
|| taskList.getFilter() instanceof CaldavFilter;
this.list = list;
this.taskDao = taskDao;
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));
}
@NonNull
@ -142,7 +101,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
taskList.broadcastRefresh();
}
private void toggle(ViewHolder viewHolder) {
void toggle(ViewHolder viewHolder) {
adapter.toggleSelection(viewHolder.task);
notifyItemChanged(viewHolder.getAdapterPosition());
if (adapter.getSelected().isEmpty()) {
@ -152,13 +111,9 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
}
}
public TaskContainer getItem(int position) {
return list.get(position);
}
public abstract TaskContainer getItem(int position);
public void submitList(List<TaskContainer> list) {
publishSubject.onNext(list);
}
public abstract void submitList(List<TaskContainer> list);
@Override
public void onInserted(int position, int count) {
@ -179,195 +134,4 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
public void onChanged(int position, int count, @Nullable Object payload) {
notifyItemRangeChanged(position, count, payload);
}
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, (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();
}
}
}

@ -2,15 +2,23 @@ package org.tasks.ui;
import static com.google.common.collect.Lists.newArrayList;
import static com.todoroo.andlib.sql.Field.field;
import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread;
import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread;
import static com.todoroo.andlib.utility.DateUtilities.now;
import static com.todoroo.astrid.activity.TaskListFragment.CALDAV_METADATA_JOIN;
import static com.todoroo.astrid.activity.TaskListFragment.GTASK_METADATA_JOIN;
import static com.todoroo.astrid.activity.TaskListFragment.TAGS_METADATA_JOIN;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;
import androidx.paging.DataSource.Factory;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
import androidx.sqlite.db.SimpleSQLiteQuery;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.sql.Criterion;
@ -27,6 +35,7 @@ import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Task;
import io.reactivex.Completable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
@ -34,6 +43,7 @@ import io.reactivex.schedulers.Schedulers;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import org.tasks.BuildConfig;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavTask;
import org.tasks.data.Geofence;
@ -45,8 +55,10 @@ import org.tasks.data.TaskContainer;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
public class TaskListViewModel extends ViewModel {
public class TaskListViewModel extends ViewModel implements Observer<PagedList<TaskContainer>> {
private static final PagedList.Config PAGED_LIST_CONFIG =
new PagedList.Config.Builder().setPageSize(20).build();
private static final Criterion JOIN_GTASK =
Criterion.and(
Task.ID.eq(field(GTASK_METADATA_JOIN + ".gt_task")),
@ -84,6 +96,7 @@ public class TaskListViewModel extends ViewModel {
private Filter filter;
private boolean manualSort;
private CompositeDisposable disposable = new CompositeDisposable();
private LiveData<PagedList<TaskContainer>> internal;
public void setFilter(@NonNull Filter filter, boolean manualSort) {
if (!filter.equals(this.filter)
@ -309,11 +322,22 @@ public class TaskListViewModel extends ViewModel {
invalidate();
}
private void removeObserver() {
if (internal != null) {
internal.removeObserver(this);
}
}
public void invalidate() {
assertMainThread();
removeObserver();
if (filter == null) {
return;
}
if (manualSort) {
disposable.add(
Single.fromCallable(
() ->
@ -327,15 +351,53 @@ public class TaskListViewModel extends ViewModel {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tasks::postValue, Timber::e));
} else {
List<String> queries = getQuery(preferences, filter, false, false);
if (BuildConfig.DEBUG && queries.size() != 1) {
throw new RuntimeException("Invalid queries");
}
SimpleSQLiteQuery query = new SimpleSQLiteQuery(queries.get(0));
Timber.d("paged query: %s", query.getSql());
Factory<Integer, TaskContainer> factory = taskDao.getTaskFactory(query);
LivePagedListBuilder<Integer, TaskContainer> builder =
new LivePagedListBuilder<>(factory, PAGED_LIST_CONFIG);
List<TaskContainer> current = tasks.getValue();
if (current instanceof PagedList) {
Object lastKey = ((PagedList<TaskContainer>) current).getLastKey();
if (lastKey instanceof Integer) {
builder.setInitialLoadKey((Integer) lastKey);
}
}
if (BuildConfig.DEBUG) {
builder.setFetchExecutor(command ->
Completable.fromAction(
() -> {
assertNotMainThread();
long start = now();
command.run();
Timber.d("*** paged list execution took %sms", now() - start);
})
.subscribeOn(Schedulers.io())
.subscribe());
}
internal = builder.build();
internal.observeForever(this);
}
}
@Override
protected void onCleared() {
disposable.dispose();
removeObserver();
}
public List<TaskContainer> getValue() {
List<TaskContainer> value = tasks.getValue();
return value != null ? value : Collections.emptyList();
}
@Override
public void onChanged(PagedList<TaskContainer> taskContainers) {
tasks.setValue(taskContainers);
}
}

Loading…
Cancel
Save