diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 900e6cffb..a3899a4f5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="83" + android:versionName="2.3.2"> diff --git a/res/drawable/notification_icon.png b/res/drawable/notif_astrid.png similarity index 100% rename from res/drawable/notification_icon.png rename to res/drawable/notif_astrid.png diff --git a/res/drawable/notif_boring_alarm.png b/res/drawable/notif_boring_alarm.png new file mode 100644 index 000000000..9bffda1ca Binary files /dev/null and b/res/drawable/notif_boring_alarm.png differ diff --git a/res/drawable/notif_boring_working.png b/res/drawable/notif_boring_working.png new file mode 100644 index 000000000..a91e799d6 Binary files /dev/null and b/res/drawable/notif_boring_working.png differ diff --git a/res/drawable/notif_pink_alarm.png b/res/drawable/notif_pink_alarm.png new file mode 100644 index 000000000..51c70e23a Binary files /dev/null and b/res/drawable/notif_pink_alarm.png differ diff --git a/res/drawable/notification_clock.png b/res/drawable/notif_pink_working.png similarity index 63% rename from res/drawable/notification_clock.png rename to res/drawable/notif_pink_working.png index 040d39ca0..4e4e561b5 100644 Binary files a/res/drawable/notification_clock.png and b/res/drawable/notif_pink_working.png differ diff --git a/res/drawable/notification_tag_pink.png b/res/drawable/notification_tag_pink.png deleted file mode 100644 index a9e0f650f..000000000 Binary files a/res/drawable/notification_tag_pink.png and /dev/null differ diff --git a/res/layout/task_edit.xml b/res/layout/task_edit.xml index 2381bda4d..b92a3bc24 100644 --- a/res/layout/task_edit.xml +++ b/res/layout/task_edit.xml @@ -207,6 +207,11 @@ android:layout_height="wrap_content"/> + + - + /> diff --git a/res/values/arrays.xml b/res/values/arrays.xml index e81db77ae..634ea3331 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -52,4 +52,16 @@ Time to shorten your todo list! + + Pink + Boring + Astrid + + + + 0 + 1 + 2 + + diff --git a/res/values/strings.xml b/res/values/strings.xml index d8f78f121..5bdb11c59 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -116,8 +116,9 @@ More Synchronization Settings - Help + Online Help Take Astrid\'s Survey! + Quick Tips Edit Task Delete Task @@ -157,6 +158,7 @@ Time Already Spent on Task Absolute Deadline Goal Deadline + Add Task To Calendar Hide Until This Date Repeat Every No Repeat Set @@ -271,6 +273,17 @@ Wish me luck!\n Remove this tag from all tasks? Stop the timer? + + +Some things you may not know about Astrid:\n +\n +- To create a task, just start typing!\n +- While editing a task, hit \'back\' to save it\n +- Select a task & press 1-4 to quickly change it\'s priority\n +- If a task has a deadline, long-press to postpone it\n +\n +Thanks for using Astrid!\n + @@ -309,7 +322,11 @@ Wish me luck!\n notification_ringtone Notification Ringtone - Choose how Astrid alerts you! + Choose a ringtone for Astrid\'s alerts + + notif_theme + Notification Icons + Choose Astrid\'s notification bar icon Appearance diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 3d65b8eee..3a928bce2 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -27,6 +27,13 @@ android:ringtoneType="notification" android:showDefault="true" android:showSilent="true" /> + + { // bundle arguments - public static final String TAG_NAME_TOKEN = "t"; - public static final String START_CHAR_TOKEN = "s"; + public static final String TAG_NAME_TOKEN = "t"; + public static final String START_CHAR_TOKEN = "s"; // menu items - private static final int SAVE_ID = Menu.FIRST; - private static final int DISCARD_ID = Menu.FIRST + 1; - private static final int DELETE_ID = Menu.FIRST + 2; + private static final int SAVE_ID = Menu.FIRST; + private static final int DISCARD_ID = Menu.FIRST + 1; + private static final int DELETE_ID = Menu.FIRST + 2; // other constants - private static final int MAX_TAGS = 5; - private static final int MAX_ALERTS = 5; - private static final String TAB_BASIC = "basic"; - private static final String TAB_DATES = "dates"; - private static final String TAB_ALERTS = "alerts"; + private static final int MAX_TAGS = 5; + private static final int MAX_ALERTS = 5; + private static final String TAB_BASIC = "basic"; + private static final String TAB_DATES = "dates"; + private static final String TAB_ALERTS = "alerts"; + private static final int DEFAULT_CAL_TIME = 3600; // UI components private EditText name; @@ -118,6 +120,7 @@ public class TaskEdit extends TaskModificationTabbedActivity { private LinearLayout alertsContainer; private Button repeatValue; private Spinner repeatInterval; + private CheckBox addToCalendar; // other instance variables private boolean shouldSaveState = true; @@ -255,11 +258,6 @@ public class TaskEdit extends TaskModificationTabbedActivity { if(name.getText().length() == 0) return; - // if we've removed a deadline, delete alarms - if((definiteDueDate.getDate() == null && model.getDefiniteDueDate() != null) || - (preferredDueDate.getDate() == null && model.getPreferredDueDate() != null)) - Notifications.deleteAlarm(this, model.getTaskIdentifier().getId()); - model.setName(name.getText().toString()); model.setEstimatedSeconds(estimatedDuration.getTimeDurationInSeconds()); model.setElapsedSeconds(elapsedDuration.getTimeDurationInSeconds()); @@ -370,6 +368,7 @@ public class TaskEdit extends TaskModificationTabbedActivity { alertsContainer = (LinearLayout)findViewById(R.id.alert_container); repeatInterval = (Spinner)findViewById(R.id.repeat_interval); repeatValue = (Button)findViewById(R.id.repeat_value); + addToCalendar = (CheckBox)findViewById(R.id.add_to_calendar); // individual ui component initialization ArrayAdapter repeatAdapter = new ArrayAdapter( @@ -663,6 +662,35 @@ public class TaskEdit extends TaskModificationTabbedActivity { protected void onPause() { if(shouldSaveState) save(); + + // create calendar event + if(addToCalendar.isChecked()) { + addToCalendar.setChecked(false); + + Intent intent = new Intent(Intent.ACTION_EDIT); + intent.setType("vnd.android.cursor.item/event"); + intent.putExtra("title", name.getText()); + + Long deadlineDate = null; + if(model.getPreferredDueDate() != null) + deadlineDate = model.getPreferredDueDate().getTime(); + else if(model.getDefaultValues() != null) + deadlineDate = model.getDefiniteDueDate().getTime(); + + if(deadlineDate != null) { + int estimatedTime = DEFAULT_CAL_TIME; + if(model.getEstimatedSeconds() != null && + model.getEstimatedSeconds() > 0) + estimatedTime = model.getEstimatedSeconds(); + + intent.putExtra("beginTime", deadlineDate - estimatedTime * 1000L); + intent.putExtra("endTime", deadlineDate); + } else { + intent.putExtra("allDay", true); + } + + startActivity(intent); + } super.onPause(); } diff --git a/src/com/timsu/astrid/activities/TaskList.java b/src/com/timsu/astrid/activities/TaskList.java index 2187245fe..a13c73261 100644 --- a/src/com/timsu/astrid/activities/TaskList.java +++ b/src/com/timsu/astrid/activities/TaskList.java @@ -70,10 +70,10 @@ public class TaskList extends Activity { public static final String VARIABLES_TAG = "v"; /** Minimum distance a fling must cover to trigger motion */ - private static final int FLING_DIST_THRESHOLD = 100; + private static final int FLING_DIST_THRESHOLD = 70; /** Minimum velocity a fling must have to trigger motion */ - private static final int FLING_VEL_THRESHOLD = 300; + private static final int FLING_VEL_THRESHOLD = 250; // view components private ViewFlipper viewFlipper; diff --git a/src/com/timsu/astrid/activities/TaskListAdapter.java b/src/com/timsu/astrid/activities/TaskListAdapter.java index 5b42ca8ab..d57039685 100644 --- a/src/com/timsu/astrid/activities/TaskListAdapter.java +++ b/src/com/timsu/astrid/activities/TaskListAdapter.java @@ -43,11 +43,9 @@ import android.view.View.OnKeyListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; -import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import android.widget.CompoundButton.OnCheckedChangeListener; import com.timsu.astrid.R; import com.timsu.astrid.data.alerts.AlertController; @@ -93,9 +91,6 @@ public class TaskListAdapter extends ArrayAdapter { private static final Format alarmFormat = new SimpleDateFormat( "MM/dd hh:mm"); - /** Threshold under which to display a task as red, in millis */ - private static final long TASK_OVERDUE_THRESHOLD = 30 * 60 * 1000L; - private final Activity activity; private List objects; private int resource; @@ -323,7 +318,7 @@ public class TaskListAdapter extends ArrayAdapter { if(task.getDefiniteDueDate() != null) { long timeLeft = task.getDefiniteDueDate().getTime() - System.currentTimeMillis(); - if(timeLeft > TASK_OVERDUE_THRESHOLD) { + if(timeLeft > 0) { label.append(r.getString(R.string.taskList_dueIn)).append(" "); } else { taskOverdue = true; @@ -339,7 +334,7 @@ public class TaskListAdapter extends ArrayAdapter { long timeLeft = task.getPreferredDueDate().getTime() - System.currentTimeMillis(); label.append(r.getString(R.string.taskList_goalPrefix)).append(" "); - if(timeLeft > TASK_OVERDUE_THRESHOLD) { + if(timeLeft > 0) { label.append(r.getString(R.string.taskList_dueIn)).append(" "); } else { label.append(r.getString(R.string.taskList_overdueBy)).append(" "); @@ -533,15 +528,14 @@ public class TaskListAdapter extends ArrayAdapter { final CheckBox progress = ((CheckBox)view.findViewById(R.id.cb1)); // clicking the check box - progress.setOnCheckedChangeListener(new OnCheckedChangeListener() { + progress.setOnClickListener(new View.OnClickListener() { @Override - public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { - View parent = (View)buttonView.getParent().getParent(); + public void onClick(View v) { + View parent = (View)v.getParent().getParent(); TaskModelForList task = (TaskModelForList)parent.getTag(); int newProgressPercentage; - if(isChecked) + if(progress.isChecked()) newProgressPercentage = TaskModelForList.getCompletedPercentage(); else @@ -552,6 +546,7 @@ public class TaskListAdapter extends ArrayAdapter { setupView(parent, task); } } + }); // clicking the text field @@ -691,7 +686,7 @@ public class TaskListAdapter extends ArrayAdapter { float completedPercentage = 0; if(task.getEstimatedSeconds() > 0) { - completedPercentage = task.getElapsedSeconds() / + completedPercentage = 1.0f * task.getElapsedSeconds() / task.getEstimatedSeconds(); } diff --git a/src/com/timsu/astrid/activities/TaskListSubActivity.java b/src/com/timsu/astrid/activities/TaskListSubActivity.java index fd00a9f41..2a16d49d9 100644 --- a/src/com/timsu/astrid/activities/TaskListSubActivity.java +++ b/src/com/timsu/astrid/activities/TaskListSubActivity.java @@ -98,6 +98,7 @@ public class TaskListSubActivity extends SubActivity { private static final int OPTIONS_SETTINGS_ID = Menu.FIRST + 11; private static final int OPTIONS_HELP_ID = Menu.FIRST + 12; private static final int OPTIONS_SURVEY_ID = Menu.FIRST + 13; + private static final int OPTIONS_QUICK_TIPS = Menu.FIRST + 14; private static final int CONTEXT_FILTER_HIDDEN = Menu.FIRST + 20; private static final int CONTEXT_FILTER_DONE = Menu.FIRST + 21; @@ -246,13 +247,15 @@ public class TaskListSubActivity extends SubActivity { R.string.taskList_menu_settings); item.setAlphabeticShortcut('p'); + item = menu.add(Menu.NONE, OPTIONS_QUICK_TIPS, Menu.NONE, + R.string.taskList_menu_tips); + item = menu.add(Menu.NONE, OPTIONS_HELP_ID, Menu.NONE, R.string.taskList_menu_help); item.setAlphabeticShortcut('h'); item = menu.add(Menu.NONE, OPTIONS_SURVEY_ID, Menu.NONE, R.string.taskList_menu_survey); - item.setAlphabeticShortcut('h'); return true; } @@ -764,6 +767,10 @@ public class TaskListSubActivity extends SubActivity { Uri.parse(Constants.SURVEY_URL)); launchActivity(browserIntent, 0); return true; + case OPTIONS_QUICK_TIPS: + DialogUtilities.okDialog(getParent(), + r.getString(R.string.quick_tips), null); + return true; // --- list context menu items case TaskListAdapter.CONTEXT_EDIT_ID: diff --git a/src/com/timsu/astrid/data/task/TaskController.java b/src/com/timsu/astrid/data/task/TaskController.java index 119a873ed..a0ec8ce90 100644 --- a/src/com/timsu/astrid/data/task/TaskController.java +++ b/src/com/timsu/astrid/data/task/TaskController.java @@ -201,7 +201,7 @@ public class TaskController extends AbstractController { if(taskId == null) throw new UnsupportedOperationException("Cannot delete uncreated task!"); long id = taskId.getId(); - Notifications.deleteAlarm(context, id); + Notifications.deleteAlarm(context, null, id); return database.delete(TASK_TABLE_NAME, KEY_ROWID + "=" + id, null) > 0; } diff --git a/src/com/timsu/astrid/utilities/Notifications.java b/src/com/timsu/astrid/utilities/Notifications.java index 3c5fd6cdd..e6ffb9126 100644 --- a/src/com/timsu/astrid/utilities/Notifications.java +++ b/src/com/timsu/astrid/utilities/Notifications.java @@ -86,7 +86,7 @@ public class Notifications extends BroadcastReceiver { ", repeat " + repeatInterval); if(!showNotification(context, id, flags, repeatInterval, reminder)) { - deleteAlarm(context, id); + deleteAlarm(context, intent, id); NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); nm.cancel((int)id); @@ -138,7 +138,7 @@ public class Notifications extends BroadcastReceiver { // return if we don't need to go any further if(shouldDeleteAlarm(task)) { - deleteAlarm(context, task.getTaskIdentifier().getId()); + deleteAlarm(context, null, task.getTaskIdentifier().getId()); return; } @@ -149,8 +149,8 @@ public class Notifications extends BroadcastReceiver { long when; // get or make up a last notification time if(task.getLastNotificationDate() == null) { - when = System.currentTimeMillis() + - (long)(interval * (0.3f + 0.7f * random.nextFloat())); + when = System.currentTimeMillis() - + (long)(interval * (0.7f * random.nextFloat())); taskController.setLastNotificationTime(task.getTaskIdentifier(), new Date(when)); } else { @@ -167,6 +167,12 @@ public class Notifications extends BroadcastReceiver { int estimatedDuration = DEADLINE_NOTIFY_SECS; if(task.getEstimatedSeconds() != null && task.getEstimatedSeconds() > DEADLINE_NOTIFY_SECS) estimatedDuration = (int)(task.getEstimatedSeconds() * 1.5f); + + clearAlarm(context, task.getTaskIdentifier().getId(), FLAG_DEFINITE_DEADLINE); + clearAlarm(context, task.getTaskIdentifier().getId(), FLAG_PREFERRED_DEADLINE); + clearAlarm(context, task.getTaskIdentifier().getId(), FLAG_DEFINITE_DEADLINE | FLAG_OVERDUE); + clearAlarm(context, task.getTaskIdentifier().getId(), FLAG_PREFERRED_DEADLINE | FLAG_OVERDUE); + if((task.getNotificationFlags() & TaskModelForList.NOTIFY_BEFORE_DEADLINE) > 0) { scheduleDeadline(context, task.getDefiniteDueDate(), -estimatedDuration, 0, FLAG_DEFINITE_DEADLINE, task); @@ -215,6 +221,7 @@ public class Notifications extends BroadcastReceiver { */ private static void scheduleDeadline(Context context, Date deadline, int offsetSeconds, int intervalSeconds, int flags, Notifiable task) { + long id = task.getTaskIdentifier().getId(); if(deadline == null) return; long when = deadline.getTime() + offsetSeconds * 1000; @@ -222,10 +229,10 @@ public class Notifications extends BroadcastReceiver { return; if (intervalSeconds == 0) - scheduleAlarm(context, task.getTaskIdentifier().getId(), when, + scheduleAlarm(context, id, when, flags); else - scheduleRepeatingAlarm(context, task.getTaskIdentifier().getId(), + scheduleRepeatingAlarm(context, id, when, flags, intervalSeconds * 1000); } @@ -255,13 +262,12 @@ public class Notifications extends BroadcastReceiver { } /** Delete the given alarm */ - public static void deleteAlarm(Context context, long id) { + public static void deleteAlarm(Context context, Intent trigger, long id) { AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); - // clear all possible alarms - for(int flag = 0; flag < (6 << FIXED_ID_SHIFT); flag++) { + if(trigger != null) { PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, - createAlarmIntent(context, id, flag), 0); + trigger, 0); am.cancel(pendingIntent); } @@ -269,6 +275,14 @@ public class Notifications extends BroadcastReceiver { clearAllNotifications(context, new TaskIdentifier(id)); } + /** Clear the alarm given by the id and flags */ + public static void clearAlarm(Context context, long id, int flags) { + AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, + createAlarmIntent(context, id, flags), 0); + am.cancel(pendingIntent); + } + /** Schedules a single alarm for a single task */ public static void scheduleAlarm(Context context, long id, long when, int flags) { @@ -386,9 +400,20 @@ public class Notifications extends BroadcastReceiver { // create notification object String appName = r.getString(R.string.app_name); + int icon; + switch(Preferences.getNotificationIconTheme(context)) { + case PINK: + icon = R.drawable.notif_pink_alarm; + break; + case BORING: + icon = R.drawable.notif_boring_alarm; + break; + default: + icon = R.drawable.notif_astrid; + } + Notification notification = new Notification( - R.drawable.notification_tag_pink, reminder, - System.currentTimeMillis()); + icon, reminder, System.currentTimeMillis()); notification.setLatestEventInfo(context, appName, reminder + " " + taskName, @@ -452,10 +477,21 @@ public class Notifications extends BroadcastReceiver { (int)taskId.getId(), notifyIntent, 0); // create notification object + int icon; + switch(Preferences.getNotificationIconTheme(context)) { + case PINK: + icon = R.drawable.notif_pink_working; + break; + case BORING: + icon = R.drawable.notif_boring_working; + break; + default: + icon = R.drawable.notif_astrid; + } + String appName = r.getString(R.string.app_name); Notification notification = new Notification( - R.drawable.notification_clock, text, - System.currentTimeMillis()); + icon, text, System.currentTimeMillis()); notification.setLatestEventInfo(context, appName, text, diff --git a/src/com/timsu/astrid/utilities/Preferences.java b/src/com/timsu/astrid/utilities/Preferences.java index 41982e14b..2fc44c447 100644 --- a/src/com/timsu/astrid/utilities/Preferences.java +++ b/src/com/timsu/astrid/utilities/Preferences.java @@ -21,6 +21,13 @@ public class Preferences { private static final String P_SYNC_RTM_LAST_SYNC = "rtmlastsync"; private static final String P_SYNC_LAST_SYNC = "lastsync"; + // pref values + public enum NotificationIconTheme { + PINK, + BORING, + ASTRID + } + // default values private static final boolean DEFAULT_PERSISTENCE_MODE = true; private static final boolean DEFAULT_COLORIZE = false; @@ -42,7 +49,7 @@ public class Preferences { editor.putString(r.getString(R.string.p_deadlineTime), "7"); } if(!prefs.contains(r.getString(R.string.p_notif_defaultRemind))) { - editor.putString(r.getString(R.string.p_notif_defaultRemind), "7"); + editor.putString(r.getString(R.string.p_notif_defaultRemind), "0"); } if(!prefs.contains(r.getString(R.string.p_colorize))) { editor.putBoolean(r.getString(R.string.p_colorize), DEFAULT_COLORIZE); @@ -121,7 +128,15 @@ public class Preferences { return getIntegerValue(context, R.string.p_notif_quietEnd); } - /** Get notification ringtone, or null if not set */ + /** returns hour at which quiet hours start, or null if not set */ + public static NotificationIconTheme getNotificationIconTheme(Context context) { + Integer index = getIntegerValue(context, R.string.p_notif_icon); + if(index == null) + index = 0; + return NotificationIconTheme.values()[index]; + } + + /** Get notification ring tone, or null if not set */ public static Uri getNotificationRingtone(Context context) { Resources r = context.getResources(); String value = getPrefs(context).getString(r.getString(