Added Locale Edit Alerts and fixed up the "untagged" tag list option. Little code cleanup here and there.

pull/14/head
Tim Su 17 years ago
parent cb0d492fcd
commit a91baa02c3

@ -4,9 +4,7 @@
android:versionCode="101" android:versionCode="101"
android:versionName="2.7.3"> android:versionName="2.7.3">
<!-- ############################################################## <!-- ============================ Metadata ============================ -->
Metadata
############################################################## -->
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
@ -18,9 +16,7 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"> <application android:icon="@drawable/icon" android:label="@string/app_name">
<!-- ############################################################## <!-- ======================== Activities ========================= -->
Activities
############################################################## -->
<!-- Activity that displays the task list --> <!-- Activity that displays the task list -->
<activity android:name=".activities.TaskList"> <activity android:name=".activities.TaskList">
@ -70,12 +66,17 @@
</intent-filter> </intent-filter>
</activity> </activity>
<!-- ############################################################## <!-- ======================== Receivers ========================= -->
Receivers
############################################################## -->
<receiver android:name=".utilities.Notifications" /> <receiver android:name=".utilities.Notifications" />
<receiver android:name=".utilities.LocaleReceiver">
<intent-filter>
<action android:name="com.timsu.astrid.action.LOCALE_ALERT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name=".utilities.StartupReceiver"> <receiver android:name=".utilities.StartupReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
@ -83,9 +84,7 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- ############################################################## <!-- ======================== Services ========================== -->
Services
############################################################## -->
<service android:name=".sync.SynchronizationService"/> <service android:name=".sync.SynchronizationService"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -28,11 +28,13 @@ import com.timsu.astrid.data.tag.TagModelForView;
public final class LocaleEditAlerts extends Activity { public final class LocaleEditAlerts extends Activity {
/** value for action type for tag alert */ /** 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 */ /** key name for tag id/name in bundle */
public static final String KEY_TAG_NAME = "tag"; public static final String KEY_TAG_ID = "tag";
public static final String KEY_TAG_NAME = "name";
private LinkedList<TagModelForView> tags = null;
private String[] tagNames = null; private String[] tagNames = null;
/** Called when the activity is first created. */ /** Called when the activity is first created. */
@ -59,7 +61,7 @@ public final class LocaleEditAlerts extends Activity {
TagController tagController = new TagController(this); TagController tagController = new TagController(this);
tagController.open(); tagController.open();
LinkedList<TagModelForView> tags = tagController.getAllTags(); tags = tagController.getAllTags();
tagController.close(); tagController.close();
tagNames = new String[tags.size()]; 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 helper method to persist the Toast message in the return {@code Intent}.
*/ */
private void updateResult() { 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 * 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 { } else {
final Intent intent = new Intent(); final Intent intent = new Intent();
intent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_ACTION_FIRE, 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(KEY_TAG_NAME, tagName);
intent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BLURB, tagName); intent.putExtra(com.twofortyfouram.Intent.EXTRA_STRING_BLURB, tagName);
setResult(RESULT_OK, intent); setResult(RESULT_OK, intent);

@ -126,33 +126,32 @@ public class TagListSubActivity extends SubActivity {
abstract int compareTo(TagListSubActivity self, TagModelForView arg0, TagModelForView arg1); abstract int compareTo(TagListSubActivity self, TagModelForView arg0, TagModelForView arg1);
}; };
/** Counts how many tasks appear in active task list */
public static int countActiveTasks(HashSet<TaskIdentifier> activeTasks, LinkedList<TaskIdentifier> tasks) {
int count = 0;
if(tasks != null) {
for(TaskIdentifier task : tasks)
if(activeTasks.contains(task))
count++;
}
return count;
}
private synchronized void sortTagArray() { private synchronized void sortTagArray() {
// get all tasks // get all tasks
HashSet<TaskIdentifier> activeTasks = HashSet<TaskIdentifier> activeTasks =
getTaskController().getActiveTaskIdentifiers(); getTaskController().getActiveTaskIdentifiers();
if(activeTasks == null)
activeTasks = new HashSet<TaskIdentifier>();
// get task count for each tag // get task count for each tag
tagToTaskCount = new HashMap<TagModelForView, Integer>(); tagToTaskCount = new HashMap<TagModelForView, Integer>();
for(TagModelForView tag : tagArray) { for(TagModelForView tag : tagArray) {
LinkedList<TaskIdentifier> tasks; LinkedList<TaskIdentifier> tasks;
tasks = getTagController().getTaggedTasks(tag.getTagIdentifier());
// Tagged vs. Untagged tasks int count = countActiveTasks(activeTasks, tasks);
boolean taggedTask = !tag.getTagIdentifier().equals(TagModelForView.UNTAGGED_IDENTIFIER); tagToTaskCount.put(tag, count);
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);
} }
// do sort // do sort
@ -161,6 +160,14 @@ public class TagListSubActivity extends SubActivity {
return sortMode.compareTo(TagListSubActivity.this, arg0, arg1); 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) if(sortReverse)
Collections.reverse(tagArray); Collections.reverse(tagArray);
} }
@ -172,9 +179,6 @@ public class TagListSubActivity extends SubActivity {
try { try {
tagArray = getTagController().getAllTags(); 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 sortTagArray(); // count and sort each tag
} catch (StaleDataException e) { } catch (StaleDataException e) {
// happens when you rotate the screen while the thread is // happens when you rotate the screen while the thread is

@ -23,7 +23,6 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
@ -494,9 +493,7 @@ public class TaskListSubActivity extends SubActivity {
if (!tagId.equals(TagModelForView.UNTAGGED_IDENTIFIER)) { if (!tagId.equals(TagModelForView.UNTAGGED_IDENTIFIER)) {
tasks = getTagController().getTaggedTasks(tagId); tasks = getTagController().getTaggedTasks(tagId);
} else { } else {
HashSet<TaskIdentifier> activeTasks = tasks = getTagController().getUntaggedTasks();
getTaskController().getActiveTaskIdentifiers();
tasks = getTagController().getUntaggedTasks(activeTasks);
} }
tasksCursor = getTaskController().getTaskListCursorById(tasks); tasksCursor = getTaskController().getTaskListCursorById(tasks);

@ -26,20 +26,16 @@ public enum Importance {
LEVEL_1(R.string.importance_1, LEVEL_1(R.string.importance_1,
R.color.importance_1, R.color.importance_1,
R.color.task_list_importance_1, R.color.task_list_importance_1),
R.drawable.bubble_red),
LEVEL_2(R.string.importance_2, LEVEL_2(R.string.importance_2,
R.color.importance_2, R.color.importance_2,
R.color.task_list_importance_2, R.color.task_list_importance_2),
R.drawable.bubble_yellow),
LEVEL_3(R.string.importance_3, LEVEL_3(R.string.importance_3,
R.color.importance_3, R.color.importance_3,
R.color.task_list_importance_3, R.color.task_list_importance_3),
R.drawable.bubble_blue),
LEVEL_4(R.string.importance_4, LEVEL_4(R.string.importance_4,
R.color.importance_4, R.color.importance_4,
R.color.task_list_importance_4, R.color.task_list_importance_4),
R.drawable.bubble_gray),
// LEAST IMPORTANT // LEAST IMPORTANT
; ;
@ -47,14 +43,12 @@ public enum Importance {
int label; int label;
int color; int color;
int taskListColor; int taskListColor;
int icon;
public static final Importance DEFAULT = LEVEL_3; 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.label = label;
this.color = color; this.color = color;
this.taskListColor = taskListColor; this.taskListColor = taskListColor;
this.icon = icon;
} }
public int getLabelResource() { public int getLabelResource() {
@ -69,7 +63,4 @@ public enum Importance {
return taskListColor; return taskListColor;
} }
public int getIconResource() {
return icon;
}
} }

@ -20,12 +20,12 @@
package com.timsu.astrid.data.tag; package com.timsu.astrid.data.tag;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import android.app.Activity;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.CursorJoiner;
import android.database.SQLException; import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase; 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.AbstractTagModel.TagModelDatabaseHelper;
import com.timsu.astrid.data.tag.TagToTaskMapping.TagToTaskMappingDatabaseHelper; import com.timsu.astrid.data.tag.TagToTaskMapping.TagToTaskMappingDatabaseHelper;
import com.timsu.astrid.data.task.TaskIdentifier; import com.timsu.astrid.data.task.TaskIdentifier;
import com.timsu.astrid.data.task.AbstractTaskModel.TaskModelDatabaseHelper;
/** Controller for Tag-related operations */ /** Controller for Tag-related operations */
public class TagController extends AbstractController { public class TagController extends AbstractController {
@ -118,7 +119,6 @@ public class TagController extends AbstractController {
return list; return list;
} }
/** Returns a list of task identifiers in the provided set that are UNtagged. /** Returns a list of task identifiers in the provided set that are UNtagged.
* *
* The calling SubActivity must provide the set of tasks, since * 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 current implementation is not very efficient, because queries
* the TagToTask map once for each active task. * the TagToTask map once for each active task.
**/ **/
public LinkedList<TaskIdentifier> getUntaggedTasks(HashSet<TaskIdentifier> public LinkedList<TaskIdentifier> getUntaggedTasks() throws SQLException {
activeTasks) throws SQLException {
LinkedList<TaskIdentifier> list = new LinkedList<TaskIdentifier>(); LinkedList<TaskIdentifier> list = new LinkedList<TaskIdentifier>();
for (TaskIdentifier taskId : activeTasks) { String[] tagMapColumns = new String[] { TagToTaskMapping.TASK };
Cursor cursor; Cursor tagMapCursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME,
cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME, tagMapColumns, null, null, TagToTaskMapping.TASK, null,
TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TASK + " = ?", TagToTaskMapping.TASK + " ASC");
new String[] { taskId.idAsString() }, null, null, null);
if (cursor.getCount() == 0) { SQLiteDatabase taskDatabase = new TaskModelDatabaseHelper(context,
list.add(taskId); TASK_TABLE_NAME, TASK_TABLE_NAME).getReadableDatabase();
} String[] taskColumns = new String[] { KEY_ROWID };
cursor.close(); Cursor taskCursor = taskDatabase.query(TASK_TABLE_NAME, taskColumns,
} null, null, null, null, KEY_ROWID + " ASC");
// Equivalent SQL Query: try {
// SELECT * FROM tasks_tbl LEFT OUTER JOIN mapping_tbl ON tasks_tbl.id = CursorJoiner joiner = new CursorJoiner(taskCursor, taskColumns,
// mapping_tbl.task_id WHERE mapping_tbl.task_id ISNULL; 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; return list;
} }
@ -261,8 +278,8 @@ public class TagController extends AbstractController {
* Constructor - takes the context to allow the database to be * Constructor - takes the context to allow the database to be
* opened/created * opened/created
*/ */
public TagController(Activity activity) { public TagController(Context context) {
this.context = activity; this.context = context;
} }
/** /**

@ -51,27 +51,28 @@ public abstract class AbstractTaskModel extends AbstractModel {
// field names // field names
static final String NAME = "name"; public static final String NAME = "name";
static final String NOTES = "notes"; public static final String NOTES = "notes";
static final String PROGRESS_PERCENTAGE = "progressPercentage"; public static final String PROGRESS_PERCENTAGE = "progressPercentage";
static final String IMPORTANCE = "importance"; public static final String IMPORTANCE = "importance";
static final String ESTIMATED_SECONDS = "estimatedSeconds"; public static final String ESTIMATED_SECONDS = "estimatedSeconds";
static final String ELAPSED_SECONDS = "elapsedSeconds"; public static final String ELAPSED_SECONDS = "elapsedSeconds";
static final String TIMER_START = "timerStart"; public static final String TIMER_START = "timerStart";
static final String DEFINITE_DUE_DATE = "definiteDueDate"; public static final String DEFINITE_DUE_DATE = "definiteDueDate";
static final String PREFERRED_DUE_DATE = "preferredDueDate"; public static final String PREFERRED_DUE_DATE = "preferredDueDate";
static final String HIDDEN_UNTIL = "hiddenUntil"; public static final String HIDDEN_UNTIL = "hiddenUntil";
static final String POSTPONE_COUNT = "postponeCount"; public static final String POSTPONE_COUNT = "postponeCount";
static final String NOTIFICATIONS = "notifications"; public static final String NOTIFICATIONS = "notifications";
static final String NOTIFICATION_FLAGS = "notificationFlags"; public static final String NOTIFICATION_FLAGS = "notificationFlags";
static final String LAST_NOTIFIED = "lastNotified"; public static final String LAST_NOTIFIED = "lastNotified";
static final String REPEAT = "repeat"; public static final String REPEAT = "repeat";
static final String CREATION_DATE = "creationDate"; public static final String CREATION_DATE = "creationDate";
static final String COMPLETION_DATE = "completionDate"; public static final String COMPLETION_DATE = "completionDate";
static final String CALENDAR_URI = "calendarUri"; public static final String CALENDAR_URI = "calendarUri";
static final String FLAGS = "flags"; public static final String FLAGS = "flags";
// reserved fields --- // reserved fields ---
static final String BLOCKING_ON = "blockingOn"; public static final String BLOCKING_ON = "blockingOn";
// notification flags // notification flags
public static final int NOTIFY_BEFORE_DEADLINE = 1 << 0; public static final int NOTIFY_BEFORE_DEADLINE = 1 << 0;
@ -113,10 +114,10 @@ public abstract class AbstractTaskModel extends AbstractModel {
// --- database helper // --- database helper
/** Database Helper manages creating new tables and updating old ones */ /** Database Helper manages creating new tables and updating old ones */
static class TaskModelDatabaseHelper extends SQLiteOpenHelper { public static class TaskModelDatabaseHelper extends SQLiteOpenHelper {
String tableName; String tableName;
TaskModelDatabaseHelper(Context context, String databaseName, String tableName) { public TaskModelDatabaseHelper(Context context, String databaseName, String tableName) {
super(context, databaseName, null, VERSION); super(context, databaseName, null, VERSION);
this.tableName = tableName; this.tableName = tableName;
} }

@ -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<TaskIdentifier> activeTasks = taskController.getActiveTaskIdentifiers();
LinkedList<TaskIdentifier> 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);
}
}
}

@ -34,6 +34,7 @@ public class Notifications extends BroadcastReceiver {
private static final String ID_KEY = "id"; private static final String ID_KEY = "id";
private static final String FLAGS_KEY = "flags"; private static final String FLAGS_KEY = "flags";
private static final String REPEAT_KEY = "repeat"; private static final String REPEAT_KEY = "repeat";
private static final int TAG_ID_OFFSET = 100000;
// stuff for scheduling // stuff for scheduling
/** minimum # of seconds before a deadline to notify */ /** minimum # of seconds before a deadline to notify */
@ -516,5 +517,68 @@ public class Notifications extends BroadcastReceiver {
return true; 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;
}
} }
Loading…
Cancel
Save