Locale plugin.

pull/14/head
Tim Su 15 years ago
parent b6bc9c1b00
commit e99e52b64f

@ -4,7 +4,7 @@
<classpathentry kind="src" path="src-legacy"/>
<classpathentry kind="src" path="api-src"/>
<classpathentry kind="src" path="common-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/rmilk/StartupReceiver.java|com/todoroo/astrid/backup/BackupService.java|com/todoroo/astrid/backup/TasksXmlExporter.java|com/todoroo/astrid/backup/TasksXmlImporter.java" kind="src" path="plugin-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/rmilk/StartupReceiver.java|com/todoroo/astrid/backup/TasksXmlExporter.java|com/todoroo/astrid/backup/TasksXmlImporter.java" kind="src" path="plugin-src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/>

@ -79,7 +79,8 @@ public final class Filter extends FilterListItem {
QueryTemplate sqlQuery, ContentValues valuesForNewTasks) {
this.listingTitle = listingTitle;
this.title = title;
this.sqlQuery = sqlQuery.toString();
if(sqlQuery != null)
this.sqlQuery = sqlQuery.toString();
this.valuesForNewTasks = valuesForNewTasks;
}

@ -4,8 +4,9 @@
<booleanAttribute key="ch.zork.quicklaunch" value="true"/>
<stringAttribute key="ch.zork.quicklaunch.icon" value="14.gif"/>
<intAttribute key="ch.zork.quicklaunch.index" value="0"/>
<stringAttribute key="ch.zork.quicklaunch.mode" value="run"/>
<stringAttribute key="ch.zork.quicklaunch.mode" value="debug"/>
<intAttribute key="com.android.ide.eclipse.adt.action" value="0"/>
<stringAttribute key="com.android.ide.eclipse.adt.avd" value="evo-8-google"/>
<stringAttribute key="com.android.ide.eclipse.adt.commandline" value="-scale 0.7"/>
<intAttribute key="com.android.ide.eclipse.adt.delay" value="0"/>
<booleanAttribute key="com.android.ide.eclipse.adt.nobootanim" value="true"/>
@ -14,9 +15,11 @@
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/astrid"/>
<listEntry value="/astrid/AndroidManifest.xml"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
<listEntry value="1"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>

@ -18,4 +18,11 @@ public final class Functions {
return new Field("UPPER(" + title.toString() + ")");
}
/**
* @return SQL now (in milliseconds)
*/
public static Field now() {
return new Field("(strftime('%s','now')*1000)");
}
}

Binary file not shown.

@ -4,7 +4,6 @@ import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import android.app.AlarmManager;
import android.app.PendingIntent;
@ -12,9 +11,6 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import com.timsu.astrid.R;
/**
* Inspired heavily by SynchronizationService
@ -55,7 +51,7 @@ public class BackupService extends Service {
}
private void startBackup(Context ctx) {
if (ctx == null || ctx.getResources() == null) {
/*if (ctx == null || ctx.getResources() == null) {
return;
}
try {
@ -84,11 +80,11 @@ public class BackupService extends Service {
ctx.getString(R.string.BPr_backup_desc_failure,
e.toString()));
}
}
}*/
}
public static void scheduleService(Context ctx) {
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
/*AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getService(ctx, 0,
createAlarmIntent(ctx), PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pendingIntent);
@ -96,7 +92,7 @@ public class BackupService extends Service {
return;
}
am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis() + BACKUP_OFFSET,
BACKUP_INTERVAL, pendingIntent);
BACKUP_INTERVAL, pendingIntent);*/
}
public static void unscheduleService(Context ctx) {
@ -148,7 +144,7 @@ public class BackupService extends Service {
private BackupDirectorySetting backupDirectorySetting = new BackupDirectorySetting() {
public File getBackupDirectory() {
return TasksXmlExporter.getExportDirectory();
return null; //TasksXmlExporter.getExportDirectory();
}
};

@ -50,7 +50,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
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(DateUtilities.now()))).
TaskCriteria.isVisible())).
orderBy(Order.asc(Task.TITLE)),
null);
alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_alpha)).getBitmap();
@ -66,7 +66,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
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(DateUtilities.now())))).
Criterion.not(TaskCriteria.isVisible()))).
orderBy(Order.asc(Task.HIDE_UNTIL)),
hiddenValues);
hidden.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_clouds)).getBitmap();
@ -74,7 +74,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
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.completedBefore(DateUtilities.now()),
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();
@ -109,7 +109,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
public static Filter buildInboxFilter(Resources r) {
Filter inbox = new Filter(r.getString(R.string.BFE_Active), r.getString(R.string.BFE_Active_title),
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
TaskCriteria.isVisible(DateUtilities.now()))),
TaskCriteria.isVisible())),
null);
inbox.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_home)).getBitmap();
return inbox;

