diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 89957c65d..26679768f 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -151,7 +151,7 @@ - + @@ -207,6 +207,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:clearTaskOnLaunch="true" /> diff --git a/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java b/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java index afc1a0f4f..7a284f37b 100644 --- a/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java +++ b/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java @@ -47,6 +47,11 @@ public class AstridApiConstants { */ public static final String EXTRAS_ADDON = "addon"; + /** + * Extras name for whether task detail request is extended + */ + public static final String EXTRAS_EXTENDED = "extended"; + // --- Add-ons API /** @@ -89,11 +94,14 @@ public class AstridApiConstants { */ public static final String BROADCAST_SEND_EDIT_CONTROLS = PACKAGE + ".SEND_EDIT_CONTROLS"; - // --- Task List Details API + // --- Task Details API /** - * Action name for broadcast intent requesting task list details for a task + * Action name for broadcast intent requesting details for a task. + * Extended details are displayed when a user presses on a task. + * * @extra EXTRAS_TASK_ID id of the task + * @extra EXTRAS_EXTENDED whether request is for standard or extended details */ public static final String BROADCAST_REQUEST_DETAILS = PACKAGE + ".REQUEST_DETAILS"; @@ -101,10 +109,43 @@ public class AstridApiConstants { * Action name for broadcast intent sending details back to Astrid * @extra EXTRAS_ADDON your add-on identifier * @extra EXTRAS_TASK_ID id of the task - * @extra EXTRAS_RESPONSE a {@link TaskDetail} object + * @extra EXTRAS_EXTENDED whether request is for standard or extended details + * @extra EXTRAS_RESPONSE a String */ public static final String BROADCAST_SEND_DETAILS = PACKAGE + ".SEND_DETAILS"; + // --- Task Actions API + + /** + * Action name for broadcast intent requesting actions for a task + * @extra EXTRAS_TASK_ID id of the task + */ + public static final String BROADCAST_REQUEST_ACTIONS = PACKAGE + ".REQUEST_ACTIONS"; + + /** + * Action name for broadcast intent sending actions back to Astrid + * @extra EXTRAS_ADDON your add-on identifier + * @extra EXTRAS_TASK_ID id of the task + * @extra EXTRAS_RESPONSE a String + */ + public static final String BROADCAST_SEND_ACTIONS = PACKAGE + ".SEND_ACTIONS"; + + // --- Task Decorations API + + /** + * Action name for broadcast intent requesting task list decorations for a task + * @extra EXTRAS_TASK_ID id of the task + */ + public static final String BROADCAST_REQUEST_DECORATIONS = PACKAGE + ".REQUEST_DECORATIONS"; + + /** + * Action name for broadcast intent sending decorations back to Astrid + * @extra EXTRAS_ADDON your add-on identifier + * @extra EXTRAS_TASK_ID id of the task + * @extra EXTRAS_RESPONSE a {@link TaskDecoration} + */ + public static final String BROADCAST_SEND_DECORATIONS = PACKAGE + ".SEND_DECORATIONS"; + // --- Actions API /** diff --git a/astrid/api-src/com/todoroo/astrid/api/Filter.java b/astrid/api-src/com/todoroo/astrid/api/Filter.java index 404f5c075..9c8c258c8 100644 --- a/astrid/api-src/com/todoroo/astrid/api/Filter.java +++ b/astrid/api-src/com/todoroo/astrid/api/Filter.java @@ -22,6 +22,14 @@ import com.todoroo.andlib.sql.QueryTemplate; */ public final class Filter extends FilterListItem { + // --- constants + + /** Constant for valuesForNewTasks to indicate the value should be replaced + * with the current time as long */ + public static final long VALUE_NOW = Long.MIN_VALUE + 1; + + // --- instance variables + /** * Expanded title of this filter. This is displayed at the top * of the screen when user is viewing this filter. diff --git a/astrid/api-src/com/todoroo/astrid/api/EditOperation.java b/astrid/api-src/com/todoroo/astrid/api/TaskAction.java similarity index 54% rename from astrid/api-src/com/todoroo/astrid/api/EditOperation.java rename to astrid/api-src/com/todoroo/astrid/api/TaskAction.java index 94fb63c3d..75aed3b1a 100644 --- a/astrid/api-src/com/todoroo/astrid/api/EditOperation.java +++ b/astrid/api-src/com/todoroo/astrid/api/TaskAction.java @@ -3,22 +3,17 @@ */ package com.todoroo.astrid.api; -import android.content.Intent; +import android.app.PendingIntent; import android.os.Parcel; import android.os.Parcelable; /** - * Represents an intent that can be called on a task being edited + * Represents an intent that can be called on a task * * @author Tim Su * */ -public final class EditOperation implements Parcelable { - - /** - * Plugin Id - */ - public final String plugin; +public class TaskAction implements Parcelable { /** * Label @@ -28,20 +23,18 @@ public final class EditOperation implements Parcelable { /** * Intent to call when invoking this operation */ - public Intent intent = null; + public PendingIntent intent = null; /** * Create an EditOperation object * - * @param plugin - * {@link Addon} identifier that encompasses object * @param text * label to display * @param intent * intent to invoke. {@link EXTRAS_TASK_ID} will be passed */ - public EditOperation(String plugin, String text, Intent intent) { - this.plugin = plugin; + public TaskAction(String text, PendingIntent intent) { + super(); this.text = text; this.intent = intent; } @@ -59,7 +52,6 @@ public final class EditOperation implements Parcelable { * {@inheritDoc} */ public void writeToParcel(Parcel dest, int flags) { - dest.writeString(plugin); dest.writeString(text); dest.writeParcelable(intent, 0); } @@ -67,20 +59,20 @@ public final class EditOperation implements Parcelable { /** * Parcelable creator */ - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { /** * {@inheritDoc} */ - public EditOperation createFromParcel(Parcel source) { - return new EditOperation(source.readString(), source.readString(), - (Intent)source.readParcelable(Intent.class.getClassLoader())); + public TaskAction createFromParcel(Parcel source) { + return new TaskAction(source.readString(), (PendingIntent)source.readParcelable( + PendingIntent.class.getClassLoader())); } /** * {@inheritDoc} */ - public EditOperation[] newArray(int size) { - return new EditOperation[size]; + public TaskAction[] newArray(int size) { + return new TaskAction[size]; }; }; diff --git a/astrid/api-src/com/todoroo/astrid/api/TaskContainer.java b/astrid/api-src/com/todoroo/astrid/api/TaskContainer.java index a12e08394..770bf51da 100644 --- a/astrid/api-src/com/todoroo/astrid/api/TaskContainer.java +++ b/astrid/api-src/com/todoroo/astrid/api/TaskContainer.java @@ -6,9 +6,10 @@ import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Task; /** - * Container class for tasks. Synchronization Providers can subclass - * this class if desired. + * Container class for transmitting tasks and including local and remote + * metadata. Synchronization Providers can subclass this class if desired. * + * @see {@link SynchronizationProvider} * @author Tim Su * */ diff --git a/astrid/api-src/com/todoroo/astrid/api/TaskDecoration.java b/astrid/api-src/com/todoroo/astrid/api/TaskDecoration.java new file mode 100644 index 000000000..e1cf4115c --- /dev/null +++ b/astrid/api-src/com/todoroo/astrid/api/TaskDecoration.java @@ -0,0 +1,96 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.api; + +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.RemoteViews; +import android.widget.RemoteViews.RemoteView; + +/** + * Represents a line of text displayed in the Task List + * + * @author Tim Su + * + */ +public final class TaskDecoration implements Parcelable { + + /** + * Place decoration between completion box and task title + */ + public static final int POSITION_LEFT = 0; + + /** + * Place decoration between task title and importance bar + */ + public static final int POSITION_RIGHT = 1; + + /** + * {@link RemoteView} decoration + */ + public RemoteViews decoration = null; + + /** + * Decoration position + */ + public int position = POSITION_LEFT; + + /** + * Decorated task background color. 0 is default + */ + public int color = 0; + + /** + * Creates a TaskDetail object + * @param text + * text to display + * @param color + * color to use for text. Use 0 for default color + */ + public TaskDecoration(RemoteViews decoration, int position, int color) { + this.decoration = decoration; + this.position = position; + this.color = color; + } + + // --- parcelable helpers + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(decoration, 0); + dest.writeInt(position); + dest.writeInt(color); + } + + /** + * Parcelable creator + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + /** + * {@inheritDoc} + */ + public TaskDecoration createFromParcel(Parcel source) { + return new TaskDecoration((RemoteViews)source.readParcelable( + RemoteViews.class.getClassLoader()), + source.readInt(), source.readInt()); + } + + /** + * {@inheritDoc} + */ + public TaskDecoration[] newArray(int size) { + return new TaskDecoration[size]; + }; + }; + +} diff --git a/astrid/api-src/com/todoroo/astrid/api/TaskDetail.java b/astrid/api-src/com/todoroo/astrid/api/TaskDetail.java deleted file mode 100644 index 4d24501e8..000000000 --- a/astrid/api-src/com/todoroo/astrid/api/TaskDetail.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.api; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Represents a line of text displayed in the Task List - * - * @author Tim Su - * - */ -public final class TaskDetail implements Parcelable { - - /** - * Text of detail - */ - public String text = null; - - /** - * Color to use for text. 0 is default - */ - public int color = 0; - - /** - * Creates a TaskDetail object - * @param text - * text to display - * @param color - * color to use for text. Use 0 for default color - */ - public TaskDetail(String text, int color) { - this.text = text; - this.color = color; - } - - /** - * Convenience constructor to make a TaskDetail with default color - * @param text - * text to display - */ - public TaskDetail(String text) { - this(text, 0); - } - - // --- parcelable helpers - - /** - * {@inheritDoc} - */ - public int describeContents() { - return 0; - } - - /** - * {@inheritDoc} - */ - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(text); - dest.writeInt(color); - } - - /** - * Parcelable creator - */ - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - /** - * {@inheritDoc} - */ - public TaskDetail createFromParcel(Parcel source) { - return new TaskDetail(source.readString(), source.readInt()); - } - - /** - * {@inheritDoc} - */ - public TaskDetail[] newArray(int size) { - return new TaskDetail[size]; - }; - }; - -} diff --git a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java index e1dcc9b9c..046dd723f 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java @@ -214,12 +214,13 @@ public class AndroidUtilities { /** * Copy a file from one place to another + * * @param in * @param out * @throws Exception */ public static void copyFile(File in, File out) throws Exception { - FileInputStream fis = new FileInputStream(in); + FileInputStream fis = new FileInputStream(in); FileOutputStream fos = new FileOutputStream(out); try { byte[] buf = new byte[1024]; @@ -227,13 +228,11 @@ public class AndroidUtilities { while ((i = fis.read(buf)) != -1) { fos.write(buf, 0, i); } - } - catch (Exception e) { + } catch (Exception e) { throw e; + } finally { + fis.close(); + fos.close(); } - finally { - if (fis != null) fis.close(); - if (fos != null) fos.close(); - } - } + } } diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CorePlugin.java b/astrid/plugin-src/com/todoroo/astrid/core/CorePlugin.java index c76847423..7f8848108 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CorePlugin.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CorePlugin.java @@ -12,9 +12,10 @@ public class CorePlugin extends BroadcastReceiver { static final String IDENTIFIER = "core"; //$NON-NLS-1$ @Override + @SuppressWarnings("nls") public void onReceive(Context context, Intent intent) { Addon plugin = new Addon(IDENTIFIER, "Core Filters", "Todoroo", - "Provides 'Inbox' and 'All Tasks' Filters"); + "Provides 'Inbox', 'Search', and 'More...' Filters"); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_ADDONS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, plugin); diff --git a/astrid/plugin-src/com/todoroo/astrid/core/PluginServices.java b/astrid/plugin-src/com/todoroo/astrid/core/PluginServices.java new file mode 100644 index 000000000..d2e348553 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/core/PluginServices.java @@ -0,0 +1,28 @@ +package com.todoroo.astrid.core; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.astrid.service.TaskService; + +public final class PluginServices { + + @Autowired + TaskService taskService; + + private static PluginServices instance; + + private PluginServices() { + DependencyInjectionService.getInstance().inject(this); + } + + private synchronized static PluginServices getInstance() { + if(instance == null) + instance = new PluginServices(); + return instance; + } + + public static TaskService getTaskService() { + return getInstance().taskService; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gcal/Calendars.java b/astrid/plugin-src/com/todoroo/astrid/gcal/Calendars.java index e68e141f1..0defec359 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gcal/Calendars.java +++ b/astrid/plugin-src/com/todoroo/astrid/gcal/Calendars.java @@ -83,30 +83,29 @@ public class Calendars { Resources r = context.getResources(); Cursor c = cr.query(getCalendarContentUri(CALENDAR_CONTENT_CALENDARS), CALENDARS_PROJECTION, CALENDARS_WHERE, null, CALENDARS_SORT); + try { + // Fetch the current setting. Invalid calendar id will + // be changed to default value. + String defaultSetting = Preferences.getStringValue(R.string.gcal_p_default); - // Fetch the current setting. Invalid calendar id will - // be changed to default value. - String defaultSetting = Preferences.getStringValue(R.string.gcal_p_default); + CalendarResult result = new CalendarResult(); - CalendarResult result = new CalendarResult(); + if (c == null || c.getCount() == 0) { + // Something went wrong when querying calendars. Only offer them + // the system default choice + result.calendars = new String[] { + r.getString(R.string.gcal_GCP_default) }; + result.calendarIds = new String[] { null }; + result.defaultIndex = 0; + return result; + } - if (c == null || c.getCount() == 0) { - // Something went wrong when querying calendars. Only offer them - // the system default choice - result.calendars = new String[] { - r.getString(R.string.gcal_GCP_default) }; - result.calendarIds = new String[] { null }; - result.defaultIndex = 0; - return result; - } + int calendarCount = c.getCount(); - int calendarCount = c.getCount(); + result.calendars = new String[calendarCount]; + result.calendarIds = new String[calendarCount]; - result.calendars = new String[calendarCount]; - result.calendarIds = new String[calendarCount]; - - // Iterate calendars one by one, and fill up the list preference - try { + // Iterate calendars one by one, and fill up the list preference int row = 0; int idColumn = c.getColumnIndex(ID_COLUMN_NAME); int nameColumn = c.getColumnIndex(DISPLAY_COLUMN_NAME); @@ -130,7 +129,8 @@ public class Calendars { return result; } finally { - c.deactivate(); + if(c != null) + c.close(); } } diff --git a/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java index a9d8b6199..a66d938f5 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/gcal/GCalTaskCompleteListener.java @@ -9,17 +9,12 @@ import android.net.Uri; import android.text.TextUtils; import com.timsu.astrid.R; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.model.Task; -import com.todoroo.astrid.service.TaskService; public class GCalTaskCompleteListener extends BroadcastReceiver { - @Autowired - private TaskService taskService; - @SuppressWarnings("nls") @Override public void onReceive(Context context, Intent intent) { @@ -27,9 +22,7 @@ public class GCalTaskCompleteListener extends BroadcastReceiver { if(taskId == -1) return; - DependencyInjectionService.getInstance().inject(this); - - Task task = taskService.fetchById(taskId, Task.ID, Task.TITLE, Task.CALENDAR_URI); + Task task = PluginServices.getTaskService().fetchById(taskId, Task.ID, Task.TITLE, Task.CALENDAR_URI); if(task == null) return; diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java index f5ef99e88..2a14ed1e6 100644 --- a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java @@ -7,27 +7,19 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.DetailExposer; -import com.todoroo.astrid.api.TaskDetail; +import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.model.Task; -import com.todoroo.astrid.service.TaskService; /** - * Exposes {@link TaskDetail} for tags, i.e. "Tags: frogs, animals" + * Exposes Task Detail for notes * * @author Tim Su * */ public class NoteDetailExposer extends BroadcastReceiver implements DetailExposer { - private static TaskService staticTaskService; - - @Autowired - private TaskService taskService; - @Override public void onReceive(Context context, Intent intent) { // get tags associated with this task @@ -35,7 +27,8 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose if(taskId == -1) return; - TaskDetail taskDetail = getTaskDetails(context, taskId); + boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false); + String taskDetail = getTaskDetails(context, taskId, extended); if(taskDetail == null) return; @@ -43,29 +36,29 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, NotesPlugin.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } @Override - public TaskDetail getTaskDetails(Context context, long id) { - synchronized(NoteDetailExposer.class) { - if(staticTaskService == null) { - DependencyInjectionService.getInstance().inject(this); - staticTaskService = taskService; - } else { - taskService = staticTaskService; - } - } + public String getTaskDetails(Context context, long id, boolean extended) { + if(!extended) + return null; - Task task = taskService.fetchById(id, Task.NOTES); + Task task = PluginServices.getTaskService().fetchById(id, Task.NOTES); if(task == null) return null; String notes = task.getValue(Task.NOTES); if(notes.length() == 0) return null; - return new TaskDetail(notes); + return notes; + } + + @Override + public String getPluginIdentifier() { + return NotesPlugin.IDENTIFIER; } } diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/NotesPlugin.java b/astrid/plugin-src/com/todoroo/astrid/notes/NotesPlugin.java index 2948c27b8..88038e84f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/notes/NotesPlugin.java +++ b/astrid/plugin-src/com/todoroo/astrid/notes/NotesPlugin.java @@ -12,6 +12,7 @@ public class NotesPlugin extends BroadcastReceiver { static final String IDENTIFIER = "notes"; //$NON-NLS-1$ @Override + @SuppressWarnings("nls") public void onReceive(Context context, Intent intent) { Addon plugin = new Addon(IDENTIFIER, "Notes", "Todoroo", "Lets you add and view notes for a task."); diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java index 894643997..46d3af6f7 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java @@ -16,27 +16,19 @@ import com.google.ical.values.Frequency; import com.google.ical.values.RRule; import com.google.ical.values.WeekdayNum; import com.timsu.astrid.R; -import com.todoroo.andlib.service.Autowired; -import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.DetailExposer; -import com.todoroo.astrid.api.TaskDetail; +import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.model.Task; -import com.todoroo.astrid.service.TaskService; /** - * Exposes {@link TaskDetail} for tags, i.e. "Tags: frogs, animals" + * Exposes Task Detail for repeats, i.e. "Repeats every 2 days" * * @author Tim Su * */ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExposer { - private static TaskService staticTaskService = null; - - @Autowired - TaskService taskService; - @Override public void onReceive(Context context, Intent intent) { // get tags associated with this task @@ -44,7 +36,8 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo if(taskId == -1) return; - TaskDetail taskDetail = getTaskDetails(context, taskId); + boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false); + String taskDetail = getTaskDetails(context, taskId, extended); if(taskDetail == null) return; @@ -52,21 +45,16 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, RepeatsPlugin.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } - public TaskDetail getTaskDetails(Context context, long id) { - synchronized(RepeatDetailExposer.class) { - if(staticTaskService == null) { - DependencyInjectionService.getInstance().inject(this); - staticTaskService = taskService; - } else { - taskService = staticTaskService; - } - } + public String getTaskDetails(Context context, long id, boolean extended) { + if(extended) + return null; - Task task = taskService.fetchById(id, Task.FLAGS, Task.RECURRENCE); + Task task = PluginServices.getTaskService().fetchById(id, Task.FLAGS, Task.RECURRENCE); if(task == null) return null; @@ -126,9 +114,14 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo else detail = context.getString(R.string.repeat_detail_duedate, interval); - return new TaskDetail(detail); + return detail; } 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 60d1ee278..f266b69dd 100644 --- a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java @@ -12,17 +12,16 @@ import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.DetailExposer; -import com.todoroo.astrid.api.TaskDetail; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.rmilk.data.MilkNote; import com.todoroo.astrid.rmilk.data.MilkTask; /** - * Exposes {@link TaskDetail}s for Remember the Milk: + * Exposes Task Details for Remember the Milk: * - RTM list * - RTM repeat information - * - whether task has been changed + * - RTM notes * * @author Tim Su * @@ -39,49 +38,59 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose if(taskId == -1) return; - TaskDetail taskDetail = getTaskDetails(context, taskId); + boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false); + String taskDetail = getTaskDetails(context, taskId, extended); if(taskDetail == null) return; Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, Utilities.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } @Override - public TaskDetail getTaskDetails(Context context, long id) { + public String getTaskDetails(Context context, long id, boolean extended) { Metadata metadata = MilkDataService.getInstance().getTaskMetadata(id); if(metadata == null) return null; StringBuilder builder = new StringBuilder(); - long listId = metadata.getValue(MilkTask.LIST_ID); - if(listId > 0) { - builder.append(context.getString(R.string.rmilk_TLA_list, - MilkDataService.getInstance().getListName(listId))).append(TaskAdapter.DETAIL_SEPARATOR); - } - int repeat = metadata.getValue(MilkTask.REPEATING); - if(repeat != 0) { - builder.append(context.getString(R.string.rmilk_TLA_repeat)).append(TaskAdapter.DETAIL_SEPARATOR); - } + if(!extended) { + long listId = metadata.getValue(MilkTask.LIST_ID); + if(listId > 0) { + builder.append(context.getString(R.string.rmilk_TLA_list, + MilkDataService.getInstance().getListName(listId))).append(TaskAdapter.DETAIL_SEPARATOR); + } - TodorooCursor notesCursor = MilkDataService.getInstance().getTaskNotesCursor(id); - try { - for(notesCursor.moveToFirst(); !notesCursor.isAfterLast(); notesCursor.moveToNext()) { - metadata.readFromCursor(notesCursor); - builder.append(MilkNote.toTaskDetail(metadata)).append(TaskAdapter.DETAIL_SEPARATOR); + int repeat = metadata.getValue(MilkTask.REPEATING); + if(repeat != 0) { + builder.append(context.getString(R.string.rmilk_TLA_repeat)).append(TaskAdapter.DETAIL_SEPARATOR); + } + } else { + TodorooCursor notesCursor = MilkDataService.getInstance().getTaskNotesCursor(id); + try { + for(notesCursor.moveToFirst(); !notesCursor.isAfterLast(); notesCursor.moveToNext()) { + metadata.readFromCursor(notesCursor); + builder.append(MilkNote.toTaskDetail(metadata)).append(TaskAdapter.DETAIL_SEPARATOR); + } + } finally { + notesCursor.close(); } - } finally { - notesCursor.close(); } if(builder.length() == 0) return null; String result = builder.toString(); - return new TaskDetail(result.substring(0, result.length() - 3)); + 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 16a7b4668..d8c4ea6fc 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java @@ -10,10 +10,9 @@ import android.content.Intent; import com.timsu.astrid.R; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.DetailExposer; -import com.todoroo.astrid.api.TaskDetail; /** - * Exposes {@link TaskDetail} for tags, i.e. "Tags: frogs, animals" + * Exposes Task Detail for tags, i.e. "Tags: frogs, animals" * * @author Tim Su * @@ -27,7 +26,8 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer if(taskId == -1) return; - TaskDetail taskDetail = getTaskDetails(context, taskId); + boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false); + String taskDetail = getTaskDetails(context, taskId, extended); if(taskDetail == null) return; @@ -35,18 +35,26 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, TagsPlugin.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } @Override - public TaskDetail getTaskDetails(Context context, long id) { - String tagList = TagService.getInstance().getTagsAsString(id); + public String getTaskDetails(Context context, long id, boolean extended) { + if(extended) + return null; + String tagList = TagService.getInstance().getTagsAsString(id); if(tagList.length() == 0) return null; - return new TaskDetail(context.getString(R.string.tag_TLA_detail, tagList)); + 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/tags/TagsPlugin.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagsPlugin.java index 42a0e3617..7a3f168fd 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagsPlugin.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagsPlugin.java @@ -9,8 +9,9 @@ import com.todoroo.astrid.api.AstridApiConstants; public class TagsPlugin extends BroadcastReceiver { - static final String IDENTIFIER = "tags"; + static final String IDENTIFIER = "tags"; //$NON-NLS-1$ + @SuppressWarnings("nls") @Override public void onReceive(Context context, Intent intent) { Addon plugin = new Addon(IDENTIFIER, "Tags", "Todoroo", 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..521d58fb9 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerActionExposer.java @@ -0,0 +1,63 @@ +/** + * 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.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.TaskAction; +import com.todoroo.astrid.api.TaskDecoration; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.model.Task; + +/** + * Exposes {@link TaskDecoration} for timers + * + * @author Tim Su + * + */ +public class TimerActionExposer extends BroadcastReceiver { + + private static final String TIMER_ACTION = "com.todoroo.astrid.TIMER_BUTTON"; //$NON-NLS-1$ + + @Override + public void onReceive(Context context, Intent intent) { + long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); + if(taskId == -1) + return; + + Task task = PluginServices.getTaskService().fetchById(taskId, Task.ID, Task.TIMER_START, + Task.ELAPSED_SECONDS); + + // 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(TIMER_ACTION); + newIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + TaskAction action = new TaskAction(label, + PendingIntent.getBroadcast(context, (int)taskId, 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 if(TIMER_ACTION.equals(intent.getAction())) { + if(task.getValue(Task.TIMER_START) == 0) + TimerPlugin.updateTimer(context, task, true); + else + TimerPlugin.updateTimer(context, task, false); + } + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java new file mode 100644 index 000000000..b8b980b7c --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerDecorationExposer.java @@ -0,0 +1,79 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.timers; + +import java.util.HashMap; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.os.SystemClock; +import android.widget.RemoteViews; + +import com.timsu.astrid.R; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.TaskDecoration; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.model.Task; + +/** + * Exposes {@link TaskDecoration} for timers + * + * @author Tim Su + * + */ +public class TimerDecorationExposer extends BroadcastReceiver { + + private static final int TIMING_BG_COLOR = Color.argb(200, 220, 50, 0); + + private static HashMap decorations = + new HashMap(); + + public static void removeFromCache(long taskId) { + decorations.remove(taskId); + } + + @Override + public void onReceive(Context context, Intent intent) { + long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); + if(taskId == -1) + return; + + Task task = PluginServices.getTaskService().fetchById(taskId, Task.ELAPSED_SECONDS, Task.TIMER_START); + if(task == null || (task.getValue(Task.ELAPSED_SECONDS) == 0 && + task.getValue(Task.TIMER_START) == 0)) + return; + + TaskDecoration decoration; + if(!decorations.containsKey(taskId)) { + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), + R.layout.timer_decoration); + decoration = new TaskDecoration(remoteViews, + TaskDecoration.POSITION_LEFT, 0); + decorations.put(taskId, decoration); + } else { + decoration = decorations.get(taskId); + } + + long elapsed = task.getValue(Task.ELAPSED_SECONDS) * 1000L; + if(task.getValue(Task.TIMER_START) != 0) { + decoration.color = TIMING_BG_COLOR; + elapsed += DateUtilities.now() - task.getValue(Task.TIMER_START); + } + + // update timer + decoration.decoration.setChronometer(R.id.timer, SystemClock.elapsedRealtime() - + elapsed, null, decoration.color != 0); + + // transmit + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DECORATIONS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, TimerPlugin.IDENTIFIER); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, decoration); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerFilterExposer.java new file mode 100644 index 000000000..224dd34fc --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerFilterExposer.java @@ -0,0 +1,60 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.timers; + +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; + +import com.timsu.astrid.R; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.astrid.activity.FilterListActivity; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.model.Task; + +/** + * Exposes "working on" filter to the {@link FilterListActivity} + * + * @author Tim Su + * + */ +public final class TimerFilterExposer extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + + if(PluginServices.getTaskService().count(Query.select(Task.ID). + where(Task.TIMER_START.gt(0))) == 0) + return; + + Filter workingOn = createFilter(context); + + // transmit filter list + FilterListItem[] list = new FilterListItem[1]; + list[0] = workingOn; + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + + public static Filter createFilter(Context context) { + Resources r = context.getResources(); + ContentValues values = new ContentValues(); + values.put(Task.TIMER_START.name, Filter.VALUE_NOW); + Filter workingOn = new Filter(r.getString(R.string.TFE_workingOn), + r.getString(R.string.TFE_workingOn), + new QueryTemplate().where(Task.TIMER_START.gt(0)), + values); + workingOn.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_clock)).getBitmap(); + return workingOn; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java new file mode 100644 index 000000000..461c38216 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java @@ -0,0 +1,97 @@ +package com.todoroo.astrid.timers; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; + +import com.timsu.astrid.R; +import com.todoroo.andlib.service.NotificationManager; +import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.activity.ShortcutActivity; +import com.todoroo.astrid.api.Addon; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.utility.Constants; + +public class TimerPlugin extends BroadcastReceiver { + + static final String IDENTIFIER = "timer"; //$NON-NLS-1$ + + @Override + @SuppressWarnings("nls") + public void onReceive(Context context, Intent intent) { + Addon plugin = new Addon(IDENTIFIER, "Timer", "Todoroo", + "Lets you time how long it takes to complete tasks."); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_ADDONS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, plugin); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + + /** + * stops timer and sets elapsed time. you still need to save the task. + * @param task + * @param start if true, start timer. else, stop it + */ + public static void updateTimer(Context context, Task task, boolean start) { + if(start) { + if(task.getValue(Task.TIMER_START) == 0) + task.setValue(Task.TIMER_START, DateUtilities.now()); + } else { + if(task.getValue(Task.TIMER_START) > 0) { + int newElapsed = (int)((DateUtilities.now() - task.getValue(Task.TIMER_START)) / 1000L); + task.setValue(Task.TIMER_START, 0L); + task.setValue(Task.ELAPSED_SECONDS, + task.getValue(Task.ELAPSED_SECONDS) + newElapsed); + } + } + PluginServices.getTaskService().save(task, true); + TimerDecorationExposer.removeFromCache(task.getId()); + + // transmit new intents + Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_ACTIONS); + intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); + new TimerDecorationExposer().onReceive(context, intent); + new TimerActionExposer().onReceive(context, intent); + + // update notification + TimerPlugin.updateNotifications(context); + } + + private static void updateNotifications(Context context) { + NotificationManager nm = new AndroidNotificationManager(context); + + int count = PluginServices.getTaskService().count(Query.select(Task.ID). + where(Task.TIMER_START.gt(0))); + if(count == 0) { + nm.cancel(Constants.NOTIFICATION_TIMER); + } else { + Filter filter = TimerFilterExposer.createFilter(context); + Intent notifyIntent = ShortcutActivity.createIntent(filter); + notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent pendingIntent = PendingIntent.getActivity(context, + Constants.NOTIFICATION_TIMER, notifyIntent, 0); + + Resources r = context.getResources(); + String appName = r.getString(R.string.app_name); + String text = r.getString(R.string.TPl_notification, + r.getQuantityString(R.plurals.Ntasks, count, count)); + Notification notification = new Notification( + R.drawable.timers_notification, text, System.currentTimeMillis()); + notification.setLatestEventInfo(context, appName, + text, pendingIntent); + notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; + notification.flags &= ~Notification.FLAG_AUTO_CANCEL; + + nm.notify(Constants.NOTIFICATION_TIMER, notification); + } + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerTaskCompleteListener.java new file mode 100644 index 000000000..8da52acaf --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerTaskCompleteListener.java @@ -0,0 +1,27 @@ +package com.todoroo.astrid.timers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.model.Task; + +public class TimerTaskCompleteListener extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); + if(taskId == -1) + return; + + Task task = PluginServices.getTaskService().fetchById(taskId, Task.ID, Task.ELAPSED_SECONDS, + Task.TIMER_START); + if(task == null || task.getValue(Task.TIMER_START) == 0) + return; + + TimerPlugin.updateTimer(context, task, false); + } + +} diff --git a/astrid/res/drawable/tango_clock.png b/astrid/res/drawable/tango_clock.png new file mode 100644 index 000000000..d676ffd46 Binary files /dev/null and b/astrid/res/drawable/tango_clock.png differ diff --git a/astrid/res/drawable/timers_decoration.png b/astrid/res/drawable/timers_decoration.png new file mode 100644 index 000000000..18b7c6781 Binary files /dev/null and b/astrid/res/drawable/timers_decoration.png differ diff --git a/astrid/res/drawable/timers_notification.png b/astrid/res/drawable/timers_notification.png new file mode 100644 index 000000000..6484e6e7d Binary files /dev/null and b/astrid/res/drawable/timers_notification.png differ diff --git a/astrid/res/layout/task_adapter_row.xml b/astrid/res/layout/task_adapter_row.xml index 20549d7c8..bad4d561a 100644 --- a/astrid/res/layout/task_adapter_row.xml +++ b/astrid/res/layout/task_adapter_row.xml @@ -10,7 +10,7 @@ android:paddingRight="6dip" android:orientation="vertical"> - + + + + + + + + + + + diff --git a/astrid/res/values/strings-timers.xml b/astrid/res/values/strings-timers.xml new file mode 100644 index 000000000..bbecc35d5 --- /dev/null +++ b/astrid/res/values/strings-timers.xml @@ -0,0 +1,22 @@ + + + + + + + + Start Timer + + + Stop Timer + + + Timers Active for %s! + + + Timer Filters + + + Tasks Being Timed + + diff --git a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java index c74bc8142..41dd55a32 100644 --- a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java @@ -314,16 +314,7 @@ public class FilterListActivity extends ExpandableListActivity { Filter filter = (Filter) item; info.targetView.setTag(filter); menuItem = menu.add(0, CONTEXT_MENU_SHORTCUT, 0, R.string.FLA_context_shortcut); - Intent shortcutIntent = new Intent(this, ShortcutActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); - shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_TITLE, filter.title); - shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_SQL, filter.sqlQuery); - if(filter.valuesForNewTasks != null) { - shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_VALUES, - filter.valuesForNewTasks.toString()); - } - - menuItem.setIntent(shortcutIntent); + menuItem.setIntent(ShortcutActivity.createIntent(filter)); } for(int i = 0; i < item.contextMenuLabels.length; i++) { diff --git a/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java b/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java index e8e528e75..a0db742f7 100644 --- a/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java @@ -24,6 +24,7 @@ import android.content.ContentValues; import android.content.Intent; import android.os.Bundle; +import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.astrid.api.Filter; @@ -90,4 +91,17 @@ public class ShortcutActivity extends Activity { } finish(); } + + public static Intent createIntent(Filter filter) { + Intent shortcutIntent = new Intent(ContextManager.getContext(), + ShortcutActivity.class); + shortcutIntent.setAction(Intent.ACTION_VIEW); + shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_TITLE, filter.title); + shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_SQL, filter.sqlQuery); + if(filter.valuesForNewTasks != null) { + shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_VALUES, + filter.valuesForNewTasks.toString()); + } + return shortcutIntent; + } } diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index bb35ff5ce..f5730dd7a 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -46,13 +46,15 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Pair; 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.TaskDetail; +import com.todoroo.astrid.api.TaskAction; +import com.todoroo.astrid.api.TaskDecoration; import com.todoroo.astrid.core.CoreFilterExposer; import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; @@ -324,6 +326,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { ContentValues forTask = new ContentValues(); forMetadata = new ContentValues(); for(Entry item : filter.valuesForNewTasks.valueSet()) { + if(Long.valueOf(Filter.VALUE_NOW).equals(item.getValue())) + item.setValue(DateUtilities.now()); if(item.getKey().startsWith(Task.TABLE.name)) AndroidUtilities.putInto(forTask, item.getKey(), item.getValue()); else @@ -378,6 +382,10 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { super.onResume(); registerReceiver(detailReceiver, new IntentFilter(AstridApiConstants.BROADCAST_SEND_DETAILS)); + registerReceiver(detailReceiver, + new IntentFilter(AstridApiConstants.BROADCAST_SEND_DECORATIONS)); + registerReceiver(detailReceiver, + new IntentFilter(AstridApiConstants.BROADCAST_SEND_ACTIONS)); } @Override @@ -387,7 +395,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { } /** - * Receiver which receives intents to add items to the filter list + * Receiver which receives detail or decoration intents * * @author Tim Su * @@ -398,8 +406,21 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { try { Bundle extras = intent.getExtras(); long taskId = extras.getLong(AstridApiConstants.EXTRAS_TASK_ID); - TaskDetail detail = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE); - taskAdapter.addDetails(getListView(), taskId, detail); + String addOn = extras.getString(AstridApiConstants.EXTRAS_ADDON); + + if(AstridApiConstants.BROADCAST_SEND_DECORATIONS.equals(intent.getAction())) { + TaskDecoration deco = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE); + taskAdapter.decorationManager.addNew(taskId, addOn, deco); + } else if(AstridApiConstants.BROADCAST_SEND_DETAILS.equals(intent.getAction())) { + String detail = extras.getString(AstridApiConstants.EXTRAS_RESPONSE); + 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.EXTRAS_RESPONSE); + taskAdapter.taskActionManager.addNew(taskId, addOn, action); + } } catch (Exception e) { exceptionService.reportError("receive-detail-" + //$NON-NLS-1$ intent.getStringExtra(AstridApiConstants.EXTRAS_ADDON), e); @@ -463,7 +484,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { if(requery) { taskCursor.requery(); - taskAdapter.flushDetailCache(); + taskAdapter.flushCaches(); taskAdapter.notifyDataSetChanged(); } startManagingCursor(taskCursor); diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index c806340c9..16d54d356 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -1,15 +1,17 @@ package com.todoroo.astrid.adapter; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashSet; +import java.util.Map; -import android.app.Activity; +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; @@ -19,11 +21,11 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.CheckBox; import android.widget.CursorAdapter; import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; import android.widget.ListView; import android.widget.TextView; @@ -37,10 +39,10 @@ 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.TaskDetail; +import com.todoroo.astrid.api.TaskAction; +import com.todoroo.astrid.api.TaskDecoration; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.notes.NoteDetailExposer; import com.todoroo.astrid.repeats.RepeatDetailExposer; @@ -97,15 +99,24 @@ public class TaskAdapter extends CursorAdapter { @Autowired DialogUtilities dialogUtilities; - protected final Activity activity; + protected final ListActivity activity; protected final HashMap completedItems; public boolean isFling = false; private final int resource; private final LayoutInflater inflater; protected OnCompletedTaskListener onCompletedTaskListener = null; private int fontSize; - private static final SoftHashMap> detailCache = - new SoftHashMap>(); + + // the task that's expanded + private long expanded = -1; + + // --- task detail and decoration soft caches + + 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 @@ -120,7 +131,7 @@ public class TaskAdapter extends CursorAdapter { * @param onCompletedTaskListener * task listener. can be null */ - public TaskAdapter(Activity activity, int resource, + public TaskAdapter(ListActivity activity, int resource, TodorooCursor c, boolean autoRequery, OnCompletedTaskListener onCompletedTaskListener) { super(activity, c, autoRequery); @@ -151,11 +162,14 @@ public class TaskAdapter extends CursorAdapter { // create view holder ViewHolder viewHolder = new ViewHolder(); viewHolder.task = new Task(); + viewHolder.view = view; viewHolder.nameView = (TextView)view.findViewById(R.id.title); viewHolder.completeBox = (CheckBox)view.findViewById(R.id.completeBox); viewHolder.dueDate = (TextView)view.findViewById(R.id.dueDate); viewHolder.details = (TextView)view.findViewById(R.id.details); + viewHolder.extendedDetails = (TextView)view.findViewById(R.id.extendedDetails); viewHolder.actions = (LinearLayout)view.findViewById(R.id.actions); + viewHolder.taskRow = (LinearLayout)view.findViewById(R.id.task_row); viewHolder.importance = (View)view.findViewById(R.id.importance); view.setTag(viewHolder); @@ -177,11 +191,12 @@ public class TaskAdapter extends CursorAdapter { public void bindView(View view, Context context, Cursor c) { TodorooCursor cursor = (TodorooCursor)c; ViewHolder viewHolder = ((ViewHolder)view.getTag()); - Task actionItem = viewHolder.task; - actionItem.readFromCursor(cursor); + + Task task = viewHolder.task; + task.readFromCursor(cursor); setFieldContentsAndVisibility(view); - setTaskAppearance(viewHolder, actionItem.isCompleted()); + setTaskAppearance(viewHolder, task.isCompleted()); } /** Helper method to set the visibility based on if there's stuff inside */ @@ -200,13 +215,17 @@ public class TaskAdapter extends CursorAdapter { */ public class ViewHolder { public Task task; + public View view; public TextView nameView; public CheckBox completeBox; public TextView dueDate; public TextView details; + public TextView extendedDetails; public View importance; public LinearLayout actions; - public boolean expanded; + public LinearLayout taskRow; + + public View[] decorations; } /** Helper method to set the contents and visibility of each field */ @@ -251,7 +270,6 @@ public class TaskAdapter extends CursorAdapter { dueDateView.setText(r.getString(R.string.TAd_completed, dateValue)); dueDateView.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemDetails); setVisibility(dueDateView); - } else { dueDateView.setVisibility(View.GONE); } @@ -265,189 +283,301 @@ public class TaskAdapter extends CursorAdapter { completeBox.setChecked(task.isCompleted()); } - // task details - send out a request for it (only if not fling) - viewHolder.details.setText(""); //$NON-NLS-1$ - if(!isFling) { - retrieveDetails(viewHolder); - } - - // importance bar - must be set at end when view height is determined + // importance bar final View importanceView = viewHolder.importance; { int value = task.getValue(Task.IMPORTANCE); importanceView.setBackgroundColor(IMPORTANCE_COLORS[value]); } + + // details and decorations, expanded + if(!isFling) { + detailManager.request(viewHolder); + decorationManager.request(viewHolder); + if(expanded == task.getId()) { + extendedDetailManager.request(viewHolder); + taskActionManager.request(viewHolder); + } else { + viewHolder.extendedDetails.setVisibility(View.GONE); + viewHolder.actions.setVisibility(View.GONE); + } + } } - // --- task details + protected TaskRowListener listener = new TaskRowListener(); + /** + * Set listeners for this view. This is called once per view when it is + * created. + */ + private void addListeners(final View container) { + // check box listener + final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox)); + completeBox.setOnClickListener(completeBoxListener); - private void retrieveDetails(final ViewHolder viewHolder) { - final long taskId = viewHolder.task.getId(); + // context menu listener + container.setOnCreateContextMenuListener(listener); - // check the cache - boolean inCache = false; - final LinkedHashSet details; - synchronized(detailCache) { - if(detailCache.containsKey(taskId)) - inCache = true; - else - detailCache.put(taskId, new LinkedHashSet()); - details = detailCache.get(taskId); - } + // tap listener + container.setOnClickListener(listener); + } - if(inCache) { - spanifyAndAdd(viewHolder.details, details); - return; - } + /* ====================================================================== + * ============================================================== add-ons + * ====================================================================== */ - // request details - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); - activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); - - // load internal details - new Thread() { - @Override - public void run() { - for(DetailExposer exposer : EXPOSERS) { - if(Thread.interrupted()) - return; - - final TaskDetail detail = exposer.getTaskDetails(activity, taskId); - if(detail == null || details.contains(detail)) - continue; + /** + * Called to tell the cache to be cleared + */ + public void flushCaches() { + detailManager.clearCache(); + extendedDetailManager.clearCache(); + decorationManager.clearCache(); + taskActionManager.clearCache(); + } - details.add(detail); - if(taskId != viewHolder.task.getId()) - continue; + /** + * AddOnManager for Details + * @author Tim Su + * + */ + public class DetailManager extends AddOnManager { + + private final boolean extended; + public DetailManager(boolean extended) { + this.extended = extended; + } + + @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; + } - activity.runOnUiThread(new Runnable() { - public void run() { - spanifyAndAdd(viewHolder.details, details); + @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, taskId, cacheList); + } + }); + } } - }); - } - }; - }.start(); - } + }; + }.start(); + return true; + } + return false; + } - @SuppressWarnings("nls") - private void spanifyAndAdd(TextView view, LinkedHashSet details) { - view.setVisibility(details.size() > 0 ? View.VISIBLE : View.GONE); - if(details.size() == 0) - return; - StringBuilder detailText = new StringBuilder(); - for(Iterator iterator = details.iterator(); iterator.hasNext(); ) { - detailText.append(iterator.next().text); - if(iterator.hasNext()) - detailText.append(DETAIL_SEPARATOR); + @SuppressWarnings("nls") + @Override + void draw(ViewHolder viewHolder, long taskId, Collection details) { + if(details == null || viewHolder.task.getId() != taskId) + return; + TextView view = extended ? viewHolder.extendedDetails : viewHolder.details; + if(details.isEmpty() || (extended && expanded != taskId)) { + view.setVisibility(View.GONE); + return; + } + 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()); } - 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 + * AddOnManager for TaskDecorations + * + * @author Tim Su + * */ - public void flushDetailCache() { - detailCache.clear(); - } + public class DecorationManager extends AddOnManager { + @Override + Intent createBroadcastIntent(long taskId) { + Intent intent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DECORATIONS); + intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + return intent; + } - @Override - public void notifyDataSetChanged() { - super.notifyDataSetChanged(); - fontSize = Preferences.getIntegerFromString(R.string.p_fontSize); + @Override + void draw(ViewHolder viewHolder, long taskId, Collection decorations) { + if(decorations == null || viewHolder.task.getId() != taskId) + return; + + if(viewHolder.decorations != null) { + for(View view : viewHolder.decorations) + viewHolder.taskRow.removeView(view); + } + viewHolder.view.setBackgroundColor(Color.TRANSPARENT); + if(decorations.size() == 0) + return; + + int i = 0; + boolean colorSet = false; + viewHolder.decorations = new View[decorations.size()]; + for(TaskDecoration decoration : decorations) { + if(decoration.color != 0 && !colorSet) { + colorSet = true; + viewHolder.view.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 addDetails(ListView list, long taskId, TaskDetail detail) { - if(detail == null) - return; + 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; + } + + @Override + void draw(final ViewHolder viewHolder, final long taskId, Collection actions) { + if(actions == null || viewHolder.task.getId() != taskId) + return; + + if(expanded != taskId) { + 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); + 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); + } - LinkedHashSet details = detailCache.get(taskId); - if(details.contains(detail)) - return; - details.add(detail); - - // update view if it is visible - int length = list.getChildCount(); - for(int i = 0; i < length; i++) { - ViewHolder viewHolder = (ViewHolder) list.getChildAt(i).getTag(); - if(viewHolder == null || viewHolder.task.getId() != taskId) - continue; - spanifyAndAdd(viewHolder.details, details); - break; } } + /* ====================================================================== + * ======================================================= event handlers + * ====================================================================== */ + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + fontSize = Preferences.getIntegerFromString(R.string.p_fontSize); + } + 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()); } }; - protected ContextMenuListener listener = new ContextMenuListener(); - /** - * Set listeners for this view. This is called once per view when it is - * created. - */ - private void addListeners(final View container) { - // check box listener - final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox)); - completeBox.setOnClickListener(completeBoxListener); - - // context menu listener - container.setOnCreateContextMenuListener(listener); - - // tap listener - container.setOnClickListener(listener); - } + private final class ActionClickListener implements View.OnClickListener { + TaskAction action; - /* ====================================================================== - * ======================================================= event handlers - * ====================================================================== */ + public ActionClickListener(TaskAction action) { + this.action = action; + } + public void onClick(View v) { + try { + action.intent.send(); + } catch (Exception e) { + exceptionService.displayAndReportError(activity, + "Error launching action", e); //$NON-NLS-1$ + } + } + }; - class ContextMenuListener implements OnCreateContextMenuListener, OnClickListener { + 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 public void onClick(View v) { + // expand view final ViewHolder viewHolder = (ViewHolder)v.getTag(); - viewHolder.expanded = !viewHolder.expanded; - LinearLayout actions = viewHolder.actions; - actions.setVisibility(viewHolder.expanded ? View.VISIBLE : View.GONE); - if(viewHolder.expanded && 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, viewHolder.task.getId()); - activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK); - } - }); - actions.addView(edit); + long taskId = viewHolder.task.getId(); + if(expanded == taskId) { + expanded = -1; + } else { + expanded = taskId; } + notifyDataSetChanged(); + ListView listView = activity.getListView(); + listView.setSelection(listView.indexOfChild(viewHolder.view)); } } @@ -507,4 +637,111 @@ public class TaskAdapter extends CursorAdapter { } } + /* ====================================================================== + * ========================================================= addon helper + * ====================================================================== */ + + abstract private class AddOnManager { + + private final Map> cache = + 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, taskId, list); + return false; + } + + // request details + draw(viewHolder, taskId, get(taskId)); + Intent broadcastIntent = createBroadcastIntent(taskId); + activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + return true; + } + + /** creates a broadcast intent for requesting */ + abstract Intent createBroadcastIntent(long taskId); + + /** updates the given view */ + abstract void draw(ViewHolder viewHolder, long taskId, 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, taskId, cacheList); + break; + } + } + } + + /** + * Clears the cache + */ + public void clearCache() { + cache.clear(); + } + + // --- internal goodies + + /** + * Retrieves a list. If it doesn't exist, list is created, but + * the method will return null + * @param taskId + * @return list if there was already one + */ + protected synchronized Collection initialize(long taskId) { + if(cache.containsKey(taskId)) + return get(taskId); + cache.put(taskId, new HashMap()); + return null; + } + + /** + * Adds an item to the cache if it doesn't exist + * @param taskId + * @param item + * @return iterator if item was added, null if it already existed + */ + protected synchronized Collection addIfNotExists(long taskId, String addOn, + TYPE item) { + HashMap list = cache.get(taskId); + if(list == null) + return null; + if(list.containsKey(addOn) && list.get(addOn).equals(item)) + return null; + list.put(addOn, item); + return get(taskId); + } + + /** + * Gets an item at the given index + * @param taskId + * @return + */ + 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 2a4e84e60..90ef95b9d 100644 --- a/astrid/src/com/todoroo/astrid/api/DetailExposer.java +++ b/astrid/src/com/todoroo/astrid/api/DetailExposer.java @@ -12,8 +12,15 @@ public interface DetailExposer { /** * @param id + * task id + * @param extended + * whether this request is for extended details (which are + * displayed when user presses a task), or standard (which are + * always displayed) * @return null if no details, or task details */ - public TaskDetail getTaskDetails(Context context, long id); + 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( diff --git a/astrid/src/com/todoroo/astrid/service/TaskService.java b/astrid/src/com/todoroo/astrid/service/TaskService.java index 9ae6ec64e..e29ea62c8 100644 --- a/astrid/src/com/todoroo/astrid/service/TaskService.java +++ b/astrid/src/com/todoroo/astrid/service/TaskService.java @@ -10,8 +10,8 @@ import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.dao.MetadataDao; -import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Task; @@ -173,6 +173,19 @@ public class TaskService { Task.IMPORTANCE + " + " + Task.COMPLETION_DATE); } + /** + * @param query + * @return how many tasks are matched by this query + */ + public int count(Query query) { + TodorooCursor cursor = taskDao.query(query); + try { + return cursor.getCount(); + } finally { + cursor.close(); + } + } + } diff --git a/astrid/src/com/todoroo/astrid/utility/Constants.java b/astrid/src/com/todoroo/astrid/utility/Constants.java index b76187437..d7ba910ef 100644 --- a/astrid/src/com/todoroo/astrid/utility/Constants.java +++ b/astrid/src/com/todoroo/astrid/utility/Constants.java @@ -32,7 +32,10 @@ public final class Constants { // --- notification id's - /** Notification Manager id for RMilk notifications */ + /** Notification Manager id for sync notifications */ public static final int NOTIFICATION_SYNC = -1; + /** Notification Manager id for timing */ + public static final int NOTIFICATION_TIMER = -2; + }