diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java index 52f787cfb..0617ba7c8 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterActivity.java @@ -14,10 +14,9 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; 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; @@ -34,7 +33,9 @@ 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.api.MultipleSelectCriterion; import com.todoroo.astrid.api.PermaSql; +import com.todoroo.astrid.api.TextInputCriterion; import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; @@ -57,9 +58,8 @@ public class CustomFilterActivity extends ListActivity { private static final String IDENTIFIER_UNIVERSE = "active"; //$NON-NLS-1$ 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; + static final int MENU_GROUP_CONTEXT_TYPE = 1; + static final int MENU_GROUP_CONTEXT_DELETE = 2; // --- hierarchy of filter classes @@ -72,14 +72,49 @@ public class CustomFilterActivity extends ListActivity { /** criteria for this instance */ public CustomFilterCriterion criterion; - /** which of the entries is selected */ + /** which of the entries is selected (MultipleSelect) */ public int selectedIndex = -1; + /** text of selection (TextInput) */ + public String selectedText = null; + /** type of join */ public int type = TYPE_INTERSECT; /** statistics for {@link FilterView} */ public int start, end, max; + + @SuppressWarnings("nls") + public String getTitleFromCriterion() { + if(criterion instanceof MultipleSelectCriterion) { + if(selectedIndex >= 0 && ((MultipleSelectCriterion)criterion).entryTitles != null && + selectedIndex < ((MultipleSelectCriterion)criterion).entryTitles.length) { + String title = ((MultipleSelectCriterion)criterion).entryTitles[selectedIndex]; + return criterion.text.replace("?", title); + } + return criterion.text; + } else if(criterion instanceof TextInputCriterion) { + if(selectedText == null) + return criterion.text; + return criterion.text.replace("?", selectedText); + } + throw new UnsupportedOperationException("Unknown criterion type"); //$NON-NLS-1$ + } + + public String getValueFromCriterion() { + if(type == TYPE_UNIVERSE) + return null; + if(criterion instanceof MultipleSelectCriterion) { + if(selectedIndex >= 0 && ((MultipleSelectCriterion)criterion).entryValues != null && + selectedIndex < ((MultipleSelectCriterion)criterion).entryValues.length) { + return ((MultipleSelectCriterion)criterion).entryValues[selectedIndex]; + } + return criterion.text; + } else if(criterion instanceof TextInputCriterion) { + return selectedText; + } + throw new UnsupportedOperationException("Unknown criterion type"); //$NON-NLS-1$ + } } private TextView filterName; @@ -129,10 +164,11 @@ public class CustomFilterActivity extends ListActivity { PermaSql.VALUE_EOD_TOMORROW, PermaSql.VALUE_EOD_DAY_AFTER, PermaSql.VALUE_EOD_NEXT_WEEK, + PermaSql.VALUE_EOD_NEXT_MONTH, }; ContentValues values = new ContentValues(); values.put(Task.DUE_DATE.name, "?"); - CustomFilterCriterion criterion = new CustomFilterCriterion( + CustomFilterCriterion criterion = new MultipleSelectCriterion( IDENTIFIER_DUEDATE, getString(R.string.CFC_dueBefore_text), Query.select(Task.ID).from(Task.TABLE).where( @@ -159,7 +195,7 @@ public class CustomFilterActivity extends ListActivity { }; values = new ContentValues(); values.put(Task.IMPORTANCE.name, "?"); - criterion = new CustomFilterCriterion( + criterion = new MultipleSelectCriterion( IDENTIFIER_IMPORTANCE, getString(R.string.CFC_importance_text), Query.select(Task.ID).from(Task.TABLE).where( @@ -179,7 +215,7 @@ public class CustomFilterActivity extends ListActivity { values = new ContentValues(); values.put(Metadata.KEY.name, TagService.KEY); values.put(TagService.TAG.name, "?"); - criterion = new CustomFilterCriterion( + criterion = new MultipleSelectCriterion( IDENTIFIER_TAG, getString(R.string.CFC_tag_text), Query.select(Metadata.TASK).from(Metadata.TABLE).join(Join.inner( @@ -191,11 +227,40 @@ public class CustomFilterActivity extends ListActivity { ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap(), getString(R.string.CFC_tag_name)); criteria.add(criterion); + + // built in criteria: tags containing X + criterion = new TextInputCriterion( + IDENTIFIER_TAG, + getString(R.string.CFC_tag_contains_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.like("%?%"))).toString(), + null, getString(R.string.CFC_tag_contains_name), "", + ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags2)).getBitmap(), + getString(R.string.CFC_tag_contains_name)); + criteria.add(criterion); + + // built in criteria: title containing X + values = new ContentValues(); + values.put(Task.TITLE.name, "?"); + criterion = new TextInputCriterion( + IDENTIFIER_TAG, + getString(R.string.CFC_title_contains_text), + Query.select(Task.ID).from(Task.TABLE).where( + Criterion.and(TaskCriteria.activeAndVisible(), + Task.TITLE.like("%?%"))).toString(), + null, getString(R.string.CFC_title_contains_name), "", + ((BitmapDrawable)r.getDrawable(R.drawable.tango_alpha)).getBitmap(), + getString(R.string.CFC_title_contains_name)); + criteria.add(criterion); + } private CriterionInstance getStartingUniverse() { CriterionInstance instance = new CriterionInstance(); - instance.criterion = new CustomFilterCriterion(IDENTIFIER_UNIVERSE, + instance.criterion = new MultipleSelectCriterion(IDENTIFIER_UNIVERSE, getString(R.string.CFA_universe_all), null, null, null, null, null, null); instance.type = CriterionInstance.TYPE_UNIVERSE; @@ -206,7 +271,6 @@ public class CustomFilterActivity extends ListActivity { ((Button)findViewById(R.id.add)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - menuItemInstance = null; getListView().showContextMenu(); } }); @@ -249,34 +313,10 @@ public class CustomFilterActivity extends ListActivity { 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]); - } + for(int i = 0; i < criteria.size(); i++) { + CustomFilterCriterion item = criteria.get(i); + menu.add(CustomFilterActivity.MENU_GROUP_FILTER, + i, 0, item.name); } } }); @@ -284,8 +324,6 @@ public class CustomFilterActivity extends ListActivity { // --- listeners and action events - CriterionInstance menuItemInstance = null; - @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if(menu.size() > 0) @@ -304,15 +342,11 @@ public class CustomFilterActivity extends ListActivity { ContentValues values = new ContentValues(); for(int i = 0; i < adapter.getCount(); i++) { CriterionInstance instance = adapter.getItem(i); - if(instance.selectedIndex < 0 && instance.criterion.entryValues != null) + String value = instance.getValueFromCriterion(); + if(value == null && instance.criterion.sql.contains("?")) continue; - String entryTitle = ""; - if(instance.criterion.entryTitles != null) { - entryTitle = instance.criterion.entryTitles[instance.selectedIndex]; - } - String title = instance.criterion.text.replace("?", entryTitle); - + String title = instance.getTitleFromCriterion(); switch(instance.type) { case CriterionInstance.TYPE_ADD: @@ -333,23 +367,19 @@ public class CustomFilterActivity extends ListActivity { } - String entryValue = ""; - if(instance.criterion.entryValues != null && instance.selectedIndex > -1) - entryValue = instance.criterion.entryValues[instance.selectedIndex]; - // special code for all tasks universe if(instance.criterion.sql == null) sql.append(TaskCriteria.activeAndVisible()).append(' '); else { - String subSql = instance.criterion.sql.replace("?", entryValue); + String subSql = instance.criterion.sql.replace("?", value); sql.append(Task.ID).append(" IN (").append(subSql).append(") "); } if(instance.criterion.valuesForNewTasks != null && instance.type == CriterionInstance.TYPE_INTERSECT) { for(Entry entry : instance.criterion.valuesForNewTasks.valueSet()) { - values.put(entry.getKey().replace("?", entryValue), - entry.getValue().toString().replace("?", entryValue)); + values.put(entry.getKey().replace("?", value), + entry.getValue().toString().replace("?", value)); } } } @@ -383,11 +413,9 @@ public class CustomFilterActivity extends ListActivity { 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; + String value = instance.getValueFromCriterion(); + if(value == null && instance.criterion.sql.contains("?")) continue; - } switch(instance.type) { case CriterionInstance.TYPE_ADD: @@ -406,8 +434,7 @@ public class CustomFilterActivity extends ListActivity { if(instance.criterion.sql == null) sql.append(TaskCriteria.activeAndVisible()).append(' '); else { - String subSql = instance.criterion.sql.replace("?", - instance.criterion.entryValues[instance.selectedIndex]); + String subSql = instance.criterion.sql.replace("?", value); subSql = PermaSql.replacePlaceholders(subSql); System.err.println(subSql); sql.append(Task.ID).append(" IN (").append(subSql).append(") "); @@ -435,26 +462,29 @@ public class CustomFilterActivity extends ListActivity { @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(); + // group filter option + if(item.getGroupId() == MENU_GROUP_FILTER) { + // give an initial value for the row before adding it + CustomFilterCriterion criterion = criteria.get(item.getItemId()); + final CriterionInstance instance = new CriterionInstance(); + instance.criterion = criterion; + adapter.showOptionsFor(instance, new Runnable() { + public void run() { + adapter.add(instance); + updateList(); + } + }); return true; } + // item type context item else if(item.getGroupId() == MENU_GROUP_CONTEXT_TYPE) { CriterionInstance instance = adapter.getItem(item.getOrder()); instance.type = item.getItemId(); updateList(); } + // delete context item else if(item.getGroupId() == MENU_GROUP_CONTEXT_DELETE) { CriterionInstance instance = adapter.getItem(item.getOrder()); adapter.remove(instance); diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java index d52df7554..5e2d980f6 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterAdapter.java @@ -6,17 +6,24 @@ package com.todoroo.astrid.core; import java.util.List; import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; 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.EditText; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import com.timsu.astrid.R; +import com.todoroo.astrid.api.MultipleSelectCriterion; +import com.todoroo.astrid.api.TextInputCriterion; import com.todoroo.astrid.core.CustomFilterActivity.CriterionInstance; import com.todoroo.astrid.data.AddOn; @@ -49,9 +56,12 @@ public class CustomFilterAdapter extends ArrayAdapter { 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(); + showOptionsFor(viewHolder.item, new Runnable() { + @Override + public void run() { + notifyDataSetInvalidated(); + } + }); } }; @@ -87,6 +97,52 @@ public class CustomFilterAdapter extends ArrayAdapter { R.string.CFA_context_delete); } + /** + * Show options menu for the given criterioninstance + * @param item + */ + public void showOptionsFor(final CriterionInstance item, final Runnable onComplete) { + AlertDialog.Builder dialog = new AlertDialog.Builder(activity). + setTitle(item.criterion.name); + + if(item.criterion instanceof MultipleSelectCriterion) { + MultipleSelectCriterion multiSelectCriterion = (MultipleSelectCriterion) item.criterion; + final String[] titles = multiSelectCriterion.entryTitles; + ArrayAdapter adapter = new ArrayAdapter(activity, + android.R.layout.simple_spinner_dropdown_item, titles); + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface click, int which) { + item.selectedIndex = which; + if(onComplete != null) + onComplete.run(); + } + }; + dialog.setAdapter(adapter, listener); + } else if(item.criterion instanceof TextInputCriterion) { + TextInputCriterion textInCriterion = (TextInputCriterion) item.criterion; + FrameLayout frameLayout = new FrameLayout(activity); + frameLayout.setPadding(10, 0, 10, 0); + final EditText editText = new EditText(activity); + editText.setText(item.selectedText); + editText.setHint(textInCriterion.hint); + frameLayout.addView(editText, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.FILL_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT)); + dialog.setMessage(textInCriterion.prompt).setView(frameLayout). + setPositiveButton(android.R.string.ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int which) { + item.selectedText = editText.getText().toString(); + if(onComplete != null) + onComplete.run(); + } + }); + } + + dialog.show().setOwnerActivity(activity); + } + // --- view construction @Override @@ -125,12 +181,7 @@ public class CustomFilterAdapter extends ArrayAdapter { 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); + String title = item.getTitleFromCriterion(); viewHolder.type.setVisibility(item.type == CriterionInstance.TYPE_UNIVERSE ? View.GONE : View.VISIBLE); diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java index 79605b126..d00c4f473 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CustomFilterExposer.java @@ -24,6 +24,7 @@ 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.IntentFilter; import com.todoroo.astrid.dao.StoreObjectDao; import com.todoroo.astrid.data.StoreObject; diff --git a/astrid/plugin-src/com/todoroo/astrid/core/IntentFilter.java b/astrid/plugin-src/com/todoroo/astrid/core/IntentFilter.java deleted file mode 100644 index 8e3619205..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/core/IntentFilter.java +++ /dev/null @@ -1,81 +0,0 @@ -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/SavedFilter.java b/astrid/plugin-src/com/todoroo/astrid/core/SavedFilter.java index b3a78e749..c60af3225 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/SavedFilter.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/SavedFilter.java @@ -97,8 +97,7 @@ public class SavedFilter { // criterion|entry|text|type|sql values.append(item.criterion.identifier.replace(AndroidUtilities.SERIALIZATION_SEPARATOR, AndroidUtilities.SEPARATOR_ESCAPE)).append(AndroidUtilities.SERIALIZATION_SEPARATOR); - if(item.selectedIndex > -1 && item.criterion.entryValues != null) - values.append(item.criterion.entryValues[item.selectedIndex].replace(AndroidUtilities.SERIALIZATION_SEPARATOR, + values.append(item.getValueFromCriterion().replace(AndroidUtilities.SERIALIZATION_SEPARATOR, AndroidUtilities.SEPARATOR_ESCAPE)); values.append(AndroidUtilities.SERIALIZATION_SEPARATOR); values.append(item.criterion.text.replace(AndroidUtilities.SERIALIZATION_SEPARATOR, diff --git a/astrid/res/values/strings-filters.xml b/astrid/res/values/strings-filters.xml index 5741c67be..9bf64fd0b 100644 --- a/astrid/res/values/strings-filters.xml +++ b/astrid/res/values/strings-filters.xml @@ -69,7 +69,7 @@ - + Due By: ? Due By... @@ -81,16 +81,27 @@ Tomorrow Day After Tomorrow Next Week + Next Month - + Importance at least ? Importance... - + Tagged: ? - Tagged... + Tagged... + + + Tag contains... + + Tag contains: ? + + + Title contains... + + Title contains: ? diff --git a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java index 47d4d104c..0855973dd 100644 --- a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java @@ -45,7 +45,7 @@ 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.core.IntentFilter; +import com.todoroo.astrid.api.IntentFilter; import com.todoroo.astrid.core.SearchFilter; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.StartupService;