@ -1,104 +1,270 @@
package com.todoroo.astrid.locale;
import java.util.LinkedList;
import android.app.Activity;
import android.app.ExpandableListActivity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ExpandableListView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.AdapterView.OnItemSelectedListener;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.tag.TagModelForView;
import com.todoroo.astrid.adapter.FilterAdapter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.utility.Constants;
import com.twofortyfouram.SharedResources;
/**
* Activity to edit alerts from Locale
*
* @author timsu
* @author Tim Su <tim@todoroo.com>
*
*/
public final class LocaleEditAlerts extends Activity {
public final class LocaleEditAlerts extends ExpandableListActivity {
// --- locale constants
/** value for action type for tag alert */
@SuppressWarnings("nls")
public static final String ACTION_LOCALE_ALERT = "com.timsu.astrid.action.LOCALE_ALERT";
public static final String ACTION_LOCALE_ALERT = "com.todoroo.astrid.action.LOCALE_ALERT";
/** key name for tag id/name in bundle */
/** key name for filter title in bundle */
@SuppressWarnings("nls")
public static final String KEY_TAG_ID = "tag";
public static final String KEY_FILTER_TITLE = "title";
/** key name for filter SQL in bundle */
@SuppressWarnings("nls")
public static final String KEY_TAG_NAME = "name";
public static final String KEY_SQL = "sql";
private LinkedList<TagModelForView> tags = null;
private String[] tagNames = null;
/** key name for interval (integer, # of seconds) */
@SuppressWarnings("nls")
public static final String KEY_INTERVAL = "interval";
// --- activity constants
/**
* Indices for interval setting
*/
public static final int INTERVAL_ONE_HOUR = 0;
public static final int INTERVAL_SIX_HOURS = 1;
public static final int INTERVAL_TWELVE_HOURS = 2;
public static final int INTERVAL_ONE_DAY = 3;
public static final int INTERVAL_THREE_DAYS = 4;
public static final int INTERVAL_ONE_WEEK = 5;
/**
* Intervals in seconds
*/
public static final int[] INTERVALS = new int[] {
3600, 6 * 3600, 12 * 3600, 24 * 3600, 3 * 24 * 3600, 7 * 24 * 3600
};
/**
* Menu ID of the save item.
*/
private static final int MENU_SAVE = 1;
/**
* Menu ID of the don't save item.
*/
private static final int MENU_DONT_SAVE = 2;
// --- implementation
FilterAdapter adapter = null;
Spinner interval = null;
/**
* Flag boolean that can only be set to true via the "Don't Save" menu item in {@link #onMenuItemSelected(int, MenuItem)}. If
* true, then this {@code Activity} should return {@link Activity#RESULT_CANCELED} in {@link #finish()}.
* <p>
* There is no need to save/restore this field's state when the {@code Activity} is paused.
*/
private boolean isCancelled = false;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.locale_edit_alerts);
// Set up the breadcrumbs in the title bar
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.locale_ellipsizing_title);
String breadcrumbString = getIntent().getStringExtra(com.twofortyfouram.Intent.EXTRA_STRING_BREADCRUMB);
if (breadcrumbString == null)
breadcrumbString = getString(R.string.locale_edit_alerts_title);
else
breadcrumbString = breadcrumbString + com.twofortyfouram.Intent.BREADCRUMB_SEPARATOR + getString(R.string.locale_edit_alerts_title);
/*
* Locale guarantees that the breadcrumb string will be present, but checking for null anyway makes your Activity more
* robust and re-usable
*/
final String breadcrumbString = getIntent().getStringExtra(com.twofortyfouram.Intent.EXTRA_STRING_BREADCRUMB);
if (breadcrumbString != null)
setTitle(String.format("%s%s%s", breadcrumbString, com.twofortyfouram.Intent.BREADCRUMB_SEPARATOR, //$NON-NLS-1$
getString(R.string.locale_edit_alerts_title)));
((TextView) findViewById(R.id.locale_ellipsizing_title_text)).setText(breadcrumbString);
setTitle(breadcrumbString);
/*
* Load the Locale background frame from Locale
*/
((LinearLayout) findViewById(R.id.frame)).setBackgroundDrawable(
SharedResources.getDrawableResource(getPackageManager(),
SharedResources.DRAWABLE_LOCALE_BORDER));
// set up UI components
interval = (Spinner) findViewById(R.id.intervalSpinner);
interval.setSelection(INTERVAL_ONE_DAY);
String selectionToMatch = null;
try {
/*
* if savedInstanceState == null, then we are entering the Activity directly from Locale and we need to check whether the
* Intent has forwarded a Bundle extra (e.g. whether we editing an old setting or creating a new one)
*/
if (savedInstanceState == null)
{
final Bundle forwardedBundle = getIntent().getBundleExtra(com.twofortyfouram.Intent.EXTRA_BUNDLE);
/*
* the forwardedBundle would be null if this was a new setting
*/
if (forwardedBundle != null)
{
final int intervalValue = getIntent().getIntExtra(KEY_INTERVAL, INTERVALS[interval.getSelectedItemPosition()]);
for(int i = 0; i < INTERVALS.length; i++) {
if(intervalValue == INTERVALS[i]) {
interval.setSelection(i);
break;
}
}
selectionToMatch = getIntent().getStringExtra(KEY_SQL);
}
}
} catch (Exception e) {
selectionToMatch = null;
Log.e("astrid-locale", "Error loading bundle", e); //$NON-NLS-1$ //$NON-NLS-2$
}
final Spinner tagSpinner = (Spinner) findViewById(R.id.spinner);
// if we match a selection, make it selected
final String finalSelection = selectionToMatch;
adapter = new FilterAdapter(this, getExpandableListView(), R.layout.filter_adapter_row) {
@Override
public void onReceiveFilter(FilterListItem item) {
if(finalSelection != null && item instanceof Filter &&
finalSelection.equals(((Filter)item).sqlQuery))
adapter.setSelection(item);
}
};
adapter.filterStyle = R.style.TextAppearance_LEA_Filter;
adapter.headerStyle = R.style.TextAppearance_LEA_Header;
adapter.categoryStyle = R.style.TextAppearance_LEA_Category;
setListAdapter(adapter);
}
TagController tagController = new TagController(this);
tagController.open();
tags = tagController.getAllTags();
tagController.close();
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
FilterListItem item = (FilterListItem) adapter.getChild(groupPosition,
childPosition);
if(item instanceof Filter) {
adapter.setSelection(item);
}
return true;
}
tagNames = new String[tags.size()];
for(int i = 0; i < tags.size(); i++)
tagNames[i] = tags.get(i).getName();
@Override
public void onGroupExpand(int groupPosition) {
FilterListItem item = (FilterListItem) adapter.getGroup(groupPosition);
if(item instanceof Filter) {
adapter.setSelection(item);
}
}
ArrayAdapter<String> tagAdapter = new ArrayAdapter<String>(
this, android.R.layout.simple_spinner_item, tagNames);
tagAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
tagSpinner.setAdapter(tagAdapter);
@Override
public void onGroupCollapse(int groupPosition) {
onGroupExpand(groupPosition);
}
// Save the state into the return Intent whenever the field
tagSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
updateResult();
/**
* Called when the {@code Activity} is being terminated. This method determines the state of the {@code Activity} and what
* sort of result should be returned to <i>Locale</i>.
*/
@Override
public void finish()
{
if (isCancelled)
setResult(RESULT_CANCELED);
else
{
final FilterListItem selected = adapter.getSelection();
final int intervalIndex = interval.getSelectedItemPosition();
/*
* If the message is of 0 length, then there isn't a setting to save.
*/
if (selected == null)
{
/*
* Note: many settings will not need to use the RESULT_REMOVE result. This is only needed for settings that have
* an "invalid" state that shouldn't be saved. For example, an saving empty Toast message doesn't make sense. The
* Ringer Volume setting doesn't have such an "invalid" state, and therefore doesn't use this result code
*/
setResult(com.twofortyfouram.Intent.RESULT_REMOVE);
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
// do nothing
else
{
/*
* This is the return Intent, into which we'll put all the required extras
*/
final Intent returnIntent = new Intent(ACTION_LOCALE_ALERT);
/*
* This extra is the data to ourselves: either for the Activity or the BroadcastReceiver. Note that anything
* placed in this bundle must be available to Locale's class loader. So storing String, int, and other basic
* objects will work just fine. You cannot store an object that only exists in your project, as Locale will be
* unable to serialize it.
*/
final Bundle storeAndForwardExtras = new Bundle();
Filter filterItem = (Filter) selected;
storeAndForwardExtras.putString(KEY_FILTER_TITLE, filterItem.title);
storeAndForwardExtras.putString(KEY_SQL, filterItem.sqlQuery);
storeAndForwardExtras.putInt(KEY_INTERVAL, INTERVALS[intervalIndex]);
returnIntent.putExtra(com.twofortyfouram.Intent.EXTRA_BUNDLE, storeAndForwardExtras);
/*
* This is the blurb concisely describing what your setting's state is. This is simply used for display in the UI.
*/
if (filterItem.title.length() > com.twofortyfouram.Intent.MAXIMUM_BLURB_LENGTH)
returnIntent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BLURB, filterItem.title.substring(0, com.twofortyfouram.Intent.MAXIMUM_BLURB_LENGTH));
else
returnIntent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BLURB, filterItem.title);
setResult(RESULT_OK, returnIntent);
}
}
});
}
super.finish();
}
// --- boring stuff
@Override
protected void onResume() {
super.onResume();
adapter.registerRecevier();
}
@Override
protected void onPause() {
super.onPause();
adapter.unregisterRecevier();
}
@Override
protected void onStart() {
super.onStart();
// set up flurry
FlurryAgent.onStartSession(this, Constants.FLURRY_KEY);
}
@ -108,86 +274,50 @@ public final class LocaleEditAlerts extends Activity {
FlurryAgent.onEndSession(this);
}
/**
* Private helper method to persist the Toast message in the return {@code Intent}.
*/
private void updateResult() {
// no tags, so it's not possible to save
if(tagNames.length == 0) {
setResult(com.twofortyfouram.Intent.RESULT_REMOVE);
return;
}
/**
* {@inheritDoc}
*/
@Override
public boolean onCreateOptionsMenu(final Menu menu)
{
super.onCreateOptionsMenu(menu);
final int index = ((Spinner) findViewById(R.id.spinner)).getSelectedItemPosition();
final String tagName = tagNames[index];
final TagModelForView tag = tags.get(index);
final PackageManager manager = getPackageManager();
/*
* If the message is of 0 length, then there isn't a setting to save
* We are dynamically loading resources from Locale's APK. This will only work if Locale is actually installed
*/
if (tagName == null) {
setResult(com.twofortyfouram.Intent.RESULT_REMOVE);
} else {
final Intent intent = new Intent();
intent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_ACTION_FIRE,
ACTION_LOCALE_ALERT);
intent.putExtra(KEY_TAG_ID, tag.getTagIdentifier().getId());
intent.putExtra(KEY_TAG_NAME, tagName);
intent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BLURB, tagName);
setResult(RESULT_OK, intent);
menu.add(0, MENU_DONT_SAVE, 0, SharedResources.getTextResource(manager, SharedResources.STRING_MENU_DONTSAVE))
.setIcon(SharedResources.getDrawableResource(manager, SharedResources.DRAWABLE_MENU_DONTSAVE)).getItemId();
menu.add(0, MENU_SAVE, 0, SharedResources.getTextResource(manager, SharedResources.STRING_MENU_SAVE))
.setIcon(SharedResources.getDrawableResource(manager, SharedResources.DRAWABLE_MENU_SAVE)).getItemId();
return true;
}
/**
* {@inheritDoc}
*/
@Override
public boolean onMenuItemSelected(final int featureId, final MenuItem item)
{
switch (item.getItemId())
{
case MENU_SAVE:
{
finish();
return true;
}
case MENU_DONT_SAVE:
{
isCancelled = true;
finish();
return true;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.locale_edit_alerts, menu);
menu.findItem(R.id.menu_save).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener()
{
public boolean onMenuItemClick(final MenuItem item)
{
updateResult();
finish();
return true;
}
});
menu.findItem(R.id.menu_dontsave).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener()
{
public boolean onMenuItemClick(final MenuItem item)
{
setResult(RESULT_CANCELED);
finish();
return true;
}
});
menu.findItem(R.id.menu_help).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener()
{
public boolean onMenuItemClick(final MenuItem item)
{
final Intent helpIntent = new Intent(com.twofortyfouram.Intent.ACTION_HELP);
helpIntent.putExtra("com.twofortyfouram.locale.intent.extra.HELP_URL", "http://www.androidlocale.com/app_data/toast/1.0/help_toast.htm"); //$NON-NLS-1$ //$NON-NLS-2$
// Set up the breadcrumbs in the title bar
String breadcrumbString = getIntent().getStringExtra(com.twofortyfouram.Intent.EXTRA_STRING_BREADCRUMB);
if (breadcrumbString == null)
helpIntent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BREADCRUMB, getString(R.string.locale_edit_alerts_title));
else
helpIntent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BREADCRUMB, breadcrumbString + com.twofortyfouram.Intent.BREADCRUMB_SEPARATOR
+ getString(R.string.locale_edit_alerts_title));
startActivity(helpIntent);
return true;
}
});
return true;
}
return super.onOptionsItemSelected(item);
}
}

@ -1,8 +1,7 @@
package com.todoroo.astrid.locale;
import java.util.HashSet;
import java.util.LinkedList;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -10,10 +9,18 @@ import android.content.res.Resources;
import android.util.Log;
import com.timsu.astrid.R;
import com.timsu.astrid.data.TaskController;
import com.timsu.astrid.data.TaskIdentifier;
import com.timsu.astrid.data.tag.TagController;
import com.timsu.astrid.data.tag.TagIdentifier;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.activity.ShortcutActivity;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Preferences;
/**
* Receiver is activated when Locale conditions are triggered
@ -23,56 +30,67 @@ import com.timsu.astrid.data.tag.TagIdentifier;
*/
public class LocaleReceiver extends BroadcastReceiver {
/** minimum amount of time between two notifications */
private static final long MIN_NOTIFY_INTERVAL = 12*3600*1000L;
@Autowired
private TaskService taskService;
/**
* Create a preference key for storing / retrieving last interval time
* @param filterTitle
* @param interval
* @return
*/
private String makePreferenceKey(String filterTitle, int interval) {
return "LOCALE:" + filterTitle + interval; //$NON-NLS-1$
}
@SuppressWarnings("nls")
@Override
/** Called when the system is started up */
public void onReceive(Context context, Intent intent) {
try {
if (LocaleEditAlerts.ACTION_LOCALE_ALERT.equals(intent.getAction())) {
final long tagId = intent.getLongExtra(LocaleEditAlerts.KEY_TAG_ID, 0);
final String tagName = intent.getStringExtra(LocaleEditAlerts.KEY_TAG_NAME);
if(tagId == 0) {
Log.w("astrid-locale", "Invalid tag identifier in alert");
return;
}
final String title = intent.getStringExtra(LocaleEditAlerts.KEY_FILTER_TITLE);
final String sql = intent.getStringExtra(LocaleEditAlerts.KEY_SQL);
final int interval = intent.getIntExtra(LocaleEditAlerts.KEY_INTERVAL, 24*3600);
// check if we've already made a notification recently
if(System.currentTimeMillis() - Preferences.
getLocaleLastAlertTime(context, tagId) < MIN_NOTIFY_INTERVAL) {
Log.w("astrid-locale", "Too soon, need " + (MIN_NOTIFY_INTERVAL - System.currentTimeMillis() + Preferences.
getLocaleLastAlertTime(context, tagId))/1000L + " more seconds");
String preferenceKey = makePreferenceKey(title, interval);
long lastNotifyTime = Preferences.getLong(preferenceKey, 0);
if(DateUtilities.now() - lastNotifyTime < interval * 1000L) {
Log.i("astrid-locale", title + ": Too soon, need " + (interval
- (DateUtilities.now() - lastNotifyTime)/1000) + " more seconds");
return;
}
// find out if we have active tasks with this tag
TaskController taskController = new TaskController(context);
taskController.open();
TagController tagController = new TagController(context);
tagController.open();
DependencyInjectionService.getInstance().inject(this);
Filter filter = new Filter(title, title, null, null);
filter.sqlQuery = sql;
TodorooCursor<Task> cursor = taskService.fetchFiltered(filter, Task.ID);
try {
HashSet<TaskIdentifier> activeTasks = taskController.getActiveVisibleTaskIdentifiers();
LinkedList<TaskIdentifier> tasks = tagController.getTaggedTasks(
new TagIdentifier(tagId));
// int count = TagListSubActivity.countActiveTasks(activeTasks, tasks);
int count = 0;
if(count > 0) {
Resources r = context.getResources();
// String reminder = r.getString(R.string.notif_tagNotification).
String reminder = "$NUM of $TAG".
replace("$NUM", r.getQuantityString(R.plurals.Ntasks, count, count)).
replace("$TAG", tagName);
// ReminderService.showTagNotification(context, tagId, reminder);
if(cursor.getCount() == 0)
return;
Resources r = context.getResources();
String reminder = r.getString(R.string.locale_notification).
replace("$NUM", r.getQuantityString(R.plurals.Ntasks,
cursor.getCount(), cursor.getCount())).
replace("$FILTER", title);
// show a reminder
Intent notifyIntent = ShortcutActivity.createIntent(filter);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context,
Constants.NOTIFICATION_TIMER, notifyIntent, 0);
Notification notification = new Notification(
R.drawable.timers_notification, reminder, System.currentTimeMillis());
notification.setLatestEventInfo(context, r.getString(R.string.locale_edit_alerts_title),
reminder, pendingIntent);
Preferences.setLocaleLastAlertTime(context, tagId,
System.currentTimeMillis());
} else {
Log.w("astrid-locale", "Locale Notification, but no tasks to show");
}
NotificationManager nm = new AndroidNotificationManager(context);
nm.notify(Constants.NOTIFICATION_TIMER, notification);
Preferences.setLong(preferenceKey, DateUtilities.now());
} finally {
taskController.close();
tagController.close();
cursor.close();
}
}
} catch (Exception e) {

@ -11,7 +11,6 @@ import android.content.Intent;
import com.timsu.astrid.R;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterCategory;
@ -44,7 +43,7 @@ public class MilkFilterExposer extends BroadcastReceiver {
MilkDataService.METADATA_JOIN).where(Criterion.and(
MetadataCriteria.withKey(MilkTask.METADATA_KEY),
TaskCriteria.isActive(),
TaskCriteria.isVisible(DateUtilities.now()),
TaskCriteria.isVisible(),
MilkTask.LIST_ID.eq(list.id))),
values);

@ -22,7 +22,6 @@ import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.SoftHashMap;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao;
@ -267,7 +266,7 @@ public final class MilkDataService {
// read all list counts
TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(MilkTask.LIST_ID, COUNT).
join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
where(Criterion.and(TaskCriteria.isVisible(DateUtilities.now()), TaskCriteria.isActive())).
where(Criterion.and(TaskCriteria.isVisible(), TaskCriteria.isActive())).
groupBy(MilkTask.LIST_ID));
try {
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {

@ -12,7 +12,6 @@ import com.todoroo.andlib.sql.Join;
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.dao.MetadataDao;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
@ -95,7 +94,7 @@ public final class TagService {
return new QueryTemplate().where(Criterion.and(
Criterion.not(Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE).where(MetadataCriteria.withKey(KEY)))),
TaskCriteria.isActive(),
TaskCriteria.isVisible(DateUtilities.now())));
TaskCriteria.isVisible()));
}
/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

@ -0,0 +1,52 @@
<?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="wrap_content"
android:background="@android:drawable/list_selector_background"
android:paddingTop="4dip"
android:paddingBottom="4dip"
android:paddingLeft="4dip"
android:paddingRight="6dip"
android:orientation="horizontal">
<!-- expander icon -->
<ImageView android:id="@+id/expander"
android:layout_width="32dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:paddingLeft="5dip"
android:paddingRight="5dip"
android:scaleType="fitCenter"
android:visibility="gone"/>
<!-- 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:scaleType="fitCenter"
android:visibility="gone"/>
<!-- filter name -->
<TextView android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="100"
android:paddingLeft="5dip"
android:gravity="center_vertical"/>
<!-- selected -->
<ImageView android:id="@+id/selected"
android:layout_width="24dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:paddingLeft="5dip"
android:paddingRight="5dip"
android:src="@drawable/filter_selected_icon"
android:scaleType="fitCenter"
android:visibility="gone"/>
</LinearLayout>

@ -5,32 +5,52 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/locale_edit_intro" />
<!-- At runtime, we'll load the Locale background frame -->
<LinearLayout
android:id="@+id/frame"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/locale_border"
android:layout_margin="10dip">
android:layout_weight="1">
<TextView
android:paddingTop="5dip"
android:paddingBottom="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/locale_pick_filter"
android:paddingBottom="10px"
android:gravity="center"
android:text="@string/locale_edit_intro"
style="@style/TextAppearance.Locale_Label" />
<Spinner
android:paddingBottom="10dip"
android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="100"
android:layout_margin="10px"
android:background="@drawable/locale_border">
<ExpandableListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical"
android:cacheColorHint="@android:color/white"/>
</FrameLayout>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/locale_interval_label"
style="@style/TextAppearance.Locale_Label" />
<Spinner android:id="@+id/intervalSpinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingBottom="10dip"
android:entries="@array/locale_interval" />
</LinearLayout>
</LinearLayout>

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/screen"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<TextView
android:id="@+id/locale_ellipsizing_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_gravity="center_vertical"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:ellipsize="start"
android:singleLine="true" />
</RelativeLayout>

