mirror of https://github.com/tasks/tasks
Merge branch 'subtasks_performance'
commit
360445034a
@ -0,0 +1,321 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.commonsware.cwac.tlv.TouchListView.DropListener;
|
||||
import com.commonsware.cwac.tlv.TouchListView.GrabberClickListener;
|
||||
import com.commonsware.cwac.tlv.TouchListView.SwipeListener;
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.activity.TaskListFragment;
|
||||
import com.todoroo.astrid.adapter.TaskAdapter;
|
||||
import com.todoroo.astrid.adapter.TaskAdapter.OnCompletedTaskListener;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.service.MetadataService;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
import com.todoroo.astrid.service.ThemeService;
|
||||
import com.todoroo.astrid.ui.DraggableListView;
|
||||
import com.todoroo.astrid.utility.AstridPreferences;
|
||||
|
||||
public class AstridOrderedListFragmentHelper<LIST> implements OrderedListFragmentHelperInterface<LIST> {
|
||||
|
||||
|
||||
private final DisplayMetrics metrics = new DisplayMetrics();
|
||||
private final AstridOrderedListUpdater<LIST> updater;
|
||||
private final TaskListFragment fragment;
|
||||
|
||||
@Autowired TaskService taskService;
|
||||
@Autowired MetadataService metadataService;
|
||||
|
||||
private DraggableTaskAdapter taskAdapter;
|
||||
|
||||
private LIST list;
|
||||
|
||||
public AstridOrderedListFragmentHelper(TaskListFragment fragment, AstridOrderedListUpdater<LIST> updater) {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
this.fragment = fragment;
|
||||
this.updater = updater;
|
||||
}
|
||||
|
||||
// --- ui component setup
|
||||
|
||||
private Activity getActivity() {
|
||||
return fragment.getActivity();
|
||||
}
|
||||
|
||||
private ListView getListView() {
|
||||
return fragment.getListView();
|
||||
}
|
||||
|
||||
private Filter getFilter() {
|
||||
return fragment.getFilter();
|
||||
}
|
||||
|
||||
public DraggableListView getTouchListView() {
|
||||
DraggableListView tlv = (DraggableListView) fragment.getListView();
|
||||
return tlv;
|
||||
}
|
||||
|
||||
public void setUpUiComponents() {
|
||||
TypedValue tv = new TypedValue();
|
||||
getActivity().getTheme().resolveAttribute(R.attr.asThemeTextColor, tv, false);
|
||||
getTouchListView().setDragndropBackgroundColor(tv.data);
|
||||
getTouchListView().setDropListener(dropListener);
|
||||
getTouchListView().setClickListener(rowClickListener);
|
||||
getTouchListView().setSwipeListener(swipeListener);
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
|
||||
if(Preferences.getInt(AstridPreferences.P_SUBTASKS_HELP, 0) == 0)
|
||||
showSubtasksHelp();
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void showSubtasksHelp() {
|
||||
String body = String.format("<h3>%s</h3><img src='%s'>" +
|
||||
"<br>%s<br><br><br><img src='%s'><br>%s",
|
||||
getActivity().getString(R.string.subtasks_help_1),
|
||||
"subtasks_vertical.png",
|
||||
getActivity().getString(R.string.subtasks_help_2),
|
||||
"subtasks_horizontal.png",
|
||||
getActivity().getString(R.string.subtasks_help_3));
|
||||
|
||||
String color = ThemeService.getDialogTextColorString();
|
||||
String html = String.format("<html><body style='text-align:center;color:%s'>%s</body></html>",
|
||||
color, body);
|
||||
|
||||
DialogUtilities.htmlDialog(getActivity(), html, R.string.subtasks_help_title);
|
||||
Preferences.setInt(AstridPreferences.P_SUBTASKS_HELP, 1);
|
||||
}
|
||||
|
||||
public void beforeSetUpTaskList(Filter filter) {
|
||||
updater.initialize(list, filter);
|
||||
}
|
||||
|
||||
private final DropListener dropListener = new DropListener() {
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
long targetTaskId = taskAdapter.getItemId(from);
|
||||
if (targetTaskId <= 0) return; // This can happen with gestures on empty parts of the list (e.g. extra space below tasks)
|
||||
long destinationTaskId = taskAdapter.getItemId(to);
|
||||
|
||||
try {
|
||||
if(to >= getListView().getCount())
|
||||
updater.moveTo(list, getFilter(), targetTaskId, -1);
|
||||
else
|
||||
updater.moveTo(list, getFilter(), targetTaskId, destinationTaskId);
|
||||
} catch (Exception e) {
|
||||
Log.e("drag", "Drag Error", e); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
fragment.reconstructCursor();
|
||||
fragment.loadTaskListContent(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final SwipeListener swipeListener = new SwipeListener() {
|
||||
@Override
|
||||
public void swipeRight(int which) {
|
||||
indent(which, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeLeft(int which) {
|
||||
indent(which, -1);
|
||||
}
|
||||
|
||||
protected void indent(int which, int delta) {
|
||||
long targetTaskId = taskAdapter.getItemId(which);
|
||||
if (targetTaskId <= 0) return; // This can happen with gestures on empty parts of the list (e.g. extra space below tasks)
|
||||
try {
|
||||
updater.indent(list, getFilter(), targetTaskId, delta);
|
||||
} catch (Exception e) {
|
||||
Log.e("drag", "Indent Error", e); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
fragment.reconstructCursor();
|
||||
fragment.loadTaskListContent(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final GrabberClickListener rowClickListener = new GrabberClickListener() {
|
||||
@Override
|
||||
public void onLongClick(final View v) {
|
||||
if(v == null)
|
||||
return;
|
||||
|
||||
fragment.registerForContextMenu(getListView());
|
||||
getListView().showContextMenuForChild(v);
|
||||
fragment.unregisterForContextMenu(getListView());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if(v == null)
|
||||
return;
|
||||
((DraggableTaskAdapter) taskAdapter).getListener().onClick(v);
|
||||
}
|
||||
};
|
||||
|
||||
public TaskAdapter createTaskAdapter(TodorooCursor<Task> cursor,
|
||||
AtomicReference<String> sqlQueryTemplate) {
|
||||
int resource = Preferences.getBoolean(R.string.p_taskRowStyle, false) ?
|
||||
R.layout.task_adapter_row_simple : R.layout.task_adapter_row;
|
||||
|
||||
taskAdapter = new DraggableTaskAdapter(fragment, resource,
|
||||
cursor, sqlQueryTemplate, false, null);
|
||||
|
||||
taskAdapter.addOnCompletedTaskListener(new OnCompletedTaskListener() {
|
||||
@Override
|
||||
public void onCompletedTask(Task item, boolean newState) {
|
||||
setCompletedForItemAndSubtasks(item, newState);
|
||||
}
|
||||
});
|
||||
|
||||
return taskAdapter;
|
||||
}
|
||||
|
||||
private final class DraggableTaskAdapter extends TaskAdapter {
|
||||
|
||||
private DraggableTaskAdapter(TaskListFragment activity, int resource,
|
||||
Cursor c, AtomicReference<String> query, boolean autoRequery,
|
||||
OnCompletedTaskListener onCompletedTaskListener) {
|
||||
super(activity, resource, c, query, autoRequery,
|
||||
onCompletedTaskListener);
|
||||
|
||||
applyListeners = APPLY_LISTENERS_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewHolder getTagFromCheckBox(View v) {
|
||||
return (ViewHolder)((View)v.getParent()).getTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = super.newView(context, cursor, parent);
|
||||
view.getLayoutParams().height = Math.round(45 * metrics.density);
|
||||
|
||||
ViewHolder vh = (ViewHolder) view.getTag();
|
||||
|
||||
MarginLayoutParams rowParams = (MarginLayoutParams) vh.rowBody.getLayoutParams();
|
||||
rowParams.topMargin = rowParams.bottomMargin = 0;
|
||||
|
||||
ViewGroup.LayoutParams pictureParams = vh.picture.getLayoutParams();
|
||||
pictureParams.width = pictureParams.height = Math.round(38 * metrics.density);
|
||||
|
||||
pictureParams = vh.pictureBorder.getLayoutParams();
|
||||
pictureParams.width = pictureParams.height = Math.round(38 * metrics.density);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setFieldContentsAndVisibility(View view) {
|
||||
super.setFieldContentsAndVisibility(view);
|
||||
|
||||
ViewHolder vh = (ViewHolder) view.getTag();
|
||||
int indent = updater.getIndentForTask(vh.task.getId());
|
||||
vh.rowBody.setPadding(Math.round(indent * 20 * metrics.density), 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addListeners(View container) {
|
||||
super.addListeners(container);
|
||||
}
|
||||
|
||||
public TaskRowListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Long, ArrayList<Long>> chainedCompletions =
|
||||
Collections.synchronizedMap(new HashMap<Long, ArrayList<Long>>());
|
||||
|
||||
private void setCompletedForItemAndSubtasks(final Task item, final boolean completedState) {
|
||||
final long itemId = item.getId();
|
||||
|
||||
final Task model = new Task();
|
||||
final long completionDate = completedState ? DateUtilities.now() : 0;
|
||||
|
||||
if(completedState == false) {
|
||||
ArrayList<Long> chained = chainedCompletions.get(itemId);
|
||||
if(chained != null) {
|
||||
for(Long taskId : chained) {
|
||||
model.setId(taskId);
|
||||
model.setValue(Task.COMPLETION_DATE, completionDate);
|
||||
taskService.save(model);
|
||||
model.clear();
|
||||
|
||||
taskAdapter.getCompletedItems().put(taskId, false);
|
||||
}
|
||||
taskAdapter.notifyDataSetInvalidated();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<Long> chained = new ArrayList<Long>();
|
||||
updater.applyToDescendants(itemId, new AstridOrderedListUpdater.OrderedListNodeVisitor() {
|
||||
@Override
|
||||
public void visitNode(AstridOrderedListUpdater.Node node) {
|
||||
// Task childTask = taskService.fetchById(node.taskId, Task.RECURRENCE);
|
||||
//
|
||||
// if(!TextUtils.isEmpty(childTask.getValue(Task.RECURRENCE))) {
|
||||
// Metadata metadata = updater.getTaskMetadata(list, node.taskId);
|
||||
// metadata.setValue(updater.indentProperty(), parentIndent);
|
||||
// metadataService.save(metadata);
|
||||
// }
|
||||
|
||||
model.setId(node.taskId);
|
||||
model.setValue(Task.COMPLETION_DATE, completionDate);
|
||||
taskService.save(model);
|
||||
model.clear();
|
||||
|
||||
taskAdapter.getCompletedItems().put(node.taskId, true);
|
||||
chained.add(node.taskId);
|
||||
}
|
||||
});
|
||||
|
||||
if(chained.size() > 0) {
|
||||
chainedCompletions.put(itemId, chained);
|
||||
taskAdapter.notifyDataSetInvalidated();
|
||||
}
|
||||
}
|
||||
|
||||
public void setList(LIST list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public void onCreateTask(Task task) {
|
||||
updater.onCreateTask(list, getFilter(), task.getId());
|
||||
fragment.reconstructCursor();
|
||||
fragment.loadTaskListContent(true);
|
||||
}
|
||||
|
||||
public void onDeleteTask(Task task) {
|
||||
updater.onDeleteTask(list, getFilter(), task.getId());
|
||||
taskAdapter.notifyDataSetInvalidated();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,361 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
|
||||
public abstract class AstridOrderedListUpdater<LIST> {
|
||||
|
||||
@Autowired
|
||||
private TaskService taskService;
|
||||
|
||||
public AstridOrderedListUpdater() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
idToNode = new HashMap<Long, Node>();
|
||||
}
|
||||
|
||||
public interface OrderedListNodeVisitor {
|
||||
public void visitNode(Node node);
|
||||
}
|
||||
|
||||
public static class Node {
|
||||
public final long taskId;
|
||||
public Node parent;
|
||||
public int indent;
|
||||
public final ArrayList<Node> children = new ArrayList<Node>();
|
||||
|
||||
public Node(long taskId, Node parent, int indent) {
|
||||
this.taskId = taskId;
|
||||
this.parent = parent;
|
||||
this.indent = indent;
|
||||
}
|
||||
}
|
||||
|
||||
private Node treeRoot;
|
||||
|
||||
private final HashMap<Long, Node> idToNode;
|
||||
|
||||
protected abstract String getSerializedTree(LIST list, Filter filter);
|
||||
protected abstract void writeSerialization(LIST list, String serialized);
|
||||
protected abstract void applyToFilter(Filter filter);
|
||||
|
||||
public int getIndentForTask(long targetTaskId) {
|
||||
Node n = idToNode.get(targetTaskId);
|
||||
if (n == null)
|
||||
return 0;
|
||||
return n.indent;
|
||||
}
|
||||
|
||||
public void initialize(LIST list, Filter filter) {
|
||||
initializeFromSerializedTree(list, filter, getSerializedTree(list, filter));
|
||||
}
|
||||
|
||||
public void initializeFromSerializedTree(LIST list, Filter filter, String serializedTree) {
|
||||
treeRoot = buildTreeModel(serializedTree);
|
||||
verifyTreeModel(list, filter);
|
||||
}
|
||||
|
||||
private void verifyTreeModel(LIST list, Filter filter) {
|
||||
boolean addedThings = false;
|
||||
TodorooCursor<Task> tasks = taskService.fetchFiltered(filter.getSqlQuery(), null, Task.ID);
|
||||
try {
|
||||
for (tasks.moveToFirst(); !tasks.isAfterLast(); tasks.moveToNext()) {
|
||||
Long id = tasks.getLong(0);
|
||||
if (idToNode.containsKey(id))
|
||||
continue;
|
||||
|
||||
addedThings = true;
|
||||
Node newNode = new Node(id, treeRoot, 0);
|
||||
treeRoot.children.add(newNode);
|
||||
idToNode.put(id, newNode);
|
||||
}
|
||||
} finally {
|
||||
tasks.close();
|
||||
}
|
||||
if (addedThings)
|
||||
writeSerialization(list, serializeTree());
|
||||
}
|
||||
|
||||
public Node findNodeForTask(long taskId) {
|
||||
return idToNode.get(taskId);
|
||||
}
|
||||
|
||||
public Long[] getOrderedIds() {
|
||||
ArrayList<Long> ids = new ArrayList<Long>();
|
||||
orderedIdHelper(treeRoot, ids);
|
||||
return ids.toArray(new Long[ids.size()]);
|
||||
}
|
||||
|
||||
public String getOrderString() {
|
||||
Long[] ids = getOrderedIds();
|
||||
return buildOrderString(ids);
|
||||
}
|
||||
|
||||
public static String buildOrderString(Long[] 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.ID.eq(ids[i]).toString());
|
||||
if (i > 0)
|
||||
builder.append(", "); //$NON-NLS-1$
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void orderedIdHelper(Node node, List<Long> ids) {
|
||||
if (node != treeRoot)
|
||||
ids.add(node.taskId);
|
||||
|
||||
for (Node child : node.children) {
|
||||
orderedIdHelper(child, ids);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyToDescendants(long 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 iterateOverList(OrderedListNodeVisitor visitor) {
|
||||
applyToDescendantsHelper(treeRoot, visitor);
|
||||
}
|
||||
|
||||
public void indent(LIST list, Filter filter, long targetTaskId, int delta) {
|
||||
Node node = idToNode.get(targetTaskId);
|
||||
indentHelper(list, filter, node, delta);
|
||||
}
|
||||
|
||||
private void indentHelper(LIST 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(LIST list, Filter filter, long targetTaskId, long beforeTaskId) {
|
||||
Node target = idToNode.get(targetTaskId);
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (beforeTaskId == -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);
|
||||
}
|
||||
|
||||
private void moveHelper(LIST 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);
|
||||
}
|
||||
|
||||
// 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(LIST list, Filter filter, Node moveThis) {
|
||||
Node parent = moveThis.parent;
|
||||
parent.children.remove(moveThis);
|
||||
treeRoot.children.add(moveThis);
|
||||
moveThis.parent = treeRoot;
|
||||
moveThis.indent = 0;
|
||||
writeSerialization(list, serializeTree());
|
||||
applyToFilter(filter);
|
||||
}
|
||||
|
||||
public void onCreateTask(LIST list, Filter filter, long taskId) {
|
||||
if (idToNode.containsKey(taskId) || taskId < 0)
|
||||
return;
|
||||
|
||||
Node newNode = new Node(taskId, treeRoot, 0);
|
||||
treeRoot.children.add(newNode);
|
||||
idToNode.put(taskId, newNode);
|
||||
writeSerialization(list, serializeTree());
|
||||
applyToFilter(filter);
|
||||
}
|
||||
|
||||
public void onDeleteTask(LIST list, Filter filter, long taskId) {
|
||||
Node task = idToNode.get(taskId);
|
||||
if (task == null)
|
||||
return;
|
||||
|
||||
Node parent = task.parent;
|
||||
ArrayList<Node> siblings = parent.children;
|
||||
int index = siblings.indexOf(task);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private Node buildTreeModel(String serializedTree) {
|
||||
Node root = new Node(-1, null, -1);
|
||||
try {
|
||||
JSONArray tree = new JSONArray(serializedTree);
|
||||
recursivelyBuildChildren(root, tree);
|
||||
} catch (JSONException e) {
|
||||
Log.e("OrderedListUpdater", "Error building tree model", e); //$NON-NLS-1$//$NON-NLS-2$
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
private void recursivelyBuildChildren(Node node, JSONArray children) throws JSONException {
|
||||
for (int i = 0; i < children.length(); i++) {
|
||||
JSONObject childObj = children.getJSONObject(i);
|
||||
JSONArray keys = childObj.names();
|
||||
if (keys == null)
|
||||
continue;
|
||||
|
||||
Long id = keys.getLong(0);
|
||||
if (id <= 0)
|
||||
continue;
|
||||
|
||||
JSONArray childsChildren = childObj.getJSONArray(Long.toString(id));
|
||||
Node child = new Node(id, node, node.indent + 1);
|
||||
recursivelyBuildChildren(child, childsChildren);
|
||||
node.children.add(child);
|
||||
idToNode.put(id, child);
|
||||
}
|
||||
}
|
||||
|
||||
protected String serializeTree() {
|
||||
JSONArray tree = new JSONArray();
|
||||
if (treeRoot == null) {
|
||||
return tree.toString();
|
||||
}
|
||||
|
||||
try {
|
||||
recursivelySerializeChildren(treeRoot, tree);
|
||||
} catch (JSONException e) {
|
||||
Log.e("OrderedListUpdater", "Error serializing tree model", e); //$NON-NLS-1$//$NON-NLS-2$
|
||||
}
|
||||
return tree.toString();
|
||||
}
|
||||
|
||||
public static void recursivelySerializeChildren(Node node, JSONArray serializeTo) throws JSONException {
|
||||
ArrayList<Node> children = node.children;
|
||||
for (Node child : children) {
|
||||
JSONObject childObj = new JSONObject();
|
||||
JSONArray childsChildren = new JSONArray();
|
||||
recursivelySerializeChildren(child, childsChildren);
|
||||
childObj.put(Long.toString(child.taskId), childsChildren);
|
||||
serializeTo.put(childObj);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.astrid.adapter.TaskAdapter;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
public interface OrderedListFragmentHelperInterface<T> {
|
||||
|
||||
void setUpUiComponents();
|
||||
void beforeSetUpTaskList(Filter filter);
|
||||
void onCreateTask(Task task);
|
||||
void onDeleteTask(Task task);
|
||||
TaskAdapter createTaskAdapter(TodorooCursor<Task> cursor, AtomicReference<String> queryTemplate);
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.sql.Functions;
|
||||
import com.todoroo.andlib.sql.Order;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.service.MetadataService;
|
||||
import com.todoroo.astrid.service.TagDataService;
|
||||
import com.todoroo.astrid.subtasks.AstridOrderedListUpdater.Node;
|
||||
|
||||
@SuppressWarnings("deprecation") // Subtasks metadata is deprecated
|
||||
public class SubtasksMetadataMigration {
|
||||
|
||||
@Autowired
|
||||
private TagDataService tagDataService;
|
||||
|
||||
@Autowired
|
||||
private MetadataService metadataService;
|
||||
|
||||
public SubtasksMetadataMigration() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
public void performMigration() {
|
||||
TodorooCursor<Metadata> subtasksMetadata = metadataService.query(Query.select(Metadata.PROPERTIES)
|
||||
.where(MetadataCriteria.withKey(SubtasksMetadata.METADATA_KEY))
|
||||
.orderBy(Order.asc(SubtasksMetadata.TAG), Order.asc(Functions.cast(SubtasksMetadata.ORDER, "LONG")))); //$NON-NLS-1$
|
||||
try {
|
||||
Metadata m = new Metadata();
|
||||
for (subtasksMetadata.moveToFirst(); !subtasksMetadata.isAfterLast(); subtasksMetadata.moveToNext()) {
|
||||
m.readFromCursor(subtasksMetadata);
|
||||
String tag = m.getValue(SubtasksMetadata.TAG);
|
||||
processTag(tag, subtasksMetadata);
|
||||
}
|
||||
} finally {
|
||||
subtasksMetadata.close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void processTag(String tag, TodorooCursor<Metadata> subtasksMetadata) {
|
||||
Metadata item = new Metadata();
|
||||
TagData td = null;
|
||||
try {
|
||||
if (!SubtasksMetadata.LIST_ACTIVE_TASKS.equals(tag)) {
|
||||
String idString = tag.replace("td:", "");
|
||||
long id = Long.parseLong(idString);
|
||||
td = tagDataService.fetchById(id, TagData.ID);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("subtasks-migration", "Could not parse tag id from " + tag, e);
|
||||
}
|
||||
|
||||
if (td == null && !SubtasksMetadata.LIST_ACTIVE_TASKS.equals(tag)) {
|
||||
for (; !subtasksMetadata.isAfterLast(); subtasksMetadata.moveToNext()) {
|
||||
item.readFromCursor(subtasksMetadata);
|
||||
if (!item.getValue(SubtasksMetadata.TAG).equals(tag))
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
String newTree = buildTreeModelFromMetadata(tag, subtasksMetadata);
|
||||
if (td != null) {
|
||||
td.setValue(TagData.TAG_ORDERING, newTree);
|
||||
tagDataService.save(td);
|
||||
} else {
|
||||
Preferences.setString(SubtasksUpdater.ACTIVE_TASKS_ORDER, newTree);
|
||||
}
|
||||
}
|
||||
|
||||
subtasksMetadata.moveToPrevious(); // Move back one to undo the last iteration of the for loop
|
||||
}
|
||||
|
||||
private String buildTreeModelFromMetadata(String tag, TodorooCursor<Metadata> cursor) {
|
||||
Metadata item = new Metadata();
|
||||
Node root = new Node(-1, null, -1);
|
||||
for (; !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||
item.clear();
|
||||
item.readFromCursor(cursor);
|
||||
if (!item.getValue(SubtasksMetadata.TAG).equals(tag))
|
||||
break;
|
||||
|
||||
int indent = 0;
|
||||
if (item.containsNonNullValue(SubtasksMetadata.INDENT))
|
||||
indent = item.getValue(SubtasksMetadata.INDENT);
|
||||
Node parent = findNextParentForIndent(root, indent);
|
||||
Node newNode = new Node(item.getValue(Metadata.TASK), parent, parent.indent + 1);
|
||||
parent.children.add(newNode);
|
||||
}
|
||||
|
||||
try {
|
||||
JSONArray array = new JSONArray();
|
||||
AstridOrderedListUpdater.recursivelySerializeChildren(root, array);
|
||||
return array.toString();
|
||||
} catch (JSONException e) {
|
||||
return "[]"; //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
private Node findNextParentForIndent(Node root, int indent) {
|
||||
if (indent <= 0)
|
||||
return root;
|
||||
|
||||
ArrayList<Node> children = root.children;
|
||||
if (children.size() == 0)
|
||||
return root;
|
||||
|
||||
return findNextParentForIndent(children.get(children.size() - 1), indent - 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.core.PluginServices;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class SubtasksMigrationTest extends SubtasksTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
Preferences.clear(SubtasksUpdater.ACTIVE_TASKS_ORDER);
|
||||
}
|
||||
|
||||
/* Starting basic state (see SubtasksTestCase):
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
private void createBasicMetadata(TagData tagData) {
|
||||
createSubtasksMetadata(tagData, 1, 1, 0);
|
||||
createSubtasksMetadata(tagData, 2, 2, 1);
|
||||
createSubtasksMetadata(tagData, 3, 3, 1);
|
||||
createSubtasksMetadata(tagData, 4, 4, 2);
|
||||
createSubtasksMetadata(tagData, 5, 5, 0);
|
||||
createSubtasksMetadata(tagData, 6, 6, 0);
|
||||
}
|
||||
|
||||
private void createSubtasksMetadata(TagData tagData, long taskId, long order, int indent) {
|
||||
Metadata m = new Metadata();
|
||||
m.setValue(Metadata.KEY, SubtasksMetadata.METADATA_KEY);
|
||||
m.setValue(Metadata.TASK, taskId);
|
||||
String tagString = (tagData == null ? SubtasksMetadata.LIST_ACTIVE_TASKS : "td:"+tagData.getId());
|
||||
m.setValue(SubtasksMetadata.TAG, tagString);
|
||||
m.setValue(SubtasksMetadata.ORDER, order);
|
||||
m.setValue(SubtasksMetadata.INDENT, indent);
|
||||
PluginServices.getMetadataService().save(m);
|
||||
}
|
||||
|
||||
private void basicTest(TagData tagData) {
|
||||
createBasicMetadata(tagData);
|
||||
SubtasksMetadataMigration migrator = new SubtasksMetadataMigration();
|
||||
migrator.performMigration();
|
||||
|
||||
String newSerializedTree = getSerializedTree(tagData);
|
||||
String expectedSerializedTree = DEFAULT_SERIALIZED_TREE;
|
||||
|
||||
assertEquals(expectedSerializedTree, newSerializedTree);
|
||||
}
|
||||
|
||||
private String getSerializedTree(TagData tagData) {
|
||||
if (tagData == null)
|
||||
return Preferences.getStringValue(SubtasksUpdater.ACTIVE_TASKS_ORDER).replaceAll("\\s", "");
|
||||
tagData = PluginServices.getTagDataService().fetchById(tagData.getId(), TagData.ID, TagData.TAG_ORDERING);
|
||||
return tagData.getValue(TagData.TAG_ORDERING).replaceAll("\\s", "");
|
||||
}
|
||||
|
||||
public void testMigrationForActiveTasks() {
|
||||
basicTest(null);
|
||||
}
|
||||
|
||||
public void testMigrationForTagData() {
|
||||
TagData td = new TagData();
|
||||
td.setValue(TagData.NAME, "tag");
|
||||
PluginServices.getTagDataService().save(td);
|
||||
|
||||
basicTest(td);
|
||||
}
|
||||
|
||||
/* Starting advanced state
|
||||
*
|
||||
* For active tasks
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*
|
||||
* For tag data
|
||||
*
|
||||
* F
|
||||
* E
|
||||
* B
|
||||
* D
|
||||
* C
|
||||
* A
|
||||
*/
|
||||
|
||||
private static final String ACTIVE_TASKS_TREE = "[{\"1\":[{\"2\":[]}]}, {\"3\":[{\"4\":[{\"5\":[]}]}, {\"6\":[]}]}]".replaceAll("\\s", "");
|
||||
private static final String TAG_DATA_TREE = "[{\"6\":[]}, {\"5\":[{\"2\":[]}, {\"4\":[{\"3\":[]}]}]}, {\"1\":[]}]".replaceAll("\\s", "");
|
||||
|
||||
private void createAdvancedMetadata(TagData tagData) {
|
||||
createSubtasksMetadata(tagData, 6, 1, 0);
|
||||
createSubtasksMetadata(tagData, 5, 2, 0);
|
||||
createSubtasksMetadata(tagData, 2, 3, 1);
|
||||
createSubtasksMetadata(tagData, 4, 4, 1);
|
||||
createSubtasksMetadata(tagData, 3, 5, 2);
|
||||
createSubtasksMetadata(tagData, 1, 6, 0);
|
||||
|
||||
createSubtasksMetadata(null, 1, 1, 0);
|
||||
createSubtasksMetadata(null, 2, 2, 1);
|
||||
createSubtasksMetadata(null, 3, 3, 0);
|
||||
createSubtasksMetadata(null, 4, 4, 1);
|
||||
createSubtasksMetadata(null, 5, 5, 2);
|
||||
createSubtasksMetadata(null, 6, 6, 1);
|
||||
}
|
||||
|
||||
public void testMigrationWithBothPresent() {
|
||||
TagData td = new TagData();
|
||||
td.setValue(TagData.NAME, "tag");
|
||||
PluginServices.getTagDataService().save(td);
|
||||
|
||||
createAdvancedMetadata(td);
|
||||
|
||||
SubtasksMetadataMigration migrator = new SubtasksMetadataMigration();
|
||||
migrator.performMigration();
|
||||
|
||||
assertEquals(TAG_DATA_TREE, getSerializedTree(td));
|
||||
assertEquals(ACTIVE_TASKS_TREE, getSerializedTree(null));
|
||||
}
|
||||
}
|
@ -1,160 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2012 Todoroo Inc
|
||||
*
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.core.CoreFilterExposer;
|
||||
import com.todoroo.astrid.core.PluginServices;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public class SubtasksMovingTest extends DatabaseTestCase {
|
||||
public class SubtasksMovingTest extends SubtasksTestCase {
|
||||
|
||||
private SubtasksUpdater updater;
|
||||
|
||||
private Filter filter;
|
||||
private Task A, B, C, D, E, F;
|
||||
private final String list = SubtasksMetadata.LIST_ACTIVE_TASKS;
|
||||
|
||||
/* Starting State:
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
|
||||
public void testMoveBeforeIntoSelf() {
|
||||
givenTasksABCDEF();
|
||||
|
||||
whenTriggerMoveBefore(A, B);
|
||||
|
||||
/*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
*/
|
||||
thenExpectMetadataOrderAndIndent(A, 0, 0);
|
||||
thenExpectMetadataOrderAndIndent(B, 1, 1);
|
||||
thenExpectMetadataOrderAndIndent(C, 2, 1);
|
||||
thenExpectMetadataOrderAndIndent(D, 3, 2);
|
||||
thenExpectMetadataOrderAndIndent(E, 4, 0);
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
createTasks();
|
||||
updater.initializeFromSerializedTree(null, filter, DEFAULT_SERIALIZED_TREE);
|
||||
}
|
||||
|
||||
public void testMoveIntoChild() {
|
||||
givenTasksABCDEF();
|
||||
|
||||
whenTriggerMoveBefore(A, C);
|
||||
|
||||
/*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
*/
|
||||
thenExpectMetadataOrderAndIndent(A, 0, 0);
|
||||
thenExpectMetadataOrderAndIndent(B, 1, 1);
|
||||
thenExpectMetadataOrderAndIndent(C, 2, 1);
|
||||
thenExpectMetadataOrderAndIndent(D, 3, 2);
|
||||
thenExpectMetadataOrderAndIndent(E, 4, 0);
|
||||
private void createTasks() {
|
||||
A = createTask("A");
|
||||
B = createTask("B");
|
||||
C = createTask("C");
|
||||
D = createTask("D");
|
||||
E = createTask("E");
|
||||
F = createTask("F");
|
||||
}
|
||||
|
||||
public void testMoveEndOfChildren() {
|
||||
givenTasksABCDEF();
|
||||
|
||||
whenTriggerMoveBefore(A, E);
|
||||
|
||||
/*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
*/
|
||||
thenExpectMetadataOrderAndIndent(A, 0, 0);
|
||||
thenExpectMetadataOrderAndIndent(B, 1, 1);
|
||||
thenExpectMetadataOrderAndIndent(C, 2, 1);
|
||||
thenExpectMetadataOrderAndIndent(D, 3, 2);
|
||||
thenExpectMetadataOrderAndIndent(E, 4, 0);
|
||||
private Task createTask(String title) {
|
||||
Task task = new Task();
|
||||
task.setValue(Task.TITLE, title);
|
||||
PluginServices.getTaskService().save(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public void testMoveAfterChildren() {
|
||||
givenTasksABCDEF();
|
||||
private void whenTriggerMoveBefore(Task target, Task before) {
|
||||
long beforeId = (before == null ? -1 : before.getId());
|
||||
updater.moveTo(null, filter, target.getId(), beforeId);
|
||||
}
|
||||
|
||||
whenTriggerMoveBefore(A, F);
|
||||
/* Starting State (see SubtasksTestCase):
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
|
||||
public void testMoveBeforeIntoSelf() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, B);
|
||||
|
||||
/*
|
||||
* E
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
*/
|
||||
thenExpectMetadataOrderAndIndent(E, 0, 0);
|
||||
thenExpectMetadataOrderAndIndent(A, 1, 0);
|
||||
thenExpectMetadataOrderAndIndent(B, 2, 1);
|
||||
thenExpectMetadataOrderAndIndent(C, 3, 1);
|
||||
thenExpectMetadataOrderAndIndent(D, 4, 2);
|
||||
expectParentAndPosition(A, null, 0);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 1);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
// --- helpers
|
||||
public void testMoveIntoDescendant() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, C);
|
||||
|
||||
/** moveTo = null => move to end */
|
||||
private void whenTriggerMoveBefore(Task target, Task moveTo) {
|
||||
System.err.println("CAN I GET A WITNESS?");
|
||||
updater.debugPrint(filter, list);
|
||||
updater.moveTo(filter, list, target.getId(), moveTo == null ? -1 : moveTo.getId());
|
||||
updater.debugPrint(filter, list);
|
||||
expectParentAndPosition(A, null, 0);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 1);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
private void thenExpectMetadataOrderAndIndent(Task task, long order, int indent) {
|
||||
Metadata metadata = updater.getTaskMetadata(list, task.getId());
|
||||
assertNotNull("metadata was found", metadata);
|
||||
assertEquals("order", order, metadata.getValue(SubtasksMetadata.ORDER).longValue());
|
||||
assertEquals("indentation", indent, (int)metadata.getValue(SubtasksMetadata.INDENT));
|
||||
public void testMoveToEndOfChildren() { // Should have no effect
|
||||
whenTriggerMoveBefore(A, E);
|
||||
|
||||
expectParentAndPosition(A, null, 0);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 1);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
public void testStandardMove() {
|
||||
whenTriggerMoveBefore(A, F);
|
||||
|
||||
updater = new SubtasksUpdater();
|
||||
filter = CoreFilterExposer.buildInboxFilter(getContext().getResources());
|
||||
updater.applySubtasksToFilter(filter, list);
|
||||
expectParentAndPosition(A, null, 1);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 0);
|
||||
expectParentAndPosition(F, null, 2);
|
||||
}
|
||||
|
||||
private Task[] givenTasksABCDEF() {
|
||||
Task[] tasks = new Task[] {
|
||||
A = createTask("A", 0, 0),
|
||||
B = createTask("B", 1, 1),
|
||||
C = createTask("C", 2, 1),
|
||||
D = createTask("D", 3, 2),
|
||||
E = createTask("E", 4, 0),
|
||||
F = createTask("F", 5, 0),
|
||||
};
|
||||
|
||||
return tasks;
|
||||
}
|
||||
public void testMoveToEndOfList() {
|
||||
whenTriggerMoveBefore(A, null);
|
||||
|
||||
private Task createTask(String title, long order, int indent) {
|
||||
Task task = new Task();
|
||||
task.setValue(Task.TITLE, title);
|
||||
PluginServices.getTaskService().save(task);
|
||||
Metadata metadata = updater.createEmptyMetadata(list, task.getId());
|
||||
metadata.setValue(SubtasksMetadata.ORDER, order);
|
||||
metadata.setValue(SubtasksMetadata.INDENT, indent);
|
||||
PluginServices.getMetadataService().save(metadata);
|
||||
return task;
|
||||
expectParentAndPosition(A, null, 2);
|
||||
expectParentAndPosition(B, A, 0);
|
||||
expectParentAndPosition(C, A, 1);
|
||||
expectParentAndPosition(D, C, 0);
|
||||
expectParentAndPosition(E, null, 0);
|
||||
expectParentAndPosition(F, null, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
package com.todoroo.astrid.subtasks;
|
||||
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.core.CoreFilterExposer;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.subtasks.AstridOrderedListUpdater.Node;
|
||||
import com.todoroo.astrid.test.DatabaseTestCase;
|
||||
|
||||
/**
|
||||
* Contains useful methods common to all subtasks tests
|
||||
* @author Sam
|
||||
*
|
||||
*/
|
||||
public class SubtasksTestCase extends DatabaseTestCase {
|
||||
|
||||
protected SubtasksUpdater updater;
|
||||
protected Filter filter;
|
||||
|
||||
/* Starting State:
|
||||
*
|
||||
* A
|
||||
* B
|
||||
* C
|
||||
* D
|
||||
* E
|
||||
* F
|
||||
*/
|
||||
public static final String DEFAULT_SERIALIZED_TREE =
|
||||
"[{\"1\":[{\"2\":[]}, {\"3\":[{\"4\":[]}]}]}, {\"5\":[]}, {\"6\":[]}]".replaceAll("\\s", "");
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
filter = CoreFilterExposer.buildInboxFilter(getContext().getResources());
|
||||
Preferences.clear(SubtasksUpdater.ACTIVE_TASKS_ORDER);
|
||||
updater = new SubtasksUpdater();
|
||||
}
|
||||
|
||||
protected void expectParentAndPosition(Task task, Task parent, int positionInParent) {
|
||||
long parentId = (parent == null ? -1 : parent.getId());
|
||||
Node n = updater.findNodeForTask(task.getId());
|
||||
assertNotNull("No node found for task " + task.getValue(Task.TITLE), n);
|
||||
assertEquals("Parent mismatch", parentId, n.parent.taskId);
|
||||
assertEquals("Position mismatch", positionInParent, n.parent.children.indexOf(n));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue