Merge branch 'dev'

pull/14/head
Tim Su 14 years ago
commit a8ca8f346d

@ -151,7 +151,7 @@
<!-- core -->
<receiver android:name="com.todoroo.astrid.core.CorePlugin">
<intent-filter>
<intent-filter android:priority="9000" >
<action android:name="com.todoroo.astrid.REQUEST_ADDONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
@ -207,6 +207,33 @@
</receiver>
<!-- notes -->
<!-- timers -->
<receiver android:name="com.todoroo.astrid.timers.TimerDecorationExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_DECORATIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.timers.TimerActionExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_ACTIONS" />
<action android:name="com.todoroo.astrid.TIMER_BUTTON" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.timers.TimerFilterExposer">
<intent-filter android:priority="10000">
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.timers.TimerTaskCompleteListener">
<intent-filter>
<action android:name="com.todoroo.astrid.TASK_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- reminders -->
<activity android:name="com.todoroo.astrid.reminders.ReminderPreferences"
@ -223,7 +250,7 @@
</intent-filter>
</receiver>
<activity android:name="com.todoroo.astrid.reminders.NotificationActivity"
android:taskAffinity="" />
android:clearTaskOnLaunch="true" />
<!-- rmilk -->
<receiver android:name="com.todoroo.astrid.rmilk.MilkFilterExposer">

@ -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
/**

@ -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.

@ -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 <tim@todoroo.com>
*
*/
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<EditOperation> CREATOR = new Parcelable.Creator<EditOperation>() {
public static final Parcelable.Creator<TaskAction> CREATOR = new Parcelable.Creator<TaskAction>() {
/**
* {@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];
};
};

@ -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 <tim@todoroo.com>
*
*/

@ -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 <tim@todoroo.com>
*
*/
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 <code>0</code> 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<TaskDecoration> CREATOR = new Parcelable.Creator<TaskDecoration>() {
/**
* {@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];
};
};
}

@ -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 <tim@todoroo.com>
*
*/
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 <code>0</code> 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<TaskDetail> CREATOR = new Parcelable.Creator<TaskDetail>() {
/**
* {@inheritDoc}
*/
public TaskDetail createFromParcel(Parcel source) {
return new TaskDetail(source.readString(), source.readInt());
}
/**
* {@inheritDoc}
*/
public TaskDetail[] newArray(int size) {
return new TaskDetail[size];
};
};
}

@ -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();
}
}
}
}

@ -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);

@ -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;
}
}

@ -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();
}
}

@ -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;

@ -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 <tim@todoroo.com>
*
*/
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;
}
}

@ -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.");

@ -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 <tim@todoroo.com>
*
*/
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;
}
}

@ -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 <tim@todoroo.com>
*
@ -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<Metadata> 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<Metadata> 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;
}
}

@ -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 <tim@todoroo.com>
*
@ -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;
}
}

@ -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",

@ -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 <tim@todoroo.com>
*
*/
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);
}
}
}

@ -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 <tim@todoroo.com>
*
*/
public class TimerDecorationExposer extends BroadcastReceiver {
private static final int TIMING_BG_COLOR = Color.argb(200, 220, 50, 0);
private static HashMap<Long, TaskDecoration> decorations =
new HashMap<Long, TaskDecoration>();
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);
}
}

@ -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 <tim@todoroo.com>
*
*/
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;
}
}

@ -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);
}
}
}

@ -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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -10,7 +10,7 @@
android:paddingRight="6dip"
android:orientation="vertical">
<LinearLayout
<LinearLayout android:id="@+id/task_row"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="100"
@ -61,6 +61,11 @@
android:layout_height="fill_parent" />
</LinearLayout>
<TextView android:id="@+id/extendedDetails"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<LinearLayout android:id="@+id/actions"
android:layout_width="fill_parent"

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:orientation="vertical"
android:paddingLeft="3px">
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="5"
android:gravity="bottom|center_horizontal"
android:scaleType="center"
android:src="@drawable/timers_decoration" />
<Chronometer android:id="@+id/timer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="top"
android:textSize="10sp" />
</LinearLayout>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Resources for built-in timers plug-in -->
<!-- Start Timer button -->
<string name="TAE_startTimer">Start Timer</string>
<!-- Stop Timer button -->
<string name="TAE_stopTimer">Stop Timer</string>
<!-- Notification Title (%s => # tasks) -->
<string name="TPl_notification">Timers Active for %s!</string>
<!-- Filter Header -->
<string name="TFE_category">Timer Filters</string>
<!-- Filter -->
<string name="TFE_workingOn">Tasks Being Timed</string>
</resources>

@ -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++) {

@ -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;
}
}

@ -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<String, Object> 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 <tim@todoroo.com>
*
@ -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);

@ -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<Long, Boolean> completedItems;
public boolean isFling = false;
private final int resource;
private final LayoutInflater inflater;
protected OnCompletedTaskListener onCompletedTaskListener = null;
private int fontSize;
private static final SoftHashMap<Long, LinkedHashSet<TaskDetail>> detailCache =
new SoftHashMap<Long, LinkedHashSet<TaskDetail>>();
// 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<Task> 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<Task> cursor = (TodorooCursor<Task>)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<TaskDetail> details;
synchronized(detailCache) {
if(detailCache.containsKey(taskId))
inCache = true;
else
detailCache.put(taskId, new LinkedHashSet<TaskDetail>());
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 <tim@todoroo.com>
*
*/
public class DetailManager extends AddOnManager<String> {
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<String> 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<TaskDetail> details) {
view.setVisibility(details.size() > 0 ? View.VISIBLE : View.GONE);
if(details.size() == 0)
return;
StringBuilder detailText = new StringBuilder();
for(Iterator<TaskDetail> 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<String> 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<String> 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", "<br>")));
else
view.setText(string.trim());
}
String string = detailText.toString();
if(string.contains("<"))
view.setText(Html.fromHtml(string.trim().replace("\n", "<br>")));
else
view.setText(string.trim());
}
/**
* Called to tell the cache to be cleared
* AddOnManager for TaskDecorations
*
* @author Tim Su <tim@todoroo.com>
*
*/
public void flushDetailCache() {
detailCache.clear();
}
public class DecorationManager extends AddOnManager<TaskDecoration> {
@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<TaskDecoration> 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 <tim@todoroo.com>
*
* @param taskId
*/
public synchronized void addDetails(ListView list, long taskId, TaskDetail detail) {
if(detail == null)
return;
public class TaskActionManager extends AddOnManager<TaskAction> {
@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<TaskAction> 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<TaskDetail> 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<TYPE> {
private final Map<Long, HashMap<String, TYPE>> cache =
new SoftHashMap<Long, HashMap<String, TYPE>>();
// --- 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<TYPE> 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<TYPE> list);
/** on receive an intent */
public void addNew(long taskId, String addOn, TYPE item) {
if(item == null)
return;
Collection<TYPE> 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<TYPE> initialize(long taskId) {
if(cache.containsKey(taskId))
return get(taskId);
cache.put(taskId, new HashMap<String, TYPE>());
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<TYPE> addIfNotExists(long taskId, String addOn,
TYPE item) {
HashMap<String, TYPE> 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<TYPE> get(long taskId) {
return cache.get(taskId).values();
}
}
}

@ -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();
}

@ -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(

@ -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<Task> cursor = taskDao.query(query);
try {
return cursor.getCount();
} finally {
cursor.close();
}
}
}

@ -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;
}

Loading…
Cancel
Save