From 0f77edb13e57ac96acf03555ea804124ad121384 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Thu, 12 Aug 2010 02:15:30 -0700 Subject: [PATCH] Wrote code for custom filter. A bunch of code. Time to see if it works --- .../astrid/api/CustomFilterCriterion.java | 172 ++++++++++++ .../com/todoroo/andlib/sql/Field.java | 4 + .../todoroo/andlib/sql/UnaryCriterion.java | 4 + .../astrid/core/CustomFilterActivity.java | 251 ++++++++++++++++++ .../astrid/core/CustomFilterAdapter.java | 165 ++++++++++++ .../com/todoroo/astrid/core/FilterView.java | 75 ++++++ .../com/todoroo/astrid/core/IntentFilter.java | 81 ++++++ .../com/todoroo/astrid/core/SearchFilter.java | 73 +++++ astrid/res/drawable/arrow_branch.png | Bin 0 -> 646 bytes astrid/res/drawable/arrow_down.png | Bin 0 -> 407 bytes astrid/res/drawable/arrow_join.png | Bin 0 -> 687 bytes astrid/res/drawable/gnome_filter.png | Bin 0 -> 832 bytes astrid/res/layout/custom_filter_activity.xml | 53 ++++ astrid/res/layout/custom_filter_row.xml | 46 ++++ astrid/res/values/strings-filters.xml | 64 +++-- astrid/res/values/styles.xml | 2 +- .../src/com/todoroo/astrid/dao/TaskDao.java | 7 + 17 files changed, 974 insertions(+), 23 deletions(-) create mode 100644 astrid/api-src/com/todoroo/astrid/api/CustomFilterCriterion.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/core/FilterView.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/core/IntentFilter.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/core/SearchFilter.java create mode 100644 astrid/res/drawable/arrow_branch.png create mode 100644 astrid/res/drawable/arrow_down.png create mode 100644 astrid/res/drawable/arrow_join.png create mode 100644 astrid/res/drawable/gnome_filter.png create mode 100644 astrid/res/layout/custom_filter_activity.xml create mode 100644 astrid/res/layout/custom_filter_row.xml 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..63a274ccd --- /dev/null +++ b/astrid/api-src/com/todoroo/astrid/api/CustomFilterCriterion.java @@ -0,0 +1,172 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.api; + +import android.content.ContentValues; +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; +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 extends FilterListItem { + + // --- constants + + /** 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$ + + // --- instance variables + + /** + * Criteria Title. If the title contains %s, this is replaced by the entry + * label string selected. + *

+ * e.g "Due: %s" + */ + @CheckForNull + public String text; + + /** + * Criterion SQL. This query should return task id's. If this contains + * %s, it will be replaced by the entry value + *

+ * Examples: + *

+ */ + @CheckForNull + public String sql; + + /** + * Values to apply to a task when quick-adding a task from a filter + * created from this criterion. %s 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} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, 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/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/CustomFilterActivity.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java new file mode 100644 index 000000000..9ecc313a1 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java @@ -0,0 +1,251 @@ +package com.todoroo.astrid.core; + +import java.util.ArrayList; +import java.util.List; + +import android.app.ListActivity; +import android.content.ContentValues; +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.View; +import android.view.ContextMenu.ContextMenuInfo; +import android.widget.Button; + +import com.timsu.astrid.R; +import com.todoroo.andlib.data.Property.CountProperty; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Query; +import com.todoroo.astrid.api.CustomFilterCriterion; +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; + + // --- 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; + + /** type of join */ + public int type; + + /** statistics for {@link FilterView} */ + public int start, end, max; + } + + private CustomFilterAdapter adapter; + private final ArrayList criteria = + new ArrayList(); + + // --- activity + + @Autowired + Database database; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.custom_filter_activity); + setTitle(R.string.CFA_title); + + DependencyInjectionService.getInstance().inject(this); + populateCriteria(); + + 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, "%s"); + CustomFilterCriterion criterion = new CustomFilterCriterion( + getString(R.string.CFC_dueBefore_text), + Query.select(Task.ID).from(Task.TABLE).where(Task.DUE_DATE.lte("%s")).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), + }; + values = new ContentValues(); + values.put(Task.IMPORTANCE.name, "%s"); + criterion = new CustomFilterCriterion( + getString(R.string.CFC_importance_text), + Query.select(Task.ID).from(Task.TABLE).where(Task.IMPORTANCE.lte("%s")).toString(), + values, r.getStringArray(R.array.EPr_default_importance), + 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, "%s"); + criterion = new CustomFilterCriterion( + getString(R.string.CFC_tag_text), + Query.select(Metadata.TASK).from(Metadata.TABLE).where(Criterion.and( + MetadataCriteria.withKey(TagService.KEY), + TagService.TAG.eq("%s"))).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) { + getListView().showContextMenu(); + } + }); + + getListView().setOnCreateContextMenuListener(this); + } + + // --- listeners and action events + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + for(int i = 0; i < criteria.size(); i++) { + CustomFilterCriterion item = criteria.get(i); + MenuItem menuItem = menu.add(MENU_GROUP_FILTER, i, i, item.name); + if(item.icon != null) + menuItem.setIcon(new BitmapDrawable(item.icon)); + } + } + + /** + * 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 "); + for(int i = 0; i < adapter.getCount(); i++) { + CriterionInstance instance = adapter.getItem(i); + + 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 + sql.append(Task.ID).append(" IN (").append(instance.criterion.sql).append(") "); + + Cursor cursor = database.getDatabase().rawQuery(sql.toString(), null); + try { + cursor.moveToNext(); + max = Math.max(max, cursor.getCount()); + instance.start = last == -1 ? cursor.getInt(0) : last; + instance.end = cursor.getInt(0); + } finally { + cursor.close(); + } + } + + for(int i = 0; i < adapter.getCount(); i++) { + CriterionInstance instance = adapter.getItem(i); + instance.max = max; + } + + adapter.notifyDataSetInvalidated(); + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if(item.getGroupId() == MENU_GROUP_FILTER) { + CustomFilterCriterion criterion = criteria.get(item.getItemId()); + CriterionInstance instance = new CriterionInstance(); + instance.criterion = criterion; + adapter.add(instance); + return true; + } + + else if(item.getGroupId() == MENU_GROUP_FILTER_OPTION) + return adapter.onMenuItemSelected(item); + + + 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..01fdfa529 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java @@ -0,0 +1,165 @@ +/** + * 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.view.ContextMenu.ContextMenuInfo; +import android.view.View.OnCreateContextMenuListener; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.timsu.astrid.R; +import com.todoroo.astrid.api.CustomFilterCriterion; +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 + viewHolder.name.showContextMenu(); + } + }; + + OnCreateContextMenuListener createContextMenuListener = new OnCreateContextMenuListener() { + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { + ViewHolder viewHolder = (ViewHolder) ((View)view.getParent()).getTag(); + CustomFilterCriterion criteria = viewHolder.item.criterion; + if(criteria.entryTitles == null || + criteria.entryTitles.length == 0) + return; + + menu.setHeaderTitle(criteria.name); + menu.setGroupCheckable(CustomFilterActivity.MENU_GROUP_FILTER_OPTION, true, true); + + for(int i = 0; i < criteria.entryTitles.length; i++) { + menu.add(CustomFilterActivity.MENU_GROUP_FILTER_OPTION, + getPosition(viewHolder.item), + i, criteria.entryTitles[i]); + } + } + }; + + + public boolean onMenuItemSelected(MenuItem item) { + CriterionInstance instance = getItem(item.getItemId()); + instance.selectedIndex = item.getOrder(); + ((CustomFilterActivity)activity).updateList(); + return true; + } + + // --- 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.setOnClickListener(filterClickListener); + viewHolder.name.setOnCreateContextMenuListener(createContextMenuListener); + + 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.selectedIndex < item.criterion.entryTitles.length) { + entryTitle = item.criterion.entryTitles[item.selectedIndex]; + } + String title = item.criterion.text.replace("%s", entryTitle); + boolean notFirst = getPosition(item) > 1; + + 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); + if(notFirst) + title = activity.getString(R.string.CFA_type_add) + " " + title; + break; + case CriterionInstance.TYPE_SUBTRACT: + viewHolder.type.setImageResource(R.drawable.arrow_branch); + if(notFirst) + title = activity.getString(R.string.CFA_type_subtract) + " " + title; + break; + case CriterionInstance.TYPE_INTERSECT: + viewHolder.type.setImageResource(R.drawable.arrow_down); + if(notFirst) + title = activity.getString(R.string.CFA_type_intersect) + " " + title; + 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/plugin-src/com/todoroo/astrid/core/SearchFilter.java b/astrid/plugin-src/com/todoroo/astrid/core/SearchFilter.java new file mode 100644 index 000000000..9b3440edb --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/core/SearchFilter.java @@ -0,0 +1,73 @@ +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. + * + * @author Tim Su + * + */ +public class SearchFilter extends FilterListItem { + + /** + * Constructor for creating a new SearchFilter + * + * @param listingTitle + * Title of this item as displayed on the lists page, e.g. Inbox + */ + public SearchFilter(String listingTitle) { + this.listingTitle = listingTitle; + } + + /** + * Constructor for creating a new SearchFilter + */ + protected SearchFilter() { + // + } + + // --- parcelable + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + /** + * Parcelable creator + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + /** + * {@inheritDoc} + */ + public SearchFilter createFromParcel(Parcel source) { + SearchFilter item = new SearchFilter(); + item.readFromParcel(source); + return item; + } + + /** + * {@inheritDoc} + */ + public SearchFilter[] newArray(int size) { + return new SearchFilter[size]; + } + + }; + +} diff --git a/astrid/res/drawable/arrow_branch.png b/astrid/res/drawable/arrow_branch.png new file mode 100644 index 0000000000000000000000000000000000000000..68cee8c73d9b69d95cc71c95f4d8d73fc2815c9e GIT binary patch literal 646 zcmV;10(t$3P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igb= z3N8+LDEQO>00IU{L_t(I%Z-vtXcJKshQB*=Cu!P{7L{rn@eL~YC@$=x;7(i!ZmKBa z$}R-4SWw(3E(ApsltPP(Du`fRO1r5Lx)5|z5wU_riV98C+J|XoGLxCi>ihoB6BOY<#Lu!>}5A!Gwa*LjEn$}jt=M_N^7G$4eA&?Ne% zgS@%B?|x-_=wkcTOFixSvH+nQAPto7pr!ZHX3uwzU*G%}(qh{u4KxhOXM$y~$psn4G+;xPF zJ0sFgp2*CB@6l~eTsVEWsWhCiuT%qsfWSrH(n&P)mZGjDBmG#Mr`{B zaO3gf?G=M$dJ5&_QEq`CqW&a~UT%4j>4}$OQV^NnQ07nCl7dIajnad&sm!W?k87Hb zc?mhAYk+QmjA7_NLgr@HrH-xys7m5UZ}MGNQ_$guFutTuo*AQxXZ g%!~g&Q07*qoM6N<$f)#)wBme*a literal 0 HcmV?d00001 diff --git a/astrid/res/drawable/arrow_down.png b/astrid/res/drawable/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..e9e134311efee86f17fb4760f4c3601a0d2bd80d GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-AbW|YuPgg44sI@OjcY-jIY3jyJzX3_EKV<- zyw{7_QNs0n+aC>Y6@^#a9vik8pIb3QRY;_Q*JaIrYYmJ((l6qfYDA#uWeg*gqOR@|=5f7q0hCA@g%DsX*b z?qFUM#O+!f5+E^wnVE_OC literal 0 HcmV?d00001 diff --git a/astrid/res/drawable/arrow_join.png b/astrid/res/drawable/arrow_join.png new file mode 100644 index 0000000000000000000000000000000000000000..171d453fb34558382898eb5a115b4c537b569f6b GIT binary patch literal 687 zcmV;g0#N;lP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igb= z3M~^c1INw)00J*bL_t(I%cYY|NK{c6hTn6~Irom^j4=JoXfk7kM3Dt$f|&gbMJ|#t zp|BJwY#l@{a}|its%TYAEsBAlHbIM`g;2H-oKl*KAhMZq;ixmt&&`>8&b_yVjg`$* z;8~r;^F80;JKsxe+s0ZBD_wfRUA7bw4(Sg z0Gy5V1YAxxj<$9nxGxA!*&m%Kqv$*wLT5098kZXfuj~)32pDJJoPiMvz=lEz&fV=* zOU3=SPpixD@}oFq;8ekifO7`M8Q71`t|}LDdS;{~uxvRJ@cChy0+wyTp=mIL{5b>0 z*o-tqD)%k7PT>jyoBS|gT{;s!(WDTHx()Tm>U_0G7Zx$MFo)SxGG!XZ3;>YQFI;$h z{$x(iM*^M(wZXd?Ai#WP9z{`vv)Tp2Ea21UX}pQW3~5?@(W{dWe-i*e-~H}J+a{MS zA^W%2@1z?&KA2J8(g8-67Yj*%IL1aZBPCwP&Ia(spKnDDI5xi$~X$50L@*M zU``rZ&67%DI&`yT%3JN-Eli2{m&D}jq4#^rapZ4lY`NYvZc9r{48Cix#E`Yd`8#*a V-gXU`{IUQ5002ovPDHLkV1i!=EOh_? literal 0 HcmV?d00001 diff --git a/astrid/res/drawable/gnome_filter.png b/astrid/res/drawable/gnome_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..5e6328928ad7ed46281b31e6f7c5403a7cde5468 GIT binary patch literal 832 zcmV-G1Hb%Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igb= z2_PI6R2oYF00O{CL_t(Y$L*C%Xq874$A2^Tk;Iq;#f8mANI|JhiioasAtFc-wFNgt z1a%`W1kpeWinxhgxKPp7q7Ar^A}WETJ}5McH26fT1WmQEX}MLo+?b^IOY%Ku=5%pM zh^UYoQ@ZI{%o%3R{Qu{09s~c&e<*5l0Z>YHipWABc~9Uft@Rz}+-`vM#Hom^Y2LrD zrm?Y+APf-!L=ZqJP^gktB|YI}=?>B0SXVNN5=WF+w1PsIk$e4vigyrnoxgB-Q=;eSj}|wO#ZX<9rMfah5E+W&5kp?U z&}e`+9up>p#5juMF=Ii*xY6=!U!S^n^HvDlm;g;pY-(!juHNbC`XrUkELqyLjGDRC zXaubknUuylM`RqvIGhO131Msr6N?eWK<=gLzH~}*54z4QYFfRwSjfq11aTC_*=)s? zU+&)cuuvHMc=!kdfwjx zoN(6oQ!({haybYb>F)aR*p?0JP}xcn>(E-El)^ZNv5weje$RVcY~8^~{?GkD8{m~w z&x?TL>o;tc7sH`=ft4q@A?fM;OWJSeT+8X}Qone)00)5iGi{oBpl#dEUGn6Qyu28R zTzB6qckpzlELpiO0geLI@7wb8fcAY~9`f%#8j$bWf0kuyJ{tkPsax$tuZEf8g z;PKI~&&jSm%>r~z_pP3ek>?eCz@So)$^ZnjN-#OEkk=;VtP^Ne2J%b@3S*AH16LM7 zx}02het#B!tVlVfIqis*)`!Kp1|X$%s>eC|?W}))Gj;A?m45*AFHfGE`%F6k0000< KMNUMnLSTaNK7vsI literal 0 HcmV?d00001 diff --git a/astrid/res/layout/custom_filter_activity.xml b/astrid/res/layout/custom_filter_activity.xml new file mode 100644 index 000000000..331111af6 --- /dev/null +++ b/astrid/res/layout/custom_filter_activity.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + +