Use recursive query for manual sorted google tasks

pull/996/head
Alex Baker 6 years ago
parent ebea1b72a5
commit 0d032d6a42

@ -6,6 +6,7 @@ import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.helper.UUIDHelper import com.todoroo.astrid.helper.UUIDHelper
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -31,13 +32,11 @@ class ManualGoogleTaskQueryTest : InjectingTestCase() {
@Inject lateinit var googleTaskDao: GoogleTaskDao @Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var taskDao: TaskDao @Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences @Inject lateinit var preferences: Preferences
private lateinit var filter: GtasksFilter private val filter: GtasksFilter = GtasksFilter(newGoogleTaskList(with(REMOTE_ID, "1234")))
@Before @Before
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
filter = GtasksFilter(newGoogleTaskList(with(REMOTE_ID, "1234")))
filter.setFilterQueryOverride(GtasksFilter.toManualOrder(filter.getSqlQuery()))
preferences.clear() preferences.clear()
preferences.setBoolean(R.string.p_manual_sort, true) preferences.setBoolean(R.string.p_manual_sort, true)
} }
@ -88,6 +87,17 @@ class ManualGoogleTaskQueryTest : InjectingTestCase() {
assertEquals(1, subtasks[2].secondarySort) assertEquals(1, subtasks[2].secondarySort)
} }
@Test
fun ignoreDisableSubtasksPreference() {
preferences.setBoolean(R.string.p_disable_subtasks, true)
newTask(1, 0, 0)
newTask(2, 0, 1)
val parent = query()[0]
assertTrue(parent.hasChildren())
}
private fun newTask(id: Long, order: Long, parent: Long = 0) { private fun newTask(id: Long, order: Long, parent: Long = 0) {
taskDao.insert(TaskMaker.newTask(with(ID, id), with(UUID, UUIDHelper.newUUID()))) taskDao.insert(TaskMaker.newTask(with(ID, id), with(UUID, UUIDHelper.newUUID())))
googleTaskDao.insert(newGoogleTask(with(LIST, filter.list.remoteId), with(TASK, id), with(PARENT, parent), with(ORDER, order))) googleTaskDao.insert(newGoogleTask(with(LIST, filter.list.remoteId), with(TASK, id), with(PARENT, parent), with(ORDER, order)))

@ -8,23 +8,20 @@ import org.tasks.data.TaskContainer
open class GoogleTaskManualSortAdapter internal constructor(val taskDao: TaskDao, val googleTaskDao: GoogleTaskDao) : TaskAdapter() { open class GoogleTaskManualSortAdapter internal constructor(val taskDao: TaskDao, val googleTaskDao: GoogleTaskDao) : TaskAdapter() {
override fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int): Boolean { override fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int): Boolean {
if (!source.hasChildren() || to <= 0 || to >= count - 1) { return if (!source.hasChildren() || to <= 0 || to >= count - 1) {
return true true
} } else if (from < to) {
return if (from < to) { when {
if (target.hasChildren()) { target.hasChildren() -> false
return false target.hasParent() -> !getTask(to + 1).hasParent()
else -> true
} }
if (target.hasParent()) {
target.isLastSubtask
} else true
} else { } else {
if (target.hasChildren()) { when {
return true target.hasChildren() -> true
target.hasParent() -> target.parent == source.id && target.secondarySort == 0L
else -> true
} }
if (target.hasParent()) {
target.parent == source.id && target.secondarySort == 0L
} else true
} }
} }

@ -72,7 +72,9 @@ public class TaskAdapterProvider {
GtasksFilter gtasksFilter = (GtasksFilter) filter; GtasksFilter gtasksFilter = (GtasksFilter) filter;
GoogleTaskList list = gtasksListService.getList(gtasksFilter.getStoreId()); GoogleTaskList list = gtasksListService.getList(gtasksFilter.getStoreId());
if (list != null) { if (list != null) {
return createGoogleTaskAdapter(gtasksFilter); return preferences.isManualSort()
? new GoogleTaskManualSortAdapter(taskDao, googleTaskDao)
: new GoogleTaskAdapter(taskDao, googleTaskDao, preferences.addGoogleTasksToTop());
} }
} else if (filter instanceof CaldavFilter) { } else if (filter instanceof CaldavFilter) {
CaldavFilter caldavFilter = (CaldavFilter) filter; CaldavFilter caldavFilter = (CaldavFilter) filter;
@ -102,14 +104,6 @@ public class TaskAdapterProvider {
return new AstridTaskAdapter(list, filter, updater, taskDao); return new AstridTaskAdapter(list, filter, updater, taskDao);
} }
private TaskAdapter createGoogleTaskAdapter(GtasksFilter filter) {
if (preferences.isManualSort()) {
filter.setFilterQueryOverride(GtasksFilter.toManualOrder(filter.getSqlQuery()));
return new GoogleTaskManualSortAdapter(taskDao, googleTaskDao);
}
return new GoogleTaskAdapter(taskDao, googleTaskDao, preferences.addGoogleTasksToTop());
}
private TaskAdapter createManualFilterTaskAdapter(Filter filter) { private TaskAdapter createManualFilterTaskAdapter(Filter filter) {
String filterId = null; String filterId = null;
String prefId = null; String prefId = null;

@ -49,28 +49,6 @@ public class GtasksFilter extends Filter {
icon = list.getIcon(); icon = list.getIcon();
} }
public static String toManualOrder(String query) {
query =
query.replace(
"WHERE",
"JOIN (SELECT 0 as indent, google_tasks.*, COUNT(c.gt_id) AS children, 0 AS siblings, google_tasks.gt_order AS primary_sort, NULL AS secondary_sort"
+ " FROM google_tasks"
+ " LEFT JOIN google_tasks AS c ON c.gt_parent = google_tasks.gt_task"
+ " WHERE google_tasks.gt_parent = 0 GROUP BY google_tasks.gt_task"
+ " UNION SELECT 1 as indent, c.*, 0 AS children, COUNT(s.gt_id) AS siblings, p.gt_order AS primary_sort, c.gt_order AS secondary_sort"
+ " FROM google_tasks AS c"
+ " INNER JOIN google_tasks AS p ON c.gt_parent = p.gt_task"
+ " INNER JOIN tasks ON c.gt_parent = tasks._id"
+ " LEFT JOIN google_tasks AS s ON s.gt_parent = p.gt_task"
+ " WHERE c.gt_parent > 0 AND ((tasks.completed=0) AND (tasks.deleted=0)"
+ " AND tasks.collapsed = 0"
+ " AND (tasks.hideUntil<(strftime('%s','now')*1000)))"
+ " GROUP BY c.gt_task) as g2 ON g2.gt_id = google_tasks.gt_id WHERE");
query = query.replaceAll("ORDER BY .*", "");
query = query + "ORDER BY primary_sort ASC, secondary_sort ASC";
return query;
}
private static QueryTemplate getQueryTemplate(GoogleTaskList list) { private static QueryTemplate getQueryTemplate(GoogleTaskList list) {
return new QueryTemplate() return new QueryTemplate()
.join(Join.left(GoogleTask.TABLE, Task.ID.eq(GoogleTask.TASK))) .join(Join.left(GoogleTask.TABLE, Task.ID.eq(GoogleTask.TASK)))

@ -29,6 +29,7 @@ public class SortHelper {
public static final int SORT_IMPORTANCE = 3; public static final int SORT_IMPORTANCE = 3;
public static final int SORT_MODIFIED = 4; public static final int SORT_MODIFIED = 4;
public static final int SORT_CREATED = 5; public static final int SORT_CREATED = 5;
public static final int SORT_GTASKS = 6;
private static final String ADJUSTED_DUE_DATE = private static final String ADJUSTED_DUE_DATE =
"(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)"; "(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)";
@ -135,6 +136,9 @@ public class SortHelper {
case SORT_CREATED: case SORT_CREATED:
select = "tasks.created AS sort_created"; select = "tasks.created AS sort_created";
break; break;
case SORT_GTASKS:
select = "google_tasks.gt_order AS sort_manual";
break;
default: default:
select ="(CASE WHEN (tasks.dueDate=0) " select ="(CASE WHEN (tasks.dueDate=0) "
+ // if no due date + // if no due date
@ -150,9 +154,9 @@ public class SortHelper {
return select; return select;
} }
public static Order orderForSortTypeRecursive(Preferences preferences) { public static Order orderForSortTypeRecursive(int sortMode, boolean reverse) {
Order order; Order order;
switch (preferences.getSortMode()) { switch (sortMode) {
case SORT_ALPHA: case SORT_ALPHA:
order = Order.asc("sort_title"); order = Order.asc("sort_title");
break; break;
@ -168,14 +172,17 @@ public class SortHelper {
case SORT_CREATED: case SORT_CREATED:
order = Order.desc("sort_created"); order = Order.desc("sort_created");
break; break;
case SORT_GTASKS:
order = Order.asc("sort_manual");
break;
default: default:
order = Order.asc("sort_smart"); order = Order.asc("sort_smart");
} }
if (preferences.getSortMode() != SORT_ALPHA) { if (sortMode != SORT_ALPHA) {
order.addSecondaryExpression(Order.asc("sort_title")); order.addSecondaryExpression(Order.asc("sort_title"));
} }
if (preferences.isReverseSort()) { if (reverse) {
order = order.reverse(); order = order.reverse();
} }

@ -4,6 +4,7 @@ import static org.tasks.Strings.isNullOrEmpty;
import static org.tasks.db.QueryUtils.showHidden; import static org.tasks.db.QueryUtils.showHidden;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter; import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.core.BuiltInFilterExposer; import com.todoroo.astrid.core.BuiltInFilterExposer;
@ -125,31 +126,28 @@ public class SubtasksHelper {
return map; return map;
} }
public boolean shouldUseSubtasksFragmentForFilter(Filter filter) { public boolean shouldUseSubtasksFragmentForFilter(@NonNull Filter filter) {
return preferences.isManualSort() && filter != null && filter.supportsManualSort(); return filter.supportsManualSort()
&& preferences.isManualSort()
&& !(filter instanceof GtasksFilter);
} }
public String applySubtasksToWidgetFilter(Filter filter, String query) { public String applySubtasksToWidgetFilter(Filter filter, String query) {
if (shouldUseSubtasksFragmentForFilter(filter)) { if (shouldUseSubtasksFragmentForFilter(filter)) {
TagData tagData = tagDataDao.getTagByName(filter.listingTitle);
if (filter instanceof GtasksFilter) { TaskListMetadata tlm = null;
query = GtasksFilter.toManualOrder(query); if (tagData != null) {
} else { tlm = taskListMetadataDao.fetchByTagOrFilter(tagData.getRemoteId());
TagData tagData = tagDataDao.getTagByName(filter.listingTitle); } else if (BuiltInFilterExposer.isInbox(context, filter)) {
TaskListMetadata tlm = null; tlm = taskListMetadataDao.fetchByTagOrFilter(TaskListMetadata.FILTER_ID_ALL);
if (tagData != null) { } else if (BuiltInFilterExposer.isTodayFilter(context, filter)) {
tlm = taskListMetadataDao.fetchByTagOrFilter(tagData.getRemoteId()); tlm = taskListMetadataDao.fetchByTagOrFilter(TaskListMetadata.FILTER_ID_TODAY);
} else if (BuiltInFilterExposer.isInbox(context, filter)) {
tlm = taskListMetadataDao.fetchByTagOrFilter(TaskListMetadata.FILTER_ID_ALL);
} else if (BuiltInFilterExposer.isTodayFilter(context, filter)) {
tlm = taskListMetadataDao.fetchByTagOrFilter(TaskListMetadata.FILTER_ID_TODAY);
}
query = query.replaceAll("ORDER BY .*", "");
query = query + String.format(" ORDER BY %s", getOrderString(tagData, tlm));
query = showHidden(query);
} }
query = query.replaceAll("ORDER BY .*", "");
query = query + String.format(" ORDER BY %s", getOrderString(tagData, tlm));
query = showHidden(query);
filter.setFilterQueryOverride(query); filter.setFilterQueryOverride(query);
} }
return query; return query;

@ -11,7 +11,6 @@ public class TaskContainer {
@Embedded public Location location; @Embedded public Location location;
public String tags; public String tags;
public int children; public int children;
public int siblings;
public long primarySort; public long primarySort;
public long secondarySort; public long secondarySort;
public int indent; public int indent;
@ -96,7 +95,6 @@ public class TaskContainer {
} }
TaskContainer that = (TaskContainer) o; TaskContainer that = (TaskContainer) o;
return children == that.children return children == that.children
&& siblings == that.siblings
&& primarySort == that.primarySort && primarySort == that.primarySort
&& secondarySort == that.secondarySort && secondarySort == that.secondarySort
&& indent == that.indent && indent == that.indent
@ -111,7 +109,7 @@ public class TaskContainer {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects return Objects
.hash(task, googletask, caldavTask, location, tags, children, siblings, primarySort, .hash(task, googletask, caldavTask, location, tags, children, primarySort,
secondarySort, indent, targetIndent); secondarySort, indent, targetIndent);
} }
@ -131,8 +129,6 @@ public class TaskContainer {
+ '\'' + '\''
+ ", children=" + ", children="
+ children + children
+ ", siblings="
+ siblings
+ ", primarySort=" + ", primarySort="
+ primarySort + primarySort
+ ", secondarySort=" + ", secondarySort="
@ -174,10 +170,6 @@ public class TaskContainer {
return children > 0; return children > 0;
} }
public boolean isLastSubtask() {
return secondarySort == siblings - 1;
}
public SubsetGoogleTask getGoogleTask() { public SubsetGoogleTask getGoogleTask() {
return googletask; return googletask;
} }

@ -50,7 +50,6 @@ public class TaskListQuery {
private static final Field PLACE = field("places.*"); private static final Field PLACE = field("places.*");
private static final Field CALDAV = field(CALDAV_METADATA_JOIN + ".*"); private static final Field CALDAV = field(CALDAV_METADATA_JOIN + ".*");
private static final Field CHILDREN = field("children"); private static final Field CHILDREN = field("children");
private static final Field SIBLINGS = field("siblings");
private static final Field PRIMARY_SORT = field("primary_sort").as("primarySort"); private static final Field PRIMARY_SORT = field("primary_sort").as("primarySort");
private static final Field SECONDARY_SORT = field("secondary_sort").as("secondarySort"); private static final Field SECONDARY_SORT = field("secondary_sort").as("secondarySort");
private static final Field INDENT = field("indent"); private static final Field INDENT = field("indent");
@ -71,13 +70,15 @@ public class TaskListQuery {
private static final List<Field> FIELDS = ImmutableList.of(TASKS, GTASK, CALDAV, GEOFENCE, PLACE); private static final List<Field> FIELDS = ImmutableList.of(TASKS, GTASK, CALDAV, GEOFENCE, PLACE);
public static List<String> getQuery( public static List<String> getQuery(
Preferences preferences, Preferences preferences, com.todoroo.astrid.api.Filter filter, SubtaskInfo subtasks) {
com.todoroo.astrid.api.Filter filter,
SubtaskInfo subtasks) { if (filter.supportsManualSort() && preferences.isManualSort()) {
if (filter.supportSubtasks() return subtasks.usesSubtasks() && filter instanceof GtasksFilter
&& subtasks.usesSubtasks() ? getRecursiveQuery(filter, preferences, subtasks)
&& preferences.showSubtasks() : getNonRecursiveQuery(filter, preferences);
&& !(preferences.isManualSort() && filter.supportsManualSort())) { }
if (filter.supportSubtasks() && subtasks.usesSubtasks() && preferences.showSubtasks()) {
return getRecursiveQuery(filter, preferences, subtasks); return getRecursiveQuery(filter, preferences, subtasks);
} else { } else {
return getNonRecursiveQuery(filter, preferences); return getNonRecursiveQuery(filter, preferences);
@ -92,6 +93,8 @@ public class TaskListQuery {
fields.add(TAG_QUERY); fields.add(TAG_QUERY);
fields.add(INDENT); fields.add(INDENT);
fields.add(CHILDREN); fields.add(CHILDREN);
fields.add(PRIMARY_SORT);
fields.add(SECONDARY_SORT);
String joinedQuery = String joinedQuery =
Join.inner(RECURSIVE, Task.ID.eq(RECURSIVE_TASK)) Join.inner(RECURSIVE, Task.ID.eq(RECURSIVE_TASK))
@ -153,20 +156,33 @@ public class TaskListQuery {
} }
joinedQuery += where; joinedQuery += where;
String sortSelect = SortHelper.orderSelectForSortTypeRecursive(preferences.getSortMode()); boolean manualSort = preferences.isManualSort();
boolean manualGtasks = manualSort && filter instanceof GtasksFilter;
int sortMode;
if (manualGtasks) {
sortMode = SortHelper.SORT_GTASKS;
} else {
sortMode = preferences.getSortMode();
}
boolean reverseSort = preferences.isReverseSort() && sortMode != SortHelper.SORT_GTASKS;
String sortSelect = SortHelper.orderSelectForSortTypeRecursive(sortMode);
String withClause = String withClause =
"CREATE TEMPORARY TABLE `recursive_tasks` AS\n" "CREATE TEMPORARY TABLE `recursive_tasks` AS\n"
+ "WITH RECURSIVE recursive_tasks (task, parent, collapsed, hidden, indent, title, sortField) AS (\n" + "WITH RECURSIVE recursive_tasks (task, parent, collapsed, hidden, indent, title, sortField, primary_sort, secondary_sort) AS (\n"
+ " SELECT tasks._id, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(tasks.title) AS sort_title, " + " SELECT tasks._id, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(tasks.title) AS sort_title, "
+ sortSelect + sortSelect
+ (manualGtasks ? ", google_tasks.gt_order as primary_sort" : ", NULL as primary_sort")
+ ", NULL as secondary_sort"
+ " FROM tasks\n" + " FROM tasks\n"
+ parentQuery + parentQuery
+ "\nUNION ALL SELECT tasks._id, recursive_tasks.task as parent, tasks.collapsed as collapsed, CASE WHEN recursive_tasks.collapsed > 0 OR recursive_tasks.hidden > 0 THEN 1 ELSE 0 END as hidden, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, " + "\nUNION ALL SELECT tasks._id, recursive_tasks.task as parent, tasks.collapsed as collapsed, CASE WHEN recursive_tasks.collapsed > 0 OR recursive_tasks.hidden > 0 THEN 1 ELSE 0 END as hidden, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, "
+ sortSelect + sortSelect
+ ", recursive_tasks.primary_sort as primary_sort"
+ (manualGtasks ? ", google_tasks.gt_order as secondary_sort" : ", NULL as secondary_sort")
+ " FROM tasks\n" + " FROM tasks\n"
+ subtaskQuery + subtaskQuery
+ "\nORDER BY sort_indent DESC, " + "\nORDER BY sort_indent DESC, "
+ SortHelper.orderForSortTypeRecursive(preferences) + SortHelper.orderForSortTypeRecursive(sortMode, reverseSort)
+ ") SELECT * FROM recursive_tasks"; + ") SELECT * FROM recursive_tasks";
return newArrayList( return newArrayList(
@ -184,13 +200,6 @@ public class TaskListQuery {
List<Field> fields = new ArrayList<>(FIELDS); List<Field> fields = new ArrayList<>(FIELDS);
fields.add(TAGS); fields.add(TAGS);
if (filter instanceof GtasksFilter && preferences.isManualSort()) {
fields.add(INDENT);
fields.add(CHILDREN);
fields.add(SIBLINGS);
fields.add(PRIMARY_SORT);
fields.add(SECONDARY_SORT);
}
// TODO: For now, we'll modify the query to join and include the things like tag data here. // TODO: For now, we'll modify the query to join and include the things like tag data here.
// Eventually, we might consider restructuring things so that this query is constructed // Eventually, we might consider restructuring things so that this query is constructed
// elsewhere. // elsewhere.

Loading…
Cancel
Save