diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index cef991eb2..f38144f62 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -214,7 +214,13 @@ - + + + + + + + - + diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java index 55a73f62a..f96d15eb4 100644 --- a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java @@ -72,4 +72,9 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose return notes; } + @Override + public String getPluginIdentifier() { + return NotesPlugin.IDENTIFIER; + } + } diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java index be7b51452..78b274e84 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java @@ -135,4 +135,9 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo return null; } + @Override + public String getPluginIdentifier() { + return RepeatsPlugin.IDENTIFIER; + } + } diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java index db816776d..f266b69dd 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java @@ -88,4 +88,9 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose return result.substring(0, result.length() - TaskAdapter.DETAIL_SEPARATOR.length()); } + @Override + public String getPluginIdentifier() { + return Utilities.IDENTIFIER; + } + } diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java index a91ae0614..d8c4ea6fc 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java @@ -52,4 +52,9 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer return context.getString(R.string.tag_TLA_detail, tagList); } + @Override + public String getPluginIdentifier() { + return TagsPlugin.IDENTIFIER; + } + } diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerActionExposer.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerActionExposer.java new file mode 100644 index 000000000..c7122c8a7 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerActionExposer.java @@ -0,0 +1,76 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.timers; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.timsu.astrid.R; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.TaskAction; +import com.todoroo.astrid.api.TaskDecoration; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.service.TaskService; + +/** + * Exposes {@link TaskDecoration} for timers + * + * @author Tim Su + * + */ +public class TimerActionExposer extends BroadcastReceiver { + + @Autowired + private TaskService taskService; + + @Override + public void onReceive(Context context, Intent intent) { + long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); + if(taskId == -1) + return; + + synchronized(TimerDecorationExposer.class) { + if(TimerDecorationExposer.staticTaskService == null) { + DependencyInjectionService.getInstance().inject(this); + TimerDecorationExposer.staticTaskService = taskService; + } else { + taskService = TimerDecorationExposer.staticTaskService; + } + } + + Task task = taskService.fetchById(taskId, Task.TIMER_START); + + // was part of a broadcast for actions + if(AstridApiConstants.BROADCAST_REQUEST_ACTIONS.equals(intent.getAction())) { + String label; + if(task.getValue(Task.TIMER_START) == 0) + label = context.getString(R.string.TAE_startTimer); + else + label = context.getString(R.string.TAE_stopTimer); + Intent newIntent = new Intent(context, TimerActionExposer.class); + TaskAction action = new TaskAction(label, + PendingIntent.getBroadcast(context, 0, newIntent, 0)); + + // transmit + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_ACTIONS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, TimerPlugin.IDENTIFIER); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, action); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } else { + // toggle the timer + if(task.getValue(Task.TIMER_START) == 0) + task.setValue(Task.TIMER_START, DateUtilities.now()); + else + task.setValue(Task.TIMER_START, 0L); + taskService.save(task, true); + } + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java index cf8ebd870..9093f1c34 100644 --- a/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java @@ -30,7 +30,7 @@ public class TimerDecorationExposer extends BroadcastReceiver { private static final int TASK_BG_COLOR = Color.argb(200, 220, 50, 0); - private static TaskService staticTaskService; + static TaskService staticTaskService; private static HashMap decorations = new HashMap(); private static HashMap timers = diff --git a/astrid/res/values/strings-timers.xml b/astrid/res/values/strings-timers.xml new file mode 100644 index 000000000..ca97fc583 --- /dev/null +++ b/astrid/res/values/strings-timers.xml @@ -0,0 +1,16 @@ + + + + + + + + Start Timer + + + Stop Timer + + + Elapsed Time: %s + + diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index ce45edc46..d63465e73 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -52,6 +52,7 @@ import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.adapter.TaskAdapter.ViewHolder; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.TaskAction; import com.todoroo.astrid.api.TaskDecoration; import com.todoroo.astrid.core.CoreFilterExposer; import com.todoroo.astrid.dao.Database; @@ -380,6 +381,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { new IntentFilter(AstridApiConstants.BROADCAST_SEND_DETAILS)); registerReceiver(detailReceiver, new IntentFilter(AstridApiConstants.BROADCAST_SEND_DECORATIONS)); + registerReceiver(detailReceiver, + new IntentFilter(AstridApiConstants.BROADCAST_SEND_ACTIONS)); } @Override @@ -400,16 +403,20 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { try { Bundle extras = intent.getExtras(); long taskId = extras.getLong(AstridApiConstants.EXTRAS_TASK_ID); + String addOn = extras.getString(AstridApiConstants.EXTRAS_ADDON); if(AstridApiConstants.BROADCAST_SEND_DECORATIONS.equals(intent.getAction())) { TaskDecoration deco = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE); - taskAdapter.addDecorations(getListView(), taskId, - deco); - } else { + taskAdapter.decorationManager.addNew(taskId, addOn, deco); + } else if(AstridApiConstants.BROADCAST_SEND_DETAILS.equals(intent.getAction())) { String detail = extras.getString(AstridApiConstants.EXTRAS_RESPONSE); - taskAdapter.addDetails(getListView(), taskId, - extras.getBoolean(AstridApiConstants.EXTRAS_EXTENDED), - detail); + if(extras.getBoolean(AstridApiConstants.EXTRAS_EXTENDED)) + taskAdapter.detailManager.addNew(taskId, addOn, detail); + else + taskAdapter.extendedDetailManager.addNew(taskId, addOn, detail); + } else if(AstridApiConstants.BROADCAST_SEND_ACTIONS.equals(intent.getAction())) { + TaskAction action = extras.getParcelable(AstridApiConstants.BROADCAST_SEND_ACTIONS); + taskAdapter.taskActionManager.addNew(taskId, addOn, action); } } catch (Exception e) { exceptionService.reportError("receive-detail-" + //$NON-NLS-1$ diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index b1ba04bf0..fe1aa4c12 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -1,19 +1,18 @@ package com.todoroo.astrid.adapter; -import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; import android.app.ListActivity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; +import android.graphics.Color; import android.graphics.Paint; import android.text.Html; import android.view.ContextMenu; @@ -41,9 +40,9 @@ import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.SoftHashMap; import com.todoroo.astrid.activity.TaskEditActivity; -import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.DetailExposer; +import com.todoroo.astrid.api.TaskAction; import com.todoroo.astrid.api.TaskDecoration; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.notes.NoteDetailExposer; @@ -111,9 +110,11 @@ public class TaskAdapter extends CursorAdapter { // --- task detail and decoration soft caches - private static final TaskCache detailCache = new TaskCache(); - private static final TaskCache extendedDetailCache = new TaskCache(); - private static final TaskCache decorationCache = new TaskCache(); + public final DetailManager detailManager = new DetailManager(false); + public final DetailManager extendedDetailManager = new DetailManager(true); + public final DecorationManager decorationManager = + new DecorationManager(); + public final TaskActionManager taskActionManager = new TaskActionManager(); /** * Constructor @@ -285,18 +286,9 @@ public class TaskAdapter extends CursorAdapter { } // details and decorations - viewHolder.details.setText(""); //$NON-NLS-1$ - if(viewHolder.decorations != null) { - for(View decoration: viewHolder.decorations) - viewHolder.taskRow.removeView(decoration); - } - viewHolder.taskRow.setBackgroundDrawable(null); if(!isFling) { - // task details - send out a request for it - retrieveDetails(viewHolder, false); - - // task decoration - send out a request for it - retrieveDecorations(viewHolder); + detailManager.request(viewHolder); + decorationManager.request(viewHolder); } } @@ -321,217 +313,198 @@ public class TaskAdapter extends CursorAdapter { * ============================================================== add-ons * ====================================================================== */ - private void retrieveDetails(final ViewHolder viewHolder, final boolean extended) { - final long taskId = viewHolder.task.getId(); - - final TaskCache cache = extended ? extendedDetailCache : detailCache; - ArrayList list = cache.initialize(taskId); - if(list != null) { - spanifyAndAdd(viewHolder, extended, list); - return; - } - - // request details - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); - activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - - // load internal details - new Thread() { - @Override - public void run() { - for(DetailExposer exposer : EXPOSERS) { - final String detail = exposer.getTaskDetails(activity, - taskId, extended); - if(detail == null) - continue; - final ArrayList cacheList = cache.addIfNotExists(taskId, detail); - if(cacheList != null) { - if(taskId != viewHolder.task.getId()) - continue; - activity.runOnUiThread(new Runnable() { - public void run() { - spanifyAndAdd(viewHolder, extended, cacheList); - } - }); - } - } - }; - }.start(); - } - - @SuppressWarnings("nls") - private void spanifyAndAdd(ViewHolder viewHolder, boolean extended, ArrayList details) { - if(details == null) - return; - TextView view = extended ? viewHolder.extendedDetails : viewHolder.details; - view.setVisibility(details.size() > 0 ? View.VISIBLE : View.GONE); - if(details.size() == 0 || (extended && !viewHolder.expanded)) { - view.setVisibility(View.GONE); - return; - } else { - view.setVisibility(View.VISIBLE); - } - StringBuilder detailText = new StringBuilder(); - for(Iterator iterator = details.iterator(); iterator.hasNext(); ) { - detailText.append(iterator.next()); - if(iterator.hasNext()) - detailText.append(DETAIL_SEPARATOR); - } - String string = detailText.toString(); - if(string.contains("<")) - view.setText(Html.fromHtml(string.trim().replace("\n", "
"))); - else - view.setText(string.trim()); - } - /** * Called to tell the cache to be cleared */ public void flushCaches() { - detailCache.clear(); - extendedDetailCache.clear(); - decorationCache.clear(); + detailManager.clearCache(); + extendedDetailManager.clearCache(); + decorationManager.clearCache(); } /** - * Respond to a request to add details for a task + * AddOnManager for Details + * @author Tim Su * - * @param taskId */ - public synchronized void addDetails(ListView listView, long taskId, - boolean extended, String detail) { - if(detail == null) - return; + public class DetailManager extends AddOnManager { - final TaskCache cache = extended ? extendedDetailCache : detailCache; - - ArrayList cacheList = cache.addIfNotExists(taskId, detail); - if(cacheList != null) { - // update view if it is visible - int length = listView.getChildCount(); - for(int i = 0; i < length; i++) { - ViewHolder viewHolder = (ViewHolder) listView.getChildAt(i).getTag(); - if(viewHolder == null || viewHolder.task.getId() != taskId) - continue; - spanifyAndAdd(viewHolder, extended, cacheList); - break; - } + private final boolean extended; + public DetailManager(boolean extended) { + this.extended = extended; } - } - private final View.OnClickListener completeBoxListener = new View.OnClickListener() { - public void onClick(View v) { - ViewHolder viewHolder = (ViewHolder)((View)v.getParent().getParent()).getTag(); - Task task = viewHolder.task; - - completeTask(task, ((CheckBox)v).isChecked()); - // set check box to actual action item state - setTaskAppearance(viewHolder, task.isCompleted()); + @Override + Intent createBroadcastIntent(long taskId) { + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); + return broadcastIntent; } - }; - - private void retrieveDecorations(final ViewHolder viewHolder) { - final long taskId = viewHolder.task.getId(); - ArrayList list = decorationCache.initialize(taskId); - if(list != null) { - decorate(viewHolder, list); - return; + @Override + public boolean request(final ViewHolder viewHolder) { + if(super.request(viewHolder)) { + final long taskId = viewHolder.task.getId(); + // load internal details + new Thread() { + @Override + public void run() { + for(DetailExposer exposer : EXPOSERS) { + final String detail = exposer.getTaskDetails(activity, + taskId, extended); + if(detail == null) + continue; + final Collection cacheList = + addIfNotExists(taskId, exposer.getPluginIdentifier(), + detail); + if(cacheList != null) { + if(taskId != viewHolder.task.getId()) + continue; + activity.runOnUiThread(new Runnable() { + public void run() { + draw(viewHolder, cacheList); + } + }); + } + } + }; + }.start(); + return true; + } + return false; } - // request details - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DECORATIONS); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); - activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - - if(timer == null) { - timer = new Timer(); - timer.scheduleAtFixedRate(decorationUpdater, 0, 1000L); + @SuppressWarnings("nls") + @Override + void draw(ViewHolder viewHolder, Collection details) { + if(details == null) + return; + TextView view = extended ? viewHolder.extendedDetails : viewHolder.details; + view.setVisibility(!details.isEmpty() ? View.VISIBLE : View.GONE); + if(details.isEmpty() || (extended && !viewHolder.expanded)) { + view.setVisibility(View.GONE); + return; + } else { + view.setVisibility(View.VISIBLE); + } + StringBuilder detailText = new StringBuilder(); + for(Iterator iterator = details.iterator(); iterator.hasNext(); ) { + detailText.append(iterator.next()); + if(iterator.hasNext()) + detailText.append(DETAIL_SEPARATOR); + } + String string = detailText.toString(); + if(string.contains("<")) + view.setText(Html.fromHtml(string.trim().replace("\n", "
"))); + else + view.setText(string.trim()); } - } - private Timer timer = null; + } /** - * Task to update decorations every second + * AddOnManager for TaskDecorations + * + * @author Tim Su + * */ - private final TimerTask decorationUpdater = new TimerTask() { + public class DecorationManager extends AddOnManager { @Override - public void run() { - ListView listView = activity.getListView(); - int length = listView.getChildCount(); - for(int i = 0; i < length; i++) { - ViewHolder viewHolder = (ViewHolder) listView.getChildAt(i).getTag(); - ArrayList list = decorationCache.get(viewHolder.task.getId()); - if(list == null) - continue; - - for(int j = 0; j < list.size(); j++) { - final TaskDecoration decoration = list.get(j); - if(decoration.decoration == null) - continue; - final View view = viewHolder.decorations[j]; - if(view == null) - continue; - activity.runOnUiThread(new Runnable() { - public void run() { - decoration.decoration.reapply(activity, view); - } - }); + Intent createBroadcastIntent(long taskId) { + Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DECORATIONS); + intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + return intent; + } + + @Override + void draw(ViewHolder viewHolder, Collection decorations) { + if(decorations == null || decorations.size() == 0) + return; + + if(viewHolder.decorations != null) { + for(View view : viewHolder.decorations) + viewHolder.taskRow.removeView(view); + ((View)viewHolder.taskRow.getParent()).setBackgroundColor(Color.TRANSPARENT); + } + + + int i = 0; + boolean colorSet = false; + viewHolder.decorations = new View[decorations.size()]; + for(TaskDecoration decoration : decorations) { + if(decoration.color != 0 && !colorSet) { + colorSet = true; + ((View)viewHolder.taskRow.getParent()).setBackgroundColor(decoration.color); + } + if(decoration.decoration != null) { + View view = decoration.decoration.apply(activity, viewHolder.taskRow); + viewHolder.decorations[i] = view; + switch(decoration.position) { + case TaskDecoration.POSITION_LEFT: + viewHolder.taskRow.addView(view, 1); + break; + case TaskDecoration.POSITION_RIGHT: + viewHolder.taskRow.addView(view, viewHolder.taskRow.getChildCount() - 2); + } } + i++; } } - }; + } /** - * Respond to a request to add details for a task + * AddOnManager for TaskActions + * + * @author Tim Su * - * @param taskId */ - public synchronized void addDecorations(ListView listView, long taskId, - TaskDecoration taskDecoration) { - if(taskDecoration == null) - return; - - ArrayList cacheList = decorationCache.addIfNotExists(taskId, - taskDecoration); - if(cacheList != null) { - // update view if it is visible - int length = listView.getChildCount(); - for(int i = 0; i < length; i++) { - ViewHolder viewHolder = (ViewHolder) listView.getChildAt(i).getTag(); - if(viewHolder == null || viewHolder.task.getId() != taskId) - continue; - decorate(viewHolder, cacheList); - break; - } + public class TaskActionManager extends AddOnManager { + @Override + Intent createBroadcastIntent(long taskId) { + Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS); + intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + return intent; } - } - private void decorate(ViewHolder viewHolder, ArrayList decorations) { - if(decorations == null || decorations.size() == 0) - return; + @Override + void draw(final ViewHolder viewHolder, Collection actions) { + if(actions == null) + return; - // apply decorations in reverse so top priority appears at the ends & - // color is set bu the most important decoration - viewHolder.decorations = new View[decorations.size()]; - for(int i = decorations.size() - 1; i >= 0; i--) { - TaskDecoration deco = decorations.get(i); - if(deco.color != 0) - ((View)viewHolder.taskRow.getParent()).setBackgroundColor(deco.color); - if(deco.decoration != null) { - View view = deco.decoration.apply(activity, viewHolder.taskRow); - viewHolder.decorations[i] = view; - switch(deco.position) { - case TaskDecoration.POSITION_LEFT: - viewHolder.taskRow.addView(view, 1); - break; - case TaskDecoration.POSITION_RIGHT: - viewHolder.taskRow.addView(view, viewHolder.taskRow.getChildCount() - 2); + if(!viewHolder.expanded) { + viewHolder.actions.setVisibility(View.GONE); + return; + } + viewHolder.actions.setVisibility(View.VISIBLE); + viewHolder.actions.removeAllViews(); + + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, + LayoutParams.FILL_PARENT, 1f); + + Button editButton = new Button(activity); + editButton.setText(R.string.TAd_actionEditTask); + editButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View arg0) { + Intent intent = new Intent(activity, TaskEditActivity.class); + long taskId = viewHolder.task.getId(); + intent.putExtra(TaskEditActivity.ID_TOKEN, taskId); + activity.startActivity(intent); } + }); + editButton.setLayoutParams(params); + viewHolder.actions.addView(editButton); + + for(TaskAction action : actions) { + Button view = new Button(activity); + view.setText(action.text); + view.setOnClickListener(new ActionClickListener(action)); + view.setLayoutParams(params); + viewHolder.actions.addView(view); } + } } @@ -545,11 +518,39 @@ public class TaskAdapter extends CursorAdapter { fontSize = Preferences.getIntegerFromString(R.string.p_fontSize); } - class TaskRowListener implements OnCreateContextMenuListener, OnClickListener { + private final View.OnClickListener completeBoxListener = new View.OnClickListener() { + public void onClick(View v) { + ViewHolder viewHolder = (ViewHolder)((View)v.getParent().getParent()).getTag(); + Task task = viewHolder.task; + + completeTask(task, ((CheckBox)v).isChecked()); + // set check box to actual action item state + setTaskAppearance(viewHolder, task.isCompleted()); + } + }; + + private final class ActionClickListener implements View.OnClickListener { + TaskAction action; + + public ActionClickListener(TaskAction action) { + this.action = action; + } + + public void onClick(View v) { + try { + action.intent.send(); + } catch (Exception e) { + // oh too bad. + } + } + }; + + private class TaskRowListener implements OnCreateContextMenuListener, OnClickListener { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - // this is all a big sham. it's actually handled in Task List Activity + // this is all a big sham. it's actually handled in Task List + // Activity. however, we need this to be here. } @Override @@ -557,34 +558,10 @@ public class TaskAdapter extends CursorAdapter { // expand view final ViewHolder viewHolder = (ViewHolder)v.getTag(); viewHolder.expanded = !viewHolder.expanded; - LinearLayout actions = viewHolder.actions; - TextView extendedDetails = viewHolder.extendedDetails; - actions.setVisibility(viewHolder.expanded ? View.VISIBLE : View.GONE); - if(!viewHolder.expanded) { - viewHolder.extendedDetails.setVisibility(View.GONE); - return; - } - - final long taskId = viewHolder.task.getId(); - extendedDetails.setText(""); //$NON-NLS-1$ - retrieveDetails(viewHolder, true); - - if(actions.getChildCount() == 0) { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1f); - Button edit = new Button(activity); - edit.setText(R.string.TAd_actionEditTask); - edit.setLayoutParams(params); - edit.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(activity, TaskEditActivity.class); - intent.putExtra(TaskEditActivity.ID_TOKEN, taskId); - activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK); - } - }); - actions.addView(edit); + if(viewHolder.expanded) { + extendedDetailManager.request(viewHolder); + taskActionManager.request(viewHolder); } } } @@ -646,13 +623,71 @@ public class TaskAdapter extends CursorAdapter { } /* ====================================================================== - * =============================================================== caches + * ========================================================= addon helper * ====================================================================== */ - private static class TaskCache { + abstract private class AddOnManager { + + private final Map> cache = + Collections.synchronizedMap(new SoftHashMap>()); + + // --- interface + + /** + * Request add-ons for the given task + * @return true if cache miss, false if cache hit + */ + public boolean request(ViewHolder viewHolder) { + long taskId = viewHolder.task.getId(); + + Collection list = initialize(taskId); + if(list != null) { + draw(viewHolder, list); + return false; + } + + // request details + Intent broadcastIntent = createBroadcastIntent(taskId); + activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + draw(viewHolder, get(taskId)); + return true; + } + + /** creates a broadcast intent for requesting */ + abstract Intent createBroadcastIntent(long taskId); + + /** updates the given view */ + abstract void draw(ViewHolder viewHolder, Collection list); + + /** on receive an intent */ + public void addNew(long taskId, String addOn, TYPE item) { + if(item == null) + return; + + Collection cacheList = addIfNotExists(taskId, addOn, item); + if(cacheList != null) { + ListView listView = activity.getListView(); + // update view if it is visible + int length = listView.getChildCount(); + for(int i = 0; i < length; i++) { + ViewHolder viewHolder = (ViewHolder) listView.getChildAt(i).getTag(); + if(viewHolder == null || viewHolder.task.getId() != taskId) + continue; + draw(viewHolder, cacheList); + break; + } + } + } - private final Map> cache = - Collections.synchronizedMap(new SoftHashMap>()); + /** + * Clears the cache + */ + public void clearCache() { + cache.clear(); + } + + // --- internal goodies /** * Retrieves a list. If it doesn't exist, list is created, but @@ -660,10 +695,10 @@ public class TaskAdapter extends CursorAdapter { * @param taskId * @return list if there was already one */ - public ArrayList initialize(long taskId) { + protected Collection initialize(long taskId) { if(cache.containsKey(taskId)) - return cache.get(taskId); - cache.put(taskId, new ArrayList()); + return get(taskId); + cache.put(taskId, new HashMap()); return null; } @@ -671,25 +706,26 @@ public class TaskAdapter extends CursorAdapter { * Adds an item to the cache if it doesn't exist * @param taskId * @param item - * @return list if item was added, null if it already existed + * @return iterator if item was added, null if it already existed */ - public ArrayList addIfNotExists(long taskId, TYPE item) { - ArrayList list = cache.get(taskId); - if(list == null || list.contains(item)) + protected Collection addIfNotExists(long taskId, String addOn, + TYPE item) { + HashMap list = cache.get(taskId); + if(list == null) return null; - list.add(item); - return list; + if(list.containsKey(addOn) && list.get(addOn).equals(item)) + return null; + list.put(addOn, item); + return get(taskId); } /** - * Clears the cache + * Gets an item at the given index + * @param taskId + * @return */ - public void clear() { - cache.clear(); - } - - public ArrayList get(long taskId) { - return cache.get(taskId); + protected Collection get(long taskId) { + return cache.get(taskId).values(); } } diff --git a/astrid/src/com/todoroo/astrid/api/DetailExposer.java b/astrid/src/com/todoroo/astrid/api/DetailExposer.java index 804b7c846..90ef95b9d 100644 --- a/astrid/src/com/todoroo/astrid/api/DetailExposer.java +++ b/astrid/src/com/todoroo/astrid/api/DetailExposer.java @@ -21,4 +21,6 @@ public interface DetailExposer { */ public String getTaskDetails(Context context, long id, boolean extended); + public String getPluginIdentifier(); + } diff --git a/astrid/src/com/todoroo/astrid/model/Task.java b/astrid/src/com/todoroo/astrid/model/Task.java index bc0e1256b..e728c639b 100644 --- a/astrid/src/com/todoroo/astrid/model/Task.java +++ b/astrid/src/com/todoroo/astrid/model/Task.java @@ -84,7 +84,7 @@ public final class Task extends AbstractModel { public static final IntegerProperty ELAPSED_SECONDS = new IntegerProperty( TABLE, "elapsedSeconds"); - public static final IntegerProperty TIMER_START = new IntegerProperty( + public static final LongProperty TIMER_START = new LongProperty( TABLE, "timerStart"); public static final IntegerProperty POSTPONE_COUNT = new IntegerProperty(