@ -35,8 +35,6 @@
<TextView android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/completeBox"
style="@style/TextAppearance.TAd_ItemTitle"
android:gravity="center_vertical"/>
@ -44,8 +42,6 @@
<TextView android:id="@+id/dueDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_toRightOf="@id/completeBox"
android:singleLine="true"/>
<TextView android:id="@+id/details"
@ -70,8 +66,6 @@
<LinearLayout android:id="@+id/actions"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/details"
android:layout_alignParentLeft="true"
android:background="#4499bbcc"
android:visibility="gone"
android:paddingTop="4dip"

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
>
<item
android:title="Help"
android:icon="@drawable/icon_help_color_40"
android:id="@+id/menu_help" />
<item
android:title="Don't Save"
android:icon="@drawable/icon_dontsave_color_40"
android:id="@+id/menu_dontsave" />
<item
android:title="Save"
android:icon="@drawable/icon_save_color_40"
android:id="@+id/menu_save" />
</menu>

@ -8,7 +8,7 @@
<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">Astrid: Home</string>
<string name="BFE_Active_title">Active Tasks</string>
<!-- Search -->
<string name="BFE_Search">Search</string>

@ -2,22 +2,54 @@
<!-- See the file "LICENSE" for the full license governing this code. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Resources for built-in locale plug-in -->
<!-- Resources for built-in locale plug-in -->
<!-- Locale Alert Editing Window Title -->
<string name="locale_edit_alerts_title">Astrid Tag Alert</string>
<string name="locale_edit_alerts_title">Astrid Filter Alert</string>
<!-- Locale Window Help -->
<string name="locale_edit_intro">Astrid will send you a reminder
when you have uncompleted tasks in the following filter:</string>
when you have any tasks in the following filter:</string>
<!-- Locale Window Filter Picker UI -->
<string name="locale_pick_filter">Filter:</string>
<!-- Locale Window Interval Label -->
<string name="locale_interval_label">Limit notifications to:</string>
<!-- Locale Window Interval Values -->
<string-array name="locale_interval">
<item>once an hour</item>
<item>once every six hours</item>
<item>once every twelve hours</item>
<item>once a day</item>
<item>once every three days</item>
<item>once a week</item>
</string-array>
<!-- Notification -->
<string name="locale_notification">You have $NUM tasks in $FILTER</string>
<!-- Locale style -->
<style name="TextAppearance.Locale_Label">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@android:color/black</item>
</style>
<!-- Locale filter list styles -->
<style name="TextAppearance.LEA_Filter">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@android:color/black</item>
</style>
<style name="TextAppearance.LEA_Header" parent="TextAppearance.LEA_Filter">
<item name="android:textSize">13sp</item>
<item name="android:textColor">#ffdddddd</item>
</style>
<style name="TextAppearance.LEA_Category" parent="TextAppearance.LEA_Filter">
<item name="android:textSize">13sp</item>
<item name="android:textStyle">bold|italic</item>
</style>
</resources>

@ -2,7 +2,7 @@
<!-- See the file "LICENSE" for the full license governing this code. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ============================ General ============================= -->
<!-- ========================================================== General -->
<style name="TextAppearance" parent="android:TextAppearance" />
@ -16,7 +16,7 @@
<item name="android:windowBackground">@null</item>
</style>
<!--========================= TaskListActivity ======================== -->
<!--=============================================== TaskListActivity == -->
<style name="TextAppearance.TLA_NoItems">
<item name="android:textSize">20sp</item>
@ -33,7 +33,7 @@
<item name="android:textColor">#000000</item>
</style>
<!-- =========================== TaskAdapter =========================== -->
<!-- ==================================================== TaskAdapter == -->
<style name="TextAppearance.TAd_ItemTitle">
</style>
@ -62,25 +62,24 @@
<item name="android:textColor">#ffee5555</item>
</style>
<!-- ======================== FilterListAdapter ======================== -->
<!-- ============================================== FilterListAdapter == -->
<style name="TextAppearance.FLA_Filter" parent="TextAppearance.TAd_ItemTitle">
<style name="TextAppearance.FLA_Filter">
<item name="android:textSize">20sp</item>
<item name="android:paddingLeft">10dip</item>
</style>
<style name="TextAppearance.FLA_Header" parent="TextAppearance.TAd_ItemTitle">
<style name="TextAppearance.FLA_Header" parent="TextAppearance.FLA_Filter">
<item name="android:textSize">18sp</item>
<item name="android:textColor">#ffdddddd</item>
</style>
<style name="TextAppearance.FLA_Category" parent="TextAppearance.TAd_ItemTitle">
<style name="TextAppearance.FLA_Category" parent="TextAppearance.FLA_Filter">
<item name="android:textSize">18sp</item>
<item name="android:textColor">#ff7ada24</item>
<item name="android:textStyle">bold|italic</item>
</style>
<!-- ======================== FilterListAdapter ======================== -->
<!-- ========================================================= Widget == -->
<style name="TextAppearance.Widget">
<item name="android:textSize">14sp</item>

