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 82e4bff7a..188061962 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.java @@ -50,6 +50,7 @@ import com.todoroo.astrid.api.GtasksFilter; import com.todoroo.astrid.api.SearchFilter; import com.todoroo.astrid.api.TagFilter; import com.todoroo.astrid.core.BuiltInFilterExposer; +import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.TaskCreator; import com.todoroo.astrid.service.TaskDeleter; @@ -129,6 +130,7 @@ public final class TaskListFragment extends InjectingFragment @Inject ActionModeProvider actionModeProvider; @Inject Toaster toaster; @Inject TaskAdapterProvider taskAdapterProvider; + @Inject TaskDao taskDao; @BindView(R.id.swipe_layout) SwipeRefreshLayout swipeRefreshLayout; @@ -259,7 +261,8 @@ public final class TaskListFragment extends InjectingFragment viewHolderFactory, this, actionModeProvider, - taskListViewModel.getValue()); + taskListViewModel.getValue(), + taskDao); taskAdapter.setHelper(recyclerAdapter); ((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); recyclerView.setLayoutManager(new LinearLayoutManager(context)); 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 20ae881c0..97deb92f5 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java @@ -162,6 +162,9 @@ public abstract class TaskDao { @Query("UPDATE tasks SET modified = datetime('now', 'localtime') WHERE _id in (:ids)") public abstract void touch(List ids); + @Query("UPDATE tasks SET collapsed = :collapsed WHERE _id = :id") + public abstract void setCollapsed(long id, boolean collapsed); + /** * 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 fc2623fcd..6fe20c531 100644 --- a/app/src/main/java/com/todoroo/astrid/data/Task.java +++ b/app/src/main/java/com/todoroo/astrid/data/Task.java @@ -622,6 +622,10 @@ public class Task implements Parcelable { return getId() == NO_ID; } + public boolean isCollapsed() { + return collapsed; + } + /** {@inheritDoc} */ @Override public int describeContents() { diff --git a/app/src/main/java/org/tasks/data/TaskContainer.java b/app/src/main/java/org/tasks/data/TaskContainer.java index fad42feca..f780d8353 100644 --- a/app/src/main/java/org/tasks/data/TaskContainer.java +++ b/app/src/main/java/org/tasks/data/TaskContainer.java @@ -239,4 +239,8 @@ public class TaskContainer { public Location getLocation() { return location; } + + public boolean isCollapsed() { + return task.isCollapsed(); + } } diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java index 886b92810..0e3bdd05a 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java +++ b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java @@ -21,6 +21,7 @@ import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.api.CaldavFilter; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.GtasksFilter; +import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.utility.Flags; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; @@ -43,6 +44,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter private final ActionModeProvider actionModeProvider; private final boolean isRemoteList; private final ItemTouchHelperCallback itemTouchHelperCallback; + private final TaskDao taskDao; private ActionMode mode = null; private List list; private PublishSubject> publishSubject = PublishSubject.create(); @@ -55,7 +57,8 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter ViewHolderFactory viewHolderFactory, TaskListFragment taskList, ActionModeProvider actionModeProvider, - List list) { + List list, + TaskDao taskDao) { this.adapter = adapter; this.recyclerView = recyclerView; this.viewHolderFactory = viewHolderFactory; @@ -66,6 +69,7 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter || taskList.getFilter() instanceof CaldavFilter; this.list = list; itemTouchHelperCallback = new ItemTouchHelperCallback(adapter, this, this::drainQueue); + this.taskDao = taskDao; new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerView); Pair, DiffResult> initial = Pair.create(list, null); disposables.add( @@ -132,6 +136,12 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter return true; } + @Override + public void toggleSubtasks(TaskContainer task, boolean collapsed) { + taskDao.setCollapsed(task.getId(), collapsed); + taskList.loadTaskListContent(); + } + public void startActionMode() { if (mode == null) { mode = actionModeProvider.startActionMode(adapter, taskList, this); diff --git a/app/src/main/java/org/tasks/tasklist/ViewHolder.java b/app/src/main/java/org/tasks/tasklist/ViewHolder.java index 692496279..54e8a0af0 100644 --- a/app/src/main/java/org/tasks/tasklist/ViewHolder.java +++ b/app/src/main/java/org/tasks/tasklist/ViewHolder.java @@ -81,6 +81,9 @@ public class ViewHolder extends RecyclerView.ViewHolder { @BindView(R.id.hidden_status) ImageView hidden; + @BindView(R.id.subtasks_chip) + Chip subtasksChip; + private int indent; private boolean selected; private boolean moving; @@ -209,6 +212,19 @@ public class ViewHolder extends RecyclerView.ViewHolder { hidden.setVisibility(task.isHidden() ? View.VISIBLE : View.GONE); setupTitleAndCheckbox(); setupDueDate(); + if (task.hasChildren()) { + subtasksChip.setVisibility(View.VISIBLE); + subtasksChip.setText( + context + .getResources() + .getQuantityString(R.plurals.subtask_count, task.children, task.children)); + subtasksChip.setChipIconResource( + task.isCollapsed() + ? R.drawable.ic_keyboard_arrow_up_black_24dp + : R.drawable.ic_keyboard_arrow_down_black_24dp); + } else { + subtasksChip.setVisibility(View.GONE); + } if (preferences.getBoolean(R.string.p_show_list_indicators, true)) { setupLocation(); setupTags(); @@ -294,6 +310,11 @@ public class ViewHolder extends RecyclerView.ViewHolder { } } + @OnClick(R.id.subtasks_chip) + void toggleSubtasks() { + callback.toggleSubtasks(task, !task.isCollapsed()); + } + @OnClick(R.id.rowBody) void onRowBodyClick() { callback.onClick(this); @@ -355,6 +376,8 @@ public class ViewHolder extends RecyclerView.ViewHolder { void onClick(Filter filter); + void toggleSubtasks(TaskContainer task, boolean collapsed); + boolean onLongPress(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 350a549df..91dc889ad 100644 --- a/app/src/main/java/org/tasks/ui/TaskListViewModel.java +++ b/app/src/main/java/org/tasks/ui/TaskListViewModel.java @@ -120,8 +120,10 @@ public class TaskListViewModel extends ViewModel { + Tag.TASK; fields.add(field("(" + tagQuery + ")").as("tags")); fields.add(INDENT); + fields.add(field("(SELECT count(*) FROM recursive_tasks WHERE parent = tasks._id GROUP BY parent)").as("children")); - String joinedQuery = Join.inner(RECURSIVE, Task.ID.eq(RECURSIVE_TASK)) + JOINS; + String joinedQuery = Join.inner(RECURSIVE, Task.ID.eq(RECURSIVE_TASK)) + JOINS + + " WHERE recursive_tasks.hidden = 0"; String parentQuery; QueryTemplate subtaskQuery = new QueryTemplate(); if (filter instanceof CaldavFilter) { @@ -198,17 +200,17 @@ public class TaskListViewModel extends ViewModel { GoogleTask.PARENT.eq(RECURSIVE_TASK), CaldavTask.PARENT.eq(RECURSIVE_TASK)))) .where(TaskCriteria.activeAndVisible()); - joinedQuery += " WHERE indent = (select max(indent) from recursive_tasks where tasks._id = recursive_tasks.task) "; + joinedQuery += " AND indent = (select max(indent) from recursive_tasks where tasks._id = recursive_tasks.task) "; } String sortSelect = SortHelper.orderSelectForSortTypeRecursive(preferences.getSortMode()); String withClause = "CREATE TEMPORARY TABLE `recursive_tasks` AS\n" - + "WITH RECURSIVE recursive_tasks (task, indent, title, sortField) AS (\n" - + " SELECT tasks._id, 0 AS sort_indent, UPPER(title) AS sort_title, " + + "WITH RECURSIVE recursive_tasks (task, parent, collapsed, hidden, indent, title, sortField) AS (\n" + + " SELECT tasks._id, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(title) AS sort_title, " + sortSelect + " FROM tasks\n" + parentQuery - + "\nUNION ALL SELECT tasks._id, 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 + " FROM tasks\n" + subtaskQuery @@ -216,11 +218,11 @@ public class TaskListViewModel extends ViewModel { + SortHelper.orderForSortTypeRecursive(preferences) + ") SELECT * FROM recursive_tasks"; - return newArrayList( "DROP TABLE IF EXISTS `temp`.`recursive_tasks`", SortHelper.adjustQueryForFlags(preferences, withClause), - "CREATE INDEX `rtasks` ON `recursive_tasks` (`task`)", + "CREATE INDEX `r_tasks` ON `recursive_tasks` (`task`)", + "CREATE INDEX `r_parents` ON `recursive_tasks` (`parent`)", Query.select(fields.toArray(new Field[0])) .withQueryTemplate(PermaSql.replacePlaceholdersForQuery(joinedQuery)) .from(Task.TABLE) diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml new file mode 100644 index 000000000..8fc6edc7a --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml new file mode 100644 index 000000000..2bd6271a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/task_adapter_row_body.xml b/app/src/main/res/layout/task_adapter_row_body.xml index 990d13728..a1cc819ed 100644 --- a/app/src/main/res/layout/task_adapter_row_body.xml +++ b/app/src/main/res/layout/task_adapter_row_body.xml @@ -126,4 +126,23 @@ app:chipSpacingHorizontal="@dimen/chip_spacing" app:chipSpacingVertical="@dimen/chip_spacing"/> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e8b39ca8..e405fe481 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -213,6 +213,10 @@ File %1$s contained %2$s.\n\n %d task %d tasks + + %d subtask + %d subtasks + time times