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