From 57f3a70089368c79323708879c8e032c340f5124 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Mon, 20 Feb 2012 21:29:46 -0800 Subject: [PATCH] Refactored GtasksTaskListUpdater into OrderedListUpdater to create a new child class --- .../todoroo/andlib/data/TodorooCursor.java | 11 + .../commonsware/cwac/tlv/TouchListView.java | 13 +- .../astrid/gtasks/GtasksListFragment.java | 8 +- .../astrid/gtasks/GtasksMetadataService.java | 11 +- .../astrid/gtasks/GtasksTaskListUpdater.java | 293 +++--------------- .../astrid/subtasks/OrderedListUpdater.java | 288 +++++++++++++++++ .../astrid/subtasks/SubtasksListFragment.java | 34 +- .../astrid/subtasks/SubtasksMetadata.java | 5 +- .../astrid/subtasks/SubtasksUpdater.java | 80 +++++ 9 files changed, 477 insertions(+), 266 deletions(-) create mode 100644 astrid/plugin-src/com/todoroo/astrid/subtasks/OrderedListUpdater.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksUpdater.java diff --git a/api/src/com/todoroo/andlib/data/TodorooCursor.java b/api/src/com/todoroo/andlib/data/TodorooCursor.java index 4ef2c8ccc..781fbac94 100644 --- a/api/src/com/todoroo/andlib/data/TodorooCursor.java +++ b/api/src/com/todoroo/andlib/data/TodorooCursor.java @@ -31,6 +31,9 @@ public class TodorooCursor extends CursorWrapper { /** Property reading visitor */ private static final CursorReadingVisitor reader = new CursorReadingVisitor(); + /** Wrapped cursor */ + private final Cursor cursor; + /** * Create an AstridCursor from the supplied {@link Cursor} * object. @@ -41,6 +44,7 @@ public class TodorooCursor extends CursorWrapper { public TodorooCursor(Cursor cursor, Property[] properties) { super(cursor); + this.cursor = cursor; this.properties = properties; columnIndexCache = new WeakHashMap(); } @@ -56,6 +60,13 @@ public class TodorooCursor extends CursorWrapper { return (PROPERTY_TYPE)property.accept(reader, this); } + /** + * @return underlying cursor + */ + public Cursor getCursor() { + return cursor; + } + /** * Gets entire property list * @return diff --git a/astrid/common-src/com/commonsware/cwac/tlv/TouchListView.java b/astrid/common-src/com/commonsware/cwac/tlv/TouchListView.java index 126dc3ede..e24dd3a6d 100644 --- a/astrid/common-src/com/commonsware/cwac/tlv/TouchListView.java +++ b/astrid/common-src/com/commonsware/cwac/tlv/TouchListView.java @@ -421,10 +421,15 @@ public class TouchListView extends ListView { if(Thread.currentThread().isInterrupted()) return; - if(mDragPos == mFirstDragPos && + if(mDragView != null && mDragPos == mFirstDragPos && Math.abs(mDragCurrentX - mDragStartX) < 10) { - stopDragging(null); - mClickListener.onLongClick(mOriginalView); + post(new Runnable() { + public void run() { + stopDragging(null); + mClickListener.onLongClick(mOriginalView); + invalidate(); + } + }); } } }; @@ -461,6 +466,7 @@ public class TouchListView extends ListView { Context.WINDOW_SERVICE); wm.removeView(mDragView); mDragView.setImageDrawable(null); + mDragView = null; if (ev != null && mClickListener != null) { // detect press & long press case @@ -473,7 +479,6 @@ public class TouchListView extends ListView { mClickListener.onLongClick(mOriginalView); } } - mDragView = null; } if(longPressThread != null) { diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListFragment.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListFragment.java index 65e4896c0..b52cf1db1 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListFragment.java @@ -97,9 +97,9 @@ public class GtasksListFragment extends DraggableTaskListFragment { long destinationTaskId = taskAdapter.getItemId(to); if(to == getListView().getCount() - 1) - gtasksTaskListUpdater.moveTo(targetTaskId, -1); + gtasksTaskListUpdater.moveTo(filter, list, targetTaskId, -1); else - gtasksTaskListUpdater.moveTo(targetTaskId, destinationTaskId); + gtasksTaskListUpdater.moveTo(filter, list, targetTaskId, destinationTaskId); gtasksSyncService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId)); loadTaskListContent(true); } @@ -109,7 +109,7 @@ public class GtasksListFragment extends DraggableTaskListFragment { @Override public void swipeRight(int which) { long targetTaskId = taskAdapter.getItemId(which); - gtasksTaskListUpdater.indent(targetTaskId, 1); + gtasksTaskListUpdater.indent(filter, list, targetTaskId, 1); gtasksSyncService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId)); loadTaskListContent(true); } @@ -117,7 +117,7 @@ public class GtasksListFragment extends DraggableTaskListFragment { @Override public void swipeLeft(int which) { long targetTaskId = taskAdapter.getItemId(which); - gtasksTaskListUpdater.indent(targetTaskId, -1); + gtasksTaskListUpdater.indent(filter, list, targetTaskId, -1); gtasksSyncService.triggerMoveForMetadata(gtasksMetadataService.getTaskMetadata(targetTaskId)); loadTaskListContent(true); } diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java index 7f50d3b64..8c438bbc0 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksMetadataService.java @@ -27,6 +27,7 @@ import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.StoreObject; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer; +import com.todoroo.astrid.subtasks.OrderedListUpdater.OrderedListIterator; import com.todoroo.astrid.sync.SyncMetadataService; import com.todoroo.astrid.sync.SyncProviderUtilities; @@ -128,17 +129,13 @@ public final class GtasksMetadataService extends SyncMetadataService sibling = new AtomicReference(); - ListIterator iterator = new ListIterator() { + OrderedListIterator iterator = new OrderedListIterator() { @Override public void processTask(long taskId, Metadata metadata) { Task t = taskDao.fetch(taskId, Task.TITLE, Task.DELETION_DATE); diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksTaskListUpdater.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksTaskListUpdater.java index c59ee0003..0f249c1a3 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksTaskListUpdater.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksTaskListUpdater.java @@ -1,33 +1,28 @@ package com.todoroo.astrid.gtasks; -import java.util.ArrayList; import java.util.HashMap; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; import android.text.TextUtils; import android.util.Log; +import com.todoroo.andlib.data.Property.IntegerProperty; +import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Query; -import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.dao.MetadataDao; import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.StoreObject; import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.subtasks.OrderedListUpdater; -public class GtasksTaskListUpdater { - - @Autowired private GtasksListService gtasksListService; - @Autowired private GtasksMetadataService gtasksMetadataService; - @Autowired private MetadataDao metadataDao; +public class GtasksTaskListUpdater extends OrderedListUpdater { /** map of task -> parent task */ final HashMap parents = new HashMap(); @@ -38,251 +33,63 @@ public class GtasksTaskListUpdater { final HashMap localToRemoteIdMap = new HashMap(); - public GtasksTaskListUpdater() { - DependencyInjectionService.getInstance().inject(this); - } - - // --- task indenting - - /** - * Indent a task and all its children - */ - public void indent(final long targetTaskId, final int delta) { - Metadata targetMetadata = gtasksMetadataService.getTaskMetadata(targetTaskId); - if(targetMetadata == null) - return; - StoreObject list = gtasksListService.getList(targetMetadata.getValue(GtasksMetadata.LIST_ID)); - if(list == GtasksListService.LIST_NOT_FOUND_OBJECT) - return; - - updateParentSiblingMapsFor(list); - - final AtomicInteger targetTaskIndent = new AtomicInteger(-1); - final AtomicInteger previousIndent = new AtomicInteger(-1); - final AtomicLong previousTask = new AtomicLong(-1); - final AtomicReference listRef = new AtomicReference(list); - - gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() { - @Override - public void processTask(long taskId, Metadata metadata) { - int indent = metadata.getValue(GtasksMetadata.INDENT); - - if(targetTaskId == taskId) { - // if indenting is warranted, indent me and my children - if(indent + delta <= previousIndent.get() + 1 && indent + delta >= 0) { - targetTaskIndent.set(indent); - metadata.setValue(GtasksMetadata.INDENT, indent + delta); - - long newParent = computeNewParent(listRef.get(), taskId, indent + delta - 1); - if (newParent == taskId) { - metadata.setValue(GtasksMetadata.PARENT_TASK, Task.NO_ID); - } else { - metadata.setValue(GtasksMetadata.PARENT_TASK, newParent); - } - saveAndUpdateModifiedDate(metadata, taskId); - } - } else if(targetTaskIndent.get() > -1) { - // found first task that is not beneath target - if(indent <= targetTaskIndent.get()) - targetTaskIndent.set(-1); - else { - metadata.setValue(GtasksMetadata.INDENT, indent + delta); - saveAndUpdateModifiedDate(metadata, taskId); - } - } else { - previousIndent.set(indent); - previousTask.set(taskId); - } - } - - }); - } - - /** - * Helper function to iterate through a list and compute a new parent for the target task - * based on the target parent's indent - * @param list - * @param targetTaskId - * @param newIndent - * @return - */ - private long computeNewParent(StoreObject list, long targetTaskId, int targetParentIndent) { - final AtomicInteger desiredParentIndent = new AtomicInteger(targetParentIndent); - final AtomicLong targetTask = new AtomicLong(targetTaskId); - final AtomicLong lastPotentialParent = new AtomicLong(-1); - final AtomicBoolean computedParent = new AtomicBoolean(false); - - GtasksMetadataService.ListIterator iterator = new GtasksMetadataService.ListIterator() { - @Override - public void processTask(long taskId, Metadata metadata) { - if (targetTask.get() == taskId) { - computedParent.set(true); - } - - int indent = metadata.getValue(GtasksMetadata.INDENT); - if (!computedParent.get() && indent == desiredParentIndent.get()) { - lastPotentialParent.set(taskId); - } - } - }; + @Autowired private GtasksListService gtasksListService; + @Autowired private GtasksMetadataService gtasksMetadataService; + @Autowired private MetadataDao metadataDao; - gtasksMetadataService.iterateThroughList(list, iterator); - if (lastPotentialParent.get() == -1) return Task.NO_ID; - return lastPotentialParent.get(); + public GtasksTaskListUpdater() { + super(); } - // --- task moving + // --- overrides - private static class Node { - public final long taskId; - public Node parent; - public final ArrayList children = new ArrayList(); - - public Node(long taskId, Node parent) { - this.taskId = taskId; - this.parent = parent; - } + @Override + protected IntegerProperty indentProperty() { + return GtasksMetadata.INDENT; } - /** - * Move a task and all its children to the position right above - * taskIdToMoveto. Will change the indent level to match taskIdToMoveTo. - * - * @param newTaskId task we will move above. if -1, moves to end of list - */ - public void moveTo(final long targetTaskId, final long moveBeforeTaskId) { - Metadata targetMetadata = gtasksMetadataService.getTaskMetadata(targetTaskId); - if(targetMetadata == null) - return; - StoreObject list = gtasksListService.getList(targetMetadata.getValue(GtasksMetadata.LIST_ID)); - if(list == GtasksListService.LIST_NOT_FOUND_OBJECT) - return; - - Node root = buildTreeModel(list); - Node target = findNode(root, targetTaskId); - - if(target != null && target.parent != null) { - if(moveBeforeTaskId == -1) { - target.parent.children.remove(target); - root.children.add(target); - target.parent = root; - } else { - Node sibling = findNode(root, moveBeforeTaskId); - if(sibling != null) { - int index = sibling.parent.children.indexOf(sibling); - target.parent.children.remove(target); - sibling.parent.children.add(index, target); - target.parent = sibling.parent; - } - } - } - - traverseTreeAndWriteValues(root, new AtomicLong(0), -1); + @Override + protected LongProperty orderProperty() { + return GtasksMetadata.ORDER; } - private void traverseTreeAndWriteValues(Node node, AtomicLong order, int indent) { - if(node.taskId != -1) { - Metadata metadata = gtasksMetadataService.getTaskMetadata(node.taskId); - if(metadata == null) - metadata = GtasksMetadata.createEmptyMetadata(node.taskId); - metadata.setValue(GtasksMetadata.ORDER, order.getAndIncrement()); - metadata.setValue(GtasksMetadata.INDENT, indent); - metadata.setValue(GtasksMetadata.PARENT_TASK, node.parent.taskId); - saveAndUpdateModifiedDate(metadata, node.taskId); - } - - for(Node child : node.children) { - traverseTreeAndWriteValues(child, order, indent + 1); - } + @Override + protected LongProperty parentProperty() { + return GtasksMetadata.PARENT_TASK; } - private Node findNode(Node node, long taskId) { - if(node.taskId == taskId) - return node; - for(Node child : node.children) { - Node found = findNode(child, taskId); - if(found != null) - return found; - } - return null; + @Override + protected Metadata getTaskMetadata(StoreObject list, long taskId) { + return gtasksMetadataService.getTaskMetadata(taskId); } - - private Node buildTreeModel(StoreObject list) { - final Node root = new Node(-1, null); - final AtomicInteger previoustIndent = new AtomicInteger(-1); - final AtomicReference currentNode = new AtomicReference(root); - - gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() { - @Override - public void processTask(long taskId, Metadata metadata) { - int indent = metadata.getValue(GtasksMetadata.INDENT); - - int previousIndentValue = previoustIndent.get(); - if(indent == previousIndentValue) { // sibling - Node parent = currentNode.get().parent; - currentNode.set(new Node(taskId, parent)); - parent.children.add(currentNode.get()); - } else if(indent > previousIndentValue) { // child - Node parent = currentNode.get(); - currentNode.set(new Node(taskId, parent)); - parent.children.add(currentNode.get()); - } else { // in a different tree - Node node = currentNode.get().parent; - for(int i = indent; i < previousIndentValue; i++) - node = node.parent; - if(node == null) - node = root; - currentNode.set(new Node(taskId, node)); - node.children.add(currentNode.get()); - } - - previoustIndent.set(indent); - } - }); - return root; + @Override + protected Metadata createEmptyMetadata(StoreObject list, long taskId) { + Metadata metadata = GtasksMetadata.createEmptyMetadata(taskId); + metadata.setValue(GtasksMetadata.LIST_ID, list.getValue(GtasksList.REMOTE_ID)); + return metadata; } - // --- utility - - public void debugPrint(String listId) { - StoreObject list = gtasksListService.getList(listId); - if(list == GtasksListService.LIST_NOT_FOUND_OBJECT) - return; - - gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() { - public void processTask(long taskId, Metadata metadata) { - System.err.format("id %d: order %d, indent:%d, parent:%d\n", taskId, //$NON-NLS-1$ - metadata.getValue(GtasksMetadata.ORDER), - metadata.getValue(GtasksMetadata.INDENT), - metadata.getValue(GtasksMetadata.PARENT_TASK)); - } - }); + @Override + protected void beforeIndent(StoreObject list) { + updateParentSiblingMapsFor(list); } - @SuppressWarnings("nls") - public void debugPrint(Node root, int depth) { - for(int i = 0; i < depth; i++) System.err.print(" + "); - System.err.format("%03d", root.taskId); - System.err.print("\n"); - - for(int i = 0; i < root.children.size(); i++) - debugPrint(root.children.get(i), depth + 1); + @Override + protected void iterateThroughList(Filter filter, StoreObject list, OrderedListIterator iterator) { + gtasksMetadataService.iterateThroughList(list, iterator); } - private final Task taskContainer = new Task(); + // --- used during synchronization - private void saveAndUpdateModifiedDate(Metadata metadata, long taskId) { - if(metadata.getSetValues().size() == 0) - return; - PluginServices.getMetadataService().save(metadata); - taskContainer.setId(taskId); - taskContainer.setValue(Task.MODIFICATION_DATE, DateUtilities.now()); - taskContainer.setValue(Task.DETAILS_DATE, DateUtilities.now()); - PluginServices.getTaskService().save(taskContainer); + /** + * Create a local tree of tasks to expedite sibling and parent lookups + */ + public void createParentSiblingMaps() { + for(StoreObject list : gtasksListService.getLists()) { + updateParentSiblingMapsFor(list); + } } - // --- used during synchronization - /** * Update order, parent, and indentation fields for all tasks in the given list * @param listId @@ -297,7 +104,7 @@ public class GtasksTaskListUpdater { final AtomicLong order = new AtomicLong(0); final AtomicInteger previousIndent = new AtomicInteger(-1); - gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() { + gtasksMetadataService.iterateThroughList(list, new OrderedListIterator() { @Override public void processTask(long taskId, Metadata metadata) { metadata.setValue(GtasksMetadata.ORDER, order.getAndAdd(1)); @@ -323,7 +130,8 @@ public class GtasksTaskListUpdater { private void orderAndIndentHelper(String listId, AtomicLong order, long parent, int indentLevel) { TodorooCursor metadata = metadataDao.query(Query.select(Metadata.PROPERTIES) - .where(Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), GtasksMetadata.LIST_ID.eq(listId), GtasksMetadata.PARENT_TASK.eq(parent))) + .where(Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), + GtasksMetadata.LIST_ID.eq(listId), GtasksMetadata.PARENT_TASK.eq(parent))) .orderBy(Order.asc(GtasksMetadata.GTASKS_ORDER))); try { if (metadata.getCount() > 0) { @@ -342,20 +150,11 @@ public class GtasksTaskListUpdater { } } - /** - * Create a local tree of tasks to expedite sibling and parent lookups - */ - public void createParentSiblingMaps() { - for(StoreObject list : gtasksListService.getLists()) { - updateParentSiblingMapsFor(list); - } - } - private void updateParentSiblingMapsFor(StoreObject list) { final AtomicLong previousTask = new AtomicLong(-1L); final AtomicInteger previousIndent = new AtomicInteger(-1); - gtasksMetadataService.iterateThroughList(list, new GtasksMetadataService.ListIterator() { + gtasksMetadataService.iterateThroughList(list, new OrderedListIterator() { @Override public void processTask(long taskId, Metadata metadata) { int indent = metadata.getValue(GtasksMetadata.INDENT); diff --git a/astrid/plugin-src/com/todoroo/astrid/subtasks/OrderedListUpdater.java b/astrid/plugin-src/com/todoroo/astrid/subtasks/OrderedListUpdater.java new file mode 100644 index 000000000..45c2aa51e --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/subtasks/OrderedListUpdater.java @@ -0,0 +1,288 @@ +package com.todoroo.astrid.subtasks; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import com.todoroo.andlib.data.Property.IntegerProperty; +import com.todoroo.andlib.data.Property.LongProperty; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.Task; + +abstract public class OrderedListUpdater { + + public OrderedListUpdater() { + DependencyInjectionService.getInstance().inject(this); + } + + public interface OrderedListIterator { + public void processTask(long taskId, Metadata metadata); + } + + // --- abstract + + abstract protected Metadata getTaskMetadata(LIST list, long taskId); + + abstract protected IntegerProperty indentProperty(); + + abstract protected LongProperty orderProperty(); + + abstract protected LongProperty parentProperty(); + + abstract protected void iterateThroughList(Filter filter, LIST list, OrderedListIterator iterator); + + abstract protected Metadata createEmptyMetadata(LIST list, long taskId); + + /** @param list */ + protected void beforeIndent(LIST list) { + // + } + + /** + * @param list + * @param taskId + * @param metadata + * @param indent + * @param order + */ + protected void beforeSaveIndent(LIST list, long taskId, Metadata metadata, int indent, int order) { + // + } + + // --- task indenting + + /** + * Indent a task and all its children + */ + public void indent(final Filter filter, final LIST list, final long targetTaskId, final int delta) { + if(list == null) + return; + + beforeIndent(list); + + final AtomicInteger targetTaskIndent = new AtomicInteger(-1); + final AtomicInteger previousIndent = new AtomicInteger(-1); + final AtomicLong previousTask = new AtomicLong(-1); + + iterateThroughList(filter, list, new OrderedListIterator() { + @Override + public void processTask(long taskId, Metadata metadata) { + int indent = metadata.getValue(indentProperty()); + + if(targetTaskId == taskId) { + // if indenting is warranted, indent me and my children + if(indent + delta <= previousIndent.get() + 1 && indent + delta >= 0) { + targetTaskIndent.set(indent); + metadata.setValue(indentProperty(), indent + delta); + + if(parentProperty() != null) { + long newParent = computeNewParent(filter, list, + taskId, indent + delta - 1); + if (newParent == taskId) + metadata.setValue(parentProperty(), Task.NO_ID); + else + metadata.setValue(parentProperty(), newParent); + } + saveAndUpdateModifiedDate(metadata, taskId); + } + } else if(targetTaskIndent.get() > -1) { + // found first task that is not beneath target + if(indent <= targetTaskIndent.get()) + targetTaskIndent.set(-1); + else { + metadata.setValue(indentProperty(), indent + delta); + saveAndUpdateModifiedDate(metadata, taskId); + } + } else { + previousIndent.set(indent); + previousTask.set(taskId); + } + } + + }); + } + + /** + * Helper function to iterate through a list and compute a new parent for the target task + * based on the target parent's indent + * @param list + * @param targetTaskId + * @param newIndent + * @return + */ + private long computeNewParent(Filter filter, LIST list, long targetTaskId, int targetParentIndent) { + final AtomicInteger desiredParentIndent = new AtomicInteger(targetParentIndent); + final AtomicLong targetTask = new AtomicLong(targetTaskId); + final AtomicLong lastPotentialParent = new AtomicLong(-1); + final AtomicBoolean computedParent = new AtomicBoolean(false); + + iterateThroughList(filter, list, new OrderedListIterator() { + @Override + public void processTask(long taskId, Metadata metadata) { + if (targetTask.get() == taskId) { + computedParent.set(true); + } + + int indent = metadata.getValue(indentProperty()); + if (!computedParent.get() && indent == desiredParentIndent.get()) { + lastPotentialParent.set(taskId); + } + } + }); + + if (lastPotentialParent.get() == -1) return Task.NO_ID; + return lastPotentialParent.get(); + } + + // --- task moving + + /** + * Move a task and all its children to the position right above + * taskIdToMoveto. Will change the indent level to match taskIdToMoveTo. + * + * @param newTaskId task we will move above. if -1, moves to end of list + */ + public void moveTo(Filter filter, LIST list, final long targetTaskId, + final long moveBeforeTaskId) { + if(list == null) + return; + + Node root = buildTreeModel(filter, list); + Node target = findNode(root, targetTaskId); + + if(target != null && target.parent != null) { + if(moveBeforeTaskId == -1) { + target.parent.children.remove(target); + root.children.add(target); + target.parent = root; + } else { + Node sibling = findNode(root, moveBeforeTaskId); + if(sibling != null) { + int index = sibling.parent.children.indexOf(sibling); + target.parent.children.remove(target); + sibling.parent.children.add(index, target); + target.parent = sibling.parent; + } + } + } + + traverseTreeAndWriteValues(list, root, new AtomicLong(0), -1); + } + + protected static class Node { + public final long taskId; + public Node parent; + public final ArrayList children = new ArrayList(); + + public Node(long taskId, Node parent) { + this.taskId = taskId; + this.parent = parent; + } + } + + protected void traverseTreeAndWriteValues(LIST list, Node node, AtomicLong order, int indent) { + if(node.taskId != -1) { + Metadata metadata = getTaskMetadata(list, node.taskId); + if(metadata == null) + metadata = createEmptyMetadata(list, node.taskId); + metadata.setValue(orderProperty(), order.getAndIncrement()); + metadata.setValue(indentProperty(), indent); + if(parentProperty() != null) + metadata.setValue(parentProperty(), node.parent.taskId); + saveAndUpdateModifiedDate(metadata, node.taskId); + } + + for(Node child : node.children) { + traverseTreeAndWriteValues(list, child, order, indent + 1); + } + } + + protected Node findNode(Node node, long taskId) { + if(node.taskId == taskId) + return node; + for(Node child : node.children) { + Node found = findNode(child, taskId); + if(found != null) + return found; + } + return null; + } + + protected Node buildTreeModel(Filter filter, LIST list) { + final Node root = new Node(-1, null); + final AtomicInteger previoustIndent = new AtomicInteger(-1); + final AtomicReference currentNode = new AtomicReference(root); + + iterateThroughList(filter, list, new OrderedListIterator() { + @Override + public void processTask(long taskId, Metadata metadata) { + int indent = metadata.getValue(indentProperty()); + + int previousIndentValue = previoustIndent.get(); + if(indent == previousIndentValue) { // sibling + Node parent = currentNode.get().parent; + currentNode.set(new Node(taskId, parent)); + parent.children.add(currentNode.get()); + } else if(indent > previousIndentValue) { // child + Node parent = currentNode.get(); + currentNode.set(new Node(taskId, parent)); + parent.children.add(currentNode.get()); + } else { // in a different tree + Node node = currentNode.get().parent; + for(int i = indent; i < previousIndentValue; i++) + node = node.parent; + if(node == null) + node = root; + currentNode.set(new Node(taskId, node)); + node.children.add(currentNode.get()); + } + + previoustIndent.set(indent); + } + }); + return root; + } + + protected final Task taskContainer = new Task(); + + protected void saveAndUpdateModifiedDate(Metadata metadata, long taskId) { + if(metadata.getSetValues().size() == 0) + return; + PluginServices.getMetadataService().save(metadata); + taskContainer.setId(taskId); + taskContainer.setValue(Task.MODIFICATION_DATE, DateUtilities.now()); + taskContainer.setValue(Task.DETAILS_DATE, DateUtilities.now()); + PluginServices.getTaskService().save(taskContainer); + } + + // --- utility + + public void debugPrint(Filter filter, LIST list) { + iterateThroughList(filter, list, new OrderedListIterator() { + public void processTask(long taskId, Metadata metadata) { + System.err.format("id %d: order %d, indent:%d, parent:%d\n", taskId, //$NON-NLS-1$ + metadata.getValue(orderProperty()), + metadata.getValue(indentProperty()), + parentProperty() == null ? -1 : metadata.getValue(parentProperty())); + } + }); + } + + + @SuppressWarnings("nls") + public void debugPrint(Node root, int depth) { + for(int i = 0; i < depth; i++) System.err.print(" + "); + System.err.format("%03d", root.taskId); + System.err.print("\n"); + + for(int i = 0; i < root.children.size(); i++) + debugPrint(root.children.get(i), depth + 1); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksListFragment.java b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksListFragment.java index 776fa7511..a1c116614 100644 --- a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksListFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksListFragment.java @@ -15,12 +15,15 @@ import com.timsu.astrid.R; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.adapter.TaskAdapter; +import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.Task; public class SubtasksListFragment extends TaskListFragment { private final DisplayMetrics metrics = new DisplayMetrics(); + private final SubtasksUpdater updater = new SubtasksUpdater(); + public TouchListView getTouchListView() { TouchListView tlv = (TouchListView) getListView(); return tlv; @@ -43,10 +46,39 @@ public class SubtasksListFragment extends TaskListFragment { getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); } + @SuppressWarnings("nls") + @Override + protected void setUpTaskList() { + String query = filter.sqlQuery; + + query = String.format("LEFT JOIN %s ON (%s = %s AND %s = '%s') %s", + Metadata.TABLE, Task.ID, Metadata.TASK, + Metadata.KEY, SubtasksMetadata.METADATA_KEY, query); + query = query.replaceAll("ORDER BY .*", ""); + query = query + String.format(" ORDER BY %s ASC, %s ASC", + SubtasksMetadata.ORDER, Task.ID); + + filter.sqlQuery = query; + + super.setUpTaskList(); + } + private final DropListener dropListener = new DropListener() { @Override public void drop(int from, int to) { - System.err.println("DROPPED TO " + to); + long targetTaskId = taskAdapter.getItemId(from); + long destinationTaskId = taskAdapter.getItemId(to); + + System.err.println("BEFORE"); + updater.debugPrint(filter, SubtasksMetadata.LIST_ACTIVE_TASKS); + if(to == getListView().getCount() - 1) + updater.moveTo(filter, SubtasksMetadata.LIST_ACTIVE_TASKS, targetTaskId, -1); + else + updater.moveTo(filter, SubtasksMetadata.LIST_ACTIVE_TASKS, targetTaskId, destinationTaskId); + System.err.println("AFTER"); + updater.debugPrint(filter, SubtasksMetadata.LIST_ACTIVE_TASKS); + + loadTaskListContent(true); } }; diff --git a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksMetadata.java b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksMetadata.java index eead5be3d..75158fc99 100644 --- a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksMetadata.java +++ b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksMetadata.java @@ -2,7 +2,6 @@ package com.todoroo.astrid.subtasks; import com.todoroo.andlib.data.Property.IntegerProperty; import com.todoroo.andlib.data.Property.LongProperty; -import com.todoroo.andlib.data.Property.StringProperty; import com.todoroo.astrid.data.Metadata; /** @@ -12,13 +11,13 @@ import com.todoroo.astrid.data.Metadata; */ public class SubtasksMetadata { - static final int VALUE_UNSET = -1; + public static final long LIST_ACTIVE_TASKS = 0; /** metadata key */ public static final String METADATA_KEY = "subtasks"; //$NON-NLS-1$ /** list id */ - public static final StringProperty LIST_ID = new StringProperty(Metadata.TABLE, + public static final LongProperty LIST_ID = new LongProperty(Metadata.TABLE, Metadata.VALUE1.name); public static final IntegerProperty INDENT = new IntegerProperty(Metadata.TABLE, diff --git a/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksUpdater.java b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksUpdater.java new file mode 100644 index 000000000..6d7ebfc5a --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/subtasks/SubtasksUpdater.java @@ -0,0 +1,80 @@ +package com.todoroo.astrid.subtasks; + +import com.todoroo.andlib.data.Property.IntegerProperty; +import com.todoroo.andlib.data.Property.LongProperty; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Query; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.service.MetadataService; +import com.todoroo.astrid.service.TaskService; + +public class SubtasksUpdater extends OrderedListUpdater { + + private static final String METADATA_ID = "mdi"; //$NON-NLS-1$ + + @Autowired MetadataService metadataService; + @Autowired TaskService taskService; + + @Override + protected IntegerProperty indentProperty() { + return SubtasksMetadata.INDENT; + } + + @Override + protected LongProperty orderProperty() { + return SubtasksMetadata.ORDER; + } + @Override + protected LongProperty parentProperty() { + return null; + } + + @Override + protected Metadata getTaskMetadata(Long list, long taskId) { + TodorooCursor cursor = metadataService.query(Query.select(Metadata.PROPERTIES).where( + Criterion.and( + Metadata.TASK.eq(taskId), + Metadata.KEY.eq(SubtasksMetadata.METADATA_KEY), + SubtasksMetadata.LIST_ID.eq(list)))); + try { + cursor.moveToFirst(); + if(cursor.isAfterLast()) + return null; + return new Metadata(cursor); + } finally { + cursor.close(); + } + } + + @Override + protected Metadata createEmptyMetadata(Long list, long taskId) { + Metadata m = new Metadata(); + m.setValue(Metadata.TASK, taskId); + m.setValue(Metadata.KEY, SubtasksMetadata.METADATA_KEY); + m.setValue(SubtasksMetadata.LIST_ID, list); + return m; + } + + @Override + protected void iterateThroughList(Filter filter, Long list, OrderedListIterator iterator) { + TodorooCursor cursor = taskService.query(Query.select(Task.ID, + Metadata.ID.as(METADATA_ID), Metadata.TASK, Metadata.KEY, SubtasksMetadata.INDENT, + SubtasksMetadata.ORDER).withQueryTemplate(filter.sqlQuery)); + TodorooCursor metadataCursor = new TodorooCursor(cursor.getCursor(), + cursor.getProperties()); + Metadata metadata = new Metadata(); + try { + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + metadata.readFromCursor(metadataCursor); + metadata.setId(cursor.getLong(cursor.getColumnIndex(METADATA_ID))); + iterator.processTask(cursor.get(Task.ID), metadata); + } + } finally { + cursor.close(); + } + } +}