From f34cdcaa6832c1ae09eb61fb642ad7f16c088233 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 24 Apr 2019 17:52:28 -0500 Subject: [PATCH] Replace paging library with @RawQuery --- app/build.gradle | 1 - app/licenses.yml | 12 -- app/src/main/assets/licenses.json | 34 +---- .../java/com/todoroo/andlib/sql/DBObject.java | 4 +- .../astrid/activity/TaskListFragment.java | 37 +++-- .../astrid/adapter/AstridTaskAdapter.java | 7 +- .../astrid/adapter/GoogleTaskAdapter.java | 15 +- .../todoroo/astrid/adapter/TaskAdapter.java | 45 ++---- .../com/todoroo/astrid/api/GtasksFilter.java | 2 +- .../java/com/todoroo/astrid/dao/TaskDao.java | 6 + .../java/com/todoroo/astrid/data/Task.java | 64 +------- .../main/java/org/tasks/data/GoogleTask.java | 6 +- .../org/tasks/data/LimitOffsetDataSource.java | 102 ------------- .../java/org/tasks/data/TaskContainer.java | 140 ++++++++++++++++++ .../java/org/tasks/tasklist/DiffCallback.java | 9 +- .../tasklist/TaskListRecyclerAdapter.java | 39 ++--- .../java/org/tasks/tasklist/ViewHolder.java | 10 +- .../java/org/tasks/ui/TaskListViewModel.java | 116 ++++++++------- 18 files changed, 271 insertions(+), 378 deletions(-) delete mode 100644 app/src/main/java/org/tasks/data/LimitOffsetDataSource.java create mode 100644 app/src/main/java/org/tasks/data/TaskContainer.java diff --git a/app/build.gradle b/app/build.gradle index d68e53c93..876457b31 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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}" diff --git a/app/licenses.yml b/app/licenses.yml index b765d487a..517eb7d03 100644 --- a/app/licenses.yml +++ b/app/licenses.yml @@ -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 diff --git a/app/src/main/assets/licenses.json b/app/src/main/assets/licenses.json index 1ea33dbef..ea0b65601 100644 --- a/app/src/main/assets/licenses.json +++ b/app/src/main/assets/licenses.json @@ -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", diff --git a/app/src/main/java/com/todoroo/andlib/sql/DBObject.java b/app/src/main/java/com/todoroo/andlib/sql/DBObject.java index e6696e96f..d7e057e40 100644 --- a/app/src/main/java/com/todoroo/andlib/sql/DBObject.java +++ b/app/src/main/java/com/todoroo/andlib/sql/DBObject.java @@ -18,7 +18,7 @@ public abstract class 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> 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)); } } diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java index a6808a2d7..2279f0e5d 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java @@ -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); diff --git a/app/src/main/java/com/todoroo/astrid/adapter/AstridTaskAdapter.java b/app/src/main/java/com/todoroo/astrid/adapter/AstridTaskAdapter.java index 857e0c55a..859bba1d7 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/AstridTaskAdapter.java +++ b/app/src/main/java/com/todoroo/astrid/adapter/AstridTaskAdapter.java @@ -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; diff --git a/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.java b/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.java index 229ded281..9ca266923 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.java +++ b/app/src/main/java/com/todoroo/astrid/adapter/GoogleTaskAdapter.java @@ -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> 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; diff --git a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.java b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.java index 70ebb7d4f..fdf92ad26 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.java @@ -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 selected = new HashSet<>(); - private AsyncPagedListDiffer helper; + private TaskListRecyclerAdapter helper; public int getCount() { return helper.getItemCount(); } - public void setHelper(AsyncPagedListDiffer 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) {} diff --git a/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java b/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java index 9b432a319..e45edc413 100644 --- a/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java +++ b/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java @@ -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()); diff --git a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java index 1b9d83799..db55b4c6e 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java @@ -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 fetchTasks(SimpleSQLiteQuery query); + /** * Saves the given task to the database.getDatabase(). Task must already exist. Returns true on * success. diff --git a/app/src/main/java/com/todoroo/astrid/data/Task.java b/app/src/main/java/com/todoroo/astrid/data/Task.java index e6fb68072..98df557af 100644 --- a/app/src/main/java/com/todoroo/astrid/data/Task.java +++ b/app/src/main/java/com/todoroo/astrid/data/Task.java @@ -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 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 { diff --git a/app/src/main/java/org/tasks/data/GoogleTask.java b/app/src/main/java/org/tasks/data/GoogleTask.java index 7478bed68..34fc9823b 100644 --- a/app/src/main/java/org/tasks/data/GoogleTask.java +++ b/app/src/main/java/org/tasks/data/GoogleTask.java @@ -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") diff --git a/app/src/main/java/org/tasks/data/LimitOffsetDataSource.java b/app/src/main/java/org/tasks/data/LimitOffsetDataSource.java deleted file mode 100644 index 69bf082fb..000000000 --- a/app/src/main/java/org/tasks/data/LimitOffsetDataSource.java +++ /dev/null @@ -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 { - - 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 convertRows(Cursor cursor) { - List result = new ArrayList<>(); - while (cursor.moveToNext()) { - result.add(new Task(cursor)); - } - return result; - } - - @Nullable - @WorkerThread - private List 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 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 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 callback) { - List list = loadRange(params.startPosition, params.loadSize); - if (list != null) { - callback.onResult(list); - } else { - invalidate(); - } - } -} diff --git a/app/src/main/java/org/tasks/data/TaskContainer.java b/app/src/main/java/org/tasks/data/TaskContainer.java new file mode 100644 index 000000000..f6275cbdd --- /dev/null +++ b/app/src/main/java/org/tasks/data/TaskContainer.java @@ -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(); + } +} diff --git a/app/src/main/java/org/tasks/tasklist/DiffCallback.java b/app/src/main/java/org/tasks/tasklist/DiffCallback.java index 9b281956c..22ef4493b 100644 --- a/app/src/main/java/org/tasks/tasklist/DiffCallback.java +++ b/app/src/main/java/org/tasks/tasklist/DiffCallback.java @@ -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 { +class DiffCallback extends ItemCallback { private final TaskAdapter adapter; @@ -14,12 +14,13 @@ class DiffCallback extends ItemCallback { } @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); } } diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java index 70f2b0a23..4095c8f72 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java +++ b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java @@ -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 +public class TaskListRecyclerAdapter extends ListAdapter 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 private final ViewHolderFactory viewHolderFactory; private final TaskListFragment taskList; private final ActionModeProvider actionModeProvider; - private final AsyncPagedListDiffer asyncPagedListDiffer; private final ItemTouchHelperCallback itemTouchHelperCallback; private ActionMode mode = null; @@ -41,14 +37,13 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter 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 @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 } @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 @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 recyclerView.setScrollY(scrollY); } - public void setList(PagedList list) { - asyncPagedListDiffer.submitList(list); - } - public void setAnimate(boolean animate) { this.animate = animate; } - public AsyncPagedListDiffer getAsyncPagedListDiffer() { - return asyncPagedListDiffer; - } - boolean isActionModeActive() { return mode != null; } @@ -223,4 +205,9 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter void onDestroyActionMode() { mode = null; } + + @Override + public TaskContainer getItem(int position) { + return super.getItem(position); + } } diff --git a/app/src/main/java/org/tasks/tasklist/ViewHolder.java b/app/src/main/java/org/tasks/tasklist/ViewHolder.java index 98b409817..4ef977fc4 100644 --- a/app/src/main/java/org/tasks/tasklist/ViewHolder.java +++ b/app/src/main/java/org/tasks/tasklist/ViewHolder.java @@ -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); diff --git a/app/src/main/java/org/tasks/ui/TaskListViewModel.java b/app/src/main/java/org/tasks/ui/TaskListViewModel.java index 9794b204f..c1635c6d0 100644 --- a/app/src/main/java/org/tasks/ui/TaskListViewModel.java +++ b/app/src/main/java/org/tasks/ui/TaskListViewModel.java @@ -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> tasks = new MutableLiveData<>(); @Inject Preferences preferences; + @Inject TaskDao taskDao; @Inject Database database; - - private LimitOffsetDataSource latest; - private LiveData> tasks; private Filter filter; - private Property[] properties; + private CompositeDisposable disposable = new CompositeDisposable(); - public LiveData> 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> 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> getLiveData(Property[] properties) { - return new LivePagedListBuilder<>( - new Factory() { - @Override - public LimitOffsetDataSource create() { - latest = toDataSource(filter, properties); - return latest; - } - }, - 20) - .build(); - } + private String getQuery(Filter filter) { + List 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(); } }