mirror of https://github.com/tasks/tasks
Convert SubtasksFilterUpdater to Kotlin
parent
5879774905
commit
3d917fda62
@ -1,477 +0,0 @@
|
|||||||
package com.todoroo.astrid.subtasks;
|
|
||||||
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
import static org.tasks.db.QueryUtils.showHiddenAndCompleted;
|
|
||||||
|
|
||||||
import com.todoroo.astrid.api.Filter;
|
|
||||||
import com.todoroo.astrid.dao.TaskDaoBlocking;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.tasks.data.TaskListMetadata;
|
|
||||||
import org.tasks.data.TaskListMetadataDaoBlocking;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class SubtasksFilterUpdater {
|
|
||||||
|
|
||||||
public static final String ACTIVE_TASKS_ORDER = "active_tasks_order"; // $NON-NLS-1$
|
|
||||||
public static final String TODAY_TASKS_ORDER = "today_tasks_order"; // $NON-NLS-1$
|
|
||||||
|
|
||||||
private final TaskListMetadataDaoBlocking taskListMetadataDao;
|
|
||||||
private final TaskDaoBlocking taskDao;
|
|
||||||
private final HashMap<String, Node> idToNode = new HashMap<>();
|
|
||||||
private Node treeRoot;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SubtasksFilterUpdater(TaskListMetadataDaoBlocking taskListMetadataDao, TaskDaoBlocking taskDao) {
|
|
||||||
this.taskDao = taskDao;
|
|
||||||
this.taskListMetadataDao = taskListMetadataDao;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String buildOrderString(String[] ids) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
if (ids.length == 0) {
|
|
||||||
return "(1)"; // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
for (int i = ids.length - 1; i >= 0; i--) {
|
|
||||||
builder.append(Task.UUID.eq(ids[i]).toString());
|
|
||||||
if (i > 0) {
|
|
||||||
builder.append(", "); // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Node buildTreeModel(String serializedTree, JSONTreeModelBuilder callback) {
|
|
||||||
Node root = new Node("-1", null, -1); // $NON-NLS-1$
|
|
||||||
try {
|
|
||||||
JSONArray tree = new JSONArray(serializedTree);
|
|
||||||
recursivelyBuildChildren(root, tree, callback);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Timber.e(e);
|
|
||||||
}
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void recursivelyBuildChildren(
|
|
||||||
Node node, JSONArray children, JSONTreeModelBuilder callback) throws JSONException {
|
|
||||||
for (int i = 1; i < children.length(); i++) {
|
|
||||||
JSONArray subarray = children.optJSONArray(i);
|
|
||||||
String uuid;
|
|
||||||
if (subarray == null) {
|
|
||||||
uuid = children.getString(i);
|
|
||||||
} else {
|
|
||||||
uuid = subarray.getString(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Node child = new Node(uuid, node, node.indent + 1);
|
|
||||||
if (subarray != null) {
|
|
||||||
recursivelyBuildChildren(child, subarray, callback);
|
|
||||||
}
|
|
||||||
node.children.add(child);
|
|
||||||
if (callback != null) {
|
|
||||||
callback.afterAddNode(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String serializeTree(Node root) {
|
|
||||||
JSONArray tree = new JSONArray();
|
|
||||||
if (root == null) {
|
|
||||||
return tree.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
recursivelySerialize(root, tree);
|
|
||||||
return tree.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void recursivelySerialize(Node node, JSONArray serializeTo) {
|
|
||||||
ArrayList<Node> children = node.children;
|
|
||||||
serializeTo.put(node.uuid);
|
|
||||||
for (Node child : children) {
|
|
||||||
if (child.children.size() > 0) {
|
|
||||||
JSONArray branch = new JSONArray();
|
|
||||||
recursivelySerialize(child, branch);
|
|
||||||
serializeTo.put(branch);
|
|
||||||
} else {
|
|
||||||
serializeTo.put(child.uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getSerializedTree(TaskListMetadata list) {
|
|
||||||
if (list == null) {
|
|
||||||
return "[]"; // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
String order = list.getTaskIds();
|
|
||||||
if (isNullOrEmpty(order) || "null".equals(order)) // $NON-NLS-1$
|
|
||||||
{
|
|
||||||
order = "[]"; // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeSerialization(TaskListMetadata list, String serialized) {
|
|
||||||
if (list != null) {
|
|
||||||
list.setTaskIds(serialized);
|
|
||||||
taskListMetadataDao.update(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initialize(TaskListMetadata list, Filter filter) {
|
|
||||||
initializeFromSerializedTree(list, filter, getSerializedTree(list));
|
|
||||||
applyToFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyToFilter(Filter filter) {
|
|
||||||
String query = filter.getSqlQuery();
|
|
||||||
|
|
||||||
query = query.replaceAll("ORDER BY .*", "");
|
|
||||||
query = query + String.format("ORDER BY %s", getOrderString());
|
|
||||||
query = showHiddenAndCompleted(query);
|
|
||||||
|
|
||||||
filter.setFilterQueryOverride(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIndentForTask(String targetTaskId) {
|
|
||||||
Node n = idToNode.get(targetTaskId);
|
|
||||||
if (n == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return n.indent;
|
|
||||||
}
|
|
||||||
|
|
||||||
void initializeFromSerializedTree(TaskListMetadata list, Filter filter, String serializedTree) {
|
|
||||||
idToNode.clear();
|
|
||||||
treeRoot = buildTreeModel(serializedTree, node -> idToNode.put(node.uuid, node));
|
|
||||||
verifyTreeModel(list, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyTreeModel(TaskListMetadata list, Filter filter) {
|
|
||||||
boolean changedThings = false;
|
|
||||||
Set<String> keySet = idToNode.keySet();
|
|
||||||
Set<String> currentIds = new HashSet<>(keySet);
|
|
||||||
Set<String> idsInQuery = new HashSet<>();
|
|
||||||
String sql = filter.getSqlQuery().replaceAll("ORDER BY .*", ""); // $NON-NLS-1$//$NON-NLS-2$
|
|
||||||
sql = sql + " ORDER BY created"; // $NON-NLS-1$
|
|
||||||
sql = showHiddenAndCompleted(sql);
|
|
||||||
List<Task> tasks = taskDao.fetchFiltered(sql);
|
|
||||||
for (Task task : tasks) {
|
|
||||||
String id = task.getUuid();
|
|
||||||
idsInQuery.add(id);
|
|
||||||
if (idToNode.containsKey(id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
changedThings = true;
|
|
||||||
Node newNode = new Node(id, treeRoot, 0);
|
|
||||||
treeRoot.children.add(0, newNode);
|
|
||||||
idToNode.put(id, newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentIds.removeAll(idsInQuery);
|
|
||||||
if (currentIds.size() > 0) {
|
|
||||||
removeNodes(currentIds);
|
|
||||||
changedThings = true;
|
|
||||||
}
|
|
||||||
if (changedThings) {
|
|
||||||
writeSerialization(list, serializeTree());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeNodes(Set<String> idsToRemove) {
|
|
||||||
for (String id : idsToRemove) {
|
|
||||||
Node node = idToNode.get(id);
|
|
||||||
if (node == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove node from tree, put all children under parent
|
|
||||||
Node parent = node.parent;
|
|
||||||
parent.children.remove(node);
|
|
||||||
for (Node child : node.children) {
|
|
||||||
child.parent = parent;
|
|
||||||
parent.children.add(child);
|
|
||||||
setNodeIndent(child, parent.indent + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Node findNodeForTask(String taskId) {
|
|
||||||
return idToNode.get(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] getOrderedIds() {
|
|
||||||
ArrayList<String> ids = new ArrayList<>();
|
|
||||||
orderedIdHelper(treeRoot, ids);
|
|
||||||
return ids.toArray(new String[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getOrderString() {
|
|
||||||
String[] ids = getOrderedIds();
|
|
||||||
return buildOrderString(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void orderedIdHelper(Node node, List<String> ids) {
|
|
||||||
if (node != treeRoot) {
|
|
||||||
ids.add(node.uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node child : node.children) {
|
|
||||||
orderedIdHelper(child, ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void applyToDescendants(String taskId, OrderedListNodeVisitor visitor) {
|
|
||||||
Node n = idToNode.get(taskId);
|
|
||||||
if (n == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
applyToDescendantsHelper(n, visitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyToDescendantsHelper(Node n, OrderedListNodeVisitor visitor) {
|
|
||||||
ArrayList<Node> children = n.children;
|
|
||||||
for (Node child : children) {
|
|
||||||
visitor.visitNode(child);
|
|
||||||
applyToDescendantsHelper(child, visitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void indent(TaskListMetadata list, Filter filter, String targetTaskId, int delta) {
|
|
||||||
Node node = idToNode.get(targetTaskId);
|
|
||||||
indentHelper(list, filter, node, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void indentHelper(TaskListMetadata list, Filter filter, Node node, int delta) {
|
|
||||||
if (node == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (delta == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Node parent = node.parent;
|
|
||||||
if (parent == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta > 0) {
|
|
||||||
ArrayList<Node> siblings = parent.children;
|
|
||||||
int index = siblings.indexOf(node);
|
|
||||||
if (index <= 0) // Can't indent first child
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Node newParent = siblings.get(index - 1);
|
|
||||||
siblings.remove(index);
|
|
||||||
node.parent = newParent;
|
|
||||||
newParent.children.add(node);
|
|
||||||
setNodeIndent(node, newParent.indent + 1);
|
|
||||||
} else if (delta < 0) {
|
|
||||||
if (parent == treeRoot) // Can't deindent a top level item
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<Node> siblings = parent.children;
|
|
||||||
int index = siblings.indexOf(node);
|
|
||||||
if (index < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node newParent = parent.parent;
|
|
||||||
ArrayList<Node> newSiblings = newParent.children;
|
|
||||||
int insertAfter = newSiblings.indexOf(parent);
|
|
||||||
siblings.remove(index);
|
|
||||||
node.parent = newParent;
|
|
||||||
setNodeIndent(node, newParent.indent + 1);
|
|
||||||
newSiblings.add(insertAfter + 1, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSerialization(list, serializeTree());
|
|
||||||
applyToFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNodeIndent(Node node, int indent) {
|
|
||||||
node.indent = indent;
|
|
||||||
adjustDescendantsIndent(node, indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void adjustDescendantsIndent(Node node, int baseIndent) {
|
|
||||||
for (Node child : node.children) {
|
|
||||||
child.indent = baseIndent + 1;
|
|
||||||
adjustDescendantsIndent(child, child.indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void moveTo(TaskListMetadata list, Filter filter, String targetTaskId, String beforeTaskId) {
|
|
||||||
Node target = idToNode.get(targetTaskId);
|
|
||||||
if (target == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("-1".equals(beforeTaskId)) { // $NON-NLS-1$
|
|
||||||
moveToEndOfList(list, filter, target);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node before = idToNode.get(beforeTaskId);
|
|
||||||
|
|
||||||
if (before == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDescendantOf(before, target)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
moveHelper(list, filter, target, before);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void moveToParentOf(String moveThis, String toParentOfThis) {
|
|
||||||
Node target = idToNode.get(toParentOfThis);
|
|
||||||
if (target == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node toMove = idToNode.get(moveThis);
|
|
||||||
if (toMove == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node newParent = target.parent;
|
|
||||||
Node oldParent = toMove.parent;
|
|
||||||
|
|
||||||
oldParent.children.remove(toMove);
|
|
||||||
toMove.parent = newParent;
|
|
||||||
newParent.children.add(toMove);
|
|
||||||
setNodeIndent(toMove, toMove.parent.indent + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveHelper(TaskListMetadata list, Filter filter, Node moveThis, Node beforeThis) {
|
|
||||||
Node oldParent = moveThis.parent;
|
|
||||||
ArrayList<Node> oldSiblings = oldParent.children;
|
|
||||||
|
|
||||||
Node newParent = beforeThis.parent;
|
|
||||||
ArrayList<Node> newSiblings = newParent.children;
|
|
||||||
|
|
||||||
int beforeIndex = newSiblings.indexOf(beforeThis);
|
|
||||||
if (beforeIndex < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int nodeIndex = oldSiblings.indexOf(moveThis);
|
|
||||||
if (nodeIndex < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
moveThis.parent = newParent;
|
|
||||||
setNodeIndent(moveThis, newParent.indent + 1);
|
|
||||||
oldSiblings.remove(moveThis);
|
|
||||||
|
|
||||||
if (newSiblings == oldSiblings && beforeIndex > nodeIndex) {
|
|
||||||
beforeIndex--;
|
|
||||||
}
|
|
||||||
newSiblings.add(beforeIndex, moveThis);
|
|
||||||
writeSerialization(list, serializeTree());
|
|
||||||
applyToFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDescendantOf(String desc, String parent) {
|
|
||||||
return isDescendantOf(idToNode.get(desc), idToNode.get(parent));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if desc is a descendant of parent
|
|
||||||
private boolean isDescendantOf(Node desc, Node parent) {
|
|
||||||
Node curr = desc;
|
|
||||||
while (curr != treeRoot) {
|
|
||||||
if (curr == parent) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
curr = curr.parent;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveToEndOfList(TaskListMetadata list, Filter filter, Node moveThis) {
|
|
||||||
Node parent = moveThis.parent;
|
|
||||||
parent.children.remove(moveThis);
|
|
||||||
treeRoot.children.add(moveThis);
|
|
||||||
moveThis.parent = treeRoot;
|
|
||||||
setNodeIndent(moveThis, 0);
|
|
||||||
writeSerialization(list, serializeTree());
|
|
||||||
applyToFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreateTask(TaskListMetadata list, Filter filter, String uuid) {
|
|
||||||
if (idToNode.containsKey(uuid) || !Task.isValidUuid(uuid)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node newNode = new Node(uuid, treeRoot, 0);
|
|
||||||
treeRoot.children.add(0, newNode);
|
|
||||||
idToNode.put(uuid, newNode);
|
|
||||||
writeSerialization(list, serializeTree());
|
|
||||||
applyToFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDeleteTask(TaskListMetadata list, Filter filter, String taskId) {
|
|
||||||
Node task = idToNode.get(taskId);
|
|
||||||
if (task == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node parent = task.parent;
|
|
||||||
ArrayList<Node> siblings = parent.children;
|
|
||||||
int index = siblings.indexOf(task);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
siblings.remove(index);
|
|
||||||
}
|
|
||||||
for (Node child : task.children) {
|
|
||||||
child.parent = parent;
|
|
||||||
siblings.add(index, child);
|
|
||||||
setNodeIndent(child, parent.indent + 1);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
idToNode.remove(taskId);
|
|
||||||
|
|
||||||
writeSerialization(list, serializeTree());
|
|
||||||
applyToFilter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String serializeTree() {
|
|
||||||
return serializeTree(treeRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OrderedListNodeVisitor {
|
|
||||||
|
|
||||||
void visitNode(Node node);
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface JSONTreeModelBuilder {
|
|
||||||
|
|
||||||
void afterAddNode(Node node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Node {
|
|
||||||
|
|
||||||
final ArrayList<Node> children = new ArrayList<>();
|
|
||||||
public String uuid;
|
|
||||||
public Node parent;
|
|
||||||
int indent;
|
|
||||||
|
|
||||||
Node(String uuid, Node parent, int indent) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.parent = parent;
|
|
||||||
this.indent = indent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,394 @@
|
|||||||
|
package com.todoroo.astrid.subtasks
|
||||||
|
|
||||||
|
import com.todoroo.astrid.api.Filter
|
||||||
|
import com.todoroo.astrid.dao.TaskDaoBlocking
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.data.Task.Companion.isValidUuid
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.data.TaskListMetadata
|
||||||
|
import org.tasks.data.TaskListMetadataDaoBlocking
|
||||||
|
import org.tasks.db.QueryUtils.showHiddenAndCompleted
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SubtasksFilterUpdater @Inject constructor(
|
||||||
|
private val taskListMetadataDao: TaskListMetadataDaoBlocking,
|
||||||
|
private val taskDao: TaskDaoBlocking) {
|
||||||
|
private val idToNode = HashMap<String, Node?>()
|
||||||
|
private var treeRoot: Node? = null
|
||||||
|
private fun getSerializedTree(list: TaskListMetadata?): String? {
|
||||||
|
if (list == null) {
|
||||||
|
return "[]" // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
var order = list.taskIds
|
||||||
|
if (isNullOrEmpty(order) || "null" == order) // $NON-NLS-1$
|
||||||
|
{
|
||||||
|
order = "[]" // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeSerialization(list: TaskListMetadata?, serialized: String?) {
|
||||||
|
if (list != null) {
|
||||||
|
list.taskIds = serialized
|
||||||
|
taskListMetadataDao.update(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initialize(list: TaskListMetadata?, filter: Filter) {
|
||||||
|
initializeFromSerializedTree(list, filter, getSerializedTree(list))
|
||||||
|
applyToFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyToFilter(filter: Filter) {
|
||||||
|
var query = filter.getSqlQuery()
|
||||||
|
query = query.replace("ORDER BY .*".toRegex(), "")
|
||||||
|
query += "ORDER BY $orderString"
|
||||||
|
query = showHiddenAndCompleted(query)
|
||||||
|
filter.setFilterQueryOverride(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIndentForTask(targetTaskId: String?): Int {
|
||||||
|
val n = idToNode[targetTaskId] ?: return 0
|
||||||
|
return n.indent
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initializeFromSerializedTree(list: TaskListMetadata?, filter: Filter, serializedTree: String?) {
|
||||||
|
idToNode.clear()
|
||||||
|
treeRoot = buildTreeModel(serializedTree) { node -> node?.let { idToNode[it.uuid] = it } }
|
||||||
|
verifyTreeModel(list, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyTreeModel(list: TaskListMetadata?, filter: Filter) {
|
||||||
|
var changedThings = false
|
||||||
|
val keySet: Set<String> = idToNode.keys
|
||||||
|
val currentIds: MutableSet<String> = HashSet(keySet)
|
||||||
|
val idsInQuery: MutableSet<String> = HashSet()
|
||||||
|
var sql = filter.getSqlQuery().replace("ORDER BY .*".toRegex(), "") // $NON-NLS-1$//$NON-NLS-2$
|
||||||
|
sql = "$sql ORDER BY created" // $NON-NLS-1$
|
||||||
|
sql = showHiddenAndCompleted(sql)
|
||||||
|
val tasks = taskDao.fetchFiltered(sql)
|
||||||
|
for (task in tasks) {
|
||||||
|
val id = task.uuid
|
||||||
|
idsInQuery.add(id)
|
||||||
|
if (idToNode.containsKey(id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changedThings = true
|
||||||
|
val newNode = Node(id, treeRoot, 0)
|
||||||
|
treeRoot!!.children.add(0, newNode)
|
||||||
|
idToNode[id] = newNode
|
||||||
|
}
|
||||||
|
currentIds.removeAll(idsInQuery)
|
||||||
|
if (currentIds.size > 0) {
|
||||||
|
removeNodes(currentIds)
|
||||||
|
changedThings = true
|
||||||
|
}
|
||||||
|
if (changedThings) {
|
||||||
|
writeSerialization(list, serializeTree())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeNodes(idsToRemove: Set<String>) {
|
||||||
|
for (id in idsToRemove) {
|
||||||
|
val node = idToNode[id] ?: continue
|
||||||
|
|
||||||
|
// Remove node from tree, put all children under parent
|
||||||
|
val parent = node.parent
|
||||||
|
parent!!.children.remove(node)
|
||||||
|
for (child in node.children) {
|
||||||
|
child.parent = parent
|
||||||
|
parent.children.add(child)
|
||||||
|
setNodeIndent(child, parent.indent + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findNodeForTask(taskId: String?): Node? {
|
||||||
|
return idToNode[taskId]
|
||||||
|
}
|
||||||
|
|
||||||
|
private val orderedIds: Array<String>
|
||||||
|
get() {
|
||||||
|
val ids = ArrayList<String>()
|
||||||
|
orderedIdHelper(treeRoot, ids)
|
||||||
|
return ids.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val orderString: String
|
||||||
|
get() {
|
||||||
|
val ids = orderedIds
|
||||||
|
return buildOrderString(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun orderedIdHelper(node: Node?, ids: MutableList<String>) {
|
||||||
|
if (node !== treeRoot) {
|
||||||
|
ids.add(node!!.uuid)
|
||||||
|
}
|
||||||
|
for (child in node!!.children) {
|
||||||
|
orderedIdHelper(child, ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyToDescendants(taskId: String?, visitor: (Node) -> Unit) {
|
||||||
|
val n = idToNode[taskId] ?: return
|
||||||
|
applyToDescendantsHelper(n, visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyToDescendantsHelper(n: Node, visitor: (Node) -> Unit) {
|
||||||
|
val children = n.children
|
||||||
|
for (child in children) {
|
||||||
|
visitor.invoke(child)
|
||||||
|
applyToDescendantsHelper(child, visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun indent(list: TaskListMetadata, filter: Filter, targetTaskId: String?, delta: Int) {
|
||||||
|
val node = idToNode[targetTaskId]
|
||||||
|
indentHelper(list, filter, node, delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun indentHelper(list: TaskListMetadata, filter: Filter, node: Node?, delta: Int) {
|
||||||
|
if (node == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (delta == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val parent = node.parent ?: return
|
||||||
|
if (delta > 0) {
|
||||||
|
val siblings = parent.children
|
||||||
|
val index = siblings.indexOf(node)
|
||||||
|
if (index <= 0) // Can't indent first child
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val newParent = siblings[index - 1]
|
||||||
|
siblings.removeAt(index)
|
||||||
|
node.parent = newParent
|
||||||
|
newParent.children.add(node)
|
||||||
|
setNodeIndent(node, newParent.indent + 1)
|
||||||
|
} else if (delta < 0) {
|
||||||
|
if (parent === treeRoot) // Can't deindent a top level item
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val siblings = parent.children
|
||||||
|
val index = siblings.indexOf(node)
|
||||||
|
if (index < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val newParent = parent.parent
|
||||||
|
val newSiblings = newParent!!.children
|
||||||
|
val insertAfter = newSiblings.indexOf(parent)
|
||||||
|
siblings.removeAt(index)
|
||||||
|
node.parent = newParent
|
||||||
|
setNodeIndent(node, newParent.indent + 1)
|
||||||
|
newSiblings.add(insertAfter + 1, node)
|
||||||
|
}
|
||||||
|
writeSerialization(list, serializeTree())
|
||||||
|
applyToFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setNodeIndent(node: Node, indent: Int) {
|
||||||
|
node.indent = indent
|
||||||
|
adjustDescendantsIndent(node, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adjustDescendantsIndent(node: Node, baseIndent: Int) {
|
||||||
|
for (child in node.children) {
|
||||||
|
child.indent = baseIndent + 1
|
||||||
|
adjustDescendantsIndent(child, child.indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveTo(list: TaskListMetadata, filter: Filter, targetTaskId: String?, beforeTaskId: String) {
|
||||||
|
val target = idToNode[targetTaskId] ?: return
|
||||||
|
if ("-1" == beforeTaskId) { // $NON-NLS-1$
|
||||||
|
moveToEndOfList(list, filter, target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val before = idToNode[beforeTaskId] ?: return
|
||||||
|
if (isDescendantOf(before, target)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
moveHelper(list, filter, target, before)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveToParentOf(moveThis: String?, toParentOfThis: String?) {
|
||||||
|
val target = idToNode[toParentOfThis] ?: return
|
||||||
|
val toMove = idToNode[moveThis] ?: return
|
||||||
|
val newParent = target.parent
|
||||||
|
val oldParent = toMove.parent
|
||||||
|
oldParent!!.children.remove(toMove)
|
||||||
|
toMove.parent = newParent
|
||||||
|
newParent!!.children.add(toMove)
|
||||||
|
setNodeIndent(toMove, toMove.parent!!.indent + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun moveHelper(list: TaskListMetadata, filter: Filter, moveThis: Node, beforeThis: Node) {
|
||||||
|
val oldParent = moveThis.parent
|
||||||
|
val oldSiblings = oldParent!!.children
|
||||||
|
val newParent = beforeThis.parent
|
||||||
|
val newSiblings = newParent!!.children
|
||||||
|
var beforeIndex = newSiblings.indexOf(beforeThis)
|
||||||
|
if (beforeIndex < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val nodeIndex = oldSiblings.indexOf(moveThis)
|
||||||
|
if (nodeIndex < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
moveThis.parent = newParent
|
||||||
|
setNodeIndent(moveThis, newParent.indent + 1)
|
||||||
|
oldSiblings.remove(moveThis)
|
||||||
|
if (newSiblings === oldSiblings && beforeIndex > nodeIndex) {
|
||||||
|
beforeIndex--
|
||||||
|
}
|
||||||
|
newSiblings.add(beforeIndex, moveThis)
|
||||||
|
writeSerialization(list, serializeTree())
|
||||||
|
applyToFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDescendantOf(desc: String?, parent: String?): Boolean {
|
||||||
|
return isDescendantOf(idToNode[desc], idToNode[parent])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if desc is a descendant of parent
|
||||||
|
private fun isDescendantOf(desc: Node?, parent: Node?): Boolean {
|
||||||
|
var curr = desc
|
||||||
|
while (curr !== treeRoot) {
|
||||||
|
if (curr === parent) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
curr = curr!!.parent
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun moveToEndOfList(list: TaskListMetadata, filter: Filter, moveThis: Node) {
|
||||||
|
val parent = moveThis.parent
|
||||||
|
parent!!.children.remove(moveThis)
|
||||||
|
treeRoot!!.children.add(moveThis)
|
||||||
|
moveThis.parent = treeRoot
|
||||||
|
setNodeIndent(moveThis, 0)
|
||||||
|
writeSerialization(list, serializeTree())
|
||||||
|
applyToFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCreateTask(list: TaskListMetadata?, filter: Filter, uuid: String) {
|
||||||
|
if (idToNode.containsKey(uuid) || !isValidUuid(uuid)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val newNode = Node(uuid, treeRoot, 0)
|
||||||
|
treeRoot!!.children.add(0, newNode)
|
||||||
|
idToNode[uuid] = newNode
|
||||||
|
writeSerialization(list, serializeTree())
|
||||||
|
applyToFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDeleteTask(list: TaskListMetadata?, filter: Filter, taskId: String?) {
|
||||||
|
val task = idToNode[taskId] ?: return
|
||||||
|
val parent = task.parent
|
||||||
|
val siblings = parent!!.children
|
||||||
|
var index = siblings.indexOf(task)
|
||||||
|
if (index >= 0) {
|
||||||
|
siblings.removeAt(index)
|
||||||
|
}
|
||||||
|
for (child in task.children) {
|
||||||
|
child.parent = parent
|
||||||
|
siblings.add(index, child)
|
||||||
|
setNodeIndent(child, parent.indent + 1)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
idToNode.remove(taskId)
|
||||||
|
writeSerialization(list, serializeTree())
|
||||||
|
applyToFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serializeTree(): String {
|
||||||
|
return serializeTree(treeRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Node internal constructor(@JvmField var uuid: String, var parent: Node?, var indent: Int) {
|
||||||
|
@JvmField
|
||||||
|
val children = ArrayList<Node>()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTIVE_TASKS_ORDER = "active_tasks_order" // $NON-NLS-1$
|
||||||
|
const val TODAY_TASKS_ORDER = "today_tasks_order" // $NON-NLS-1$
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun buildOrderString(ids: Array<String>): String {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
if (ids.isEmpty()) {
|
||||||
|
return "(1)" // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
for (i in ids.indices.reversed()) {
|
||||||
|
builder.append(Task.UUID.eq(ids[i]).toString())
|
||||||
|
if (i > 0) {
|
||||||
|
builder.append(", ") // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun buildTreeModel(serializedTree: String?, callback: ((Node?) -> Unit)?): Node {
|
||||||
|
val root = Node("-1", null, -1) // $NON-NLS-1$
|
||||||
|
try {
|
||||||
|
val tree = JSONArray(serializedTree)
|
||||||
|
recursivelyBuildChildren(root, tree, callback)
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
private fun recursivelyBuildChildren(
|
||||||
|
node: Node, children: JSONArray, callback: ((Node?) -> Unit)?) {
|
||||||
|
for (i in 1 until children.length()) {
|
||||||
|
val subarray = children.optJSONArray(i)
|
||||||
|
var uuid: String
|
||||||
|
uuid = if (subarray == null) {
|
||||||
|
children.getString(i)
|
||||||
|
} else {
|
||||||
|
subarray.getString(0)
|
||||||
|
}
|
||||||
|
val child = Node(uuid, node, node.indent + 1)
|
||||||
|
subarray?.let { recursivelyBuildChildren(child, it, callback) }
|
||||||
|
node.children.add(child)
|
||||||
|
callback?.invoke(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun serializeTree(root: Node?): String {
|
||||||
|
val tree = JSONArray()
|
||||||
|
if (root == null) {
|
||||||
|
return tree.toString()
|
||||||
|
}
|
||||||
|
recursivelySerialize(root, tree)
|
||||||
|
return tree.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recursivelySerialize(node: Node, serializeTo: JSONArray) {
|
||||||
|
val children = node.children
|
||||||
|
serializeTo.put(node.uuid)
|
||||||
|
for (child in children) {
|
||||||
|
if (child.children.size > 0) {
|
||||||
|
val branch = JSONArray()
|
||||||
|
recursivelySerialize(child, branch)
|
||||||
|
serializeTo.put(branch)
|
||||||
|
} else {
|
||||||
|
serializeTo.put(child.uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue