Merge remote branch 'upstream/dev' into dev

pull/14/head
Arne Jans 14 years ago
commit 49ac72b647

@ -164,15 +164,19 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="com.todoroo.astrid.core.CustomFilterActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<!-- alarms -->
<receiver android:name="com.todoroo.astrid.alarms.AlarmTaskRepeatListener">
<intent-filter>
<action android:name="com.todoroo.astrid.TASK_REPEATED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</receiver>
<!-- tags -->
<receiver android:name="com.todoroo.astrid.tags.TagsPlugin">

@ -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 <tim@todoroo.com>
*
*/
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.
* <p>
* 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
* <p>
* Examples:
* <ul>
* <li><code>SELECT _id FROM tasks WHERE dueDate <= ?</code>
* <li><code>SELECT task FROM metadata WHERE value = '?'</code>
* </ul>
*/
@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<CustomFilterCriterion> CREATOR = new Parcelable.Creator<CustomFilterCriterion>() {
/**
* {@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];
}
};
}

@ -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());
}
}
/**

@ -37,6 +37,10 @@ public class Field extends DBObject<Field> {
return UnaryCriterion.lt(this, value);
}
public Criterion lte(final Object value) {
return UnaryCriterion.lte(this, value);
}
public Criterion isNull() {
return UnaryCriterion.isNull(this);
}

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

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

@ -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 <tim@todoroo.com>
*
*/
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<CustomFilterCriterion> criteria =
new ArrayList<CustomFilterCriterion>();
// --- 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<CriterionInstance> startingCriteria = new ArrayList<CriterionInstance>();
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);
}
}

@ -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 <tim@todoroo.com>
*
*/
public class CustomFilterAdapter extends ArrayAdapter<CriterionInstance> {
private final Activity activity;
private final LayoutInflater inflater;
public CustomFilterAdapter(Activity activity, List<CriterionInstance> 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);
}
}

@ -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 <tim@todoroo.com>
*
*/
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);
}
}

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

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

@ -62,7 +62,7 @@ public class NoteDetailExposer extends BroadcastReceiver implements DetailExpose
if(notes.length() == 0)
return null;
return notes;
return "<img src='silk_note'/> " + notes; //$NON-NLS-1$
}
@Override

@ -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("<img src='silk_script'/> ").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("<img src='silk_user_gray'/> ").append(user).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$
}
}
} else {

@ -114,7 +114,7 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo
else
detail = context.getString(R.string.repeat_detail_duedate, interval);
return detail;
return "<img src='silk_date'/> " + detail; //$NON-NLS-1$
}
return null;
}

@ -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("<img src='silk_script'/> ").append(listName).append(TaskAdapter.DETAIL_SEPARATOR); //$NON-NLS-1$
}
int repeat = metadata.getValue(MilkTask.REPEATING);

@ -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 "<img src='silk_tag_pink'/> " + tagList; //$NON-NLS-1$
}
@Override

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/background_gradient"
android:orientation="vertical">
<!-- Filter Name -->
<EditText android:id="@+id/filterName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/CFA_filterName_hint"/>
<!-- List -->
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="100"
android:scrollbars="vertical"
android:cacheColorHint="#00000000"/>
<!-- help text -->
<TextView android:id="@+id/help"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/CFA_help"/>
<!-- buttons -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:padding="5dip"
android:background="@drawable/edit_header"
android:baselineAligned="false">
<Button android:id="@+id/add"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableLeft="@drawable/tango_add"
android:text="@string/CFA_button_add" />
<Button
android:id="@+id/saveAndView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/CFA_button_save"
android:drawableRight="@drawable/tango_save" />
</LinearLayout>
</LinearLayout>

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See the file "LICENSE" for the full license governing this code. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="55dip"
android:background="@android:drawable/list_selector_background"
android:paddingLeft="4dip"
android:paddingRight="6dip"
android:orientation="horizontal">
<!-- filter intersection type icon -->
<ImageView android:id="@+id/type"
android:layout_width="32dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:paddingLeft="5dip"
android:paddingRight="5dip"
android:scaleType="center"/>
<!-- filter icon -->
<ImageView android:id="@+id/icon"
android:layout_width="32dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:paddingLeft="5dip"
android:paddingRight="5dip"
android:visibility="gone"/>
<!-- filter text -->
<TextView android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="100"
android:paddingLeft="5dip"
android:textSize="18sp"
android:gravity="center_vertical"/>
<!-- filter graphic -->
<com.todoroo.astrid.core.FilterView android:id="@+id/filter"
android:layout_width="64px"
android:layout_height="fill_parent"
android:layout_weight="1"
android:paddingLeft="5dip"
android:paddingRight="5dip"/>
</LinearLayout>

