Added filters for "tag contains" and "title contains". Added the requisite supporting classes to make that happen. Fixes AST-287.

pull/14/head
Tim Su 15 years ago
parent 85c0ebfb1a
commit ebc277d4db

@ -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]);
}
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<String, Object> 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) {
// 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());
menuItemInstance = new CriterionInstance();
menuItemInstance.criterion = criterion;
}
menuItemInstance.selectedIndex = item.getOrder();
if(adapter.getPosition(menuItemInstance) == -1)
adapter.add(menuItemInstance);
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);

@ -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<CriterionInstance> {
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<CriterionInstance> {
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<String> adapter = new ArrayAdapter<String>(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<CriterionInstance> {
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);

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

@ -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 <tim@todoroo.com>
*
*/
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<IntentFilter> CREATOR = new Parcelable.Creator<IntentFilter>() {
/**
* {@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];
}
};
}

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

@ -69,7 +69,7 @@
<!-- =========================================== CustomFilterCriteria == -->
<!-- Criteria: due by X - display text -->
<!-- Criteria: due by X - display text (? -> user input) -->
<string name="CFC_dueBefore_text">Due By: ?</string>
<!-- Criteria: due by X - name of criteria -->
<string name="CFC_dueBefore_name">Due By...</string>
@ -81,16 +81,27 @@
<item>Tomorrow</item>
<item>Day After Tomorrow</item>
<item>Next Week</item>
<item>Next Month</item>
</string-array>
<!-- Criteria: importance - display text -->
<!-- Criteria: importance - display text (? -> user input)-->
<string name="CFC_importance_text">Importance at least ?</string>
<!-- Criteria: importance - name of criteria -->
<string name="CFC_importance_name">Importance...</string>
<!-- Criteria: tag - display text -->
<!-- Criteria: tag - display text (? -> user input) -->
<string name="CFC_tag_text">Tagged: ?</string>
<!-- Criteria: tag - name of criteria -->
<string name="CFC_tag_name">Tagged...</string>
<!-- Criteria: tag_contains - name of criteria -->
<string name="CFC_tag_contains_name">Tag contains...</string>
<!-- Criteria: tag_contains - text (? -> user input) -->
<string name="CFC_tag_contains_text">Tag contains: ?</string>
<!-- Criteria: title_contains - name of criteria -->
<string name="CFC_title_contains_name">Title contains...</string>
<!-- Criteria: title_contains - text (? -> user input) -->
<string name="CFC_title_contains_text">Title contains: ?</string>
</resources>

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

Loading…
Cancel
Save