First pass at task decorations, timer. Works but doesn't update.

pull/14/head
Tim Su 14 years ago
parent 95154ca51c
commit 8f757ac9fa

@ -208,6 +208,14 @@
<!-- notes --> <!-- 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>
<!-- reminders --> <!-- reminders -->
<activity android:name="com.todoroo.astrid.reminders.ReminderPreferences" <activity android:name="com.todoroo.astrid.reminders.ReminderPreferences"
android:label="@string/rmd_EPr_alerts_header"> android:label="@string/rmd_EPr_alerts_header">

@ -47,6 +47,11 @@ public class AstridApiConstants {
*/ */
public static final String EXTRAS_ADDON = "addon"; 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 // --- Add-ons API
/** /**
@ -89,11 +94,14 @@ public class AstridApiConstants {
*/ */
public static final String BROADCAST_SEND_EDIT_CONTROLS = PACKAGE + ".SEND_EDIT_CONTROLS"; 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_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"; 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 * Action name for broadcast intent sending details back to Astrid
* @extra EXTRAS_ADDON your add-on identifier * @extra EXTRAS_ADDON your add-on identifier
* @extra EXTRAS_TASK_ID id of the task * @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"; 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 // --- Actions API
/** /**

@ -8,17 +8,12 @@ import android.os.Parcel;
import android.os.Parcelable; 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> * @author Tim Su <tim@todoroo.com>
* *
*/ */
public final class EditOperation implements Parcelable { public class TaskAction implements Parcelable {
/**
* Plugin Id
*/
public final String plugin;
/** /**
* Label * Label
@ -33,15 +28,13 @@ public final class EditOperation implements Parcelable {
/** /**
* Create an EditOperation object * Create an EditOperation object
* *
* @param plugin
* {@link Addon} identifier that encompasses object
* @param text * @param text
* label to display * label to display
* @param intent * @param intent
* intent to invoke. {@link EXTRAS_TASK_ID} will be passed * intent to invoke. {@link EXTRAS_TASK_ID} will be passed
*/ */
public EditOperation(String plugin, String text, Intent intent) { public TaskAction(String text, Intent intent) {
this.plugin = plugin; super();
this.text = text; this.text = text;
this.intent = intent; this.intent = intent;
} }
@ -59,7 +52,6 @@ public final class EditOperation implements Parcelable {
* {@inheritDoc} * {@inheritDoc}
*/ */
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(plugin);
dest.writeString(text); dest.writeString(text);
dest.writeParcelable(intent, 0); dest.writeParcelable(intent, 0);
} }
@ -67,20 +59,20 @@ public final class EditOperation implements Parcelable {
/** /**
* Parcelable creator * 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} * {@inheritDoc}
*/ */
public EditOperation createFromParcel(Parcel source) { public TaskAction createFromParcel(Parcel source) {
return new EditOperation(source.readString(), source.readString(), return new TaskAction(source.readString(), (Intent)source.readParcelable(
(Intent)source.readParcelable(Intent.class.getClassLoader())); Intent.class.getClassLoader()));
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public EditOperation[] newArray(int size) { public TaskAction[] newArray(int size) {
return new EditOperation[size]; return new TaskAction[size];
}; };
}; };

@ -6,9 +6,10 @@ import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
/** /**
* Container class for tasks. Synchronization Providers can subclass * Container class for transmitting tasks and including local and remote
* this class if desired. * metadata. Synchronization Providers can subclass this class if desired.
* *
* @see {@link SynchronizationProvider}
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
*/ */

@ -0,0 +1,101 @@
/**
* 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 update interval (minimum of 1000 millis), 0 to never update
*/
public long updateInterval = 0;
/**
* 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,6 +214,7 @@ public class AndroidUtilities {
/** /**
* Copy a file from one place to another * Copy a file from one place to another
*
* @param in * @param in
* @param out * @param out
* @throws Exception * @throws Exception
@ -227,13 +228,11 @@ public class AndroidUtilities {
while ((i = fis.read(buf)) != -1) { while ((i = fis.read(buf)) != -1) {
fos.write(buf, 0, i); fos.write(buf, 0, i);
} }
} } catch (Exception e) {
catch (Exception e) {
throw e; throw e;
} } finally {
finally { fis.close();
if (fis != null) fis.close(); fos.close();
if (fos != null) fos.close();
} }
} }
} }

@ -12,9 +12,10 @@ public class CorePlugin extends BroadcastReceiver {
static final String IDENTIFIER = "core"; //$NON-NLS-1$ static final String IDENTIFIER = "core"; //$NON-NLS-1$
@Override @Override
@SuppressWarnings("nls")
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Addon plugin = new Addon(IDENTIFIER, "Core Filters", "Todoroo", 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); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_ADDONS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, plugin); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, plugin);

@ -11,12 +11,11 @@ import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.api.TaskDetail;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.TaskService; 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> * @author Tim Su <tim@todoroo.com>
* *
@ -35,7 +34,8 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
if(taskId == -1) if(taskId == -1)
return; return;
TaskDetail taskDetail = getTaskDetails(context, taskId); boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false);
String taskDetail = getTaskDetails(context, taskId, extended);
if(taskDetail == null) if(taskDetail == null)
return; return;
@ -43,12 +43,16 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, NotesPlugin.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, NotesPlugin.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override @Override
public TaskDetail getTaskDetails(Context context, long id) { public String getTaskDetails(Context context, long id, boolean extended) {
if(!extended)
return null;
synchronized(NoteDetailExposer.class) { synchronized(NoteDetailExposer.class) {
if(staticTaskService == null) { if(staticTaskService == null) {
DependencyInjectionService.getInstance().inject(this); DependencyInjectionService.getInstance().inject(this);
@ -65,7 +69,7 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
if(notes.length() == 0) if(notes.length() == 0)
return null; return null;
return new TaskDetail(notes); return notes;
} }
} }

@ -12,6 +12,7 @@ public class NotesPlugin extends BroadcastReceiver {
static final String IDENTIFIER = "notes"; //$NON-NLS-1$ static final String IDENTIFIER = "notes"; //$NON-NLS-1$
@Override @Override
@SuppressWarnings("nls")
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Addon plugin = new Addon(IDENTIFIER, "Notes", "Todoroo", Addon plugin = new Addon(IDENTIFIER, "Notes", "Todoroo",
"Lets you add and view notes for a task."); "Lets you add and view notes for a task.");

@ -20,12 +20,11 @@ import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.api.TaskDetail;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.TaskService; 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> * @author Tim Su <tim@todoroo.com>
* *
@ -44,7 +43,8 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo
if(taskId == -1) if(taskId == -1)
return; return;
TaskDetail taskDetail = getTaskDetails(context, taskId); boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false);
String taskDetail = getTaskDetails(context, taskId, extended);
if(taskDetail == null) if(taskDetail == null)
return; return;
@ -52,11 +52,15 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, RepeatsPlugin.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, RepeatsPlugin.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
public TaskDetail getTaskDetails(Context context, long id) { public String getTaskDetails(Context context, long id, boolean extended) {
if(extended)
return null;
synchronized(RepeatDetailExposer.class) { synchronized(RepeatDetailExposer.class) {
if(staticTaskService == null) { if(staticTaskService == null) {
DependencyInjectionService.getInstance().inject(this); DependencyInjectionService.getInstance().inject(this);
@ -126,7 +130,7 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo
else else
detail = context.getString(R.string.repeat_detail_duedate, interval); detail = context.getString(R.string.repeat_detail_duedate, interval);
return new TaskDetail(detail); return detail;
} }
return null; return null;
} }

@ -12,17 +12,16 @@ import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.api.TaskDetail;
import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.rmilk.data.MilkDataService;
import com.todoroo.astrid.rmilk.data.MilkNote; import com.todoroo.astrid.rmilk.data.MilkNote;
import com.todoroo.astrid.rmilk.data.MilkTask; 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 list
* - RTM repeat information * - RTM repeat information
* - whether task has been changed * - RTM notes
* *
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
* *
@ -39,24 +38,28 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose
if(taskId == -1) if(taskId == -1)
return; return;
TaskDetail taskDetail = getTaskDetails(context, taskId); boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false);
String taskDetail = getTaskDetails(context, taskId, extended);
if(taskDetail == null) if(taskDetail == null)
return; return;
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, Utilities.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, Utilities.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override @Override
public TaskDetail getTaskDetails(Context context, long id) { public String getTaskDetails(Context context, long id, boolean extended) {
Metadata metadata = MilkDataService.getInstance().getTaskMetadata(id); Metadata metadata = MilkDataService.getInstance().getTaskMetadata(id);
if(metadata == null) if(metadata == null)
return null; return null;
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
if(!extended) {
long listId = metadata.getValue(MilkTask.LIST_ID); long listId = metadata.getValue(MilkTask.LIST_ID);
if(listId > 0) { if(listId > 0) {
builder.append(context.getString(R.string.rmilk_TLA_list, builder.append(context.getString(R.string.rmilk_TLA_list,
@ -67,7 +70,7 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose
if(repeat != 0) { if(repeat != 0) {
builder.append(context.getString(R.string.rmilk_TLA_repeat)).append(TaskAdapter.DETAIL_SEPARATOR); builder.append(context.getString(R.string.rmilk_TLA_repeat)).append(TaskAdapter.DETAIL_SEPARATOR);
} }
} else {
TodorooCursor<Metadata> notesCursor = MilkDataService.getInstance().getTaskNotesCursor(id); TodorooCursor<Metadata> notesCursor = MilkDataService.getInstance().getTaskNotesCursor(id);
try { try {
for(notesCursor.moveToFirst(); !notesCursor.isAfterLast(); notesCursor.moveToNext()) { for(notesCursor.moveToFirst(); !notesCursor.isAfterLast(); notesCursor.moveToNext()) {
@ -77,11 +80,12 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose
} finally { } finally {
notesCursor.close(); notesCursor.close();
} }
}
if(builder.length() == 0) if(builder.length() == 0)
return null; return null;
String result = builder.toString(); String result = builder.toString();
return new TaskDetail(result.substring(0, result.length() - 3)); return result.substring(0, result.length() - TaskAdapter.DETAIL_SEPARATOR.length());
} }
} }

@ -10,10 +10,9 @@ import android.content.Intent;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer; 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> * @author Tim Su <tim@todoroo.com>
* *
@ -27,7 +26,8 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer
if(taskId == -1) if(taskId == -1)
return; return;
TaskDetail taskDetail = getTaskDetails(context, taskId); boolean extended = intent.getBooleanExtra(AstridApiConstants.EXTRAS_EXTENDED, false);
String taskDetail = getTaskDetails(context, taskId, extended);
if(taskDetail == null) if(taskDetail == null)
return; return;
@ -35,18 +35,21 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, TagsPlugin.IDENTIFIER); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, TagsPlugin.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, taskDetail);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }
@Override @Override
public TaskDetail getTaskDetails(Context context, long id) { public String getTaskDetails(Context context, long id, boolean extended) {
String tagList = TagService.getInstance().getTagsAsString(id); if(extended)
return null;
String tagList = TagService.getInstance().getTagsAsString(id);
if(tagList.length() == 0) if(tagList.length() == 0)
return null; return null;
return new TaskDetail(context.getString(R.string.tag_TLA_detail, tagList)); return context.getString(R.string.tag_TLA_detail, tagList);
} }
} }

@ -9,8 +9,9 @@ import com.todoroo.astrid.api.AstridApiConstants;
public class TagsPlugin extends BroadcastReceiver { public class TagsPlugin extends BroadcastReceiver {
static final String IDENTIFIER = "tags"; static final String IDENTIFIER = "tags"; //$NON-NLS-1$
@SuppressWarnings("nls")
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Addon plugin = new Addon(IDENTIFIER, "Tags", "Todoroo", Addon plugin = new Addon(IDENTIFIER, "Tags", "Todoroo",

@ -0,0 +1,116 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.timers;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.widget.RemoteViews;
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.TaskDecoration;
import com.todoroo.astrid.service.TaskService;
/**
* Exposes {@link TaskDecoration} for timers
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class TimerDecorationExposer extends BroadcastReceiver {
private static final int TASK_BG_COLOR = Color.argb(200, 220, 50, 0);
private static TaskService staticTaskService;
private static HashMap<Long, TaskDecoration> decorations =
new HashMap<Long, TaskDecoration>();
private static HashMap<Long, Timer> timers =
new HashMap<Long, Timer>();
@Autowired
private TaskService taskService;
private static class TimerTimerTask extends TimerTask {
int time;
RemoteViews remoteViews;
public TimerTimerTask(int time, RemoteViews remoteViews) {
super();
this.time = time;
this.remoteViews = remoteViews;
}
@Override
public void run() {
time++;
int seconds = time % 60;
int minutes = time / 60;
if(minutes > 59) {
int hours = minutes / 60;
minutes %= 60;
remoteViews.setTextViewText(R.id.timer,
String.format("%02d:%02d:%02d", //$NON-NLS-1$
hours, minutes, seconds));
} else {
remoteViews.setTextViewText(R.id.timer,
String.format("%02d:%02d", //$NON-NLS-1$
minutes, seconds));
}
}
}
public void removeFromCache(long taskId) {
decorations.remove(taskId);
timers.get(taskId).cancel();
timers.remove(taskId);
}
@Override
public void onReceive(Context context, Intent intent) {
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
if(taskId == -1)
return;
synchronized(TimerDecorationExposer.class) {
if(staticTaskService == null) {
DependencyInjectionService.getInstance().inject(this);
staticTaskService = taskService;
} else {
taskService = staticTaskService;
}
}
TaskDecoration decoration;
if(!decorations.containsKey(taskId)) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.timer_decoration);
decoration = new TaskDecoration(remoteViews,
TaskDecoration.POSITION_LEFT, TASK_BG_COLOR);
decorations.put(taskId, decoration);
Timer timer = new Timer();
timers.put(taskId, timer);
timer.scheduleAtFixedRate(new TimerTimerTask(0,
remoteViews), 0, 1000L);
} else {
decoration = decorations.get(taskId);
}
// 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,25 @@
package com.todoroo.astrid.timers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.todoroo.astrid.api.Addon;
import com.todoroo.astrid.api.AstridApiConstants;
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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

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

@ -0,0 +1,20 @@
<?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:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="fill_parent">
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scaleType="center"
android:src="@drawable/timers_decoration" />
<TextView android:id="@+id/timer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="10sp" />
</LinearLayout>

@ -52,7 +52,7 @@ import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.adapter.TaskAdapter.ViewHolder; import com.todoroo.astrid.adapter.TaskAdapter.ViewHolder;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.TaskDetail; import com.todoroo.astrid.api.TaskDecoration;
import com.todoroo.astrid.core.CoreFilterExposer; import com.todoroo.astrid.core.CoreFilterExposer;
import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
@ -378,6 +378,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener {
super.onResume(); super.onResume();
registerReceiver(detailReceiver, registerReceiver(detailReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_SEND_DETAILS)); new IntentFilter(AstridApiConstants.BROADCAST_SEND_DETAILS));
registerReceiver(detailReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_SEND_DECORATIONS));
} }
@Override @Override
@ -387,7 +389,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> * @author Tim Su <tim@todoroo.com>
* *
@ -398,8 +400,17 @@ public class TaskListActivity extends ListActivity implements OnScrollListener {
try { try {
Bundle extras = intent.getExtras(); Bundle extras = intent.getExtras();
long taskId = extras.getLong(AstridApiConstants.EXTRAS_TASK_ID); long taskId = extras.getLong(AstridApiConstants.EXTRAS_TASK_ID);
TaskDetail detail = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE);
taskAdapter.addDetails(getListView(), taskId, detail); if(AstridApiConstants.BROADCAST_SEND_DECORATIONS.equals(intent.getAction())) {
TaskDecoration deco = extras.getParcelable(AstridApiConstants.EXTRAS_RESPONSE);
taskAdapter.addDecorations(getListView(), taskId,
deco);
} else {
String detail = extras.getString(AstridApiConstants.EXTRAS_RESPONSE);
taskAdapter.addDetails(getListView(), taskId,
extras.getBoolean(AstridApiConstants.EXTRAS_EXTENDED),
detail);
}
} catch (Exception e) { } catch (Exception e) {
exceptionService.reportError("receive-detail-" + //$NON-NLS-1$ exceptionService.reportError("receive-detail-" + //$NON-NLS-1$
intent.getStringExtra(AstridApiConstants.EXTRAS_ADDON), e); intent.getStringExtra(AstridApiConstants.EXTRAS_ADDON), e);
@ -463,7 +474,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener {
if(requery) { if(requery) {
taskCursor.requery(); taskCursor.requery();
taskAdapter.flushDetailCache(); taskAdapter.flushCaches();
taskAdapter.notifyDataSetChanged(); taskAdapter.notifyDataSetChanged();
} }
startManagingCursor(taskCursor); startManagingCursor(taskCursor);

@ -1,11 +1,15 @@
package com.todoroo.astrid.adapter; package com.todoroo.astrid.adapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity; import android.app.ListActivity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
@ -19,11 +23,11 @@ import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener; import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CursorAdapter; import android.widget.CursorAdapter;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
@ -40,7 +44,7 @@ import com.todoroo.astrid.activity.TaskEditActivity;
import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.DetailExposer; import com.todoroo.astrid.api.DetailExposer;
import com.todoroo.astrid.api.TaskDetail; import com.todoroo.astrid.api.TaskDecoration;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.notes.NoteDetailExposer; import com.todoroo.astrid.notes.NoteDetailExposer;
import com.todoroo.astrid.repeats.RepeatDetailExposer; import com.todoroo.astrid.repeats.RepeatDetailExposer;
@ -97,15 +101,19 @@ public class TaskAdapter extends CursorAdapter {
@Autowired @Autowired
DialogUtilities dialogUtilities; DialogUtilities dialogUtilities;
protected final Activity activity; protected final ListActivity activity;
protected final HashMap<Long, Boolean> completedItems; protected final HashMap<Long, Boolean> completedItems;
public boolean isFling = false; public boolean isFling = false;
private final int resource; private final int resource;
private final LayoutInflater inflater; private final LayoutInflater inflater;
protected OnCompletedTaskListener onCompletedTaskListener = null; protected OnCompletedTaskListener onCompletedTaskListener = null;
private int fontSize; private int fontSize;
private static final SoftHashMap<Long, LinkedHashSet<TaskDetail>> detailCache =
new SoftHashMap<Long, LinkedHashSet<TaskDetail>>(); // --- task detail and decoration soft caches
private static final TaskCache<String> detailCache = new TaskCache<String>();
private static final TaskCache<String> extendedDetailCache = new TaskCache<String>();
private static final TaskCache<TaskDecoration> decorationCache = new TaskCache<TaskDecoration>();
/** /**
* Constructor * Constructor
@ -120,7 +128,7 @@ public class TaskAdapter extends CursorAdapter {
* @param onCompletedTaskListener * @param onCompletedTaskListener
* task listener. can be null * task listener. can be null
*/ */
public TaskAdapter(Activity activity, int resource, public TaskAdapter(ListActivity activity, int resource,
TodorooCursor<Task> c, boolean autoRequery, TodorooCursor<Task> c, boolean autoRequery,
OnCompletedTaskListener onCompletedTaskListener) { OnCompletedTaskListener onCompletedTaskListener) {
super(activity, c, autoRequery); super(activity, c, autoRequery);
@ -155,7 +163,9 @@ public class TaskAdapter extends CursorAdapter {
viewHolder.completeBox = (CheckBox)view.findViewById(R.id.completeBox); viewHolder.completeBox = (CheckBox)view.findViewById(R.id.completeBox);
viewHolder.dueDate = (TextView)view.findViewById(R.id.dueDate); viewHolder.dueDate = (TextView)view.findViewById(R.id.dueDate);
viewHolder.details = (TextView)view.findViewById(R.id.details); 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.actions = (LinearLayout)view.findViewById(R.id.actions);
viewHolder.taskRow = (LinearLayout)view.findViewById(R.id.task_row);
viewHolder.importance = (View)view.findViewById(R.id.importance); viewHolder.importance = (View)view.findViewById(R.id.importance);
view.setTag(viewHolder); view.setTag(viewHolder);
@ -204,9 +214,13 @@ public class TaskAdapter extends CursorAdapter {
public CheckBox completeBox; public CheckBox completeBox;
public TextView dueDate; public TextView dueDate;
public TextView details; public TextView details;
public TextView extendedDetails;
public View importance; public View importance;
public LinearLayout actions; public LinearLayout actions;
public LinearLayout taskRow;
public boolean expanded; public boolean expanded;
public View[] decorations;
} }
/** Helper method to set the contents and visibility of each field */ /** Helper method to set the contents and visibility of each field */
@ -251,7 +265,6 @@ public class TaskAdapter extends CursorAdapter {
dueDateView.setText(r.getString(R.string.TAd_completed, dateValue)); dueDateView.setText(r.getString(R.string.TAd_completed, dateValue));
dueDateView.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemDetails); dueDateView.setTextAppearance(activity, R.style.TextAppearance_TAd_ItemDetails);
setVisibility(dueDateView); setVisibility(dueDateView);
} else { } else {
dueDateView.setVisibility(View.GONE); dueDateView.setVisibility(View.GONE);
} }
@ -265,43 +278,63 @@ public class TaskAdapter extends CursorAdapter {
completeBox.setChecked(task.isCompleted()); completeBox.setChecked(task.isCompleted());
} }
// task details - send out a request for it (only if not fling) // importance bar
viewHolder.details.setText(""); //$NON-NLS-1$
if(!isFling) {
retrieveDetails(viewHolder);
}
// importance bar - must be set at end when view height is determined
final View importanceView = viewHolder.importance; { final View importanceView = viewHolder.importance; {
int value = task.getValue(Task.IMPORTANCE); int value = task.getValue(Task.IMPORTANCE);
importanceView.setBackgroundColor(IMPORTANCE_COLORS[value]); importanceView.setBackgroundColor(IMPORTANCE_COLORS[value]);
} }
// details and decorations
viewHolder.details.setText(""); //$NON-NLS-1$
if(viewHolder.decorations != null) {
for(View decoration: viewHolder.decorations)
viewHolder.taskRow.removeView(decoration);
} }
viewHolder.taskRow.setBackgroundDrawable(null);
if(!isFling) {
// task details - send out a request for it
retrieveDetails(viewHolder, false);
// --- task details // task decoration - send out a request for it
retrieveDecorations(viewHolder);
}
}
private void retrieveDetails(final ViewHolder viewHolder) { protected TaskRowListener listener = new TaskRowListener();
final long taskId = viewHolder.task.getId(); /**
* 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);
// check the cache // context menu listener
boolean inCache = false; container.setOnCreateContextMenuListener(listener);
final LinkedHashSet<TaskDetail> details;
synchronized(detailCache) { // tap listener
if(detailCache.containsKey(taskId)) container.setOnClickListener(listener);
inCache = true;
else
detailCache.put(taskId, new LinkedHashSet<TaskDetail>());
details = detailCache.get(taskId);
} }
if(inCache) { /* ======================================================================
spanifyAndAdd(viewHolder.details, details); * ============================================================== add-ons
* ====================================================================== */
private void retrieveDetails(final ViewHolder viewHolder, final boolean extended) {
final long taskId = viewHolder.task.getId();
final TaskCache<String> cache = extended ? extendedDetailCache : detailCache;
ArrayList<String> list = cache.initialize(taskId);
if(list != null) {
spanifyAndAdd(viewHolder, extended, list);
return; return;
} }
// request details // request details
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_EXTENDED, extended);
activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
// load internal details // load internal details
@ -309,35 +342,40 @@ public class TaskAdapter extends CursorAdapter {
@Override @Override
public void run() { public void run() {
for(DetailExposer exposer : EXPOSERS) { for(DetailExposer exposer : EXPOSERS) {
if(Thread.interrupted()) final String detail = exposer.getTaskDetails(activity,
return; taskId, extended);
if(detail == null)
final TaskDetail detail = exposer.getTaskDetails(activity, taskId);
if(detail == null || details.contains(detail))
continue; continue;
final ArrayList<String> cacheList = cache.addIfNotExists(taskId, detail);
details.add(detail); if(cacheList != null) {
if(taskId != viewHolder.task.getId()) if(taskId != viewHolder.task.getId())
continue; continue;
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(new Runnable() {
public void run() { public void run() {
spanifyAndAdd(viewHolder.details, details); spanifyAndAdd(viewHolder, extended, cacheList);
} }
}); });
} }
}
}; };
}.start(); }.start();
} }
@SuppressWarnings("nls") @SuppressWarnings("nls")
private void spanifyAndAdd(TextView view, LinkedHashSet<TaskDetail> details) { private void spanifyAndAdd(ViewHolder viewHolder, boolean extended, ArrayList<String> details) {
if(details == null)
return;
TextView view = extended ? viewHolder.extendedDetails : viewHolder.details;
view.setVisibility(details.size() > 0 ? View.VISIBLE : View.GONE); view.setVisibility(details.size() > 0 ? View.VISIBLE : View.GONE);
if(details.size() == 0) if(details.size() == 0 || (extended && !viewHolder.expanded)) {
view.setVisibility(View.GONE);
return; return;
} else {
view.setVisibility(View.VISIBLE);
}
StringBuilder detailText = new StringBuilder(); StringBuilder detailText = new StringBuilder();
for(Iterator<TaskDetail> iterator = details.iterator(); iterator.hasNext(); ) { for(Iterator<String> iterator = details.iterator(); iterator.hasNext(); ) {
detailText.append(iterator.next().text); detailText.append(iterator.next());
if(iterator.hasNext()) if(iterator.hasNext())
detailText.append(DETAIL_SEPARATOR); detailText.append(DETAIL_SEPARATOR);
} }
@ -351,14 +389,10 @@ public class TaskAdapter extends CursorAdapter {
/** /**
* Called to tell the cache to be cleared * Called to tell the cache to be cleared
*/ */
public void flushDetailCache() { public void flushCaches() {
detailCache.clear(); detailCache.clear();
} extendedDetailCache.clear();
decorationCache.clear();
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
fontSize = Preferences.getIntegerFromString(R.string.p_fontSize);
} }
/** /**
@ -366,25 +400,26 @@ public class TaskAdapter extends CursorAdapter {
* *
* @param taskId * @param taskId
*/ */
public synchronized void addDetails(ListView list, long taskId, TaskDetail detail) { public synchronized void addDetails(ListView listView, long taskId,
boolean extended, String detail) {
if(detail == null) if(detail == null)
return; return;
LinkedHashSet<TaskDetail> details = detailCache.get(taskId); final TaskCache<String> cache = extended ? extendedDetailCache : detailCache;
if(details.contains(detail))
return;
details.add(detail);
ArrayList<String> cacheList = cache.addIfNotExists(taskId, detail);
if(cacheList != null) {
// update view if it is visible // update view if it is visible
int length = list.getChildCount(); int length = listView.getChildCount();
for(int i = 0; i < length; i++) { for(int i = 0; i < length; i++) {
ViewHolder viewHolder = (ViewHolder) list.getChildAt(i).getTag(); ViewHolder viewHolder = (ViewHolder) listView.getChildAt(i).getTag();
if(viewHolder == null || viewHolder.task.getId() != taskId) if(viewHolder == null || viewHolder.task.getId() != taskId)
continue; continue;
spanifyAndAdd(viewHolder.details, details); spanifyAndAdd(viewHolder, extended, cacheList);
break; break;
} }
} }
}
private final View.OnClickListener completeBoxListener = new View.OnClickListener() { private final View.OnClickListener completeBoxListener = new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
@ -397,29 +432,120 @@ public class TaskAdapter extends CursorAdapter {
} }
}; };
protected ContextMenuListener listener = new ContextMenuListener(); private void retrieveDecorations(final ViewHolder viewHolder) {
final long taskId = viewHolder.task.getId();
ArrayList<TaskDecoration> list = decorationCache.initialize(taskId);
if(list != null) {
decorate(viewHolder, list);
return;
}
// request details
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_DECORATIONS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
if(timer == null) {
timer = new Timer();
timer.scheduleAtFixedRate(decorationUpdater, 0, 1000L);
}
}
private Timer timer = null;
/** /**
* Set listeners for this view. This is called once per view when it is * Task to update decorations every second
* created.
*/ */
private void addListeners(final View container) { private final TimerTask decorationUpdater = new TimerTask() {
// check box listener @Override
final CheckBox completeBox = ((CheckBox)container.findViewById(R.id.completeBox)); public void run() {
completeBox.setOnClickListener(completeBoxListener); ListView listView = activity.getListView();
int length = listView.getChildCount();
for(int i = 0; i < length; i++) {
ViewHolder viewHolder = (ViewHolder) listView.getChildAt(i).getTag();
ArrayList<TaskDecoration> list = decorationCache.get(viewHolder.task.getId());
if(list == null)
continue;
// context menu listener for(int j = 0; j < list.size(); j++) {
container.setOnCreateContextMenuListener(listener); final TaskDecoration decoration = list.get(j);
if(decoration.decoration == null)
continue;
final View view = viewHolder.decorations[j];
if(view == null)
continue;
activity.runOnUiThread(new Runnable() {
public void run() {
decoration.decoration.reapply(activity, view);
}
});
}
}
}
};
// tap listener /**
container.setOnClickListener(listener); * Respond to a request to add details for a task
*
* @param taskId
*/
public synchronized void addDecorations(ListView listView, long taskId,
TaskDecoration taskDecoration) {
if(taskDecoration == null)
return;
ArrayList<TaskDecoration> cacheList = decorationCache.addIfNotExists(taskId,
taskDecoration);
if(cacheList != null) {
// update view if it is visible
int length = listView.getChildCount();
for(int i = 0; i < length; i++) {
ViewHolder viewHolder = (ViewHolder) listView.getChildAt(i).getTag();
if(viewHolder == null || viewHolder.task.getId() != taskId)
continue;
decorate(viewHolder, cacheList);
break;
}
}
}
private void decorate(ViewHolder viewHolder, ArrayList<TaskDecoration> decorations) {
if(decorations == null || decorations.size() == 0)
return;
// apply decorations in reverse so top priority appears at the ends &
// color is set bu the most important decoration
viewHolder.decorations = new View[decorations.size()];
for(int i = decorations.size() - 1; i >= 0; i--) {
TaskDecoration deco = decorations.get(i);
if(deco.color != 0)
((View)viewHolder.taskRow.getParent()).setBackgroundColor(deco.color);
if(deco.decoration != null) {
View view = deco.decoration.apply(activity, viewHolder.taskRow);
viewHolder.decorations[i] = view;
switch(deco.position) {
case TaskDecoration.POSITION_LEFT:
viewHolder.taskRow.addView(view, 1);
break;
case TaskDecoration.POSITION_RIGHT:
viewHolder.taskRow.addView(view, viewHolder.taskRow.getChildCount() - 2);
}
}
}
} }
/* ====================================================================== /* ======================================================================
* ======================================================= event handlers * ======================================================= event handlers
* ====================================================================== */ * ====================================================================== */
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
fontSize = Preferences.getIntegerFromString(R.string.p_fontSize);
}
class ContextMenuListener implements OnCreateContextMenuListener, OnClickListener { class TaskRowListener implements OnCreateContextMenuListener, OnClickListener {
public void onCreateContextMenu(ContextMenu menu, View v, public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) { ContextMenuInfo menuInfo) {
@ -428,11 +554,23 @@ public class TaskAdapter extends CursorAdapter {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// expand view
final ViewHolder viewHolder = (ViewHolder)v.getTag(); final ViewHolder viewHolder = (ViewHolder)v.getTag();
viewHolder.expanded = !viewHolder.expanded; viewHolder.expanded = !viewHolder.expanded;
LinearLayout actions = viewHolder.actions; LinearLayout actions = viewHolder.actions;
TextView extendedDetails = viewHolder.extendedDetails;
actions.setVisibility(viewHolder.expanded ? View.VISIBLE : View.GONE); actions.setVisibility(viewHolder.expanded ? View.VISIBLE : View.GONE);
if(viewHolder.expanded && actions.getChildCount() == 0) { if(!viewHolder.expanded) {
viewHolder.extendedDetails.setVisibility(View.GONE);
return;
}
final long taskId = viewHolder.task.getId();
extendedDetails.setText(""); //$NON-NLS-1$
retrieveDetails(viewHolder, true);
if(actions.getChildCount() == 0) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1f); LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1f);
Button edit = new Button(activity); Button edit = new Button(activity);
@ -442,7 +580,7 @@ public class TaskAdapter extends CursorAdapter {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
Intent intent = new Intent(activity, TaskEditActivity.class); Intent intent = new Intent(activity, TaskEditActivity.class);
intent.putExtra(TaskEditActivity.ID_TOKEN, viewHolder.task.getId()); intent.putExtra(TaskEditActivity.ID_TOKEN, taskId);
activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK); activity.startActivityForResult(intent, TaskListActivity.ACTIVITY_EDIT_TASK);
} }
}); });
@ -507,4 +645,53 @@ public class TaskAdapter extends CursorAdapter {
} }
} }
/* ======================================================================
* =============================================================== caches
* ====================================================================== */
private static class TaskCache<TYPE> {
private final Map<Long, ArrayList<TYPE>> cache =
Collections.synchronizedMap(new SoftHashMap<Long, ArrayList<TYPE>>());
/**
* 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
*/
public ArrayList<TYPE> initialize(long taskId) {
if(cache.containsKey(taskId))
return cache.get(taskId);
cache.put(taskId, new ArrayList<TYPE>());
return null;
}
/**
* Adds an item to the cache if it doesn't exist
* @param taskId
* @param item
* @return list if item was added, null if it already existed
*/
public ArrayList<TYPE> addIfNotExists(long taskId, TYPE item) {
ArrayList<TYPE> list = cache.get(taskId);
if(list == null || list.contains(item))
return null;
list.add(item);
return list;
}
/**
* Clears the cache
*/
public void clear() {
cache.clear();
}
public ArrayList<TYPE> get(long taskId) {
return cache.get(taskId);
}
}
} }

@ -12,8 +12,13 @@ public interface DetailExposer {
/** /**
* @param id * @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 * @return null if no details, or task details
*/ */
public TaskDetail getTaskDetails(Context context, long id); public String getTaskDetails(Context context, long id, boolean extended);
} }

Loading…
Cancel
Save