@ -4,37 +4,83 @@
<!-- Resources for built-in filter plug-in -->
<!-- ================================================= Filter Exposer == -->
<!-- Active Tasks Filter -->
<string name="BFE_Active">Active Tasks</string>
<!-- Title for Active Tasks Filter (what user sees when first opening the app) -->
<string name="BFE_Active_title">Active Tasks</string>
<!-- Search Filter -->
<string name="BFE_Search">Search</string>
<!-- Extended Filters Category -->
<string name="BFE_Extended">More...</string>
<!-- sort recent modification filter -->
<string name="BFE_Recent">Recently Modified</string>
<string name="BFE_Search">Search...</string>
<!-- Completed Filter -->
<string name="BFE_Completed">Completed Tasks</string>
<!-- hidden tasks filter -->
<string name="BFE_Hidden">Hidden Tasks</string>
<!-- sort Alphabetical filter -->
<string name="BFE_Alphabetical">By Title</string>
<!-- Build Your Own Filter -->
<string name="BFE_Custom">Custom Filter...</string>
<!-- sort Due Date filter -->
<string name="BFE_DueDate">By Due Date</string>
<!-- sort Importance filter -->
<string name="BFE_Importance">By Importance</string>
<!-- =========================================== CustomFilterActivity == -->
<!-- Build Your Own Filter Activity Title-->
<string name="CFA_title">Custom Filter</string>
<!-- Filter Name edit box hint -->
<string name="CFA_filterName_hint">Filter Name</string>
<!-- Filter Name default for new filters -->
<string name="CFA_filterName_new">New Filter</string>
<!-- Filter Name default for copied filters (%s => old filter name) -->
<string name="CFA_filterName_copy">Copy of %s</string>
<!-- Filter Starting Universe: all tasks -->
<string name="CFA_universe_all">Active Tasks</string>
<!-- Filter Criteria Type: add (at the begging of title of the criteria) -->
<string name="CFA_type_add">or</string>
<!-- Filter Criteria Type: subtract (at the begging of title of the criteria) -->
<string name="CFA_type_subtract">not</string>
<!-- Filter Criteria Type: intersect (at the begging of title of the criteria) -->
<string name="CFA_type_intersect">also</string>
<!-- Filter Criteria Context Menu: chaining (%s chain type as above) -->
<string name="CFA_context_chain">Chaining: %s</string>
<!-- Filter Criteria Context Menu: delete -->
<string name="CFA_context_delete">Delete Row</string>
<!-- Filter Screen Help Text -->
<string name="CFA_help">This screen lets you create a new filters. Add
criteria using the button below, short or long-press them to adjust, and
then click \"Save &amp; View\"!</string>
<!-- Filter Button: add new -->
<string name="CFA_button_add">Add Criteria</string>
<!-- Filter Button: save & go -->
<string name="CFA_button_save">Save &amp; View</string>
<!-- deleted tasks filter -->
<string name="BFE_Deleted">Deleted Tasks</string>
<!-- =========================================== CustomFilterCriteria == -->
<!-- Criteria: due by X - display text -->
<string name="CFC_dueBefore_text">Due By: ?</string>
<!-- Criteria: due by X - name of criteria -->
<string name="CFC_dueBefore_name">Due By...</string>
<string-array name="CFC_dueBefore_entries">
<!-- Criteria: due by X - options -->
<item>Yesterday</item>
<item>Today</item>
<item>Tomorrow</item>
<item>Day After Tomorrow</item>
<item>Next Week</item>
</string-array>
<!-- Criteria: importance - display text -->
<string name="CFC_importance_text">Importance: ?</string>
<!-- Criteria: importance - name of criteria -->
<string name="CFC_importance_name">Importance...</string>
<!-- Criteria: tag - display text -->
<string name="CFC_tag_text">Tagged: ?</string>
<!-- Criteria: tag - name of criteria -->
<string name="CFC_tag_name">Tagged...</string>
</resources>

@ -37,9 +37,9 @@
<string name="repeat_detail_byday">$I on $D</string>
<!-- task detail for repeat from due date (%s -> interval) -->
<string name="repeat_detail_duedate">Repeats every %s</string>
<string name="repeat_detail_duedate">Every %s</string>
<!-- task detail for repeat from completion date (%s -> interval) -->
<string name="repeat_detail_completion">Repeats %s after completion</string>
<string name="repeat_detail_completion">%s after completion</string>
</resources>

