diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3fbe3773f..a31f6101c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,9 +4,7 @@
android:versionCode="101"
android:versionName="2.7.3">
-
+
@@ -18,9 +16,7 @@
-
+
@@ -70,12 +66,17 @@
-
+
+
+
+
+
+
+
+
@@ -83,9 +84,7 @@
-
+
diff --git a/res/drawable/bubble_blue.png b/res/drawable/bubble_blue.png
deleted file mode 100644
index e82e1acfb..000000000
Binary files a/res/drawable/bubble_blue.png and /dev/null differ
diff --git a/res/drawable/bubble_gray.png b/res/drawable/bubble_gray.png
deleted file mode 100644
index ac071326a..000000000
Binary files a/res/drawable/bubble_gray.png and /dev/null differ
diff --git a/res/drawable/bubble_red.png b/res/drawable/bubble_red.png
deleted file mode 100644
index e639565ad..000000000
Binary files a/res/drawable/bubble_red.png and /dev/null differ
diff --git a/res/drawable/bubble_yellow.png b/res/drawable/bubble_yellow.png
deleted file mode 100644
index c4a2ee2c3..000000000
Binary files a/res/drawable/bubble_yellow.png and /dev/null differ
diff --git a/res/drawable/notif_tag.png b/res/drawable/notif_tag.png
new file mode 100644
index 000000000..a519a5e58
Binary files /dev/null and b/res/drawable/notif_tag.png differ
diff --git a/src/com/timsu/astrid/activities/LocaleEditAlerts.java b/src/com/timsu/astrid/activities/LocaleEditAlerts.java
index 40659b8fa..d1b3af1e4 100644
--- a/src/com/timsu/astrid/activities/LocaleEditAlerts.java
+++ b/src/com/timsu/astrid/activities/LocaleEditAlerts.java
@@ -28,11 +28,13 @@ import com.timsu.astrid.data.tag.TagModelForView;
public final class LocaleEditAlerts extends Activity {
/** value for action type for tag alert */
- public static final String ACTION_TAG_ALERT = "com.timsu.astrid.action.TAG_ALERT";
+ public static final String ACTION_LOCALE_ALERT = "com.timsu.astrid.action.LOCALE_ALERT";
- /** key name for tag name in bundle */
- public static final String KEY_TAG_NAME = "tag";
+ /** key name for tag id/name in bundle */
+ public static final String KEY_TAG_ID = "tag";
+ public static final String KEY_TAG_NAME = "name";
+ private LinkedList tags = null;
private String[] tagNames = null;
/** Called when the activity is first created. */
@@ -59,7 +61,7 @@ public final class LocaleEditAlerts extends Activity {
TagController tagController = new TagController(this);
tagController.open();
- LinkedList tags = tagController.getAllTags();
+ tags = tagController.getAllTags();
tagController.close();
tagNames = new String[tags.size()];
@@ -91,7 +93,9 @@ public final class LocaleEditAlerts extends Activity {
* Private helper method to persist the Toast message in the return {@code Intent}.
*/
private void updateResult() {
- final String tagName = (String)((Spinner) findViewById(R.id.spinner)).getSelectedItem();
+ final int index = ((Spinner) findViewById(R.id.spinner)).getSelectedItemPosition();
+ final String tagName = tagNames[index];
+ final TagModelForView tag = tags.get(index);
/*
* If the message is of 0 length, then there isn't a setting to save
@@ -101,7 +105,8 @@ public final class LocaleEditAlerts extends Activity {
} else {
final Intent intent = new Intent();
intent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_ACTION_FIRE,
- ACTION_TAG_ALERT);
+ 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);
diff --git a/src/com/timsu/astrid/activities/TagListSubActivity.java b/src/com/timsu/astrid/activities/TagListSubActivity.java
index 41358830c..8457c2d99 100644
--- a/src/com/timsu/astrid/activities/TagListSubActivity.java
+++ b/src/com/timsu/astrid/activities/TagListSubActivity.java
@@ -126,33 +126,32 @@ public class TagListSubActivity extends SubActivity {
abstract int compareTo(TagListSubActivity self, TagModelForView arg0, TagModelForView arg1);
};
+ /** Counts how many tasks appear in active task list */
+ public static int countActiveTasks(HashSet activeTasks, LinkedList tasks) {
+ int count = 0;
+ if(tasks != null) {
+ for(TaskIdentifier task : tasks)
+ if(activeTasks.contains(task))
+ count++;
+ }
+ return count;
+ }
+
private synchronized void sortTagArray() {
// get all tasks
HashSet activeTasks =
getTaskController().getActiveTaskIdentifiers();
+ if(activeTasks == null)
+ activeTasks = new HashSet();
// get task count for each tag
tagToTaskCount = new HashMap();
+
for(TagModelForView tag : tagArray) {
LinkedList tasks;
-
- // Tagged vs. Untagged tasks
- boolean taggedTask = !tag.getTagIdentifier().equals(TagModelForView.UNTAGGED_IDENTIFIER);
- if (taggedTask)
- tasks = getTagController().getTaggedTasks(tag.getTagIdentifier());
- else
- tasks = getTagController().getUntaggedTasks(activeTasks);
-
- int count = 0;
- for(TaskIdentifier task : tasks)
- if(activeTasks.contains(task))
- count++;
-
- // don't show Untagged if there aren't any untagged tasks
- if (taggedTask || count!=0)
- tagToTaskCount.put(tag, count);
- else
- tagArray.remove(tag);
+ tasks = getTagController().getTaggedTasks(tag.getTagIdentifier());
+ int count = countActiveTasks(activeTasks, tasks);
+ tagToTaskCount.put(tag, count);
}
// do sort
@@ -161,6 +160,14 @@ public class TagListSubActivity extends SubActivity {
return sortMode.compareTo(TagListSubActivity.this, arg0, arg1);
}
});
+
+ // show "untagged" as a category at the top, in the proper language/localization
+ String untaggedLabel = getResources().getString(R.string.tagList_untagged);
+ TagModelForView untaggedModel = TagModelForView.getUntaggedModel(untaggedLabel);
+ tagArray.addFirst(untaggedModel);
+ int count = countActiveTasks(activeTasks, getTagController().getUntaggedTasks());
+ tagToTaskCount.put(untaggedModel, count);
+
if(sortReverse)
Collections.reverse(tagArray);
}
@@ -172,9 +179,6 @@ public class TagListSubActivity extends SubActivity {
try {
tagArray = getTagController().getAllTags();
- // Show "Untagged" as a category, in the proper language/localization
- String untaggedLabel = getResources().getString(R.string.tagList_untagged);
- tagArray.add(TagModelForView.getUntaggedModel(untaggedLabel));
sortTagArray(); // count and sort each tag
} catch (StaleDataException e) {
// happens when you rotate the screen while the thread is
diff --git a/src/com/timsu/astrid/activities/TaskListSubActivity.java b/src/com/timsu/astrid/activities/TaskListSubActivity.java
index 6c2c66a55..4901e441e 100644
--- a/src/com/timsu/astrid/activities/TaskListSubActivity.java
+++ b/src/com/timsu/astrid/activities/TaskListSubActivity.java
@@ -23,7 +23,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
@@ -494,9 +493,7 @@ public class TaskListSubActivity extends SubActivity {
if (!tagId.equals(TagModelForView.UNTAGGED_IDENTIFIER)) {
tasks = getTagController().getTaggedTasks(tagId);
} else {
- HashSet activeTasks =
- getTaskController().getActiveTaskIdentifiers();
- tasks = getTagController().getUntaggedTasks(activeTasks);
+ tasks = getTagController().getUntaggedTasks();
}
tasksCursor = getTaskController().getTaskListCursorById(tasks);
diff --git a/src/com/timsu/astrid/data/enums/Importance.java b/src/com/timsu/astrid/data/enums/Importance.java
index ddfbdd162..b7082d225 100644
--- a/src/com/timsu/astrid/data/enums/Importance.java
+++ b/src/com/timsu/astrid/data/enums/Importance.java
@@ -26,20 +26,16 @@ public enum Importance {
LEVEL_1(R.string.importance_1,
R.color.importance_1,
- R.color.task_list_importance_1,
- R.drawable.bubble_red),
+ R.color.task_list_importance_1),
LEVEL_2(R.string.importance_2,
R.color.importance_2,
- R.color.task_list_importance_2,
- R.drawable.bubble_yellow),
+ R.color.task_list_importance_2),
LEVEL_3(R.string.importance_3,
R.color.importance_3,
- R.color.task_list_importance_3,
- R.drawable.bubble_blue),
+ R.color.task_list_importance_3),
LEVEL_4(R.string.importance_4,
R.color.importance_4,
- R.color.task_list_importance_4,
- R.drawable.bubble_gray),
+ R.color.task_list_importance_4),
// LEAST IMPORTANT
;
@@ -47,14 +43,12 @@ public enum Importance {
int label;
int color;
int taskListColor;
- int icon;
public static final Importance DEFAULT = LEVEL_3;
- private Importance(int label, int color, int taskListColor, int icon) {
+ private Importance(int label, int color, int taskListColor) {
this.label = label;
this.color = color;
this.taskListColor = taskListColor;
- this.icon = icon;
}
public int getLabelResource() {
@@ -69,7 +63,4 @@ public enum Importance {
return taskListColor;
}
- public int getIconResource() {
- return icon;
- }
}
diff --git a/src/com/timsu/astrid/data/tag/TagController.java b/src/com/timsu/astrid/data/tag/TagController.java
index 31856a8e8..facebba99 100644
--- a/src/com/timsu/astrid/data/tag/TagController.java
+++ b/src/com/timsu/astrid/data/tag/TagController.java
@@ -20,12 +20,12 @@
package com.timsu.astrid.data.tag;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedList;
-import android.app.Activity;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
+import android.database.CursorJoiner;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
@@ -33,6 +33,7 @@ import com.timsu.astrid.data.AbstractController;
import com.timsu.astrid.data.tag.AbstractTagModel.TagModelDatabaseHelper;
import com.timsu.astrid.data.tag.TagToTaskMapping.TagToTaskMappingDatabaseHelper;
import com.timsu.astrid.data.task.TaskIdentifier;
+import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper;
/** Controller for Tag-related operations */
public class TagController extends AbstractController {
@@ -118,7 +119,6 @@ public class TagController extends AbstractController {
return list;
}
-
/** Returns a list of task identifiers in the provided set that are UNtagged.
*
* The calling SubActivity must provide the set of tasks, since
@@ -127,26 +127,43 @@ public class TagController extends AbstractController {
* The current implementation is not very efficient, because queries
* the TagToTask map once for each active task.
**/
- public LinkedList getUntaggedTasks(HashSet
- activeTasks) throws SQLException {
-
+ public LinkedList getUntaggedTasks() throws SQLException {
LinkedList list = new LinkedList();
- for (TaskIdentifier taskId : activeTasks) {
- Cursor cursor;
- cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
- TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TASK + " = ?",
- new String[] { taskId.idAsString() }, null, null, null);
+ String[] tagMapColumns = new String[] { TagToTaskMapping.TASK };
+ Cursor tagMapCursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
+ tagMapColumns, null, null, TagToTaskMapping.TASK, null,
+ TagToTaskMapping.TASK + " ASC");
- if (cursor.getCount() == 0) {
- list.add(taskId);
- }
- cursor.close();
- }
+ SQLiteDatabase taskDatabase = new TaskModelDatabaseHelper(context,
+ TASK_TABLE_NAME, TASK_TABLE_NAME).getReadableDatabase();
+ String[] taskColumns = new String[] { KEY_ROWID };
+ Cursor taskCursor = taskDatabase.query(TASK_TABLE_NAME, taskColumns,
+ null, null, null, null, KEY_ROWID + " ASC");
- // Equivalent SQL Query:
- // SELECT * FROM tasks_tbl LEFT OUTER JOIN mapping_tbl ON tasks_tbl.id =
- // mapping_tbl.task_id WHERE mapping_tbl.task_id ISNULL;
+ try {
+ CursorJoiner joiner = new CursorJoiner(taskCursor, taskColumns,
+ tagMapCursor, tagMapColumns);
+ for (CursorJoiner.Result joinerResult : joiner) {
+ switch (joinerResult) {
+ case LEFT:
+ long taskId = taskCursor.getLong(0);
+
+ // conditional is necessary for handling deleted tasks
+ if(tagMapCursor.isLast() || tagMapCursor.getLong(0) > taskId) {
+ list.add(new TaskIdentifier(taskId));
+ }
+ break;
+ default:
+ // in other cases, do nothing
+ break;
+ }
+ }
+ } finally {
+ taskCursor.close();
+ tagMapCursor.close();
+ taskDatabase.close();
+ }
return list;
}
@@ -261,8 +278,8 @@ public class TagController extends AbstractController {
* Constructor - takes the context to allow the database to be
* opened/created
*/
- public TagController(Activity activity) {
- this.context = activity;
+ public TagController(Context context) {
+ this.context = context;
}
/**
diff --git a/src/com/timsu/astrid/data/task/AbstractTaskModel.java b/src/com/timsu/astrid/data/task/AbstractTaskModel.java
index 0d796a753..b022f2a29 100644
--- a/src/com/timsu/astrid/data/task/AbstractTaskModel.java
+++ b/src/com/timsu/astrid/data/task/AbstractTaskModel.java
@@ -51,27 +51,28 @@ public abstract class AbstractTaskModel extends AbstractModel {
// field names
- static final String NAME = "name";
- static final String NOTES = "notes";
- static final String PROGRESS_PERCENTAGE = "progressPercentage";
- static final String IMPORTANCE = "importance";
- static final String ESTIMATED_SECONDS = "estimatedSeconds";
- static final String ELAPSED_SECONDS = "elapsedSeconds";
- static final String TIMER_START = "timerStart";
- static final String DEFINITE_DUE_DATE = "definiteDueDate";
- static final String PREFERRED_DUE_DATE = "preferredDueDate";
- static final String HIDDEN_UNTIL = "hiddenUntil";
- static final String POSTPONE_COUNT = "postponeCount";
- static final String NOTIFICATIONS = "notifications";
- static final String NOTIFICATION_FLAGS = "notificationFlags";
- static final String LAST_NOTIFIED = "lastNotified";
- static final String REPEAT = "repeat";
- static final String CREATION_DATE = "creationDate";
- static final String COMPLETION_DATE = "completionDate";
- static final String CALENDAR_URI = "calendarUri";
- static final String FLAGS = "flags";
+ public static final String NAME = "name";
+ public static final String NOTES = "notes";
+ public static final String PROGRESS_PERCENTAGE = "progressPercentage";
+ public static final String IMPORTANCE = "importance";
+ public static final String ESTIMATED_SECONDS = "estimatedSeconds";
+ public static final String ELAPSED_SECONDS = "elapsedSeconds";
+ public static final String TIMER_START = "timerStart";
+ public static final String DEFINITE_DUE_DATE = "definiteDueDate";
+ public static final String PREFERRED_DUE_DATE = "preferredDueDate";
+ public static final String HIDDEN_UNTIL = "hiddenUntil";
+ public static final String POSTPONE_COUNT = "postponeCount";
+ public static final String NOTIFICATIONS = "notifications";
+ public static final String NOTIFICATION_FLAGS = "notificationFlags";
+ public static final String LAST_NOTIFIED = "lastNotified";
+ public static final String REPEAT = "repeat";
+ public static final String CREATION_DATE = "creationDate";
+ public static final String COMPLETION_DATE = "completionDate";
+ public static final String CALENDAR_URI = "calendarUri";
+ public static final String FLAGS = "flags";
+
// reserved fields ---
- static final String BLOCKING_ON = "blockingOn";
+ public static final String BLOCKING_ON = "blockingOn";
// notification flags
public static final int NOTIFY_BEFORE_DEADLINE = 1 << 0;
@@ -113,10 +114,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
// --- database helper
/** Database Helper manages creating new tables and updating old ones */
- static class TaskModelDatabaseHelper extends SQLiteOpenHelper {
+ public static class TaskModelDatabaseHelper extends SQLiteOpenHelper {
String tableName;
- TaskModelDatabaseHelper(Context context, String databaseName, String tableName) {
+ public TaskModelDatabaseHelper(Context context, String databaseName, String tableName) {
super(context, databaseName, null, VERSION);
this.tableName = tableName;
}
diff --git a/src/com/timsu/astrid/utilities/LocaleReceiver.java b/src/com/timsu/astrid/utilities/LocaleReceiver.java
new file mode 100644
index 000000000..7652e9298
--- /dev/null
+++ b/src/com/timsu/astrid/utilities/LocaleReceiver.java
@@ -0,0 +1,64 @@
+package com.timsu.astrid.utilities;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.timsu.astrid.R;
+import com.timsu.astrid.activities.LocaleEditAlerts;
+import com.timsu.astrid.activities.TagListSubActivity;
+import com.timsu.astrid.data.tag.TagController;
+import com.timsu.astrid.data.tag.TagIdentifier;
+import com.timsu.astrid.data.task.TaskController;
+import com.timsu.astrid.data.task.TaskIdentifier;
+
+/**
+ * Receiver is activated when Locale conditions are triggered
+ *
+ * @author timsu
+ *
+ */
+public class LocaleReceiver extends BroadcastReceiver {
+
+ @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;
+ }
+
+ // find out if we have active tasks with this tag
+ TaskController taskController = new TaskController(context);
+ taskController.open();
+ TagController tagController = new TagController(context);
+ tagController.open();
+ try {
+ HashSet activeTasks = taskController.getActiveTaskIdentifiers();
+ LinkedList tasks = tagController.getTaggedTasks(
+ new TagIdentifier(tagId));
+ int count = TagListSubActivity.countActiveTasks(activeTasks, tasks);
+ if(count > 0) {
+ String reminder = context.getResources().getString(
+ R.string.notif_tagNotification, count, tagName);
+ Notifications.showTagNotification(context, tagId, reminder);
+ }
+ } finally {
+ taskController.close();
+ tagController.close();
+ }
+ }
+ } catch (Exception e) {
+ Log.e("astrid-localerx", "Error receiving intent", e);
+ }
+ }
+
+}
diff --git a/src/com/timsu/astrid/utilities/Notifications.java b/src/com/timsu/astrid/utilities/Notifications.java
index 51b71e1ed..19bb5efa7 100644
--- a/src/com/timsu/astrid/utilities/Notifications.java
+++ b/src/com/timsu/astrid/utilities/Notifications.java
@@ -34,6 +34,7 @@ public class Notifications extends BroadcastReceiver {
private static final String ID_KEY = "id";
private static final String FLAGS_KEY = "flags";
private static final String REPEAT_KEY = "repeat";
+ private static final int TAG_ID_OFFSET = 100000;
// stuff for scheduling
/** minimum # of seconds before a deadline to notify */
@@ -516,5 +517,68 @@ public class Notifications extends BroadcastReceiver {
return true;
}
+ /** Schedule a new notification about the given tag. */
+ public static boolean showTagNotification(Context context, long tagId,
+ String reminder) {
+ // quiet hours? only for periodic reminders
+ boolean quietHours = false;
+ Integer quietHoursStart = Preferences.getQuietHourStart(context);
+ Integer quietHoursEnd = Preferences.getQuietHourEnd(context);
+ if(quietHoursStart != null && quietHoursEnd != null) {
+ int hour = new Date().getHours();
+ if(quietHoursStart < quietHoursEnd) {
+ if(hour >= quietHoursStart && hour < quietHoursEnd)
+ quietHours = true;
+ } else { // wrap across 24/hour boundary
+ if(hour >= quietHoursStart || hour < quietHoursEnd)
+ quietHours = true;
+ }
+ }
+
+ NotificationManager nm = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ Resources r = context.getResources();
+
+ Intent notifyIntent = new Intent(context, TaskListNotify.class);
+ notifyIntent.putExtra(TaskListSubActivity.TAG_TOKEN, tagId);
+ notifyIntent.putExtra(TaskListSubActivity.FROM_NOTIFICATION_TOKEN, true);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context,
+ TAG_ID_OFFSET + (int)tagId, notifyIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ // set up properties (name and icon) for the notification
+ String appName = r.getString(R.string.app_name);
+ int icon = R.drawable.notif_tag;
+
+ // create notification object
+ Notification notification = new Notification(
+ icon, reminder, System.currentTimeMillis());
+ notification.setLatestEventInfo(context,
+ appName,
+ reminder,
+ pendingIntent);
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.ledARGB = Color.BLUE;
+ notification.defaults = Notification.DEFAULT_LIGHTS;
+
+ if(quietHours) {
+ notification.vibrate = null;
+ notification.sound = null;
+ } else {
+ notification.defaults |= Notification.DEFAULT_VIBRATE;
+ Uri notificationSound = Preferences.getNotificationRingtone(context);
+ if(notificationSound != null &&
+ !notificationSound.toString().equals("")) {
+ notification.sound = notificationSound;
+ } else {
+ notification.defaults |= Notification.DEFAULT_SOUND;
+ }
+ }
+
+ if(Constants.DEBUG)
+ Log.w("Astrid", "Logging tag notification: " + reminder);
+ nm.notify(TAG_ID_OFFSET + (int)tagId, notification);
+
+ return true;
+ }
}
\ No newline at end of file