diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml
index 3167a06b8..1f665b0b3 100644
--- a/astrid/AndroidManifest.xml
+++ b/astrid/AndroidManifest.xml
@@ -164,15 +164,19 @@
-
+
+
+
+
+
+
-
-
+
diff --git a/astrid/api-src/com/todoroo/astrid/api/CustomFilterCriterion.java b/astrid/api-src/com/todoroo/astrid/api/CustomFilterCriterion.java
new file mode 100644
index 000000000..b48051d51
--- /dev/null
+++ b/astrid/api-src/com/todoroo/astrid/api/CustomFilterCriterion.java
@@ -0,0 +1,198 @@
+/**
+ * See the file "LICENSE" for the full license governing this code.
+ */
+package com.todoroo.astrid.api;
+
+import java.util.Date;
+
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.todoroo.andlib.utility.DateUtilities;
+
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+
+/**
+ * CustomFilterCriteria allow users to build a custom filter by chaining
+ * together criteria
+ *
+ * @author Tim Su
+ *
+ */
+public final class CustomFilterCriterion implements Parcelable {
+
+ // --- placeholder strings
+
+ /** value to be replaced with the current time as long */
+ public static final String VALUE_NOW = "NOW()"; //$NON-NLS-1$
+
+ /** value to be replaced by end of day as long */
+ public static final String VALUE_EOD = "EOD()"; //$NON-NLS-1$
+
+ /** value to be replaced by end of day yesterday as long */
+ public static final String VALUE_EOD_YESTERDAY = "EODY()"; //$NON-NLS-1$
+
+ /** value to be replaced by end of day tomorrow as long */
+ public static final String VALUE_EOD_TOMORROW = "EODT()"; //$NON-NLS-1$
+
+ /** value to be replaced by end of day day after tomorrow as long */
+ public static final String VALUE_EOD_DAY_AFTER = "EODTT()"; //$NON-NLS-1$
+
+ /** value to be replaced by end of day next week as long */
+ public static final String VALUE_EOD_NEXT_WEEK = "EODW()"; //$NON-NLS-1$
+
+ /** Replace placeholder strings with actual */
+ public static String replacePlaceholders(String value) {
+ if(value.contains(VALUE_NOW))
+ value = value.replace(VALUE_NOW, Long.toString(DateUtilities.now()));
+ if(value.contains(VALUE_EOD) || value.contains(VALUE_EOD_DAY_AFTER) ||
+ value.contains(VALUE_EOD_NEXT_WEEK) || value.contains(VALUE_EOD_TOMORROW) ||
+ value.contains(VALUE_EOD_YESTERDAY)) {
+ Date date = new Date();
+ date.setHours(23);
+ date.setMinutes(59);
+ date.setSeconds(59);
+ long time = date.getTime();
+ value = value.replace(VALUE_EOD_YESTERDAY, Long.toString(time - DateUtilities.ONE_DAY));
+ value = value.replace(VALUE_EOD, Long.toString(time));
+ value = value.replace(VALUE_EOD_TOMORROW, Long.toString(time + DateUtilities.ONE_DAY));
+ value = value.replace(VALUE_EOD_DAY_AFTER, Long.toString(time + 2 * DateUtilities.ONE_DAY));
+ value = value.replace(VALUE_EOD_NEXT_WEEK, Long.toString(time + 7 * DateUtilities.ONE_DAY));
+ }
+ return value;
+ }
+
+
+ // --- instance variables
+
+ /**
+ * Criteria Title. If the title contains ?, this is replaced by the entry
+ * label string selected.
+ *
+ * e.g "Due: ?"
+ */
+ @CheckForNull
+ public String text;
+
+ /**
+ * Criterion SQL. This query should return task id's. If this contains
+ * ?, it will be replaced by the entry value
+ *
+ * Examples:
+ *
+ *
SELECT _id FROM tasks WHERE dueDate <= ?
+ *
SELECT task FROM metadata WHERE value = '?'
+ *
+ */
+ @CheckForNull
+ public String sql;
+
+ /**
+ * Values to apply to a task when quick-adding a task from a filter
+ * created from this criterion. ? will be replaced with the entry value.
+ * For example, when a user views tasks tagged 'ABC', the
+ * tasks they create should also be tagged 'ABC'. If set to null, no
+ * additional values will be stored for a task.
+ */
+ @CheckForNull
+ public ContentValues valuesForNewTasks = null;
+
+ /**
+ * Array of entries for user to select from
+ */
+ @CheckForNull
+ public String[] entryTitles;
+
+ /**
+ * Array of entry values corresponding to entries
+ */
+ @CheckForNull
+ public String[] entryValues;
+
+ /**
+ * Icon for this criteria. Can be null for no bitmap
+ */
+ @CheckForNull
+ public Bitmap icon;
+
+ /**
+ * Criteria name. This is displayed when users are selecting a criteria
+ */
+ @CheckForNull
+ public String name;
+
+ /**
+ * Create a new CustomFilterCriteria object
+ *
+ * @param title
+ * @param sql
+ * @param valuesForNewTasks
+ * @param entryTitles
+ * @param entryValues
+ * @param icon
+ * @param name
+ */
+ public CustomFilterCriterion(String title, String sql,
+ ContentValues valuesForNewTasks, String[] entryTitles,
+ String[] entryValues, Bitmap icon, String name) {
+ this.text = title;
+ this.sql = sql;
+ this.valuesForNewTasks = valuesForNewTasks;
+ this.entryTitles = entryTitles;
+ this.entryValues = entryValues;
+ this.icon = icon;
+ this.name = name;
+ }
+
+ // --- parcelable
+
+ /**
+ * {@inheritDoc}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(text);
+ dest.writeString(sql);
+ dest.writeParcelable(valuesForNewTasks, 0);
+ dest.writeStringArray(entryTitles);
+ dest.writeStringArray(entryValues);
+ dest.writeParcelable(icon, 0);
+ dest.writeString(name);
+ }
+
+ /**
+ * Parcelable Creator Object
+ */
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+
+ /**
+ * {@inheritDoc}
+ */
+ public CustomFilterCriterion createFromParcel(Parcel source) {
+ CustomFilterCriterion item = new CustomFilterCriterion(
+ source.readString(), source.readString(),
+ (ContentValues)source.readParcelable(ContentValues.class.getClassLoader()),
+ source.createStringArray(), source.createStringArray(),
+ (Bitmap)source.readParcelable(Bitmap.class.getClassLoader()),
+ source.readString());
+ return item;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CustomFilterCriterion[] newArray(int size) {
+ return new CustomFilterCriterion[size];
+ }
+
+ };
+
+}
diff --git a/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java b/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java
index b92fd84b0..468c1ff46 100644
--- a/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java
+++ b/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java
@@ -9,8 +9,8 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.util.Log;
import com.todoroo.andlib.data.Property.PropertyVisitor;
@@ -95,9 +95,12 @@ abstract public class AbstractDatabase {
}
protected synchronized final void initializeHelper() {
- if(helper == null)
+ if(helper == null) {
+ if(ContextManager.getContext() == null)
+ throw new NullPointerException("Null context creating database helper");
helper = new DatabaseHelper(ContextManager.getContext(),
getName(), null, getVersion());
+ }
}
/**
diff --git a/astrid/common-src/com/todoroo/andlib/sql/Field.java b/astrid/common-src/com/todoroo/andlib/sql/Field.java
index e967eb664..47d44bcb5 100644
--- a/astrid/common-src/com/todoroo/andlib/sql/Field.java
+++ b/astrid/common-src/com/todoroo/andlib/sql/Field.java
@@ -37,6 +37,10 @@ public class Field extends DBObject {
return UnaryCriterion.lt(this, value);
}
+ public Criterion lte(final Object value) {
+ return UnaryCriterion.lte(this, value);
+ }
+
public Criterion isNull() {
return UnaryCriterion.isNull(this);
}
diff --git a/astrid/common-src/com/todoroo/andlib/sql/UnaryCriterion.java b/astrid/common-src/com/todoroo/andlib/sql/UnaryCriterion.java
index 98467b9a0..0dcff2bd5 100644
--- a/astrid/common-src/com/todoroo/andlib/sql/UnaryCriterion.java
+++ b/astrid/common-src/com/todoroo/andlib/sql/UnaryCriterion.java
@@ -63,6 +63,10 @@ public class UnaryCriterion extends Criterion {
return new UnaryCriterion(field, Operator.lt, value);
}
+ public static Criterion lte(Field field, Object value) {
+ return new UnaryCriterion(field, Operator.lte, value);
+ }
+
public static Criterion isNull(Field field) {
return new UnaryCriterion(field, Operator.isNull, null) {
@Override
diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java
index 0dfb33002..0232418f2 100644
--- a/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java
+++ b/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java
@@ -3,8 +3,8 @@
*/
package com.todoroo.astrid.core;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -12,17 +12,12 @@ import android.graphics.drawable.BitmapDrawable;
import com.timsu.astrid.R;
import com.todoroo.andlib.sql.Criterion;
-import com.todoroo.andlib.sql.Functions;
-import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.sql.QueryTemplate;
-import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.activity.FilterListActivity;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
-import com.todoroo.astrid.api.FilterCategory;
import com.todoroo.astrid.api.FilterListItem;
-import com.todoroo.astrid.api.SearchFilter;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.model.Metadata;
@@ -44,89 +39,20 @@ public final class CoreFilterExposer extends BroadcastReceiver {
// core filters
Filter inbox = buildInboxFilter(r);
- SearchFilter searchFilter = new SearchFilter(CorePlugin.IDENTIFIER,
- r.getString(R.string.BFE_Search));
+ SearchFilter searchFilter = new SearchFilter(r.getString(R.string.BFE_Search));
searchFilter.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_search)).getBitmap();
- // extended
-
- FilterCategory extended = new FilterCategory(r.getString(R.string.BFE_Extended),
- new Filter[7]);
-
- Filter recent = new Filter(r.getString(R.string.BFE_Recent),
- r.getString(R.string.BFE_Recent),
- new QueryTemplate().orderBy(Order.desc(Task.MODIFICATION_DATE)).limit(15),
- null);
- recent.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_new)).getBitmap();
-
- ContentValues hiddenValues = new ContentValues();
- hiddenValues.put(Task.HIDE_UNTIL.name, DateUtilities.now() + DateUtilities.ONE_DAY);
- Filter hidden = new Filter(r.getString(R.string.BFE_Hidden),
- r.getString(R.string.BFE_Hidden),
- new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
- Criterion.not(TaskCriteria.isVisible()))).
- orderBy(Order.asc(Task.HIDE_UNTIL)),
- hiddenValues);
- hidden.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_clouds)).getBitmap();
-
- ContentValues completedValues = new ContentValues();
- hiddenValues.put(Task.COMPLETION_DATE.name, DateUtilities.now());
- Filter completed = new Filter(r.getString(R.string.BFE_Completed), r.getString(R.string.BFE_Completed),
- new QueryTemplate().where(Criterion.and(TaskCriteria.completed(),
- Criterion.not(TaskCriteria.isDeleted()))). orderBy(Order.desc(Task.COMPLETION_DATE)),
- completedValues);
- completed.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_check)).getBitmap();
-
- Filter deleted = new Filter(r.getString(R.string.BFE_Deleted),
- r.getString(R.string.BFE_Deleted),
- new QueryTemplate().where(TaskCriteria.isDeleted()).
- orderBy(Order.desc(Task.DELETION_DATE)),
- null);
- deleted.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_trash)).getBitmap();
-
- // sorting filters
-
- Filter alphabetical = new Filter(r.getString(R.string.BFE_Alphabetical),
- r.getString(R.string.BFE_Alphabetical),
- new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
- TaskCriteria.isVisible())).
- orderBy(Order.asc(Functions.upper(Task.TITLE))),
- null);
- alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_alpha)).getBitmap();
-
- Filter dueDate = new Filter(r.getString(R.string.BFE_DueDate),
- r.getString(R.string.BFE_DueDate),
- new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
- TaskCriteria.isVisible())).
- orderBy(Order.asc(Functions.caseStatement(Task.DUE_DATE.eq(0),
- DateUtilities.now()*2, Task.DUE_DATE) + "+" + Task.IMPORTANCE)), //$NON-NLS-1$
- null);
- dueDate.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_calendar)).getBitmap();
-
- Filter importance = new Filter(r.getString(R.string.BFE_Importance),
- r.getString(R.string.BFE_Importance),
- new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
- TaskCriteria.isVisible())).
- orderBy(Order.asc(Task.IMPORTANCE + "*" + (2*DateUtilities.now()) + //$NON-NLS-1$
- "+" + Functions.caseStatement(Task.DUE_DATE.eq(0), //$NON-NLS-1$
- Functions.now() + "+" + DateUtilities.ONE_WEEK, //$NON-NLS-1$
- Task.DUE_DATE))),
- null);
- importance.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_warning)).getBitmap();
-
- extended.children[0] = recent;
- extended.children[1] = hidden;
- extended.children[2] = completed;
- extended.children[3] = deleted;
- extended.children[4] = alphabetical;
- extended.children[5] = dueDate;
- extended.children[6] = importance;
+ PendingIntent customFilterIntent = PendingIntent.getActivity(context, 0,
+ new Intent(context, CustomFilterActivity.class), 0);
+ IntentFilter customFilter = new IntentFilter(r.getString(R.string.BFE_Custom),
+ customFilterIntent);
+ customFilter.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.gnome_filter)).getBitmap();
// transmit filter list
FilterListItem[] list = new FilterListItem[3];
list[0] = inbox;
list[1] = searchFilter;
- list[2] = extended;
+ list[2] = customFilter;
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
@@ -137,7 +63,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
* @return
*/
public static Filter buildInboxFilter(Resources r) {
- Filter inbox = new Filter(r.getString(R.string.BFE_Active), r.getString(R.string.BFE_Active_title),
+ Filter inbox = new Filter(r.getString(R.string.BFE_Active), r.getString(R.string.BFE_Active),
new QueryTemplate().where(
Criterion.and(TaskCriteria.isActive(), TaskCriteria.isVisible(),
Criterion.not(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE).where(
diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java
new file mode 100644
index 000000000..5f2890150
--- /dev/null
+++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java
@@ -0,0 +1,414 @@
+package com.todoroo.astrid.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.ListActivity;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.timsu.astrid.R;
+import com.todoroo.andlib.data.Property.CountProperty;
+import com.todoroo.andlib.service.Autowired;
+import com.todoroo.andlib.service.ContextManager;
+import com.todoroo.andlib.service.DependencyInjectionService;
+import com.todoroo.andlib.sql.Criterion;
+import com.todoroo.andlib.sql.Join;
+import com.todoroo.andlib.sql.Query;
+import com.todoroo.astrid.activity.TaskListActivity;
+import com.todoroo.astrid.api.CustomFilterCriterion;
+import com.todoroo.astrid.api.Filter;
+import com.todoroo.astrid.dao.Database;
+import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
+import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
+import com.todoroo.astrid.model.Metadata;
+import com.todoroo.astrid.model.Task;
+import com.todoroo.astrid.tags.TagService;
+import com.todoroo.astrid.tags.TagService.Tag;
+
+/**
+ * Activity that allows users to build custom filters
+ *
+ * @author Tim Su
+ *
+ */
+public class CustomFilterActivity extends ListActivity {
+
+ static final int MENU_GROUP_FILTER = 0;
+ static final int MENU_GROUP_FILTER_OPTION = 1;
+ static final int MENU_GROUP_CONTEXT_TYPE = 2;
+ static final int MENU_GROUP_CONTEXT_DELETE = 3;
+
+ // --- hierarchy of filter classes
+
+ public static class CriterionInstance {
+ public static final int TYPE_ADD = 0;
+ public static final int TYPE_SUBTRACT = 1;
+ public static final int TYPE_INTERSECT = 2;
+ public static final int TYPE_UNIVERSE = 3;
+
+ /** criteria for this instance */
+ public CustomFilterCriterion criterion;
+
+ /** which of the entries is selected */
+ public int selectedIndex = -1;
+
+ /** type of join */
+ public int type = TYPE_INTERSECT;
+
+ /** statistics for {@link FilterView} */
+ public int start, end, max;
+ }
+
+ private TextView filterName;
+ private CustomFilterAdapter adapter;
+ private final ArrayList criteria =
+ new ArrayList();
+
+ // --- activity
+
+ @Autowired
+ Database database;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ContextManager.setContext(this);
+
+ setContentView(R.layout.custom_filter_activity);
+ setTitle(R.string.CFA_title);
+
+ DependencyInjectionService.getInstance().inject(this);
+ database.openForReading();
+ populateCriteria();
+
+ filterName = (TextView)findViewById(R.id.filterName);
+ List startingCriteria = new ArrayList();
+ startingCriteria.add(getStartingUniverse());
+ adapter = new CustomFilterAdapter(this, startingCriteria);
+ setListAdapter(adapter);
+ updateList();
+
+ setUpListeners();
+ }
+
+ /**
+ * Populate criteria list with built in and plugin criteria
+ */
+ @SuppressWarnings("nls")
+ private void populateCriteria() {
+ Resources r = getResources();
+
+ // built in criteria: due date
+ String[] entryValues = new String[] {
+ CustomFilterCriterion.VALUE_EOD_YESTERDAY,
+ CustomFilterCriterion.VALUE_EOD,
+ CustomFilterCriterion.VALUE_EOD_TOMORROW,
+ CustomFilterCriterion.VALUE_EOD_DAY_AFTER,
+ CustomFilterCriterion.VALUE_EOD_NEXT_WEEK,
+ };
+ ContentValues values = new ContentValues();
+ values.put(Task.DUE_DATE.name, "?");
+ CustomFilterCriterion criterion = new CustomFilterCriterion(
+ getString(R.string.CFC_dueBefore_text),
+ Query.select(Task.ID).from(Task.TABLE).where(
+ Criterion.and(
+ TaskCriteria.activeAndVisible(),
+ Task.DUE_DATE.gt(0),
+ Task.DUE_DATE.lte("?"))).toString(),
+ values, r.getStringArray(R.array.CFC_dueBefore_entries),
+ entryValues, ((BitmapDrawable)r.getDrawable(R.drawable.tango_calendar)).getBitmap(),
+ getString(R.string.CFC_dueBefore_name));
+ criteria.add(criterion);
+
+ // built in criteria: importance
+ entryValues = new String[] {
+ Integer.toString(Task.IMPORTANCE_DO_OR_DIE),
+ Integer.toString(Task.IMPORTANCE_MUST_DO),
+ Integer.toString(Task.IMPORTANCE_SHOULD_DO),
+ Integer.toString(Task.IMPORTANCE_NONE),
+ };
+ String[] entries = new String[] {
+ "!!!!", "!!!", "!!", "!"
+ };
+ values = new ContentValues();
+ values.put(Task.IMPORTANCE.name, "?");
+ criterion = new CustomFilterCriterion(
+ getString(R.string.CFC_importance_text),
+ Query.select(Task.ID).from(Task.TABLE).where(
+ Criterion.and(TaskCriteria.activeAndVisible(),
+ Task.IMPORTANCE.lte("?"))).toString(),
+ values, entries,
+ entryValues, ((BitmapDrawable)r.getDrawable(R.drawable.tango_warning)).getBitmap(),
+ getString(R.string.CFC_importance_name));
+ criteria.add(criterion);
+
+ // built in criteria: tags
+ Tag[] tags = TagService.getInstance().getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE, Criterion.all);
+ String[] tagNames = new String[tags.length];
+ for(int i = 0; i < tags.length; i++)
+ tagNames[i] = tags[i].tag;
+ values = new ContentValues();
+ values.put(Metadata.KEY.name, TagService.KEY);
+ values.put(TagService.TAG.name, "?");
+ criterion = new CustomFilterCriterion(
+ getString(R.string.CFC_tag_text),
+ Query.select(Metadata.TASK).from(Metadata.TABLE).join(Join.inner(
+ Task.TABLE, Metadata.TASK.eq(Task.ID))).where(Criterion.and(
+ TaskCriteria.activeAndVisible(),
+ MetadataCriteria.withKey(TagService.KEY),
+ TagService.TAG.eq("?"))).toString(),
+ values, tagNames, tagNames,
+ ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap(),
+ getString(R.string.CFC_tag_name));
+ criteria.add(criterion);
+ }
+
+ private CriterionInstance getStartingUniverse() {
+ CriterionInstance instance = new CriterionInstance();
+ instance.criterion = new CustomFilterCriterion(getString(R.string.CFA_universe_all),
+ null, null, null, null, null, null);
+ instance.type = CriterionInstance.TYPE_UNIVERSE;
+ return instance;
+ }
+
+ private void setUpListeners() {
+ ((Button)findViewById(R.id.add)).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ menuItemInstance = null;
+ getListView().showContextMenu();
+ }
+ });
+
+ ((Button)findViewById(R.id.saveAndView)).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ saveAndView();
+ }
+ });
+
+ getListView().setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ if(menu.hasVisibleItems())
+ return;
+
+ if(menuItemInstance == null) {
+ for(int i = 0; i < criteria.size(); i++) {
+ CustomFilterCriterion item = criteria.get(i);
+ SubMenu subMenu = menu.addSubMenu(item.name);
+ if(item.icon != null)
+ subMenu.setIcon(new BitmapDrawable(item.icon));
+
+ for(int j = 0; j < item.entryTitles.length; j++) {
+ subMenu.add(CustomFilterActivity.MENU_GROUP_FILTER_OPTION,
+ i, j, item.entryTitles[j]);
+ }
+ }
+ }
+
+ // was invoked by short-pressing row
+ else {
+ CustomFilterCriterion criterion = menuItemInstance.criterion;
+ if(criterion.entryTitles == null ||
+ criterion.entryTitles.length == 0)
+ return;
+
+ menu.setHeaderTitle(criterion.name);
+ menu.setGroupCheckable(CustomFilterActivity.MENU_GROUP_FILTER_OPTION, true, true);
+
+ for(int i = 0; i < criterion.entryTitles.length; i++) {
+ menu.add(CustomFilterActivity.MENU_GROUP_FILTER_OPTION,
+ -1, i, criterion.entryTitles[i]);
+ }
+ }
+ }
+ });
+ }
+
+ // --- listeners and action events
+
+ CriterionInstance menuItemInstance = null;
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ if(menu.size() > 0)
+ menu.clear();
+
+ // view holder
+ if(v.getTag() != null) {
+ adapter.onCreateContextMenu(menu, v);
+ }
+ }
+
+ @SuppressWarnings("nls")
+ void saveAndView() {
+ StringBuilder sql = new StringBuilder(" WHERE ");
+ for(int i = 0; i < adapter.getCount(); i++) {
+ CriterionInstance instance = adapter.getItem(i);
+ if(instance.selectedIndex < 0 && instance.criterion.entryValues != null)
+ continue;
+
+ switch(instance.type) {
+ case CriterionInstance.TYPE_ADD:
+ sql.append("OR ");
+ break;
+ case CriterionInstance.TYPE_SUBTRACT:
+ sql.append("AND NOT ");
+ break;
+ case CriterionInstance.TYPE_INTERSECT:
+ sql.append("AND ");
+ break;
+ case CriterionInstance.TYPE_UNIVERSE:
+ }
+
+ // special code for all tasks universe
+ if(instance.criterion.sql == null)
+ sql.append(TaskCriteria.activeAndVisible()).append(' ');
+ else {
+ String subSql = instance.criterion.sql.replace("?",
+ instance.criterion.entryValues[instance.selectedIndex]);
+ subSql = CustomFilterCriterion.replacePlaceholders(subSql);
+ sql.append(Task.ID).append(" IN (").append(subSql).append(") ");
+ }
+ }
+
+ String title;
+ if(filterName.getText().length() > 0)
+ title = filterName.getText().toString();
+ else
+ title = filterName.getHint().toString();
+
+ ContentValues values = new ContentValues(); // TODO
+ Filter filter = new Filter(title, title, null, values);
+ filter.sqlQuery = sql.toString();
+
+ // TODO save
+
+ Intent taskListActivity = new Intent(this, TaskListActivity.class);
+ taskListActivity.putExtra(TaskListActivity.TOKEN_FILTER, filter);
+ startActivity(taskListActivity);
+ }
+
+ /**
+ * Recalculate all sizes
+ */
+ @SuppressWarnings("nls")
+ void updateList() {
+ int max = 0, last = -1;
+
+ StringBuilder sql = new StringBuilder(Query.select(new CountProperty()).from(Task.TABLE).toString()).
+ append(" WHERE ");
+ StringBuilder suggestedTitle = new StringBuilder();
+
+ for(int i = 0; i < adapter.getCount(); i++) {
+ CriterionInstance instance = adapter.getItem(i);
+ if(instance.selectedIndex < 0 && instance.criterion.entryValues != null) {
+ instance.start = last;
+ instance.end = last;
+ continue;
+ }
+
+ String entryTitle = "";
+ if(instance.criterion.entryTitles != null) {
+ entryTitle = instance.criterion.entryTitles[instance.selectedIndex];
+ }
+ String title = instance.criterion.text.replace("?", entryTitle);
+
+ switch(instance.type) {
+ case CriterionInstance.TYPE_ADD:
+ sql.append("OR ");
+ title = getString(R.string.CFA_type_add) + " " + title;
+ break;
+ case CriterionInstance.TYPE_SUBTRACT:
+ sql.append("AND NOT ");
+ title = getString(R.string.CFA_type_subtract) + " " + title;
+ break;
+ case CriterionInstance.TYPE_INTERSECT:
+ sql.append("AND ");
+ break;
+ case CriterionInstance.TYPE_UNIVERSE:
+ }
+
+ suggestedTitle.append(title).append(' ');
+
+ // special code for all tasks universe
+ if(instance.criterion.sql == null)
+ sql.append(TaskCriteria.activeAndVisible()).append(' ');
+ else {
+ String subSql = instance.criterion.sql.replace("?",
+ instance.criterion.entryValues[instance.selectedIndex]);
+ subSql = CustomFilterCriterion.replacePlaceholders(subSql);
+ System.err.println(subSql);
+ sql.append(Task.ID).append(" IN (").append(subSql).append(") ");
+ }
+
+ Cursor cursor = database.getDatabase().rawQuery(sql.toString(), null);
+ try {
+ cursor.moveToNext();
+ instance.start = last == -1 ? cursor.getInt(0) : last;
+ instance.end = cursor.getInt(0);
+ last = instance.end;
+ max = Math.max(max, last);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ for(int i = 0; i < adapter.getCount(); i++) {
+ CriterionInstance instance = adapter.getItem(i);
+ instance.max = max;
+ }
+
+ adapter.notifyDataSetInvalidated();
+
+ if(adapter.getCount() > 1 && filterName.getText().length() == 0)
+ filterName.setHint(suggestedTitle);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ if(item.getGroupId() == MENU_GROUP_FILTER_OPTION) {
+ if(menuItemInstance == null) {
+ CustomFilterCriterion criterion = criteria.get(item.getItemId());
+ menuItemInstance = new CriterionInstance();
+ menuItemInstance.criterion = criterion;
+ }
+
+ menuItemInstance.selectedIndex = item.getOrder();
+ if(adapter.getPosition(menuItemInstance) == -1)
+ adapter.add(menuItemInstance);
+ updateList();
+ return true;
+ }
+
+ else if(item.getGroupId() == MENU_GROUP_CONTEXT_TYPE) {
+ CriterionInstance instance = adapter.getItem(item.getOrder());
+ instance.type = item.getItemId();
+ updateList();
+ }
+
+ else if(item.getGroupId() == MENU_GROUP_CONTEXT_DELETE) {
+ CriterionInstance instance = adapter.getItem(item.getOrder());
+ adapter.remove(instance);
+ updateList();
+ }
+
+ return super.onMenuItemSelected(featureId, item);
+ }
+
+}
diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java
new file mode 100644
index 000000000..f603b5a62
--- /dev/null
+++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java
@@ -0,0 +1,164 @@
+/**
+ * See the file "LICENSE" for the full license governing this code.
+ */
+package com.todoroo.astrid.core;
+
+import java.util.List;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.timsu.astrid.R;
+import com.todoroo.astrid.core.CustomFilterActivity.CriterionInstance;
+import com.todoroo.astrid.model.AddOn;
+
+/**
+ * Adapter for {@link AddOn}s
+ *
+ * @author Tim Su
+ *
+ */
+public class CustomFilterAdapter extends ArrayAdapter {
+
+ private final Activity activity;
+ private final LayoutInflater inflater;
+
+ public CustomFilterAdapter(Activity activity, List objects) {
+ super(activity, R.id.name, objects);
+ this.activity = activity;
+ inflater = (LayoutInflater) activity.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ // --- view event handling
+
+ View.OnClickListener filterClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ViewHolder viewHolder = (ViewHolder) v.getTag();
+ if(viewHolder == null)
+ return;
+ if(viewHolder.item.type == CriterionInstance.TYPE_UNIVERSE)
+ return;
+
+ // keep the filter options in the name context menu
+ ((CustomFilterActivity)activity).menuItemInstance = viewHolder.item;
+ ((CustomFilterActivity)activity).getListView().showContextMenu();
+ }
+ };
+
+ public void onCreateContextMenu(ContextMenu menu, View v) {
+ // view holder
+ ViewHolder viewHolder = (ViewHolder) v.getTag();
+ if(viewHolder == null || viewHolder.item.type == CriterionInstance.TYPE_UNIVERSE)
+ return;
+
+ int index = getPosition(viewHolder.item);
+
+ menu.setHeaderTitle(viewHolder.name.getText());
+ if(viewHolder.icon.getVisibility() == View.VISIBLE)
+ menu.setHeaderIcon(viewHolder.icon.getDrawable());
+
+
+ MenuItem item = menu.add(CustomFilterActivity.MENU_GROUP_CONTEXT_TYPE, CriterionInstance.TYPE_INTERSECT, index,
+ activity.getString(R.string.CFA_context_chain,
+ activity.getString(R.string.CFA_type_intersect)));
+ item.setChecked(viewHolder.item.type == CriterionInstance.TYPE_INTERSECT);
+ item = menu.add(CustomFilterActivity.MENU_GROUP_CONTEXT_TYPE, CriterionInstance.TYPE_ADD, index,
+ activity.getString(R.string.CFA_context_chain,
+ activity.getString(R.string.CFA_type_add)));
+ item.setChecked(viewHolder.item.type == CriterionInstance.TYPE_ADD);
+
+ item = menu.add(CustomFilterActivity.MENU_GROUP_CONTEXT_TYPE, CriterionInstance.TYPE_SUBTRACT, index,
+ activity.getString(R.string.CFA_context_chain,
+ activity.getString(R.string.CFA_type_subtract)));
+ item.setChecked(viewHolder.item.type == CriterionInstance.TYPE_SUBTRACT);
+ menu.setGroupCheckable(CustomFilterActivity.MENU_GROUP_CONTEXT_TYPE, true, true);
+
+ menu.add(CustomFilterActivity.MENU_GROUP_CONTEXT_DELETE, 0, index,
+ R.string.CFA_context_delete);
+ }
+
+ // --- view construction
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if(convertView == null) {
+ convertView = inflater.inflate(R.layout.custom_filter_row, parent, false);
+ ViewHolder viewHolder = new ViewHolder();
+ viewHolder.type = (ImageView) convertView.findViewById(R.id.type);
+ viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon);
+ viewHolder.name= (TextView) convertView.findViewById(R.id.name);
+ viewHolder.filterView = (FilterView) convertView.findViewById(R.id.filter);
+ convertView.setTag(viewHolder);
+ }
+
+ ViewHolder viewHolder = (ViewHolder)convertView.getTag();
+ viewHolder.item = getItem(position);
+ initializeView(convertView);
+
+ // listeners
+ convertView.setOnCreateContextMenuListener(activity);
+ convertView.setOnClickListener(filterClickListener);
+
+ return convertView;
+ }
+
+ private class ViewHolder {
+ public CriterionInstance item;
+ public ImageView type;
+ public ImageView icon;
+ public TextView name;
+ public FilterView filterView;
+ }
+
+ @SuppressWarnings("nls")
+ private void initializeView(View convertView) {
+ ViewHolder viewHolder = (ViewHolder) convertView.getTag();
+ CriterionInstance item = viewHolder.item;
+
+ String entryTitle = "";
+ if(item.selectedIndex >= 0 && item.criterion.entryTitles != null &&
+ item.selectedIndex < item.criterion.entryTitles.length) {
+ entryTitle = item.criterion.entryTitles[item.selectedIndex];
+ }
+ String title = item.criterion.text.replace("?", entryTitle);
+
+ viewHolder.type.setVisibility(item.type == CriterionInstance.TYPE_UNIVERSE ?
+ View.GONE : View.VISIBLE);
+ switch(item.type) {
+ case CriterionInstance.TYPE_ADD:
+ viewHolder.type.setImageResource(R.drawable.arrow_join);
+ title = activity.getString(R.string.CFA_type_add) + " " + title;
+ break;
+ case CriterionInstance.TYPE_SUBTRACT:
+ viewHolder.type.setImageResource(R.drawable.arrow_branch);
+ title = activity.getString(R.string.CFA_type_subtract) + " " + title;
+ break;
+ case CriterionInstance.TYPE_INTERSECT:
+ viewHolder.type.setImageResource(R.drawable.arrow_down);
+ break;
+ }
+
+ viewHolder.icon.setVisibility(item.criterion.icon == null ? View.GONE :
+ View.VISIBLE);
+ if(item.criterion.icon != null)
+ viewHolder.icon.setImageBitmap(item.criterion.icon);
+
+ viewHolder.name.setText(title);
+
+ viewHolder.filterView.setMax(item.max);
+ viewHolder.filterView.setStart(item.start);
+ viewHolder.filterView.setEnd(item.end);
+ }
+
+
+}
diff --git a/astrid/plugin-src/com/todoroo/astrid/core/FilterView.java b/astrid/plugin-src/com/todoroo/astrid/core/FilterView.java
new file mode 100644
index 000000000..520f08620
--- /dev/null
+++ b/astrid/plugin-src/com/todoroo/astrid/core/FilterView.java
@@ -0,0 +1,75 @@
+package com.todoroo.astrid.core;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Draws filters
+ *
+ * @author Tim Su
+ *
+ */
+public class FilterView extends View {
+
+ private int start = 0, end = 0, max = 1;
+
+ private static final int FILTER_COLOR = Color.rgb(85, 155, 255);
+ private static final int BG_COLOR = Color.WHITE;
+ private static final int TEXT_COLOR = Color.BLACK;
+
+ // --- boilerplate
+
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ public void setEnd(int end) {
+ this.end = end;
+ }
+
+ public void setMax(int max) {
+ this.max = max;
+ }
+
+ public FilterView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public FilterView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FilterView(Context context) {
+ super(context);
+ }
+
+ // --- painting code
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ Paint paint = new Paint();
+ paint.setColor(BG_COLOR);
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+
+ paint.setColor(FILTER_COLOR);
+ Path path = new Path();
+ path.moveTo(getWidth() * (0.5f - 0.5f * start / max), 0);
+ path.lineTo(getWidth() * (0.5f + 0.5f * start / max), 0);
+ path.lineTo(getWidth() * (0.5f + 0.5f * end / max), getHeight());
+ path.lineTo(getWidth() * (0.5f - 0.5f * end / max), getHeight());
+ path.close();
+ canvas.drawPath(path, paint);
+
+ paint.setColor(TEXT_COLOR);
+ paint.setTextAlign(Paint.Align.CENTER);
+ paint.setTextSize(16);
+ canvas.drawText(Integer.toString(end), getWidth() / 2, getHeight() / 2, paint);
+ }
+
+}
diff --git a/astrid/plugin-src/com/todoroo/astrid/core/IntentFilter.java b/astrid/plugin-src/com/todoroo/astrid/core/IntentFilter.java
new file mode 100644
index 000000000..8e3619205
--- /dev/null
+++ b/astrid/plugin-src/com/todoroo/astrid/core/IntentFilter.java
@@ -0,0 +1,81 @@
+package com.todoroo.astrid.core;
+
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.todoroo.astrid.api.FilterListItem;
+
+/**
+ * Special filter that triggers the search functionality when accessed.
+ *
+ * @author Tim Su
+ *
+ */
+public class IntentFilter extends FilterListItem {
+
+ public PendingIntent intent;
+
+ /**
+ * Constructor for creating a new IntentFilter
+ *
+ * @param listingTitle
+ * Title of this item as displayed on the lists page, e.g. Inbox
+ * @param intent
+ * intent to load
+ */
+ public IntentFilter(String listingTitle, PendingIntent intent) {
+ this.listingTitle = listingTitle;
+ this.intent = intent;
+ }
+
+ /**
+ * Constructor for creating a new IntentFilter used internally
+ */
+ protected IntentFilter(PendingIntent intent) {
+ this.intent = intent;
+ }
+
+ // --- parcelable
+
+ /**
+ * {@inheritDoc}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(intent, 0);
+ super.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Parcelable creator
+ */
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+
+ /**
+ * {@inheritDoc}
+ */
+ public IntentFilter createFromParcel(Parcel source) {
+ IntentFilter item = new IntentFilter((PendingIntent) source.readParcelable(
+ PendingIntent.class.getClassLoader()));
+ item.readFromParcel(source);
+ return item;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public IntentFilter[] newArray(int size) {
+ return new IntentFilter[size];
+ }
+
+ };
+
+}
diff --git a/astrid/src/com/todoroo/astrid/api/SearchFilter.java b/astrid/plugin-src/com/todoroo/astrid/core/SearchFilter.java
similarity index 70%
rename from astrid/src/com/todoroo/astrid/api/SearchFilter.java
rename to astrid/plugin-src/com/todoroo/astrid/core/SearchFilter.java
index 3a2081d35..9b3440edb 100644
--- a/astrid/src/com/todoroo/astrid/api/SearchFilter.java
+++ b/astrid/plugin-src/com/todoroo/astrid/core/SearchFilter.java
@@ -1,8 +1,10 @@
-package com.todoroo.astrid.api;
+package com.todoroo.astrid.core;
import android.os.Parcel;
import android.os.Parcelable;
+import com.todoroo.astrid.api.FilterListItem;
+
/**
* Special filter that triggers the search functionality when accessed.
*
@@ -11,32 +13,21 @@ import android.os.Parcelable;
*/
public class SearchFilter extends FilterListItem {
- /**
- * Plug-in Identifier
- */
- public final String plugin;
-
/**
* Constructor for creating a new SearchFilter
*
- * @param plugin
- * {@link Addon} identifier that encompasses object
* @param listingTitle
* Title of this item as displayed on the lists page, e.g. Inbox
*/
- public SearchFilter(String plugin, String listingTitle) {
- this.plugin = plugin;
+ public SearchFilter(String listingTitle) {
this.listingTitle = listingTitle;
}
/**
* Constructor for creating a new SearchFilter
- *
- * @param plugin
- * {@link Addon} identifier that encompasses object
*/
- protected SearchFilter(String plugin) {
- this.plugin = plugin;
+ protected SearchFilter() {
+ //
}
// --- parcelable
@@ -53,7 +44,6 @@ public class SearchFilter extends FilterListItem {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(plugin);
super.writeToParcel(dest, flags);
}
@@ -66,7 +56,7 @@ public class SearchFilter extends FilterListItem {
* {@inheritDoc}
*/
public SearchFilter createFromParcel(Parcel source) {
- SearchFilter item = new SearchFilter(source.readString());
+ SearchFilter item = new SearchFilter();
item.readFromParcel(source);
return item;
}
diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java
index d08adf488..ba9a879c0 100644
--- a/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java
+++ b/astrid/plugin-src/com/todoroo/astrid/notes/NoteDetailExposer.java
@@ -62,7 +62,7 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
if(notes.length() == 0)
return null;
- return notes;
+ return " " + notes; //$NON-NLS-1$
}
@Override
diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java
index 31d754e9c..c26ee6cc0 100644
--- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java
+++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevDetailExposer.java
@@ -7,7 +7,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.astrid.adapter.TaskAdapter;
import com.todoroo.astrid.api.AstridApiConstants;
@@ -78,8 +77,7 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
!= Preferences.getLong(ProducteevUtilities.PREF_DEFAULT_DASHBOARD, 0L) &&
ownerDashboard != null) {
String dashboardName = ownerDashboard.getValue(ProducteevDashboard.NAME);
- builder.append(context.getString(R.string.producteev_TLA_dashboard,
- dashboardName)).append(TaskAdapter.DETAIL_SEPARATOR);
+ builder.append(" ").append(dashboardName).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$
}
// display responsible user if not current one
@@ -90,8 +88,7 @@ public class ProducteevDetailExposer extends BroadcastReceiver implements Detail
if(index > -1) {
String user = users.substring(users.indexOf(',', index) + 1,
users.indexOf(';', index + 1));
- builder.append(context.getString(R.string.producteev_TLA_responsible,
- user)).append(TaskAdapter.DETAIL_SEPARATOR);
+ builder.append(" ").append(user).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$
}
}
} else {
diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java
index 4d332b583..e3cc35d97 100644
--- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java
+++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java
@@ -114,7 +114,7 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo
else
detail = context.getString(R.string.repeat_detail_duedate, interval);
- return detail;
+ return " " + detail; //$NON-NLS-1$
}
return null;
}
diff --git a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java
index 08f827acf..d32bd8f08 100644
--- a/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java
+++ b/astrid/plugin-src/com/todoroo/astrid/rmilk/MilkDetailExposer.java
@@ -66,9 +66,8 @@ public class MilkDetailExposer extends BroadcastReceiver implements DetailExpose
if(listName == null)
return null;
- if(listId > 0) {
- builder.append(context.getString(R.string.rmilk_TLA_list,
- listName)).append(TaskAdapter.DETAIL_SEPARATOR);
+ if(listId > 0 && !"Inbox".equals(listName)) { //$NON-NLS-1$
+ builder.append(" ").append(listName).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$
}
int repeat = metadata.getValue(MilkTask.REPEATING);
diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java
index d8c4ea6fc..21172fe2e 100644
--- a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java
+++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java
@@ -7,7 +7,6 @@ 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.DetailExposer;
@@ -49,7 +48,7 @@ public class TagDetailExposer extends BroadcastReceiver implements DetailExposer
if(tagList.length() == 0)
return null;
- return context.getString(R.string.tag_TLA_detail, tagList);
+ return " " + tagList; //$NON-NLS-1$
}
@Override
diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java
index 7d2276fcc..4710a7e8b 100644
--- a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java
+++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java
@@ -32,10 +32,8 @@ public class TagFilterExposer extends BroadcastReceiver {
private TagService tagService;
- @SuppressWarnings("nls")
private Filter filterFromTag(Context context, Tag tag, Criterion criterion) {
- String listTitle = context.getString(R.string.tag_FEx_tag_w_size).
- replace("$T", tag.tag).replace("$C", Integer.toString(tag.count));
+ String listTitle = tag.tag;
String title = context.getString(R.string.tag_FEx_name, tag.tag);
QueryTemplate tagTemplate = tag.queryTemplate(criterion);
ContentValues contentValues = new ContentValues();
@@ -61,55 +59,35 @@ public class TagFilterExposer extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
tagService = TagService.getInstance();
- Tag[] tagsByAlpha = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_ALPHA, TaskCriteria.notDeleted());
+ Tag[] tags = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE, TaskCriteria.notDeleted());
// If user does not have any tags, don't show this section at all
- if(tagsByAlpha.length == 0)
+ if(tags.length == 0)
return;
Resources r = context.getResources();
- Filter[] filtersByAlpha = new Filter[tagsByAlpha.length];
- for(int i = 0; i < tagsByAlpha.length; i++)
- filtersByAlpha[i] = filterFromTag(context, tagsByAlpha[i], TaskCriteria.notDeleted());
-
- Tag[] tagsBySize = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE, TaskCriteria.isActive());
- Filter[] filtersBySize = new Filter[tagsBySize.length];
- for(int i = 0; i < tagsBySize.length; i++)
- filtersBySize[i] = filterFromTag(context, tagsBySize[i], TaskCriteria.isActive());
-
- Tag[] completed = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE, TaskCriteria.completed());
- Filter[] filtersCompleted = new Filter[completed.length];
- for(int i = 0; i < completed.length; i++)
- filtersCompleted[i] = filterFromTag(context, completed[i], TaskCriteria.completed());
+ FilterListItem[] list = new FilterListItem[3];
FilterListHeader tagsHeader = new FilterListHeader(context.getString(R.string.tag_FEx_header));
+ list[0] = tagsHeader;
Filter untagged = new Filter(r.getString(R.string.tag_FEx_untagged),
r.getString(R.string.tag_FEx_untagged),
tagService.untaggedTemplate(),
null);
untagged.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_untagged)).getBitmap();
+ list[1] = untagged;
- FilterCategory tagsCategoryBySize = new FilterCategory(context.getString(R.string.tag_FEx_by_size),
- filtersBySize);
- tagsCategoryBySize.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap();
- FilterCategory tagsCategoryAllByAlpha = new FilterCategory(context.getString(R.string.tag_FEx_alpha),
- filtersByAlpha);
- tagsCategoryAllByAlpha.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap();
+ Filter[] filters = new Filter[tags.length];
+ for(int i = 0; i < tags.length; i++)
+ filters[i] = filterFromTag(context, tags[i], TaskCriteria.isActive());
+ FilterCategory tagsFilter = new FilterCategory(context.getString(R.string.tag_FEx_by_size), filters);
+ list[2] = tagsFilter;
- FilterCategory tagsCategoryCompleted = new FilterCategory(context.getString(R.string.tag_FEx_completed),
- filtersCompleted);
- tagsCategoryCompleted.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags2)).getBitmap();
// transmit filter list
- FilterListItem[] list = new FilterListItem[5];
- list[0] = tagsHeader;
- list[1] = untagged;
- list[2] = tagsCategoryBySize;
- list[3] = tagsCategoryCompleted;
- list[4] = tagsCategoryAllByAlpha;
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
diff --git a/astrid/res/drawable/arrow_branch.png b/astrid/res/drawable/arrow_branch.png
new file mode 100644
index 000000000..68cee8c73
Binary files /dev/null and b/astrid/res/drawable/arrow_branch.png differ
diff --git a/astrid/res/drawable/arrow_down.png b/astrid/res/drawable/arrow_down.png
new file mode 100644
index 000000000..e9e134311
Binary files /dev/null and b/astrid/res/drawable/arrow_down.png differ
diff --git a/astrid/res/drawable/arrow_join.png b/astrid/res/drawable/arrow_join.png
new file mode 100644
index 000000000..171d453fb
Binary files /dev/null and b/astrid/res/drawable/arrow_join.png differ
diff --git a/astrid/res/drawable/gnome_filter.png b/astrid/res/drawable/gnome_filter.png
new file mode 100644
index 000000000..5e6328928
Binary files /dev/null and b/astrid/res/drawable/gnome_filter.png differ
diff --git a/astrid/res/drawable/ic_menu_rmilk.png b/astrid/res/drawable/ic_menu_rmilk.png
deleted file mode 100644
index 12f9cf24a..000000000
Binary files a/astrid/res/drawable/ic_menu_rmilk.png and /dev/null differ
diff --git a/astrid/res/drawable/silk_date.png b/astrid/res/drawable/silk_date.png
new file mode 100644
index 000000000..783c83357
Binary files /dev/null and b/astrid/res/drawable/silk_date.png differ
diff --git a/astrid/res/drawable/silk_folder.png b/astrid/res/drawable/silk_folder.png
new file mode 100644
index 000000000..3c4724da2
Binary files /dev/null and b/astrid/res/drawable/silk_folder.png differ
diff --git a/astrid/res/drawable/silk_note.png b/astrid/res/drawable/silk_note.png
new file mode 100644
index 000000000..18357c7d9
Binary files /dev/null and b/astrid/res/drawable/silk_note.png differ
diff --git a/astrid/res/drawable/silk_script.png b/astrid/res/drawable/silk_script.png
new file mode 100644
index 000000000..afc499be9
Binary files /dev/null and b/astrid/res/drawable/silk_script.png differ
diff --git a/astrid/res/drawable/silk_tag_pink.png b/astrid/res/drawable/silk_tag_pink.png
new file mode 100644
index 000000000..45c5041ac
Binary files /dev/null and b/astrid/res/drawable/silk_tag_pink.png differ
diff --git a/astrid/res/drawable/silk_user_gray.png b/astrid/res/drawable/silk_user_gray.png
new file mode 100644
index 000000000..20b8cdf7b
Binary files /dev/null and b/astrid/res/drawable/silk_user_gray.png differ
diff --git a/astrid/res/layout/custom_filter_activity.xml b/astrid/res/layout/custom_filter_activity.xml
new file mode 100644
index 000000000..0fcaac130
--- /dev/null
+++ b/astrid/res/layout/custom_filter_activity.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/astrid/res/layout/custom_filter_row.xml b/astrid/res/layout/custom_filter_row.xml
new file mode 100644
index 000000000..e90e56526
--- /dev/null
+++ b/astrid/res/layout/custom_filter_row.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/astrid/res/values/strings-filters.xml b/astrid/res/values/strings-filters.xml
index 1261dbcdc..52cf4ebbf 100644
--- a/astrid/res/values/strings-filters.xml
+++ b/astrid/res/values/strings-filters.xml
@@ -4,37 +4,83 @@
+
+
Active Tasks
-
- Active Tasks
-
- Search
-
-
- More...
-
-
- Recently Modified
+ Search...
-
- Completed Tasks
-
-
- Hidden Tasks
-
-
- By Title
+
+ Custom Filter...
-
- By Due Date
-
-
- By Importance
+
+
+
+ Custom Filter
+
+
+ Filter Name
+
+
+ New Filter
+
+
+ Copy of %s
+
+
+ Active Tasks
+
+
+ or
+
+
+ not
+
+
+ also
+
+
+ Chaining: %s
+
+
+ Delete Row
+
+
+ This screen lets you create a new filters. Add
+ criteria using the button below, short or long-press them to adjust, and
+ then click \"Save & View\"!
+
+
+ Add Criteria
+
+
+ Save & View
-
- Deleted Tasks
+
+
+ Due By: ?
+
+ Due By...
+
+
+ Yesterday
+ Today
+ Tomorrow
+ Day After Tomorrow
+ Next Week
+
+
+
+ Importance: ?
+
+ Importance...
+
+
+ Tagged: ?
+
+ Tagged...
+
diff --git a/astrid/res/values/strings-repeat.xml b/astrid/res/values/strings-repeat.xml
index cb4a4a99c..f0b09acef 100644
--- a/astrid/res/values/strings-repeat.xml
+++ b/astrid/res/values/strings-repeat.xml
@@ -37,9 +37,9 @@
$I on $D
- Repeats every %s
+ Every %s
- Repeats %s after completion
+ %s after completion
diff --git a/astrid/res/values/strings-tags.xml b/astrid/res/values/strings-tags.xml
index 5bb965056..86ed091ae 100644
--- a/astrid/res/values/strings-tags.xml
+++ b/astrid/res/values/strings-tags.xml
@@ -11,11 +11,6 @@
Tag Name
-
-
-
-
- Tags: %s
@@ -23,20 +18,11 @@
Tags
- Active
-
-
- Completed
-
-
- All Tags
+ Sorted By SizeUntagged
-
- $T ($C)
-
Tagged \'%s\'
diff --git a/astrid/res/values/styles.xml b/astrid/res/values/styles.xml
index 4435774ea..fde5b5bd7 100644
--- a/astrid/res/values/styles.xml
+++ b/astrid/res/values/styles.xml
@@ -50,7 +50,7 @@
diff --git a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java
index 4d9f5c47e..ef0237be6 100644
--- a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java
+++ b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java
@@ -6,6 +6,7 @@ package com.todoroo.astrid.activity;
import android.app.AlertDialog;
import android.app.ExpandableListActivity;
import android.app.SearchManager;
+import android.app.PendingIntent.CanceledException;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -42,7 +43,8 @@ import com.todoroo.astrid.adapter.FilterAdapter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterCategory;
import com.todoroo.astrid.api.FilterListItem;
-import com.todoroo.astrid.api.SearchFilter;
+import com.todoroo.astrid.core.IntentFilter;
+import com.todoroo.astrid.core.SearchFilter;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.StartupService;
import com.todoroo.astrid.utility.Constants;
@@ -202,6 +204,12 @@ public class FilterListActivity extends ExpandableListActivity {
return true;
} else if(item instanceof SearchFilter) {
onSearchRequested();
+ } else if(item instanceof IntentFilter) {
+ try {
+ ((IntentFilter)item).intent.send();
+ } catch (CanceledException e) {
+ // ignore
+ }
}
return false;
}
diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java
index 7724c392b..3b77f959c 100644
--- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java
+++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java
@@ -13,7 +13,9 @@ import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
import android.text.Html;
+import android.text.Html.ImageGetter;
import android.text.util.Linkify;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@@ -52,6 +54,7 @@ import com.todoroo.astrid.repeats.RepeatDetailExposer;
import com.todoroo.astrid.rmilk.MilkDetailExposer;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagDetailExposer;
+import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences;
/**
@@ -376,6 +379,18 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
*/
public class DetailManager extends AddOnManager {
+ private final ImageGetter imageGetter = new ImageGetter() {
+ public Drawable getDrawable(String source) {
+ Resources r = activity.getResources();
+ int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$
+ if(drawable == 0)
+ return null;
+ Drawable d = r.getDrawable(drawable);
+ d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
+ return d;
+ }
+ };
+
private final boolean extended;
public DetailManager(boolean extended) {
this.extended = extended;
@@ -441,7 +456,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
}
String string = detailText.toString();
if(string.contains("<"))
- view.setText(Html.fromHtml(string.trim().replace("\n", " ")));
+ view.setText(Html.fromHtml(string.trim().replace("\n", " "), imageGetter, null));
else
view.setText(string.trim());
Linkify.addLinks(view, Linkify.ALL);
diff --git a/astrid/src/com/todoroo/astrid/dao/TaskDao.java b/astrid/src/com/todoroo/astrid/dao/TaskDao.java
index bf35b44a1..10ed9292b 100644
--- a/astrid/src/com/todoroo/astrid/dao/TaskDao.java
+++ b/astrid/src/com/todoroo/astrid/dao/TaskDao.java
@@ -68,6 +68,13 @@ public class TaskDao extends GenericDao {
return Task.DELETION_DATE.eq(0);
}
+ /** @return tasks that have not yet been completed or deleted */
+ public static Criterion activeAndVisible() {
+ return Criterion.and(Task.COMPLETION_DATE.eq(0),
+ Task.DELETION_DATE.eq(0),
+ Task.HIDE_UNTIL.lt(Functions.now()));
+ }
+
/** @return tasks that have not yet been completed or deleted */
public static Criterion isActive() {
return Criterion.and(Task.COMPLETION_DATE.eq(0),
diff --git a/astrid/src/com/todoroo/astrid/service/AddOnService.java b/astrid/src/com/todoroo/astrid/service/AddOnService.java
index 4ed0197b9..52a99910a 100644
--- a/astrid/src/com/todoroo/astrid/service/AddOnService.java
+++ b/astrid/src/com/todoroo/astrid/service/AddOnService.java
@@ -227,7 +227,7 @@ public class AddOnService {
list[2] = new AddOn(true, true, "Remember the Milk", null,
"Synchronize with Remember The Milk service.",
Constants.PACKAGE, "http://www.rmilk.com",
- ((BitmapDrawable)r.getDrawable(R.drawable.ic_menu_rmilk)).getBitmap());
+ ((BitmapDrawable)r.getDrawable(R.drawable.ic_menu_refresh)).getBitmap());
list[3] = new AddOn(true, true, "Producteev", null,
"Synchronize with Producteev service. Also changes Astrid's importance levels to stars.",
diff --git a/astrid/src/com/todoroo/astrid/utility/Constants.java b/astrid/src/com/todoroo/astrid/utility/Constants.java
index bd463e64f..e700e0eaa 100644
--- a/astrid/src/com/todoroo/astrid/utility/Constants.java
+++ b/astrid/src/com/todoroo/astrid/utility/Constants.java
@@ -35,7 +35,7 @@ public final class Constants {
/**
* Whether to turn on debugging logging and UI
*/
- public static final boolean DEBUG = true;
+ public static final boolean DEBUG = false;
/**
* Upgrade time