@ -11,11 +11,6 @@
<!-- Tags hint -->
<string name="TEA_tag_hint">Tag Name</string>
<!-- ===================================================== Task Details == -->
<!-- tag text that displays in task list. %s => tag name -->
<string name="tag_TLA_detail">Tags: %s</string>
<!-- ========================================================== Filters == -->
@ -23,20 +18,11 @@
<string name="tag_FEx_header">Tags</string>
<!-- filter header for tags, sorted by size -->
<string name="tag_FEx_by_size">Active</string>
<!-- filter header for tags of completed tasks -->
<string name="tag_FEx_completed">Completed</string>
<!-- filter header for all tags, sorted by name -->
<string name="tag_FEx_alpha">All Tags</string>
<string name="tag_FEx_by_size">Sorted By Size</string>
<!-- filter for untagged tasks -->
<string name="tag_FEx_untagged">Untagged</string>
<!-- $T => tag, $C => count -->
<string name="tag_FEx_tag_w_size">$T ($C)</string>
<!-- %s => tag name -->
<string name="tag_FEx_name">Tagged \'%s\'</string>

@ -50,7 +50,7 @@
</style>
<style name="TextAppearance.TAd_ItemDetails">
<item name="android:textSize">10sp</item>
<item name="android:textSize">11sp</item>
<item name="android:textColor">#ff777777</item>
</style>

@ -6,6 +6,7 @@ package com.todoroo.astrid.activity;
import android.app.AlertDialog;
import android.app.ExpandableListActivity;
import android.app.SearchManager;
import android.app.PendingIntent.CanceledException;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
@ -42,7 +43,8 @@ import com.todoroo.astrid.adapter.FilterAdapter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterCategory;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.api.SearchFilter;
import com.todoroo.astrid.core.IntentFilter;
import com.todoroo.astrid.core.SearchFilter;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.StartupService;
import com.todoroo.astrid.utility.Constants;
@ -202,6 +204,12 @@ public class FilterListActivity extends ExpandableListActivity {
return true;
} else if(item instanceof SearchFilter) {
onSearchRequested();
} else if(item instanceof IntentFilter) {
try {
((IntentFilter)item).intent.send();
} catch (CanceledException e) {
// ignore
}
}
return false;
}

@ -13,7 +13,9 @@ import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.text.util.Linkify;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@ -52,6 +54,7 @@ import com.todoroo.astrid.repeats.RepeatDetailExposer;
import com.todoroo.astrid.rmilk.MilkDetailExposer;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagDetailExposer;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences;
/**
@ -376,6 +379,18 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
*/
public class DetailManager extends AddOnManager<String> {
private final ImageGetter imageGetter = new ImageGetter() {
public Drawable getDrawable(String source) {
Resources r = activity.getResources();
int drawable = r.getIdentifier("drawable/" + source, null, Constants.PACKAGE); //$NON-NLS-1$
if(drawable == 0)
return null;
Drawable d = r.getDrawable(drawable);
d.setBounds(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
return d;
}
};
private final boolean extended;
public DetailManager(boolean extended) {
this.extended = extended;
@ -441,7 +456,7 @@ public class TaskAdapter extends CursorAdapter implements Filterable {
}
String string = detailText.toString();
if(string.contains("<"))
view.setText(Html.fromHtml(string.trim().replace("\n", "<br>")));
view.setText(Html.fromHtml(string.trim().replace("\n", "<br>"), imageGetter, null));
else
view.setText(string.trim());
Linkify.addLinks(view, Linkify.ALL);

@ -68,6 +68,13 @@ public class TaskDao extends GenericDao<Task> {
return Task.DELETION_DATE.eq(0);
}
/** @return tasks that have not yet been completed or deleted */
public static Criterion activeAndVisible() {
return Criterion.and(Task.COMPLETION_DATE.eq(0),
Task.DELETION_DATE.eq(0),
Task.HIDE_UNTIL.lt(Functions.now()));
}
/** @return tasks that have not yet been completed or deleted */
public static Criterion isActive() {
return Criterion.and(Task.COMPLETION_DATE.eq(0),

@ -227,7 +227,7 @@ public class AddOnService {
list[2] = new AddOn(true, true, "Remember the Milk", null,
"Synchronize with Remember The Milk service.",
Constants.PACKAGE, "http://www.rmilk.com",
((BitmapDrawable)r.getDrawable(R.drawable.ic_menu_rmilk)).getBitmap());
((BitmapDrawable)r.getDrawable(R.drawable.ic_menu_refresh)).getBitmap());
list[3] = new AddOn(true, true, "Producteev", null,
"Synchronize with Producteev service. Also changes Astrid's importance levels to stars.",

@ -35,7 +35,7 @@ public final class Constants {
/**
* Whether to turn on debugging logging and UI
*/
public static final boolean DEBUG = true;
public static final boolean DEBUG = false;
/**
* Upgrade time

Loading…
Cancel
Save