Replace paging library with @RawQuery

pull/820/head
Alex Baker 5 years ago
parent 7cd7699b95
commit f34cdcaa68

@ -137,7 +137,6 @@ dependencies {
annotationProcessor "androidx.room:room-compiler:${ROOM_VERSION}"
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "androidx.paging:paging-runtime:2.1.0"
annotationProcessor "com.jakewharton:butterknife-compiler:${BUTTERKNIFE_VERSION}"
implementation "com.jakewharton:butterknife:${BUTTERKNIFE_VERSION}"

@ -323,12 +323,6 @@
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-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
- artifact: androidx.arch.core:core-runtime:+
name: Android Arch-Runtime
copyrightHolder: Android Open Source Project
@ -480,12 +474,6 @@
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-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.lifecycle:lifecycle-service:+
name: Android Lifecycle Service
copyrightHolder: Android Open Source Project

@ -864,22 +864,6 @@
"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-Common",
"artifactId": {
"name": "paging-common",
"group": "androidx.paging",
"version": "2.1.0"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
@ -925,7 +909,7 @@
"artifactId": {
"name": "ical4j",
"group": "org.mnode.ical4j",
"version": "2.2.0"
"version": "2.2.3"
}
},
{
@ -1280,22 +1264,6 @@
"version": "2.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.0"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",

@ -18,7 +18,7 @@ public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
this.expression = expression;
}
protected T as(String newAlias) {
public T as(String newAlias) {
try {
T clone = (T) clone();
clone.alias = newAlias;
@ -76,7 +76,7 @@ public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
sb.append(SPACE).append(AS).append(SPACE).append(alias);
} else {
int pos = expression.indexOf('.');
if (pos > 0) {
if (pos > 0 && !expression.endsWith("*")) {
sb.append(SPACE).append(AS).append(SPACE).append(expression.substring(pos + 1));
}
}

@ -115,7 +115,7 @@ public final class TaskListFragment extends InjectingFragment
@Inject SyncAdapters syncAdapters;
@Inject TaskDeleter taskDeleter;
@Inject @ForActivity Context context;
@Inject protected Preferences preferences;
@Inject Preferences preferences;
@Inject DialogBuilder dialogBuilder;
@Inject CheckBoxes checkBoxes;
@Inject TaskCreator taskCreator;
@ -228,7 +228,7 @@ public final class TaskListFragment extends InjectingFragment
taskAdapter = taskAdapterProvider.createTaskAdapter(filter);
recyclerAdapter =
new TaskListRecyclerAdapter(taskAdapter, viewHolderFactory, this, actionModeProvider);
taskAdapter.setHelper(recyclerAdapter.getAsyncPagedListDiffer());
taskAdapter.setHelper(recyclerAdapter);
}
@Override
@ -453,26 +453,25 @@ public final class TaskListFragment extends InjectingFragment
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
taskListViewModel
.getTasks(filter, taskAdapter.getTaskProperties())
.observe(
this,
list -> {
if (list.isEmpty()) {
swipeRefreshLayout.setVisibility(View.GONE);
emptyRefreshLayout.setVisibility(View.VISIBLE);
} else {
swipeRefreshLayout.setVisibility(View.VISIBLE);
emptyRefreshLayout.setVisibility(View.GONE);
}
taskListViewModel.observe(
this,
filter,
list -> {
if (list.isEmpty()) {
swipeRefreshLayout.setVisibility(View.GONE);
emptyRefreshLayout.setVisibility(View.VISIBLE);
} else {
swipeRefreshLayout.setVisibility(View.VISIBLE);
emptyRefreshLayout.setVisibility(View.GONE);
}
// stash selected items
Bundle saveState = recyclerAdapter.getSaveState();
// stash selected items
Bundle saveState = recyclerAdapter.getSaveState();
recyclerAdapter.setList(list);
recyclerAdapter.submitList(list);
recyclerAdapter.restoreSaveState(saveState);
});
recyclerAdapter.restoreSaveState(saveState);
});
((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
recyclerAdapter.applyToRecyclerView(recyclerView);

@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.tasks.data.TaskContainer;
import org.tasks.data.TaskListMetadata;
import timber.log.Timber;
@ -32,12 +33,12 @@ public final class AstridTaskAdapter extends TaskAdapter {
}
@Override
public int getIndent(Task task) {
public int getIndent(TaskContainer task) {
return updater.getIndentForTask(task.getUuid());
}
@Override
public boolean canIndent(int position, Task task) {
public boolean canIndent(int position, TaskContainer task) {
String parentUuid = getItemUuid(position - 1);
int parentIndent = updater.getIndentForTask(parentUuid);
return getIndent(task) <= parentIndent;
@ -93,7 +94,7 @@ public final class AstridTaskAdapter extends TaskAdapter {
}
@Override
public void onCompletedTask(Task item, boolean completedState) {
public void onCompletedTask(TaskContainer item, boolean completedState) {
final String itemId = item.getUuid();
final long completionDate = completedState ? DateUtilities.now() : 0;

@ -14,6 +14,7 @@ import java.util.Map;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.TaskContainer;
import timber.log.Timber;
public final class GoogleTaskAdapter extends TaskAdapter {
@ -37,12 +38,12 @@ public final class GoogleTaskAdapter extends TaskAdapter {
}
@Override
public int getIndent(Task task) {
public int getIndent(TaskContainer task) {
return task.getIndent();
}
@Override
public boolean canIndent(int position, Task task) {
public boolean canIndent(int position, TaskContainer task) {
Task parent = getTask(position - 1);
return parent != null && getIndent(task) == 0;
}
@ -86,21 +87,13 @@ public final class GoogleTaskAdapter extends TaskAdapter {
}
}
@Override
public Property<?>[] getTaskProperties() {
ArrayList<Property<?>> properties = new ArrayList<>(Arrays.asList(TaskAdapter.PROPERTIES));
properties.add(GoogleTask.ORDER);
properties.add(GoogleTask.INDENT);
return properties.toArray(new Property<?>[properties.size()]);
}
@Override
public void onTaskDeleted(Task task) {
updater.onDeleteTask(list, task.getId());
}
@Override
public void onCompletedTask(Task item, boolean completedState) {
public void onCompletedTask(TaskContainer item, boolean completedState) {
final long itemId = item.getId();
final long completionDate = completedState ? DateUtilities.now() : 0;

@ -9,15 +9,12 @@ package com.todoroo.astrid.adapter;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.primitives.Longs.asList;
import androidx.paging.AsyncPagedListDiffer;
import com.google.common.collect.ObjectArrays;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.data.Task;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.tasks.data.TaskContainer;
import org.tasks.tasklist.TaskListRecyclerAdapter;
/**
* Adapter for displaying a user's tasks as a list
@ -26,32 +23,14 @@ import java.util.Set;
*/
public class TaskAdapter {
private static final StringProperty TAGS =
new StringProperty(
null, "group_concat(" + TaskListFragment.TAGS_METADATA_JOIN + ".tag_uid" + ", ',')")
.as("tags");
private static final StringProperty GTASK =
new StringProperty(null, TaskListFragment.GTASK_METADATA_JOIN + ".list_id").as("googletask");
private static final StringProperty CALDAV =
new StringProperty(null, TaskListFragment.CALDAV_METADATA_JOIN + ".calendar").as("caldav");
static final Property<?>[] PROPERTIES =
ObjectArrays.concat(
Task.PROPERTIES,
new Property<?>[] {
TAGS, // Concatenated list of tags
GTASK,
CALDAV
},
Property.class);
private final Set<Long> selected = new HashSet<>();
private AsyncPagedListDiffer<Task> helper;
private TaskListRecyclerAdapter helper;
public int getCount() {
return helper.getItemCount();
}
public void setHelper(AsyncPagedListDiffer<Task> helper) {
public void setHelper(TaskListRecyclerAdapter helper) {
this.helper = helper;
}
@ -72,19 +51,19 @@ public class TaskAdapter {
selected.clear();
}
public int getIndent(Task task) {
public int getIndent(TaskContainer task) {
return 0;
}
public boolean canIndent(int position, Task task) {
public boolean canIndent(int position, TaskContainer task) {
return false;
}
public boolean isSelected(Task task) {
public boolean isSelected(TaskContainer task) {
return selected.contains(task.getId());
}
public void toggleSelection(Task task) {
public void toggleSelection(TaskContainer task) {
long id = task.getId();
if (selected.contains(id)) {
selected.remove(id);
@ -106,18 +85,14 @@ public class TaskAdapter {
}
public Task getTask(int position) {
return helper.getItem(position);
return helper.getItem(position).getTask();
}
String getItemUuid(int position) {
return getTask(position).getUuid();
}
public Property<?>[] getTaskProperties() {
return PROPERTIES;
}
public void onCompletedTask(Task task, boolean newState) {}
public void onCompletedTask(TaskContainer task, boolean newState) {}
public void onTaskCreated(String uuid) {}

@ -51,7 +51,7 @@ public class GtasksFilter extends Filter {
public static String toManualOrder(String query) {
query = query.replaceAll("ORDER BY .*", "");
query = query + " ORDER BY `gtasks_order` ASC";
query = query + " ORDER BY google_tasks.`order` ASC";
return query.replace(
TaskDao.TaskCriteria.activeAndVisible().toString(),
TaskDao.TaskCriteria.notDeleted().toString());

@ -12,7 +12,9 @@ import android.database.Cursor;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.room.Update;
import androidx.sqlite.db.SimpleSQLiteQuery;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Functions;
@ -23,6 +25,7 @@ import com.todoroo.astrid.helper.UUIDHelper;
import java.util.ArrayList;
import java.util.List;
import org.tasks.BuildConfig;
import org.tasks.data.TaskContainer;
import org.tasks.jobs.WorkManager;
import timber.log.Timber;
@ -124,6 +127,9 @@ public abstract class TaskDao {
+ "WHERE completed > 0 AND calendarUri NOT NULL AND calendarUri != ''")
public abstract int clearCompletedCalendarEvents();
@RawQuery
public abstract List<TaskContainer> fetchTasks(SimpleSQLiteQuery query);
/**
* Saves the given task to the database.getDatabase(). Task must already exist. Returns true on
* success.

@ -207,10 +207,6 @@ public class Task implements Parcelable {
public String remoteId = NO_UUID;
// --- due and hide until date management
@Ignore private transient int indent;
@Ignore private transient String tags;
@Ignore private transient String googleTaskList;
@Ignore private transient String caldav;
@Ignore private transient HashMap<String, Object> transitoryData = null;
public Task() {}
@ -238,10 +234,6 @@ public class Task implements Parcelable {
final int _cursorIndexOfRepeatUntil = _cursor.getColumnIndexOrThrow("repeatUntil");
final int _cursorIndexOfCalendarUri = _cursor.getColumnIndexOrThrow("calendarUri");
final int _cursorIndexOfRemoteId = _cursor.getColumnIndexOrThrow("remoteId");
final int _cursorIndexOfIndent = _cursor.getColumnIndex("indent");
final int _cursorIndexOfTags = _cursor.getColumnIndex("tags");
final int _cursorIndexOfGoogleTasks = _cursor.getColumnIndex("googletask");
final int _cursorIndexOfCaldav = _cursor.getColumnIndex("caldav");
if (_cursor.isNull(_cursorIndexOfId)) {
id = null;
} else {
@ -327,18 +319,6 @@ public class Task implements Parcelable {
}
calendarUri = _cursor.getString(_cursorIndexOfCalendarUri);
remoteId = _cursor.getString(_cursorIndexOfRemoteId);
if (_cursorIndexOfIndent >= 0) {
indent = _cursor.getInt(_cursorIndexOfIndent);
}
if (_cursorIndexOfTags >= 0) {
tags = _cursor.getString(_cursorIndexOfTags);
}
if (_cursorIndexOfGoogleTasks >= 0) {
googleTaskList = _cursor.getString(_cursorIndexOfGoogleTasks);
}
if (_cursorIndexOfCaldav >= 0) {
caldav = _cursor.getString(_cursorIndexOfCaldav);
}
}
@Ignore
@ -389,7 +369,6 @@ public class Task implements Parcelable {
title = parcel.readString();
remoteId = parcel.readString();
transitoryData = parcel.readHashMap(ContentValues.class.getClassLoader());
indent = parcel.readInt();
}
/**
@ -804,7 +783,6 @@ public class Task implements Parcelable {
dest.writeString(title);
dest.writeString(remoteId);
dest.writeMap(transitoryData);
dest.writeInt(indent);
}
@Override
@ -862,14 +840,6 @@ public class Task implements Parcelable {
+ '}';
}
public int getIndent() {
return indent;
}
public void setIndent(int indent) {
this.indent = indent;
}
public boolean insignificantChange(Task task) {
if (this == task) {
return true;
@ -1051,24 +1021,17 @@ public class Task implements Parcelable {
return trans != null;
}
public String getTagsString() {
return tags;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Task)) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Task task = (Task) o;
if (indent != task.indent) {
return false;
}
if (id != null ? !id.equals(task.id) : task.id != null) {
return false;
}
@ -1139,18 +1102,7 @@ public class Task implements Parcelable {
if (calendarUri != null ? !calendarUri.equals(task.calendarUri) : task.calendarUri != null) {
return false;
}
if (remoteId != null ? !remoteId.equals(task.remoteId) : task.remoteId != null) {
return false;
}
if (tags != null ? !tags.equals(task.tags) : task.tags != null) {
return false;
}
if (googleTaskList != null
? !googleTaskList.equals(task.googleTaskList)
: task.googleTaskList != null) {
return false;
}
return caldav != null ? caldav.equals(task.caldav) : task.caldav == null;
return remoteId != null ? remoteId.equals(task.remoteId) : task.remoteId == null;
}
@Override
@ -1176,21 +1128,9 @@ public class Task implements Parcelable {
result = 31 * result + (repeatUntil != null ? repeatUntil.hashCode() : 0);
result = 31 * result + (calendarUri != null ? calendarUri.hashCode() : 0);
result = 31 * result + (remoteId != null ? remoteId.hashCode() : 0);
result = 31 * result + indent;
result = 31 * result + (tags != null ? tags.hashCode() : 0);
result = 31 * result + (googleTaskList != null ? googleTaskList.hashCode() : 0);
result = 31 * result + (caldav != null ? caldav.hashCode() : 0);
return result;
}
public String getGoogleTaskList() {
return googleTaskList;
}
public String getCaldav() {
return caldav;
}
@Retention(SOURCE)
@IntDef({Priority.HIGH, Priority.MEDIUM, Priority.LOW, Priority.NONE})
public @interface Priority {

@ -15,13 +15,9 @@ public class GoogleTask {
@Deprecated public static final Table TABLE = new Table("google_tasks");
@Deprecated
public static final Property.IntegerProperty INDENT =
new Property.IntegerProperty(GoogleTask.TABLE, "indent");
@Deprecated
public static final Property.IntegerProperty ORDER =
new Property.IntegerProperty(GoogleTask.TABLE, "`order`").as("gtasks_order");
new Property.IntegerProperty(GoogleTask.TABLE, "`order`");
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")

@ -1,102 +0,0 @@
package org.tasks.data;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.paging.PositionalDataSource;
import androidx.room.RoomDatabase;
import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.tasks.analytics.Tracker;
public class LimitOffsetDataSource extends PositionalDataSource<Task> {
private final String mCountQuery;
private final String mLimitOffsetQuery;
private final RoomDatabase mDb;
public LimitOffsetDataSource(RoomDatabase db, String query) {
mDb = db;
mCountQuery = "SELECT COUNT(*) FROM ( " + query + " )";
mLimitOffsetQuery = "SELECT * FROM ( " + query + " ) LIMIT ? OFFSET ?";
}
@WorkerThread
private int countItems() {
Cursor cursor;
try {
cursor = mDb.query(mCountQuery, null);
} catch (Exception e) {
Tracker.report(e);
return 0;
}
try {
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}
return 0;
} finally {
cursor.close();
}
}
@SuppressWarnings("WeakerAccess")
protected List<Task> convertRows(Cursor cursor) {
List<Task> result = new ArrayList<>();
while (cursor.moveToNext()) {
result.add(new Task(cursor));
}
return result;
}
@Nullable
@WorkerThread
private List<Task> loadRange(int startPosition, int loadCount) {
Cursor cursor = mDb.query(mLimitOffsetQuery, new Object[] {loadCount, startPosition});
//noinspection TryFinallyCanBeTryWithResources
try {
return convertRows(cursor);
} finally {
cursor.close();
}
}
@Override
public void loadInitial(
@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Task> callback) {
int totalCount = countItems();
if (totalCount == 0) {
callback.onResult(Collections.emptyList(), 0, 0);
return;
}
// bound the size requested, based on known count
final int firstLoadPosition = computeInitialLoadPosition(params, totalCount);
final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);
// convert from legacy behavior
List<Task> list = loadRange(firstLoadPosition, firstLoadSize);
if (list != null && list.size() == firstLoadSize) {
callback.onResult(list, firstLoadPosition, totalCount);
} else {
// null list, or size doesn't match request
// The size check is a WAR for Room 1.0, subsequent versions do the check in Room
invalidate();
}
}
@WorkerThread
@Override
public void loadRange(
@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Task> callback) {
List<Task> list = loadRange(params.startPosition, params.loadSize);
if (list != null) {
callback.onResult(list);
} else {
invalidate();
}
}
}

@ -0,0 +1,140 @@
package org.tasks.data;
import androidx.room.Embedded;
import com.todoroo.astrid.data.Task;
public class TaskContainer {
@Embedded public Task task;
public String tags;
public String googletask;
public String caldav;
public int order;
public int indent;
public int getIndent() {
return indent;
}
public void setIndent(int indent) {
this.indent = indent;
}
public String getTagsString() {
return tags;
}
public String getGoogleTaskList() {
return googletask;
}
public String getCaldav() {
return caldav;
}
public String getNotes() {
return task.getNotes();
}
public boolean hasNotes() {
return task.hasNotes();
}
public String getTitle() {
return task.getTitle();
}
public boolean isHidden() {
return task.isHidden();
}
public boolean isCompleted() {
return task.isCompleted();
}
public int getPriority() {
return task.getPriority();
}
public String getRecurrence() {
return task.getRecurrence();
}
public boolean hasDueDate() {
return task.hasDueDate();
}
public boolean isOverdue() {
return task.isOverdue();
}
public long getDueDate() {
return task.getDueDate();
}
public Task getTask() {
return task;
}
public long getId() {
return task.getId();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TaskContainer that = (TaskContainer) o;
if (indent != that.indent) {
return false;
}
if (task != null ? !task.equals(that.task) : that.task != null) {
return false;
}
if (tags != null ? !tags.equals(that.tags) : that.tags != null) {
return false;
}
if (googletask != null ? !googletask.equals(that.googletask) : that.googletask != null) {
return false;
}
return caldav != null ? caldav.equals(that.caldav) : that.caldav == null;
}
@Override
public int hashCode() {
int result = task != null ? task.hashCode() : 0;
result = 31 * result + (tags != null ? tags.hashCode() : 0);
result = 31 * result + (googletask != null ? googletask.hashCode() : 0);
result = 31 * result + (caldav != null ? caldav.hashCode() : 0);
result = 31 * result + indent;
return result;
}
@Override
public String toString() {
return "TaskContainer{"
+ "task="
+ task
+ ", tags='"
+ tags
+ '\''
+ ", googletask='"
+ googletask
+ '\''
+ ", caldav='"
+ caldav
+ '\''
+ ", indent="
+ indent
+ '}';
}
public String getUuid() {
return task.getUuid();
}
}

@ -3,9 +3,9 @@ package org.tasks.tasklist;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.data.Task;
import org.tasks.data.TaskContainer;
class DiffCallback extends ItemCallback<Task> {
class DiffCallback extends ItemCallback<TaskContainer> {
private final TaskAdapter adapter;
@ -14,12 +14,13 @@ class DiffCallback extends ItemCallback<Task> {
}
@Override
public boolean areItemsTheSame(@NonNull Task oldItem, @NonNull Task newItem) {
public boolean areItemsTheSame(@NonNull TaskContainer oldItem, @NonNull TaskContainer newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull Task oldItem, @NonNull Task newItem) {
public boolean areContentsTheSame(
@NonNull TaskContainer oldItem, @NonNull TaskContainer newItem) {
return oldItem.equals(newItem) && oldItem.getIndent() == adapter.getIndent(newItem);
}
}

@ -4,23 +4,20 @@ import android.os.Bundle;
import android.view.ViewGroup;
import androidx.appcompat.view.ActionMode;
import androidx.fragment.app.FragmentActivity;
import androidx.paging.AsyncPagedListDiffer;
import androidx.paging.PagedList;
import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import com.google.common.primitives.Longs;
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.utility.Flags;
import java.util.List;
import org.tasks.data.TaskContainer;
import org.tasks.intents.TaskIntents;
public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
public class TaskListRecyclerAdapter extends ListAdapter<TaskContainer, ViewHolder>
implements ViewHolder.ViewHolderCallbacks, ListUpdateCallback {
private static final String EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids";
@ -29,7 +26,6 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
private final ViewHolderFactory viewHolderFactory;
private final TaskListFragment taskList;
private final ActionModeProvider actionModeProvider;
private final AsyncPagedListDiffer<Task> asyncPagedListDiffer;
private final ItemTouchHelperCallback itemTouchHelperCallback;
private ActionMode mode = null;
@ -41,14 +37,13 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
ViewHolderFactory viewHolderFactory,
TaskListFragment taskList,
ActionModeProvider actionModeProvider) {
super(new DiffCallback(adapter));
this.adapter = adapter;
this.viewHolderFactory = viewHolderFactory;
this.taskList = taskList;
this.actionModeProvider = actionModeProvider;
itemTouchHelperCallback = new ItemTouchHelperCallback(adapter, this, taskList);
asyncPagedListDiffer =
new AsyncPagedListDiffer<>(
this, new AsyncDifferConfig.Builder<>(new DiffCallback(adapter)).build());
}
public void applyToRecyclerView(RecyclerView recyclerView) {
@ -82,7 +77,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Task task = asyncPagedListDiffer.getItem(position);
TaskContainer task = getItem(position);
if (task != null) {
holder.bindView(task);
holder.setMoving(false);
@ -94,12 +89,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
}
@Override
public int getItemCount() {
return asyncPagedListDiffer.getItemCount();
}
@Override
public void onCompletedTask(Task task, boolean newState) {
public void onCompletedTask(TaskContainer task, boolean newState) {
adapter.onCompletedTask(task, newState);
taskList.loadTaskListContent();
}
@ -107,7 +97,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
@Override
public void onClick(ViewHolder viewHolder) {
if (mode == null) {
taskList.onTaskListItemClicked(viewHolder.task);
taskList.onTaskListItemClicked(viewHolder.task.getTask());
} else {
toggle(viewHolder);
}
@ -204,18 +194,10 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
recyclerView.setScrollY(scrollY);
}
public void setList(PagedList<Task> list) {
asyncPagedListDiffer.submitList(list);
}
public void setAnimate(boolean animate) {
this.animate = animate;
}
public AsyncPagedListDiffer<Task> getAsyncPagedListDiffer() {
return asyncPagedListDiffer;
}
boolean isActionModeActive() {
return mode != null;
}
@ -223,4 +205,9 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
void onDestroyActionMode() {
mode = null;
}
@Override
public TaskContainer getItem(int position) {
return super.getItem(position);
}
}

@ -26,10 +26,10 @@ import com.google.android.material.chip.ChipGroup;
import com.google.common.collect.Lists;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.ui.CheckableImageView;
import java.util.List;
import org.tasks.R;
import org.tasks.data.TaskContainer;
import org.tasks.dialogs.Linkify;
import org.tasks.locale.Locale;
import org.tasks.preferences.Preferences;
@ -59,7 +59,7 @@ class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.due_date)
public TextView dueDate;
public Task task;
public TaskContainer task;
@BindView(R.id.rowBody)
ViewGroup rowBody;
@ -202,7 +202,7 @@ class ViewHolder extends RecyclerView.ViewHolder {
return indent > 0;
}
void bindView(Task task) {
void bindView(TaskContainer task) {
this.task = task;
setFieldContentsAndVisibility();
@ -314,7 +314,7 @@ class ViewHolder extends RecyclerView.ViewHolder {
if (newState != task.isCompleted()) {
callback.onCompletedTask(task, newState);
taskDao.setComplete(task, newState);
taskDao.setComplete(task.getTask(), newState);
}
// set check box to actual action item state
@ -323,7 +323,7 @@ class ViewHolder extends RecyclerView.ViewHolder {
interface ViewHolderCallbacks {
void onCompletedTask(Task task, boolean newState);
void onCompletedTask(TaskContainer task, boolean newState);
void onClick(ViewHolder viewHolder);

@ -1,16 +1,18 @@
package org.tasks.ui;
import static com.todoroo.andlib.sql.Field.field;
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.LiveData;
import androidx.lifecycle.LifecycleOwner;
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 com.todoroo.andlib.data.Property;
import androidx.sqlite.db.SimpleSQLiteQuery;
import com.google.common.collect.Lists;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Join;
@ -22,75 +24,73 @@ import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.api.TagFilter;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.util.Arrays;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
import javax.inject.Inject;
import org.tasks.data.CaldavTask;
import org.tasks.data.GoogleTask;
import org.tasks.data.LimitOffsetDataSource;
import org.tasks.data.Tag;
import org.tasks.data.TaskContainer;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
public class TaskListViewModel extends ViewModel {
private static final Field TASKS = field("*");
private static final StringProperty GTASK =
new StringProperty(null, GTASK_METADATA_JOIN + ".list_id").as("googletask");
private static final StringProperty CALDAV =
new StringProperty(null, CALDAV_METADATA_JOIN + ".calendar").as("caldav");
private static final Field INDENT = field("google_tasks.indent").as("indent");
private static final StringProperty TAGS =
new StringProperty(null, "group_concat(" + TAGS_METADATA_JOIN + ".tag_uid" + ", ',')")
.as("tags");
private final MutableLiveData<List<TaskContainer>> tasks = new MutableLiveData<>();
@Inject Preferences preferences;
@Inject TaskDao taskDao;
@Inject Database database;
private LimitOffsetDataSource latest;
private LiveData<PagedList<Task>> tasks;
private Filter filter;
private Property<?>[] properties;
private CompositeDisposable disposable = new CompositeDisposable();
public LiveData<PagedList<Task>> getTasks(@NonNull Filter filter, Property<?>[] properties) {
if (tasks == null
|| !filter.equals(this.filter)
|| !filter.getSqlQuery().equals(this.filter.getSqlQuery())
|| !Arrays.equals(this.properties, properties)) {
public void observe(
LifecycleOwner owner, @NonNull Filter filter, Observer<List<TaskContainer>> observer) {
if (!filter.equals(this.filter) || !filter.getSqlQuery().equals(this.filter.getSqlQuery())) {
this.filter = filter;
this.properties = properties;
tasks = getLiveData(properties);
invalidate();
}
return tasks;
tasks.observe(owner, observer);
}
private LiveData<PagedList<Task>> getLiveData(Property<?>[] properties) {
return new LivePagedListBuilder<>(
new Factory<Integer, Task>() {
@Override
public LimitOffsetDataSource create() {
latest = toDataSource(filter, properties);
return latest;
}
},
20)
.build();
}
private String getQuery(Filter filter) {
List<Field> fields = Lists.newArrayList(TASKS, TAGS, GTASK, CALDAV);
private LimitOffsetDataSource toDataSource(Filter filter, Property<?>[] properties) {
Criterion tagsJoinCriterion =
Criterion.and(Task.ID.eq(Field.field(TAGS_METADATA_JOIN + ".task")));
Criterion tagsJoinCriterion = Criterion.and(Task.ID.eq(field(TAGS_METADATA_JOIN + ".task")));
Criterion gtaskJoinCriterion =
Criterion.and(
Task.ID.eq(Field.field(GTASK_METADATA_JOIN + ".task")),
Field.field(GTASK_METADATA_JOIN + ".deleted").eq(0));
Task.ID.eq(field(GTASK_METADATA_JOIN + ".task")),
field(GTASK_METADATA_JOIN + ".deleted").eq(0));
Criterion caldavJoinCriterion =
Criterion.and(
Task.ID.eq(Field.field(CALDAV_METADATA_JOIN + ".task")),
Field.field(CALDAV_METADATA_JOIN + ".deleted").eq(0));
Task.ID.eq(field(CALDAV_METADATA_JOIN + ".task")),
field(CALDAV_METADATA_JOIN + ".deleted").eq(0));
if (filter instanceof TagFilter) {
String uuid = ((TagFilter) filter).getUuid();
tagsJoinCriterion =
Criterion.and(tagsJoinCriterion, Field.field(TAGS_METADATA_JOIN + ".tag_uid").neq(uuid));
Criterion.and(tagsJoinCriterion, field(TAGS_METADATA_JOIN + ".tag_uid").neq(uuid));
} else if (filter instanceof GtasksFilter) {
String listId = ((GtasksFilter) filter).getRemoteId();
gtaskJoinCriterion =
Criterion.and(
gtaskJoinCriterion, Field.field(GTASK_METADATA_JOIN + ".list_id").neq(listId));
Criterion.and(gtaskJoinCriterion, field(GTASK_METADATA_JOIN + ".list_id").neq(listId));
fields.add(INDENT);
} else if (filter instanceof CaldavFilter) {
String uuid = ((CaldavFilter) filter).getUuid();
caldavJoinCriterion =
Criterion.and(
caldavJoinCriterion, Field.field(CALDAV_METADATA_JOIN + ".calendar").neq(uuid));
Criterion.and(caldavJoinCriterion, field(CALDAV_METADATA_JOIN + ".calendar").neq(uuid));
}
// TODO: For now, we'll modify the query to join and include the things like tag data here.
@ -114,17 +114,10 @@ public class TaskListViewModel extends ViewModel {
groupedQuery = query + " GROUP BY " + Task.ID;
}
return getLimitOffsetDataSource(groupedQuery, properties);
}
private LimitOffsetDataSource getLimitOffsetDataSource(
String queryTemplate, Property<?>... properties) {
String query =
Query.select(properties)
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(queryTemplate))
.from(Task.TABLE)
.toString();
return new LimitOffsetDataSource(database, query);
return Query.select(fields.toArray(new Field[0]))
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(groupedQuery))
.from(Task.TABLE)
.toString();
}
public void searchByFilter(Filter filter) {
@ -133,8 +126,17 @@ public class TaskListViewModel extends ViewModel {
}
public void invalidate() {
if (latest != null) {
latest.invalidate();
}
String query = getQuery(filter);
Timber.v(query);
disposable.add(
Single.fromCallable(() -> taskDao.fetchTasks(new SimpleSQLiteQuery(query)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tasks::setValue, Timber::e));
}
@Override
protected void onCleared() {
disposable.dispose();
}
}

Loading…
Cancel
Save