got saved filter saving working. also updated the icon on the task list activity

pull/14/head
Tim Su 14 years ago
parent 23df93f19c
commit 7cbc7478c0

@ -157,6 +157,12 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.core.CustomFilterExposer">
<intent-filter android:priority="9000">
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<activity android:name="com.todoroo.astrid.core.DefaultsPreferences"
android:label="@string/EPr_defaults_header">
<intent-filter>
@ -164,11 +170,7 @@
<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>
<activity android:name="com.todoroo.astrid.core.CustomFilterActivity" />
<!-- alarms -->
<receiver android:name="com.todoroo.astrid.alarms.AlarmTaskRepeatListener">

@ -3,15 +3,10 @@
*/
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;
/**
@ -23,49 +18,13 @@ import edu.umd.cs.findbugs.annotations.CheckForNull;
*/
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 Identifier. This identifier allows saved filters to be reloaded.
* <p>
* e.g "duedate"
*/
@CheckForNull
public String identifier;
/**
* Criteria Title. If the title contains ?, this is replaced by the entry
@ -134,9 +93,10 @@ public final class CustomFilterCriterion implements Parcelable {
* @param icon
* @param name
*/
public CustomFilterCriterion(String title, String sql,
public CustomFilterCriterion(String identifier, String title, String sql,
ContentValues valuesForNewTasks, String[] entryTitles,
String[] entryValues, Bitmap icon, String name) {
this.identifier = identifier;
this.text = title;
this.sql = sql;
this.valuesForNewTasks = valuesForNewTasks;
@ -159,6 +119,7 @@ public final class CustomFilterCriterion implements Parcelable {
* {@inheritDoc}
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(identifier);
dest.writeString(text);
dest.writeString(sql);
dest.writeParcelable(valuesForNewTasks, 0);
@ -178,7 +139,7 @@ public final class CustomFilterCriterion implements Parcelable {
*/
public CustomFilterCriterion createFromParcel(Parcel source) {
CustomFilterCriterion item = new CustomFilterCriterion(
source.readString(), source.readString(),
source.readString(), source.readString(), source.readString(),
(ContentValues)source.readParcelable(ContentValues.class.getClassLoader()),
source.createStringArray(), source.createStringArray(),
(Bitmap)source.readParcelable(Bitmap.class.getClassLoader()),

@ -42,7 +42,7 @@ public final class Filter extends FilterListItem {
public String title;
/**
* SQL query for this filter. The query will be appended to the select
* {@link PermaSql} query for this filter. The query will be appended to the select
* statement after "<code>SELECT fields FROM table %s</code>". It is
* recommended that you use a {@link QueryTemplate} to construct your
* query.
@ -63,13 +63,13 @@ public final class Filter extends FilterListItem {
* Values to apply to a task when quick-adding a task from this filter.
* 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.
* additional values will be stored for a task. Can use {@link PermaSql}
*/
@CheckForNull
public ContentValues valuesForNewTasks = null;
/**
* Utility constructor for creating a TaskList object
* Utility constructor for creating a Filter object
* @param listingTitle
* Title of this item as displayed on the lists page, e.g. Inbox
* @param title
@ -82,10 +82,27 @@ public final class Filter extends FilterListItem {
*/
public Filter(String listingTitle, String title,
QueryTemplate sqlQuery, ContentValues valuesForNewTasks) {
this(listingTitle, title, sqlQuery == null ? null : sqlQuery.toString(),
valuesForNewTasks);
}
/**
* Utility constructor for creating a Filter object
* @param listingTitle
* Title of this item as displayed on the lists page, e.g. Inbox
* @param title
* Expanded title of this filter when user is viewing this
* filter, e.g. Inbox (20 tasks)
* @param sqlQuery
* SQL query for this list (see {@link sqlQuery} for examples).
* @param valuesForNewTasks
* see {@link sqlForNewTasks}
*/
public Filter(String listingTitle, String title,
String sqlQuery, ContentValues valuesForNewTasks) {
this.listingTitle = listingTitle;
this.title = title;
if(sqlQuery != null)
this.sqlQuery = sqlQuery.toString();
this.sqlQuery = sqlQuery;
this.valuesForNewTasks = valuesForNewTasks;
}

@ -0,0 +1,61 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.api;
import java.util.Date;
import com.todoroo.andlib.utility.DateUtilities;
/**
* PermaSql allows for creating SQL statements that can be saved and used
* later without dates getting stale. It also allows these values to be
* used in
*
* @author Tim Su <tim@todoroo.com>
*
*/
public final class PermaSql {
// --- 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;
}
}

@ -23,8 +23,8 @@ import android.text.InputType;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.View.OnTouchListener;
import android.widget.TextView;
import com.todoroo.andlib.service.Autowired;
@ -39,6 +39,9 @@ import com.todoroo.andlib.service.ExceptionService;
*/
public class AndroidUtilities {
public static final String SEPARATOR_ESCAPE = "!PIPE!"; //$NON-NLS-1$
public static final String SERIALIZATION_SEPARATOR = "|"; //$NON-NLS-1$
// --- utility methods
private static class ExceptionHelper {
@ -183,6 +186,62 @@ public class AndroidUtilities {
return -1;
}
/**
* Serializes a content value into a string
*/
public static String contentValuesToSerializedString(ContentValues source) {
StringBuilder result = new StringBuilder();
for(Entry<String, Object> entry : source.valueSet()) {
result.append(entry.getKey().replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE)).append(
SERIALIZATION_SEPARATOR);
Object value = entry.getValue();
if(value instanceof Integer)
result.append('i').append(value);
else if(value instanceof Double)
result.append('d').append(value);
else if(value instanceof Long)
result.append('l').append(value);
else if(value instanceof String)
result.append('s').append(value.toString());
else
throw new UnsupportedOperationException(value.getClass().toString());
result.append(SERIALIZATION_SEPARATOR);
}
return result.toString();
}
/**
* Turn ContentValues into a string
* @param string
* @return
*/
public static ContentValues contentValuesFromSerializedString(String string) {
if(string == null)
return new ContentValues();
String[] pairs = string.split("\\" + SERIALIZATION_SEPARATOR); //$NON-NLS-1$
ContentValues result = new ContentValues();
for(int i = 0; i < pairs.length; i += 2) {
String key = pairs[i].replaceAll(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR);
String value = pairs[i+1].substring(1);
switch(pairs[i+1].charAt(0)) {
case 'i':
result.put(key, Integer.parseInt(value));
break;
case 'd':
result.put(key, Double.parseDouble(value));
break;
case 'l':
result.put(key, Long.parseLong(value));
break;
case 's':
result.put(key, value.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR));
break;
}
}
return result;
}
/**
* Turn ContentValues into a string
* @param string

@ -3,7 +3,6 @@
*/
package com.todoroo.astrid.core;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -42,17 +41,10 @@ public final class CoreFilterExposer extends BroadcastReceiver {
SearchFilter searchFilter = new SearchFilter(r.getString(R.string.BFE_Search));
searchFilter.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_search)).getBitmap();
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];
FilterListItem[] list = new FilterListItem[2];
list[0] = inbox;
list[1] = searchFilter;
list[2] = customFilter;
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);

@ -2,6 +2,7 @@ package com.todoroo.astrid.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import android.app.ListActivity;
import android.content.ContentValues;
@ -10,6 +11,8 @@ import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.SubMenu;
@ -25,11 +28,14 @@ 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.Field;
import com.todoroo.andlib.sql.Functions;
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.api.PermaSql;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
@ -46,6 +52,11 @@ import com.todoroo.astrid.tags.TagService.Tag;
*/
public class CustomFilterActivity extends ListActivity {
private static final String IDENTIFIER_TAG = "tag"; //$NON-NLS-1$
private static final String IDENTIFIER_IMPORTANCE = "importance"; //$NON-NLS-1$
private static final String IDENTIFIER_DUEDATE = "dueDate"; //$NON-NLS-1$
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;
@ -113,20 +124,23 @@ public class CustomFilterActivity extends ListActivity {
// 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,
"0",
PermaSql.VALUE_EOD_YESTERDAY,
PermaSql.VALUE_EOD,
PermaSql.VALUE_EOD_TOMORROW,
PermaSql.VALUE_EOD_DAY_AFTER,
PermaSql.VALUE_EOD_NEXT_WEEK,
};
ContentValues values = new ContentValues();
values.put(Task.DUE_DATE.name, "?");
CustomFilterCriterion criterion = new CustomFilterCriterion(
IDENTIFIER_DUEDATE,
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.gt(Functions.caseStatement(Field.field("?").eq(0),
-1, 0)),
Task.DUE_DATE.lte("?"))).toString(),
values, r.getStringArray(R.array.CFC_dueBefore_entries),
entryValues, ((BitmapDrawable)r.getDrawable(R.drawable.tango_calendar)).getBitmap(),
@ -146,6 +160,7 @@ public class CustomFilterActivity extends ListActivity {
values = new ContentValues();
values.put(Task.IMPORTANCE.name, "?");
criterion = new CustomFilterCriterion(
IDENTIFIER_IMPORTANCE,
getString(R.string.CFC_importance_text),
Query.select(Task.ID).from(Task.TABLE).where(
Criterion.and(TaskCriteria.activeAndVisible(),
@ -164,6 +179,7 @@ public class CustomFilterActivity extends ListActivity {
values.put(Metadata.KEY.name, TagService.KEY);
values.put(TagService.TAG.name, "?");
criterion = new CustomFilterCriterion(
IDENTIFIER_TAG,
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(
@ -178,7 +194,8 @@ public class CustomFilterActivity extends ListActivity {
private CriterionInstance getStartingUniverse() {
CriterionInstance instance = new CriterionInstance();
instance.criterion = new CustomFilterCriterion(getString(R.string.CFA_universe_all),
instance.criterion = new CustomFilterCriterion(IDENTIFIER_UNIVERSE,
getString(R.string.CFA_universe_all),
null, null, null, null, null, null);
instance.type = CriterionInstance.TYPE_UNIVERSE;
return instance;
@ -193,13 +210,37 @@ public class CustomFilterActivity extends ListActivity {
}
});
((Button)findViewById(R.id.saveAndView)).setOnClickListener(new View.OnClickListener() {
final Button saveAndView = ((Button)findViewById(R.id.saveAndView));
saveAndView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
saveAndView();
}
});
filterName.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if(s.length() == 0) {
saveAndView.setText(R.string.CFA_button_view);
saveAndView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.tango_next, 0);
} else {
saveAndView.setText(R.string.CFA_button_save);
saveAndView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.tango_save, 0);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
//
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
//
}
});
getListView().setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
@ -258,47 +299,73 @@ public class CustomFilterActivity extends ListActivity {
@SuppressWarnings("nls")
void saveAndView() {
StringBuilder sql = new StringBuilder(" WHERE ");
StringBuilder suggestedTitle = new StringBuilder();
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)
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 ");
suggestedTitle.append(getString(R.string.CFA_type_add)).append(' ').
append(title).append(' ');
break;
case CriterionInstance.TYPE_SUBTRACT:
sql.append("AND NOT ");
suggestedTitle.append(getString(R.string.CFA_type_subtract)).append(' ').
append(title).append(' ');
break;
case CriterionInstance.TYPE_INTERSECT:
sql.append("AND ");
suggestedTitle.append(title).append(' ');
break;
case CriterionInstance.TYPE_UNIVERSE:
}
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("?",
instance.criterion.entryValues[instance.selectedIndex]);
subSql = CustomFilterCriterion.replacePlaceholders(subSql);
String subSql = instance.criterion.sql.replace("?", entryValue);
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.putAll(instance.criterion.valuesForNewTasks);
}
}
String title;
if(filterName.getText().length() > 0)
if(filterName.getText().length() > 0) {
// persist saved filter
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
SavedFilter.persist(adapter, title, sql.toString(), values);
} else {
// temporary
title = suggestedTitle.toString();
}
// view
Filter filter = new Filter(title, title, sql.toString(), values);
Intent taskListActivity = new Intent(this, TaskListActivity.class);
taskListActivity.putExtra(TaskListActivity.TOKEN_FILTER, filter);
startActivity(taskListActivity);
@ -313,7 +380,6 @@ public class CustomFilterActivity extends ListActivity {
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);
@ -323,20 +389,12 @@ public class CustomFilterActivity extends ListActivity {
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 ");
@ -344,15 +402,13 @@ public class CustomFilterActivity extends ListActivity {
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);
subSql = PermaSql.replacePlaceholders(subSql);
System.err.println(subSql);
sql.append(Task.ID).append(" IN (").append(subSql).append(") ");
}
@ -375,9 +431,6 @@ public class CustomFilterActivity extends ListActivity {
}
adapter.notifyDataSetInvalidated();
if(adapter.getCount() > 1 && filterName.getText().length() == 0)
filterName.setHint(suggestedTitle);
}
@Override

@ -0,0 +1,83 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.core;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable;
import com.timsu.astrid.R;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.activity.FilterListActivity;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListHeader;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.model.StoreObject;
/**
* Exposes Astrid's built in filters to the {@link FilterListActivity}
*
* @author Tim Su <tim@todoroo.com>
*
*/
public final class CustomFilterExposer extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Resources r = context.getResources();
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();
Filter[] customFilters = buildSavedFilters();
FilterListItem[] list;
if(customFilters.length == 0) {
list = new FilterListItem[1];
} else {
list = new FilterListItem[customFilters.length + 2];
list[1] = new FilterListHeader(r.getString(R.string.BFE_Saved));
for(int i = 0; i < customFilters.length; i++)
list[i + 2] = customFilters[i];
}
list[0] = customFilter;
// transmit filter list
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
private Filter[] buildSavedFilters() {
StoreObjectDao dao = PluginServices.getStoreObjectDao();
TodorooCursor<StoreObject> cursor = dao.query(Query.select(StoreObject.PROPERTIES).where(
StoreObject.TYPE.eq(SavedFilter.TYPE)).orderBy(Order.asc(SavedFilter.NAME)));
try {
Filter[] list = new Filter[cursor.getCount()];
StoreObject savedFilter = new StoreObject();
for(int i = 0; i < list.length; i++) {
cursor.moveToNext();
savedFilter.readFromCursor(cursor);
list[i] = SavedFilter.load(savedFilter);
}
return list;
} finally {
cursor.close();
}
}
}

@ -6,6 +6,7 @@ import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.service.AddOnService;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.MetadataService;
@ -40,6 +41,9 @@ public final class PluginServices {
@Autowired
AddOnService addOnService;
@Autowired
StoreObjectDao storeObjectDao;
private static PluginServices instance;
static {
@ -81,4 +85,8 @@ public final class PluginServices {
public static DateUtilities getDateUtilities() {
return getInstance().dateUtilities;
}
public static StoreObjectDao getStoreObjectDao() {
return getInstance().storeObjectDao;
}
}

@ -0,0 +1,132 @@
package com.todoroo.astrid.core;
import android.content.ContentValues;
import android.text.TextUtils;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.data.Property.StringProperty;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.core.CustomFilterActivity.CriterionInstance;
import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.model.StoreObject;
/**
* {@link StoreObject} entries for a saved custom filter
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class SavedFilter {
/** type */
public static final String TYPE = "filter"; //$NON-NLS-1$
/** saved filter name */
public static final StringProperty NAME = new StringProperty(StoreObject.TABLE,
StoreObject.ITEM.name);
/** perma-sql */
public static final StringProperty SQL = new StringProperty(StoreObject.TABLE,
StoreObject.VALUE1.name);
/** serialized new task content values */
public static final StringProperty VALUES = new StringProperty(StoreObject.TABLE,
StoreObject.VALUE2.name);
/** serialized list of filters applied */
public static final StringProperty FILTERS = new StringProperty(StoreObject.TABLE,
StoreObject.VALUE3.name);
// --- data storage and retrieval methods
/**
* Save a filter
*
* @param adapter
* @param title
* @param sql2
* @param values2
*/
public static void persist(CustomFilterAdapter adapter, String title,
String sql, ContentValues values) {
if(title == null || title.length() == 0)
return;
// if filter of this name exists, edit it
StoreObjectDao dao = PluginServices.getStoreObjectDao();
StoreObject storeObject = new StoreObject();
TodorooCursor<StoreObject> cursor = dao.query(Query.select(StoreObject.ID).where(NAME.eq(title)));
try {
if(!cursor.isAfterLast()) {
cursor.moveToNext();
storeObject.readFromCursor(cursor);
}
} finally {
cursor.close();
}
// populate saved filter properties
storeObject.setValue(StoreObject.TYPE, TYPE);
storeObject.setValue(NAME, title);
storeObject.setValue(SQL, sql);
if(values == null)
storeObject.setValue(VALUES, ""); //$NON-NLS-1$
else
storeObject.setValue(VALUES, AndroidUtilities.contentValuesToSerializedString(values));
String filters = serializeFilters(adapter);
storeObject.setValue(FILTERS, filters);
dao.persist(storeObject);
}
/**
* Turn a series of CriterionInstance objects into a string
* @param adapter
* @return
*/
private static String serializeFilters(CustomFilterAdapter adapter) {
StringBuilder values = new StringBuilder();
for(int i = 0; i < adapter.getCount(); i++) {
CriterionInstance item = adapter.getItem(i);
// 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,
AndroidUtilities.SEPARATOR_ESCAPE));
values.append(AndroidUtilities.SERIALIZATION_SEPARATOR);
values.append(item.criterion.text.replace(AndroidUtilities.SERIALIZATION_SEPARATOR,
AndroidUtilities.SEPARATOR_ESCAPE)).append(AndroidUtilities.SERIALIZATION_SEPARATOR);
values.append(item.type).append(AndroidUtilities.SERIALIZATION_SEPARATOR);
if(item.criterion.sql != null)
values.append(item.criterion.sql);
values.append('\n');
}
return values.toString();
}
/**
* Read filter from store
* @param savedFilter
* @return
*/
public static Filter load(StoreObject savedFilter) {
String title = savedFilter.getValue(NAME);
String sql = savedFilter.getValue(SQL);
String values = savedFilter.getValue(VALUES);
ContentValues contentValues = null;
if(!TextUtils.isEmpty(values))
contentValues = AndroidUtilities.contentValuesFromSerializedString(values);
return new Filter(title, title, sql, contentValues);
}
}

@ -68,8 +68,7 @@ public class LocaleReceiver extends BroadcastReceiver {
// find out if we have active tasks with this tag
DependencyInjectionService.getInstance().inject(this);
Filter filter = new Filter(title, title, null, null);
filter.sqlQuery = sql;
Filter filter = new Filter(title, title, sql, null);
TodorooCursor<Task> cursor = PluginServices.getTaskService().fetchFiltered(
sql, null, Task.ID);
try {

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

@ -17,10 +17,10 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="false" android:drawable="@drawable/tango_previous_normal" />
android:state_focused="false" android:drawable="@drawable/tango_windows_normal" />
<item android:state_pressed="true" android:state_enabled="true"
android:drawable="@drawable/tango_previous_pressed" />
android:drawable="@drawable/tango_windows_pressed" />
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="true" android:drawable="@drawable/tango_previous_pressed" />
android:state_focused="true" android:drawable="@drawable/tango_windows_pressed" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

@ -51,8 +51,8 @@
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" />
android:text="@string/CFA_button_view"
android:drawableRight="@drawable/tango_next" />
</LinearLayout>

@ -10,24 +10,26 @@
<!-- Header -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_height="42dip"
android:layout_weight="1"
android:orientation="horizontal"
android:background="@drawable/edit_header">
<!-- Back Button -->
<!-- Filters Button -->
<ImageView android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"
android:src="@drawable/tango_previous"
android:src="@drawable/tango_windows"
android:scaleType="center"
android:paddingTop="8dip"
android:paddingLeft="5dip"/>
android:paddingLeft="5dip"
android:paddingRight="5dip"/>
<!-- List Label -->
<TextView android:id="@+id/listLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="100"
android:singleLine="true"
android:paddingTop="6dip"

@ -20,9 +20,10 @@
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"
android:src="@drawable/tango_previous"
android:paddingLeft="5dip"
android:paddingRight="8dip"/>
android:src="@drawable/tango_windows"
android:paddingTop="8dip"
android:paddingLeft="5dip"
android:paddingRight="5dip"/>
<!-- List Label -->
<TextView android:id="@+id/listLabel"

@ -14,17 +14,17 @@
<!-- Build Your Own Filter -->
<string name="BFE_Custom">Custom Filter...</string>
<!-- Saved Filters Header -->
<string name="BFE_Saved">Saved Filters</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 edit box hint (if user types here, filter will be saved) -->
<string name="CFA_filterName_hint">Name this filter to save it...</string>
<!-- Filter Name default for copied filters (%s => old filter name) -->
<string name="CFA_filterName_copy">Copy of %s</string>
@ -50,12 +50,15 @@
<!-- 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>
then click \"View\"!</string>
<!-- Filter Button: add new -->
<string name="CFA_button_add">Add Criteria</string>
<!-- Filter Button: save & go -->
<!-- Filter Button: view without saving -->
<string name="CFA_button_view">View</string>
<!-- Filter Button: save & view filter -->
<string name="CFA_button_save">Save &amp; View</string>
<!-- =========================================== CustomFilterCriteria == -->
@ -66,6 +69,7 @@
<string name="CFC_dueBefore_name">Due By...</string>
<string-array name="CFC_dueBefore_entries">
<!-- Criteria: due by X - options -->
<item>No Due Date</item>
<item>Yesterday</item>
<item>Today</item>
<item>Tomorrow</item>

@ -25,7 +25,7 @@
</style>
<style name="TextAppearance.TLA_Header">
<item name="android:textSize">24sp</item>
<item name="android:textSize">20sp</item>
<item name="android:gravity">center</item>
</style>

@ -9,9 +9,10 @@ import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao;
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;
@ -179,6 +180,8 @@ public class TaskService {
} else
sql = queryTemplate;
sql = PermaSql.replacePlaceholders(sql);
return taskDao.query(Query.select(properties).withQueryTemplate(sql));
}

Loading…
Cancel
Save