@ -6,32 +6,28 @@ package com.todoroo.astrid.activity;
import android.app.AlertDialog;
import android.app.ExpandableListActivity;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.DisplayMetrics;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
@ -42,9 +38,7 @@ import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.adapter.FilterAdapter;
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.model.Task;
@ -77,7 +71,6 @@ public class FilterListActivity extends ExpandableListActivity {
protected DialogUtilities dialogUtilities;
FilterAdapter adapter = null;
FilterReceiver filterReceiver = new FilterReceiver();
/* ======================================================================
* ======================================================= initialization
@ -98,7 +91,6 @@ public class FilterListActivity extends ExpandableListActivity {
setTitle(R.string.FLA_title);
setUpList();
getLists();
onNewIntent(getIntent());
}
@ -169,44 +161,13 @@ public class FilterListActivity extends ExpandableListActivity {
@Override
protected void onResume() {
super.onResume();
registerReceiver(filterReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_SEND_FILTERS));
adapter.registerRecevier();
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(filterReceiver);
}
/**
* Receiver which receives intents to add items to the filter list
*
* @author Tim Su <tim@todoroo.com>
*
*/
protected class FilterReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
final Parcelable[] filters = intent.getExtras().
getParcelableArray(AstridApiConstants.EXTRAS_RESPONSE);
for (Parcelable item : filters) {
adapter.add((FilterListItem)item);
}
adapter.notifyDataSetChanged();
runOnUiThread(new Runnable() {
@Override
public void run() {
expandList(filters);
}
});
} catch (Exception e) {
exceptionService.reportError("receive-filter-" + //$NON-NLS-1$
intent.getStringExtra(AstridApiConstants.EXTRAS_ADDON), e);
}
}
adapter.unregisterRecevier();
}
/* ======================================================================
@ -215,39 +176,11 @@ public class FilterListActivity extends ExpandableListActivity {
/** Sets up the coach list adapter */
protected void setUpList() {
adapter = new FilterAdapter(this);
adapter = new FilterAdapter(this, getExpandableListView(),
R.layout.filter_adapter_row);
setListAdapter(adapter);
registerForContextMenu(getExpandableListView());
getExpandableListView().setGroupIndicator(
getResources().getDrawable(R.drawable.expander_group));
}
/**
* Expand the first category filter in this group
* @param filters
*/
protected void expandList(Parcelable[] filters) {
ExpandableListView list = getExpandableListView();
for(Parcelable filter : filters) {
if(filter instanceof FilterCategory) {
for(int i = 0; i < adapter.getGroupCount(); i++)
if(adapter.getGroup(i) == filter) {
list.expandGroup(i);
return;
}
}
}
}
/**
* Broadcast a request for lists. The request is sent to every
* application registered to listen for this broadcast. Each application
* can then add lists to this activity
*/
protected void getLists() {
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_FILTERS);
sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
/* ======================================================================

@ -6,35 +6,62 @@ package com.todoroo.astrid.adapter;
import java.util.ArrayList;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.os.Parcelable;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseExpandableListAdapter;
import android.widget.FrameLayout;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ImageView.ScaleType;
import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.FilterCategory;
import com.todoroo.astrid.api.FilterListHeader;
import com.todoroo.astrid.api.FilterListItem;
public class FilterAdapter extends BaseExpandableListAdapter {
private final ArrayList<FilterListItem> items;
// --- style constants
public int filterStyle = R.style.TextAppearance_FLA_Filter;
public int categoryStyle = R.style.TextAppearance_FLA_Category;
public int headerStyle = R.style.TextAppearance_FLA_Header;
// --- instance variables
protected final Activity activity;
protected final ExpandableListView listView;
private final ArrayList<FilterListItem> items;
private final DisplayMetrics metrics = new DisplayMetrics();
private final FilterReceiver filterReceiver = new FilterReceiver();
private final int layout;
private final LayoutInflater inflater;
public FilterAdapter(Activity activity) {
public FilterAdapter(Activity activity, ExpandableListView listView,
int rowLayout) {
super();
this.activity = activity;
this.items = new ArrayList<FilterListItem>();
this.listView = listView;
this.layout = rowLayout;
inflater = (LayoutInflater) activity.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
listView.setGroupIndicator(
activity.getResources().getDrawable(R.drawable.expander_group));
getLists();
}
public boolean hasStableIds() {
@ -49,6 +76,35 @@ public class FilterAdapter extends BaseExpandableListAdapter {
items.clear();
}
/**
* Create or reuse a view
* @param convertView
* @param parent
* @return
*/
protected View newView(View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = inflater.inflate(layout, parent, false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.view = convertView;
viewHolder.expander = (ImageView)convertView.findViewById(R.id.expander);
viewHolder.icon = (ImageView)convertView.findViewById(R.id.icon);
viewHolder.name = (TextView)convertView.findViewById(R.id.name);
viewHolder.selected = (ImageView)convertView.findViewById(R.id.selected);
convertView.setTag(viewHolder);
}
return convertView;
}
private class ViewHolder {
public FilterListItem item;
public ImageView expander;
public ImageView icon;
public TextView name;
public ImageView selected;
public View view;
}
/* ======================================================================
* ========================================================== child nodes
* ====================================================================== */
@ -74,9 +130,13 @@ public class FilterAdapter extends BaseExpandableListAdapter {
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
FilterListItem item = (FilterListItem)getChild(groupPosition, childPosition);
View textView = getStandardView(item, true);
return textView;
convertView = newView(convertView, parent);
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.item = (FilterListItem)getChild(groupPosition, childPosition);
populateView(viewHolder, true, false);
return convertView;
}
/* ======================================================================
@ -97,8 +157,11 @@ public class FilterAdapter extends BaseExpandableListAdapter {
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
View view = getView((FilterListItem)getGroup(groupPosition), false, isExpanded);
return view;
convertView = newView(convertView, parent);
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.item = (FilterListItem) getGroup(groupPosition);
populateView(viewHolder, false, isExpanded);
return convertView;
}
public boolean isChildSelectable(int groupPosition, int childPosition) {
@ -106,110 +169,156 @@ public class FilterAdapter extends BaseExpandableListAdapter {
}
/* ======================================================================
* ================================================================ views
* ============================================================ selection
* ====================================================================== */
public View getView(FilterListItem item, boolean isChild, boolean isExpanded) {
if(item instanceof FilterListHeader)
return getHeaderView((FilterListHeader)item, isChild);
else if(item instanceof FilterCategory)
return getCategoryView((FilterCategory)item, isExpanded);
else
return getStandardView(item, isChild);
}
public View getCategoryView(FilterCategory filter, boolean isExpanded) {
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
private FilterListItem selection = null;
FrameLayout layout = new FrameLayout(activity);
layout.setLayoutParams(lp);
ImageView image = new ImageView(activity);
if(isExpanded)
image.setImageResource(R.drawable.expander_ic_maximized);
/**
* Sets the selected item to this one
* @param picked
*/
public void setSelection(FilterListItem picked) {
if(picked == selection)
selection = null;
else
image.setImageResource(R.drawable.expander_ic_minimized);
FrameLayout.LayoutParams expansionImageLayout = new FrameLayout.LayoutParams(
32, 32);
expansionImageLayout.gravity = Gravity.CENTER_VERTICAL;
image.setPadding(5, 0, 5, 0);
image.setLayoutParams(expansionImageLayout);
image.setScaleType(ScaleType.FIT_CENTER);
layout.addView(image);
TextView textView = new TextView(activity);
textView.setGravity(Gravity.CENTER_VERTICAL);
textView.setText(filter.listingTitle);
textView.setTextAppearance(activity, R.style.TextAppearance_FLA_Category);
selection = picked;
int scroll = listView.getScrollY();
notifyDataSetInvalidated();
listView.scrollTo(0, scroll);
}
View view = augmentView(textView, filter);
view.setPadding((int) (33 * metrics.density), 5, 0, 5);
FrameLayout.LayoutParams rowLayout = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rowLayout.gravity = Gravity.CENTER_VERTICAL;
view.setLayoutParams(rowLayout);
/**
* Gets the currently selected item
* @return null if no item is to be selected
*/
public FilterListItem getSelection() {
return selection;
}
layout.addView(view);
/* ======================================================================
* ============================================================= receiver
* ====================================================================== */
return layout;
/**
* Receiver which receives intents to add items to the filter list
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class FilterReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
final Parcelable[] filters = intent.getExtras().
getParcelableArray(AstridApiConstants.EXTRAS_RESPONSE);
for (Parcelable item : filters) {
add((FilterListItem)item);
onReceiveFilter((FilterListItem)item);
}
notifyDataSetChanged();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
expandList(filters);
}
});
} catch (Exception e) {
Log.e("receive-filter-" + //$NON-NLS-1$
intent.getStringExtra(AstridApiConstants.EXTRAS_ADDON),
e.toString(), e);
}
}
}
/**
* Decorate textview and add an image if the filter requests it
* @param textView
* @param filter
* @return final view ready to be added
* Expand the first category filter in this group
* @param filters
*/
private View augmentView(TextView textView, FilterListItem filter) {
if(filter.listingIcon != null) {
LinearLayout layout = new LinearLayout(activity);
layout.setGravity(textView.getGravity());
layout.setOrientation(LinearLayout.HORIZONTAL);
ImageView icon = new ImageView(activity);
icon.setImageBitmap(filter.listingIcon);
icon.setScaleType(ScaleType.CENTER);
icon.setPadding(0, 0, 15, 0);
layout.addView(icon);
layout.addView(textView);
return layout;
protected void expandList(Parcelable[] filters) {
for(Parcelable filter : filters) {
if(filter instanceof FilterCategory) {
for(int i = 0; i < getGroupCount(); i++)
if(getGroup(i) == filter) {
listView.expandGroup(i);
return;
}
}
}
return textView;
}
public View getStandardView(FilterListItem filter, boolean isChild) {
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
/**
* Broadcast a request for lists. The request is sent to every
* application registered to listen for this broadcast. Each application
* can then add lists to this activity
*/
protected void getLists() {
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_REQUEST_FILTERS);
activity.sendOrderedBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
}
TextView textView = new TextView(activity);
textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
textView.setText(filter.listingTitle);
textView.setTextAppearance(activity, R.style.TextAppearance_FLA_Filter);
/**
* Call this method from your activity's onResume() method
*/
public void registerRecevier() {
activity.registerReceiver(filterReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_SEND_FILTERS));
}
View view = augmentView(textView, filter);
view.setBackgroundDrawable(null);
view.setLayoutParams(lp);
view.setPadding((int) ((isChild ? 27 : 7) * metrics.density), 8, 0, 8);
/**
* Call this method from your activity's onResume() method
*/
public void unregisterRecevier() {
activity.unregisterReceiver(filterReceiver);
}
return view;
/**
* Called when an item comes through. Override if you like
* @param item
*/
public void onReceiveFilter(FilterListItem item) {
// do nothing
}
public View getHeaderView(FilterListHeader header, boolean isChild) {
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
/* ======================================================================
* ================================================================ views
* ====================================================================== */
TextView textView = new TextView(activity);
textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
textView.setTextAppearance(activity, R.style.TextAppearance_FLA_Header);
textView.setText(header.listingTitle);
public void populateView(ViewHolder viewHolder, boolean isChild, boolean isExpanded) {
FilterListItem filter = viewHolder.item;
viewHolder.view.setBackgroundResource(0);
viewHolder.expander.setVisibility(View.GONE);
if(viewHolder.item instanceof FilterListHeader) {
viewHolder.name.setTextAppearance(activity, headerStyle);
viewHolder.view.setBackgroundResource(R.drawable.edit_titlebar);
viewHolder.view.setPadding((int) ((isChild ? 33 : 7) * metrics.density), 5, 0, 5);
} else if(viewHolder.item instanceof FilterCategory) {
viewHolder.expander.setVisibility(View.VISIBLE);
if(isExpanded)
viewHolder.expander.setImageResource(R.drawable.expander_ic_maximized);
else
viewHolder.expander.setImageResource(R.drawable.expander_ic_minimized);
viewHolder.name.setTextAppearance(activity, categoryStyle);
viewHolder.view.setPadding((int)(7 * metrics.density), 8, 0, 8);
} else {
viewHolder.name.setTextAppearance(activity, filterStyle);
viewHolder.view.setPadding((int) ((isChild ? 27 : 7) * metrics.density), 8, 0, 8);
}
viewHolder.icon.setVisibility(filter.listingIcon != null ? View.VISIBLE : View.GONE);
viewHolder.icon.setImageBitmap(filter.listingIcon);
View view = augmentView(textView, header);
view.setBackgroundResource(R.drawable.edit_titlebar);
view.setLayoutParams(lp);
view.setPadding((int) ((isChild ? 33 : 7) * metrics.density), 5, 0, 5);
viewHolder.name.setText(filter.listingTitle);
return view;
// selection
if(selection == viewHolder.item) {
viewHolder.selected.setVisibility(View.VISIBLE);
viewHolder.view.setBackgroundColor(Color.rgb(128, 230, 0));
} else
viewHolder.selected.setVisibility(View.GONE);
}
}

@ -16,6 +16,7 @@ import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
@ -71,9 +72,9 @@ public class TaskDao extends GenericDao<Task> {
Task.DELETION_DATE.eq(0));
}
/** @return tasks that are not hidden at given unixtime */
public static Criterion isVisible(long time) {
return Task.HIDE_UNTIL.lt(time);
/** @return tasks that are not hidden at current time */
public static Criterion isVisible() {
return Task.HIDE_UNTIL.lt(Functions.now());
}
/** @return tasks that have a due date */
@ -82,18 +83,18 @@ public class TaskDao extends GenericDao<Task> {
}
/** @return tasks that are due before a certain unixtime */
public static Criterion dueBefore(long time) {
return Criterion.and(Task.DUE_DATE.gt(0), Task.DUE_DATE.lt(time));
public static Criterion dueBeforeNow() {
return Criterion.and(Task.DUE_DATE.gt(0), Task.DUE_DATE.lt(Functions.now()));
}
/** @return tasks that are due after a certain unixtime */
public static Criterion dueAfter(long time) {
return Task.DUE_DATE.gt(time);
public static Criterion dueAfterNow() {
return Task.DUE_DATE.gt(Functions.now());
}
/** @return tasks completed before a given unixtime */
public static Criterion completedBefore(long time) {
return Criterion.and(Task.COMPLETION_DATE.gt(0), Task.COMPLETION_DATE.lt(time));
public static Criterion completed() {
return Criterion.and(Task.COMPLETION_DATE.gt(0), Task.COMPLETION_DATE.lt(Functions.now()));
}
/** @return tasks that have a blank or null title */

@ -21,6 +21,7 @@ import android.util.Log;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
import com.timsu.astrid.R;
import com.timsu.astrid.utilities.LegacyTasksXmlExporter;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.GenericDao;
import com.todoroo.andlib.data.Property;
@ -32,7 +33,6 @@ import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.alarms.Alarm;
import com.todoroo.astrid.alarms.AlarmDatabase;
import com.todoroo.astrid.backup.TasksXmlExporter;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao;
@ -42,6 +42,7 @@ import com.todoroo.astrid.rmilk.data.MilkTask;
import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.Preferences;
@SuppressWarnings("deprecation")
public class Astrid2To3UpgradeHelper {
@Autowired
@ -119,13 +120,7 @@ public class Astrid2To3UpgradeHelper {
dialog = dialogUtilities.progressDialog(context, context.getString(R.string.DLG_wait));
// initiate a backup
try {
TasksXmlExporter exporter = new TasksXmlExporter(true);
exporter.setContext(ContextManager.getContext());
exporter.exportTasks(TasksXmlExporter.getExportDirectory());
} catch (Exception e) {
// unable to create a backup before upgrading :(
}
legacyBackup();
database.openForWriting();
@ -192,6 +187,19 @@ public class Astrid2To3UpgradeHelper {
// --- database upgrade helpers
/**
* Create a legacy backup file
*/
private void legacyBackup() {
try {
LegacyTasksXmlExporter exporter = new LegacyTasksXmlExporter(true);
exporter.setContext(ContextManager.getContext());
exporter.exportTasks(LegacyTasksXmlExporter.getExportDirectory());
} catch (Exception e) {
// unable to create a backup before upgrading :(
}
}
protected static final class UpgradeVisitorContainer<TYPE extends AbstractModel> {
public int columnIndex;
public Cursor cursor;

@ -8,8 +8,8 @@ import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@ -19,7 +19,6 @@ import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.service.ExceptionService.TodorooUncaughtExceptionHandler;
import com.todoroo.astrid.backup.BackupService;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.utility.Constants;
@ -104,9 +103,6 @@ public class StartupService {
am.setInexactRepeating(AlarmManager.RTC, 0,
Constants.WIDGET_UPDATE_INTERVAL, pendingIntent);
// start backup service
BackupService.scheduleService(context);
database.openForWriting();
taskService.cleanup();
}

@ -83,7 +83,7 @@ public class Preferences {
return PreferenceManager.getDefaultSharedPreferences(context);
}
// --- preference fetching
// --- preference fetching (string)
/** Gets an string value from a string preference. Returns null
* if the value is not set
@ -167,6 +167,28 @@ public class Preferences {
}
}
/**
* Sets string preference
*/
public static void setString(int keyResource, String newValue) {
Context context = ContextManager.getContext();
Editor editor = getPrefs(context).edit();
editor.putString(context.getString(keyResource), newValue);
editor.commit();
}
/**
* Sets string preference from integer value
*/
public static void setStringFromInteger(int keyResource, int newValue) {
Context context = ContextManager.getContext();
Editor editor = getPrefs(context).edit();
editor.putString(context.getString(keyResource), Integer.toString(newValue));
editor.commit();
}
// --- preference fetching (boolean)
/** Gets a boolean preference (e.g. a CheckBoxPreference setting)
*
* @param key
@ -200,23 +222,31 @@ public class Preferences {
editor.commit();
}
/**
* Sets string preference
// --- preference fetching (long)
/** Gets a long preference
*
* @param key
* @param defValue
* @return default if value is unset otherwise the value
*/
public static void setString(int keyResource, String newValue) {
public static long getLong(String key, long defValue) {
Context context = ContextManager.getContext();
Editor editor = getPrefs(context).edit();
editor.putString(context.getString(keyResource), newValue);
editor.commit();
return getPrefs(context).getLong(key, defValue);
}
/**
* Sets string preference from integer value
* Sets long preference
* @param key
* @param value
*/
public static void setStringFromInteger(int keyResource, int newValue) {
public static void setLong(String key, long value) {
Context context = ContextManager.getContext();
Editor editor = getPrefs(context).edit();
editor.putString(context.getString(keyResource), Integer.toString(newValue));
editor.putLong(key, value);
editor.commit();
}
}

@ -131,24 +131,24 @@ public class TaskDaoTests extends DatabaseTestCase {
// check due before / after
cursor = taskDao.query(Query.select(TITLES).where(TaskCriteria.
dueBefore(DateUtilities.now())));
dueBeforeNow()));
cursor.moveToNext();
assertEquals(1, cursor.getCount());
cursor.close();
cursor = taskDao.query(Query.select(TITLES).where(TaskCriteria.
dueAfter(DateUtilities.now())));
dueAfterNow()));
assertEquals(1, cursor.getCount());
cursor.close();
// check completed before
cursor = taskDao.query(Query.select(TITLES).where(TaskCriteria.
completedBefore(DateUtilities.now())));
completed()));
assertEquals(1, cursor.getCount());
cursor.close();
// check is visible
cursor = taskDao.query(Query.select(TITLES).where(TaskCriteria.
isVisible(DateUtilities.now())));
isVisible()));
assertEquals(5, cursor.getCount());
cursor.close();
}

Loading…
Cancel
Save