/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.subtasks;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import android.app.Activity;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
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.Property;
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.Metadata;
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.subtasks.OrderedMetadataListUpdater.Node;
import com.todoroo.astrid.subtasks.OrderedMetadataListUpdater.OrderedListNodeVisitor;
import com.todoroo.astrid.ui.DraggableListView;
import com.todoroo.astrid.utility.AstridPreferences;
public class OrderedMetadataListFragmentHelper<LIST> implements OrderedListFragmentHelperInterface<LIST> {
private final DisplayMetrics metrics = new DisplayMetrics();
private final OrderedMetadataListUpdater<LIST> updater;
private final TaskListFragment fragment;
@Autowired TaskService taskService;
@Autowired MetadataService metadataService;
private DraggableTaskAdapter taskAdapter;
private LIST list;
public OrderedMetadataListFragmentHelper(TaskListFragment fragment, OrderedMetadataListUpdater<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);
}
public Property<?>[] taskProperties() {
Property<?>[] baseProperties = TaskAdapter.PROPERTIES;
if (Preferences.getIntegerFromString(R.string.p_taskRowStyle_v2, 0) == 2)
baseProperties = TaskAdapter.BASIC_PROPERTIES;
ArrayList<Property<?>> properties = new ArrayList<Property<?>>(Arrays.asList(baseProperties));
properties.add(updater.indentProperty());
properties.add(updater.orderProperty());
return properties.toArray(new Property<?>[properties.size()]);
}
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(getFilter(), list, targetTaskId, -1);
else
updater.moveTo(getFilter(), list, targetTaskId, destinationTaskId);
} catch (Exception e) {
Log.e("drag", "Drag Error", e); //$NON-NLS-1$ //$NON-NLS-2$
}
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(getFilter(), list, targetTaskId, delta);
} catch (Exception e) {
Log.e("drag", "Indent Error", e); //$NON-NLS-1$ //$NON-NLS-2$
}
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) {
taskAdapter = new DraggableTaskAdapter(fragment, TaskListFragment.getTaskRowResource(),
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 synchronized void setFieldContentsAndVisibility(View view) {
super.setFieldContentsAndVisibility(view);
ViewHolder vh = (ViewHolder) view.getTag();
int indent = vh.task.getValue(updater.indentProperty());
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>();
final int parentIndent = item.getValue(updater.indentProperty());
updater.applyToChildren(getFilter(), list, itemId, new OrderedListNodeVisitor() {
@Override
public void visitNode(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;
}
@Override
public void onCreateTask(Task task) {
//
}
public void onDeleteTask(Task task) {
updater.onDeleteTask(getFilter(), list, task.getId());
taskAdapter.notifyDataSetInvalidated();
}
}