Initial WIP commit for CalDAV subtasks support (refs #552)

pull/848/head^2
Chris Heywood 5 years ago committed by Alex Baker
parent 7b2334e8a3
commit 48680983c2

@ -23,6 +23,7 @@ public final class Query {
private final ArrayList<Join> joins = new ArrayList<>();
private SqlTable table;
private String queryTemplate = null;
private String preClause = null;
private Query(Field... fields) {
this.fields.addAll(asList(fields));
@ -61,6 +62,9 @@ public final class Query {
@Override
public String toString() {
StringBuilder sql = new StringBuilder();
if (preClause != null) {
sql.append(preClause);
}
visitSelectClause(sql);
visitFromClause(sql);
@ -118,4 +122,9 @@ public final class Query {
queryTemplate = template;
return this;
}
public Query withPreClause(String clause) {
preClause = clause;
return this;
}
}

@ -259,7 +259,7 @@ public final class TaskListFragment extends InjectingFragment
taskAdapter.isManuallySorted());
recyclerAdapter =
taskAdapter.isManuallySorted()
taskAdapter.supportsParentingOrManualSort()
? new ManualSortRecyclerAdapter(
taskAdapter,
recyclerView,

@ -55,6 +55,11 @@ public final class AstridTaskAdapter extends TaskAdapter {
return true;
}
@Override
public boolean supportsParentingOrManualSort() {
return true;
}
@Override
public void moved(int from, int to, int indent) {
TaskContainer source = getTask(from);

@ -0,0 +1,159 @@
package com.todoroo.astrid.adapter;
import static com.todoroo.andlib.utility.DateUtilities.now;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask;
import org.tasks.data.TaskContainer;
import org.tasks.tasklist.ViewHolder;
import timber.log.Timber;
import java.util.ArrayList;
import java.util.List;
public final class CaldavTaskAdapter extends TaskAdapter {
private final TaskDao taskDao;
private final CaldavDao caldavDao;
CaldavTaskAdapter(TaskDao taskDao, CaldavDao caldavDao) {
this.taskDao = taskDao;
this.caldavDao = caldavDao;
}
@Override
public int getIndent(TaskContainer task) {
return task.getIndent();
}
@Override
public boolean canMove(ViewHolder sourceVh, ViewHolder targetVh) {
TaskContainer source = sourceVh.task;
int to = targetVh.getAdapterPosition();
if (taskIsChild(source.getCaldavTask(), to)) {
return false;
}
return true;
}
@Override
public int maxIndent(int previousPosition, TaskContainer task) {
TaskContainer previous = getTask(previousPosition);
return previous.getIndent() + 1;
}
@Override
public int minIndent(int nextPosition, TaskContainer task) {
return 0;
}
@Override
public boolean isManuallySorted() {
return false;
}
@Override
public boolean supportsParentingOrManualSort() {
return true;
}
@Override
public void moved(int from, int to, int indent) {
TaskContainer task = getTask(from);
TaskContainer previous = to > 0 ? getTask(to-1) : null;
String prevTitle = previous != null ? previous.getTitle() : "";
Timber.d("Moving %s (index %s) to %s (index %s)", task.getTitle(), from, prevTitle, to);
long newParent = task.getParent();
if (indent == 0) {
newParent = 0;
} else if (previous != null) {
if (indent == previous.getIndent()) {
newParent = previous.getParent();
} else if (indent > previous.getIndent()) {
newParent = previous.getId();
}
}
// If nothing is changing, return
if (newParent == task.getParent()) {
return;
}
changeParent(task, newParent);
Task update = task.getTask();
update.setModificationDate(now());
taskDao.save(update);
}
public void changeParent(TaskContainer task, long newParent) {
CaldavTask caldavTask = task.getCaldavTask();
if (newParent == 0) {
caldavTask.setRemoteParent("");
caldavTask.setParent(0);
} else {
CaldavTask parentTask = caldavDao.getTask(newParent);
if (parentTask == null)
return;
caldavTask.setRemoteParent(parentTask.getRemoteId());
caldavTask.setParent(newParent);
}
caldavDao.update(caldavTask);
}
private boolean taskIsChild(CaldavTask parent, int destinationIndex) {
// Don't allow dropping a parent onto their child
TaskContainer ownChildCheck = getTask(destinationIndex);
long movingCaldavTaskId = parent.getId();
int itemIndex = destinationIndex;
// Iterate levels of the hierarchy
while (ownChildCheck != null && ownChildCheck.getParent() != 0) {
// If the task we're trying to move is a parent of the destination, cancel the move
if (ownChildCheck.getParent() == movingCaldavTaskId)
return true;
// Loop through the items in the view above the current task, looking for the parent
long searchParent = ownChildCheck.getParent();
while (ownChildCheck.getId() != searchParent) {
// Handle case of parent not found in search, which shouldn't ever occur
if (itemIndex == 0) {
Timber.w("Couldn't find parent");
return true;
}
ownChildCheck = getTask(--itemIndex);
}
}
return false;
}
@Override
public void onCompletedTask(TaskContainer item, boolean completedState) {
final long completionDate = completedState ? DateUtilities.now() : 0;
// TODO handle recurring tasks ala AstridTaskManager?
List<Long> parents = new ArrayList<>();
parents.add(item.getCaldavTask().getId());
TaskContainer checkTask;
Task updateTask;
for (int i = 0; i < getCount(); i++) {
checkTask = getTask(i);
if (parents.contains(checkTask.getParent())) {
Timber.d("Marking child %s completed (%s)", checkTask.getTitle(), completionDate);
updateTask = checkTask.getTask();
updateTask.setCompletionDate(completionDate);
taskDao.save(updateTask);
parents.add(checkTask.getCaldavTask().getId());
}
}
}
}

@ -70,6 +70,11 @@ public final class GoogleTaskAdapter extends TaskAdapter {
return true;
}
@Override
public boolean supportsParentingOrManualSort() {
return true;
}
@Override
public void moved(int from, int to, int indent) {
TaskContainer task = getTask(from);

@ -85,6 +85,10 @@ public class TaskAdapter {
return false;
}
public boolean supportsParentingOrManualSort() {
return false;
}
public void moved(int from, int to, int indent) {}
public TaskContainer getTask(int position) {

@ -78,7 +78,7 @@ public class TaskAdapterProvider {
CaldavFilter caldavFilter = (CaldavFilter) filter;
CaldavCalendar calendar = caldavDao.getCalendarByUuid(caldavFilter.getUuid());
if (calendar != null) {
return new TaskAdapter();
return new CaldavTaskAdapter(taskDao, caldavDao);
}
} else {
return subtasksHelper.shouldUseSubtasksFragmentForFilter(filter)

@ -51,7 +51,7 @@ public class CaldavFilter extends Filter {
private static QueryTemplate queryTemplate(CaldavCalendar caldavCalendar) {
return new QueryTemplate()
.join(Join.left(CaldavTask.TABLE, Task.ID.eq(Field.field("caldav_tasks.cd_task"))))
.join(getJoin())
.where(
Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
@ -106,4 +106,12 @@ public class CaldavFilter extends Filter {
public boolean areContentsTheSame(@NonNull FilterListItem other) {
return calendar.equals(((CaldavFilter) other).calendar);
}
private static Join getJoin() {
return Join.left(CaldavTask.TABLE, Task.ID.eq(Field.field("caldav_tasks.cd_task")));
}
public static String getJoinSql() {
return getJoin().toString();
}
}

@ -49,24 +49,31 @@ public class SortHelper {
originalSql += " ORDER BY " + order;
}
return adjustQueryForFlags(preferences, originalSql);
}
public static String adjustQueryForFlags(
Preferences preferences, String originalSql) {
String adjustedSql = originalSql;
// flags
if (preferences.getBoolean(R.string.p_show_completed_tasks, false)) {
originalSql =
originalSql.replace(Task.COMPLETION_DATE.eq(0).toString(), Criterion.all.toString());
adjustedSql =
adjustedSql.replace(Task.COMPLETION_DATE.eq(0).toString(), Criterion.all.toString());
} else {
originalSql =
originalSql.replace(
Task.COMPLETION_DATE.eq(0).toString(),
Criterion.or(
Task.COMPLETION_DATE.lte(0),
Task.COMPLETION_DATE.gt(DateUtilities.now() - 60000))
.toString());
adjustedSql =
adjustedSql.replace(
Task.COMPLETION_DATE.eq(0).toString(),
Criterion.or(
Task.COMPLETION_DATE.lte(0),
Task.COMPLETION_DATE.gt(DateUtilities.now() - 60000))
.toString());
}
if (preferences.getBoolean(R.string.p_show_hidden_tasks, false)) {
originalSql = originalSql.replace(isVisible().toString(), Criterion.all.toString());
adjustedSql = adjustedSql.replace(isVisible().toString(), Criterion.all.toString());
}
return originalSql;
return adjustedSql;
}
private static Order orderForSortType(int sortType) {
@ -110,4 +117,70 @@ public class SortHelper {
return order;
}
public static String orderSelectForSortTypeRecursive(int sortType) {
String select;
switch (sortType) {
case SORT_ALPHA:
// Return an empty string, providing a value to fill the WITH clause template
select = "''";
break;
case SORT_DUE:
select = "(CASE WHEN (tasks.dueDate=0) THEN (strftime('%s','now')*1000)*2 ELSE "
+ ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate")
+ " END)+tasks.importance AS sort_duedate";
break;
case SORT_IMPORTANCE:
select = "tasks.importance*(strftime('%s','now')*1000)+(CASE WHEN (tasks.dueDate=0) THEN (strftime('%s','now')*1000) ELSE tasks.dueDate END) AS sort_importance";
break;
case SORT_MODIFIED:
select = "tasks.modified AS sort_modified";
break;
case SORT_WIDGET:
default:
select ="(CASE WHEN (tasks.dueDate=0) "
+ // if no due date
"THEN (strftime('%s','now')*1000)*2 "
+ // then now * 2
"ELSE ("
+ ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate")
+ ") END) "
+ // else due time
"+ 172800000 * tasks.importance AS sort_smart"; // add 2 days * importance
}
return select;
}
public static Order orderForSortTypeRecursive(Preferences preferences) {
Order order;
switch (preferences.getSortMode()) {
case SORT_ALPHA:
order = Order.asc("sort_title");
break;
case SORT_DUE:
order = Order.asc("sort_duedate");
break;
case SORT_IMPORTANCE:
order = Order.asc("sort_importance");
break;
case SORT_MODIFIED:
order = Order.desc("sort_modified");
break;
case SORT_WIDGET:
default:
order = Order.asc("sort_smart");
}
if (preferences.getSortMode() != SORT_ALPHA) {
order.addSecondaryExpression(Order.asc("sort_title"));
}
if (preferences.getBoolean(R.string.p_reverse_sort, false)) {
order = order.reverse();
}
return order;
}
}

@ -60,6 +60,7 @@ import org.tasks.data.TagDao;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDao;
import org.tasks.injection.ForApplication;
import retrofit2.http.HEAD;
import timber.log.Timber;
public class CaldavSynchronizer {

@ -11,88 +11,88 @@ import java.util.List;
import org.tasks.filters.CaldavFilters;
@Dao
public interface CaldavDao {
public abstract class CaldavDao {
@Query("SELECT * FROM caldav_lists")
LiveData<List<CaldavCalendar>> subscribeToCalendars();
public abstract LiveData<List<CaldavCalendar>> subscribeToCalendars();
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1")
CaldavCalendar getCalendarByUuid(String uuid);
public abstract CaldavCalendar getCalendarByUuid(String uuid);
@Query("SELECT * FROM caldav_accounts WHERE cda_uuid = :uuid LIMIT 1")
CaldavAccount getAccountByUuid(String uuid);
public abstract CaldavAccount getAccountByUuid(String uuid);
@Query("SELECT COUNT(*) FROM caldav_accounts")
Single<Integer> accountCount();
public abstract Single<Integer> accountCount();
@Query("SELECT * FROM caldav_accounts ORDER BY UPPER(cda_name) ASC")
List<CaldavAccount> getAccounts();
public abstract List<CaldavAccount> getAccounts();
@Insert
long insert(CaldavAccount caldavAccount);
public abstract long insert(CaldavAccount caldavAccount);
@Update
void update(CaldavAccount caldavAccount);
public abstract void update(CaldavAccount caldavAccount);
@Insert
long insert(CaldavCalendar caldavCalendar);
public abstract long insert(CaldavCalendar caldavCalendar);
@Update
void update(CaldavCalendar caldavCalendar);
public abstract void update(CaldavCalendar caldavCalendar);
@Insert
long insert(CaldavTask caldavTask);
public abstract long insert(CaldavTask caldavTask);
@Insert
void insert(Iterable<CaldavTask> tasks);
public abstract void insert(Iterable<CaldavTask> tasks);
@Update
void update(CaldavTask caldavTask);
public abstract void update(CaldavTask caldavTask);
@Delete
void delete(CaldavTask caldavTask);
public abstract void delete(CaldavTask caldavTask);
@Query("SELECT * FROM caldav_tasks WHERE cd_deleted > 0 AND cd_calendar = :calendar")
List<CaldavTask> getDeleted(String calendar);
public abstract List<CaldavTask> getDeleted(String calendar);
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted = 0 LIMIT 1")
CaldavTask getTask(long taskId);
public abstract CaldavTask getTask(long taskId);
@Query("SELECT * FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object = :object LIMIT 1")
CaldavTask getTask(String calendar, String object);
public abstract CaldavTask getTask(String calendar, String object);
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId")
List<CaldavTask> getTasks(long taskId);
public abstract List<CaldavTask> getTasks(long taskId);
@Query(
"SELECT task.*, caldav_task.* FROM tasks AS task "
+ "INNER JOIN caldav_tasks AS caldav_task ON _id = cd_task "
+ "WHERE cd_deleted = 0 AND cd_vtodo IS NOT NULL AND cd_vtodo != ''")
List<CaldavTaskContainer> getTasks();
public abstract List<CaldavTaskContainer> getTasks();
@Query("SELECT * FROM caldav_lists ORDER BY cdl_name COLLATE NOCASE")
List<CaldavCalendar> getCalendars();
public abstract List<CaldavCalendar> getCalendars();
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1")
CaldavCalendar getCalendar(String uuid);
public abstract CaldavCalendar getCalendar(String uuid);
@Query("SELECT cd_object FROM caldav_tasks WHERE cd_calendar = :calendar")
List<String> getObjects(String calendar);
public abstract List<String> getObjects(String calendar);
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_object IN (:objects)")
List<Long> getTasks(String calendar, List<String> objects);
public abstract List<Long> getTasks(String calendar, List<String> objects);
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url NOT IN (:urls)")
List<CaldavCalendar> findDeletedCalendars(String account, List<String> urls);
public abstract List<CaldavCalendar> findDeletedCalendars(String account, List<String> urls);
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account AND cdl_url = :url LIMIT 1")
CaldavCalendar getCalendarByUrl(String account, String url);
public abstract CaldavCalendar getCalendarByUrl(String account, String url);
@Query("SELECT * FROM caldav_accounts WHERE cda_name = :name COLLATE NOCASE LIMIT 1")
CaldavAccount getAccountByName(String name);
public abstract CaldavAccount getAccountByName(String name);
@Query("SELECT DISTINCT cd_calendar FROM caldav_tasks WHERE cd_deleted = 0 AND cd_task IN (:tasks)")
List<String> getCalendars(List<Long> tasks);
public abstract List<String> getCalendars(List<Long> tasks);
@Query(
"SELECT caldav_lists.*, caldav_accounts.*, COUNT(tasks._id) AS count"
@ -102,17 +102,17 @@ public interface CaldavDao {
+ " LEFT JOIN tasks ON caldav_tasks.cd_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now"
+ " GROUP BY caldav_lists.cdl_uuid"
+ " ORDER BY caldav_accounts.cda_name COLLATE NOCASE, caldav_lists.cdl_name COLLATE NOCASE")
List<CaldavFilters> getCaldavFilters(long now);
public abstract List<CaldavFilters> getCaldavFilters(long now);
@Query(
"SELECT tasks._id FROM tasks "
+ "INNER JOIN tags ON tags.task = tasks._id "
+ "INNER JOIN caldav_tasks ON cd_task = tasks._id "
+ "GROUP BY tasks._id")
List<Long> getTasksWithTags();
public abstract List<Long> getTasksWithTags();
@Query("UPDATE caldav_tasks SET cd_parent = IFNULL((SELECT cd_task FROM caldav_tasks AS p WHERE p.cd_remote_id = caldav_tasks.cd_remote_parent), cd_parent) WHERE cd_calendar = :calendar AND cd_remote_parent IS NOT NULL and cd_remote_parent != ''")
void updateParents(String calendar);
public abstract void updateParents(String calendar);
@Query("WITH RECURSIVE "
+ " recursive_caldav (cd_task) AS ( "
@ -132,5 +132,5 @@ public interface CaldavDao {
+ " WHERE tasks.deleted = 0 "
+ " ) "
+ "SELECT cd_task FROM recursive_caldav")
List<Long> getChildren(List<Long> ids);
public abstract List<Long> getChildren(List<Long> ids);
}

@ -190,11 +190,21 @@ public class TaskContainer {
}
public long getParent() {
return googletask == null ? 0 : googletask.getParent();
if (googletask != null) {
return googletask.getParent();
} else if (caldavTask != null) {
return caldavTask.getParent();
} else {
return 0;
}
}
public void setParent(long parent) {
googletask.setParent(parent);
if (googletask != null) {
googletask.setParent(parent);
} else if (caldavTask != null) {
caldavTask.setParent(parent);
}
}
public boolean hasParent() {
@ -213,6 +223,10 @@ public class TaskContainer {
return googletask;
}
public CaldavTask getCaldavTask() {
return caldavTask;
}
public int getTargetIndent() {
return targetIndent;
}

@ -9,6 +9,7 @@ import android.graphics.Canvas;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.todoroo.astrid.adapter.CaldavTaskAdapter;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.utility.Flags;
import org.tasks.data.TaskContainer;
@ -42,7 +43,7 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return adapter.isManuallySorted() && adapter.getNumSelected() == 0
return adapter.supportsParentingOrManualSort() && adapter.getNumSelected() == 0
? makeMovementFlags(UP | DOWN | LEFT | RIGHT, 0)
: makeMovementFlags(0, 0);
}
@ -140,13 +141,17 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
if (from < to) {
to++;
}
vh.task.setIndent(targetIndent);
vh.setIndent(targetIndent);
if (!(adapter instanceof CaldavTaskAdapter)) {
vh.task.setIndent(targetIndent);
vh.setIndent(targetIndent);
}
recyclerAdapter.moved(from, to, targetIndent);
} else if (task.getIndent() != targetIndent) {
int position = vh.getAdapterPosition();
vh.task.setIndent(targetIndent);
vh.setIndent(targetIndent);
if (!(adapter instanceof CaldavTaskAdapter)) {
vh.task.setIndent(targetIndent);
vh.setIndent(targetIndent);
}
recyclerAdapter.moved(position, position, targetIndent);
}
}

@ -16,6 +16,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
@ -103,8 +104,10 @@ public class ManualSortRecyclerAdapter extends TaskListRecyclerAdapter {
void moved(int from, int to, int indent) {
adapter.moved(from, to, indent);
TaskContainer task = list.remove(from);
list.add(from < to ? to - 1 : to, task);
if (list instanceof ArrayList) {
TaskContainer task = list.remove(from);
list.add(from < to ? to - 1 : to, task);
}
taskList.loadTaskListContent();
}
}

@ -91,7 +91,7 @@ public abstract class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewH
@Override
public boolean onLongPress(ViewHolder viewHolder) {
if (!adapter.isManuallySorted()) {
if (!adapter.supportsParentingOrManualSort()) {
startActionMode();
}
if (mode != null && !viewHolder.isMoving()) {
@ -104,7 +104,7 @@ public abstract class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewH
if (mode == null) {
mode = actionModeProvider.startActionMode(adapter, taskList, this);
updateModeTitle();
if (adapter.isManuallySorted()) {
if (adapter.supportsParentingOrManualSort()) {
Flags.set(Flags.TLFP_NO_INTERCEPT_TOUCH);
}
}

@ -18,9 +18,11 @@ import androidx.paging.PagedList;
import androidx.sqlite.db.SimpleSQLiteQuery;
import com.google.common.collect.Lists;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
@ -36,6 +38,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import org.tasks.data.CaldavTask;
@ -61,9 +64,16 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
private static final Field SIBLINGS = field("siblings");
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 INDENT = field("indent");
private static final StringProperty TAGS =
new StringProperty(null, "group_concat(distinct(" + TAGS_METADATA_JOIN + ".tag_uid)" + ")")
.as("tags");
private static final StringProperty TAGS_RECURSIVE =
new StringProperty(null, "(SELECT group_concat(distinct(tag_uid))\n" +
"FROM tags WHERE tags.task = recursive_caldav.cd_task\n" +
"GROUP BY tags.task)")
.as("tags");
@Inject Preferences preferences;
@Inject TaskDao taskDao;
@Inject Database database;
@ -89,7 +99,7 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
}
private String getQuery(Filter filter) {
List<Field> fields = Lists.newArrayList(TASKS, TAGS, GTASK, CALDAV, GEOFENCE, PLACE);
List<Field> fields = Lists.newArrayList(TASKS, GTASK, CALDAV, GEOFENCE, PLACE);
Criterion tagsJoinCriterion = Criterion.and(Task.ID.eq(field(TAGS_METADATA_JOIN + ".task")));
Criterion gtaskJoinCriterion =
@ -114,32 +124,96 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
} else if (filter instanceof CaldavFilter) {
String uuid = ((CaldavFilter) filter).getUuid();
caldavJoinCriterion =
Criterion.and(caldavJoinCriterion, field(CALDAV_METADATA_JOIN + ".cd_calendar").neq(uuid));
Criterion.and(caldavJoinCriterion, field(CALDAV_METADATA_JOIN + ".cd_calendar").eq(uuid));
}
// 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
// elsewhere.
String joinedQuery =
Join.left(Tag.TABLE.as(TAGS_METADATA_JOIN), tagsJoinCriterion).toString()
+ Join.left(GoogleTask.TABLE.as(GTASK_METADATA_JOIN), gtaskJoinCriterion)
+ Join.left(CaldavTask.TABLE.as(CALDAV_METADATA_JOIN), caldavJoinCriterion)
+ Join.left(Geofence.TABLE, field(Geofence.TABLE_NAME + ".task").eq(Task.ID))
+ Join.left(Place.TABLE, field(Place.TABLE_NAME + ".uid").eq(field("geofences.place")))
+ filter.getSqlQuery();
String query =
SortHelper.adjustQueryForFlagsAndSort(preferences, joinedQuery, preferences.getSortMode());
String groupedQuery =
query.contains("ORDER BY")
? query.replace("ORDER BY", "GROUP BY " + Task.ID + " ORDER BY")
: query + " GROUP BY " + Task.ID;
return Query.select(fields.toArray(new Field[0]))
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(groupedQuery))
.from(Task.TABLE)
.toString();
if (filter instanceof CaldavFilter) {
// TODO This is in some ways a proof of concept demonstrating a recursive query used to pull
// in CalDAV tasks providing parenting across different sort modes. Tags are implemented
// as a subquery, which is ugly, but aggregate recursive queries aren't supported. The
// link to eg. GoogleTasks remains as it was originally explored as a drop-in replacement
// for the main query. Need to verify the approach and look at how this can be applied
// across backends plus investigate integrating more closely with the query-building
// classes in the architecture.
fields.add(TAGS_RECURSIVE);
fields.add(INDENT);
String joinedQuery =
Join.left(GoogleTask.TABLE.as(GTASK_METADATA_JOIN), gtaskJoinCriterion).toString()
+ Join.left(CaldavTask.TABLE.as(CALDAV_METADATA_JOIN), caldavJoinCriterion)
+ Join.left(Geofence.TABLE, field(Geofence.TABLE_NAME + ".task").eq(Task.ID))
+ Join.left(Place.TABLE, field(Place.TABLE_NAME + ".uid").eq(field("geofences.place")));
joinedQuery = "LEFT JOIN tasks\n"
+ "ON tasks._id = recursive_caldav.cd_task\n"
+ joinedQuery + "\n";
String uuid = ((CaldavFilter) filter).getUuid();
String sortSelect = SortHelper.orderSelectForSortTypeRecursive(preferences.getSortMode());
Order order = SortHelper.orderForSortTypeRecursive(preferences);
String filterSql = filter.getSqlQuery();
// Remove unwanted join
String joinSql = ((CaldavFilter) filter).getJoinSql();
filterSql = filterSql.replace(joinSql, "");
String calDavWithClause = "WITH RECURSIVE\n"
+ " recursive_caldav (cd_id, cd_task, indent, title, sortField) AS (\n"
+ " SELECT cd_id, cd_task, 0 AS sort_indent, UPPER(title) AS sort_title, " + sortSelect + "\n"
+ " FROM tasks\n"
+ " INNER JOIN caldav_tasks\n"
+ " ON tasks._id = cd_task\n"
+ " WHERE cd_parent = 0\n"
+ " AND cd_calendar='" + uuid + "'\n"
+ " AND " + filterSql.replace("WHERE", "") + "\n"
+ " UNION ALL\n"
+ " SELECT caldav_tasks.cd_id, caldav_tasks.cd_task, recursive_caldav.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, " + sortSelect + "\n"
+ " FROM tasks\n"
+ " INNER JOIN caldav_tasks\n"
+ " ON tasks._id = caldav_tasks.cd_task\n"
+ " INNER JOIN recursive_caldav\n"
+ " ON recursive_caldav.cd_task = caldav_tasks.cd_parent\n"
+ filterSql + "\n"
+ " ORDER BY sort_indent DESC, " + order + "\n"
+ " )\n";
calDavWithClause =
SortHelper.adjustQueryForFlags(preferences, calDavWithClause);
return Query.select(fields.toArray(new Field[0]))
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(joinedQuery))
.withPreClause(calDavWithClause)
.from(new Table("recursive_caldav"))
.toString();
} else {
fields.add(TAGS);
// 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
// elsewhere.
String joinedQuery =
Join.left(Tag.TABLE.as(TAGS_METADATA_JOIN), tagsJoinCriterion).toString()
+ Join.left(GoogleTask.TABLE.as(GTASK_METADATA_JOIN), gtaskJoinCriterion)
+ Join.left(CaldavTask.TABLE.as(CALDAV_METADATA_JOIN), caldavJoinCriterion)
+ Join.left(Geofence.TABLE, field(Geofence.TABLE_NAME + ".task").eq(Task.ID))
+ Join.left(Place.TABLE, field(Place.TABLE_NAME + ".uid").eq(field("geofences.place")))
+ filter.getSqlQuery();
String query =
SortHelper.adjustQueryForFlagsAndSort(preferences, joinedQuery, preferences.getSortMode());
String groupedQuery =
query.contains("ORDER BY")
? query.replace("ORDER BY", "GROUP BY " + Task.ID + " ORDER BY")
: query + " GROUP BY " + Task.ID;
return Query.select(fields.toArray(new Field[0]))
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(groupedQuery))
.from(Task.TABLE)
.toString();
}
}
public void searchByFilter(Filter filter) {
@ -196,6 +270,24 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
@Override
public void onChanged(PagedList<TaskContainer> taskContainers) {
if (filter instanceof CaldavFilter) {
// Populate child count for CalDAV
// TODO Review if there's a better place to call this, and regardless, where to
// put a function that does this work
HashMap<Long, TaskContainer> parents = new HashMap<>();
TaskContainer prev = null;
for (TaskContainer cont: taskContainers) {
CaldavTask caldavTask = cont.getCaldavTask();
if (caldavTask.getParent() != 0) {
long parentId = caldavTask.getParent();
if (!parents.containsKey(parentId) && prev != null && prev.getId() == parentId) {
parents.put(parentId, prev);
}
parents.get(parentId).children++;
}
prev = cont;
}
}
tasks.setValue(taskContainers);
}
}

Loading…
Cancel
Save