From 7290b074533318fd0f086e8c2588eb7eb41a9f0d Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 00:31:36 -0700 Subject: [PATCH 01/40] Fix for AST-140: widget does not update and refresh for task lists with size < 5 --- astrid/src/com/todoroo/astrid/widget/TasksWidget.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/astrid/src/com/todoroo/astrid/widget/TasksWidget.java b/astrid/src/com/todoroo/astrid/widget/TasksWidget.java index c0d274f9a..4c286860f 100644 --- a/astrid/src/com/todoroo/astrid/widget/TasksWidget.java +++ b/astrid/src/com/todoroo/astrid/widget/TasksWidget.java @@ -123,6 +123,8 @@ public class TasksWidget extends AppWidgetProvider { for(int i = cursor.getCount() - 1; i < separatorIDs.length; i++) { if(i >= 0) views.setViewVisibility(separatorIDs[i], View.INVISIBLE); + if(i > cursor.getCount() - 1) + views.setViewVisibility(textIDs[i], View.INVISIBLE); } } catch (Exception e) { // can happen if database is not ready From 28b44894cd48cc47188e642b6e5b31707d72e1f5 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 00:40:54 -0700 Subject: [PATCH 02/40] Fix for AST-158 - disallowed Astrid from moving to SD card because it breaks widget, service, and alarms --- astrid/AndroidManifest.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index f28f5466d..e08e02b5c 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -2,7 +2,9 @@ + android:installLocation="internalOnly"> + + @@ -46,7 +48,7 @@ - From be1ef4decc8e2817a6c6894126abab83a8871752 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 02:13:45 -0700 Subject: [PATCH 03/40] Fix for AST-148: filter shortcuts that are two words cause crash --- .../andlib/utility/AndroidUtilities.java | 4 +- .../astrid/activity/FilterListActivity.java | 108 +++++++++--------- .../astrid/activity/ShortcutActivity.java | 53 +++++++-- .../todoroo/astrid/adapter/FilterAdapter.java | 14 +-- 4 files changed, 104 insertions(+), 75 deletions(-) diff --git a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java index e8015658b..c6719d4d9 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java @@ -23,8 +23,8 @@ import android.text.InputType; import android.util.Log; import android.view.MotionEvent; import android.view.View; -import android.view.View.OnTouchListener; import android.view.ViewGroup; +import android.view.View.OnTouchListener; import android.widget.TextView; import com.todoroo.andlib.service.Autowired; @@ -193,7 +193,7 @@ public class AndroidUtilities { if(string == null) return null; - String[] pairs = string.split(" "); + String[] pairs = string.split("="); ContentValues result = new ContentValues(); for(String item : pairs) { String[] keyValue = item.split("="); diff --git a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java index be75863ee..6aa1e81e2 100644 --- a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java @@ -15,19 +15,19 @@ import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; 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.TextView.OnEditorActionListener; import android.widget.Toast; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.TextView.OnEditorActionListener; import com.flurry.android.FlurryAgent; import com.timsu.astrid.R; @@ -251,7 +251,6 @@ public class FilterListActivity extends ExpandableListActivity { if(item instanceof Filter) { Filter filter = (Filter) item; - info.targetView.setTag(filter); menuItem = menu.add(0, CONTEXT_MENU_SHORTCUT, 0, R.string.FLA_context_shortcut); menuItem.setIntent(ShortcutActivity.createIntent(filter)); } @@ -295,13 +294,9 @@ public class FilterListActivity extends ExpandableListActivity { bitmap.getWidth(), bitmap.getHeight()), null); Intent createShortcutIntent = new Intent(); - createShortcutIntent.putExtra( - Intent.EXTRA_SHORTCUT_INTENT, - shortcutIntent); - createShortcutIntent.putExtra( - Intent.EXTRA_SHORTCUT_NAME, label); - createShortcutIntent.putExtra( - Intent.EXTRA_SHORTCUT_ICON, bitmap); + createShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + createShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); + createShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); createShortcutIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); //$NON-NLS-1$ sendBroadcast(createShortcutIntent); @@ -331,49 +326,9 @@ public class FilterListActivity extends ExpandableListActivity { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo)item.getMenuInfo(); final Intent shortcutIntent = item.getIntent(); - final Filter filter = (Filter)info.targetView.getTag(); - - FrameLayout frameLayout = new FrameLayout(this); - frameLayout.setPadding(10, 0, 10, 0); - final EditText editText = new EditText(this); - if(filter.listingTitle == null) - filter.listingTitle = ""; //$NON-NLS-1$ - editText.setText(filter.listingTitle. - replaceAll("\\(\\d+\\)$", "").trim()); //$NON-NLS-1$ //$NON-NLS-2$ - frameLayout.addView(editText, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.FILL_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT)); - - final Runnable createShortcut = new Runnable() { - @Override - public void run() { - String label = editText.getText().toString(); - createShortcut(filter, shortcutIntent, label); - } - }; - editText.setOnEditorActionListener(new OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if(actionId == EditorInfo.IME_NULL) { - createShortcut.run(); - return true; - } - return false; - } - }); - - new AlertDialog.Builder(this) - .setTitle(R.string.FLA_shortcut_dialog_title) - .setMessage(R.string.FLA_shortcut_dialog) - .setView(frameLayout) - .setIcon(android.R.drawable.ic_dialog_info) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - createShortcut.run(); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .show(); + FilterListItem filter = ((FilterAdapter.ViewHolder)info.targetView.getTag()).item; + if(filter instanceof Filter) + showCreateShortcutDialog(shortcutIntent, (Filter)filter); return true; } @@ -388,4 +343,49 @@ public class FilterListActivity extends ExpandableListActivity { return false; } + private void showCreateShortcutDialog(final Intent shortcutIntent, + final Filter filter) { + FrameLayout frameLayout = new FrameLayout(this); + frameLayout.setPadding(10, 0, 10, 0); + final EditText editText = new EditText(this); + if(filter.listingTitle == null) + filter.listingTitle = ""; //$NON-NLS-1$ + editText.setText(filter.listingTitle. + replaceAll("\\(\\d+\\)$", "").trim()); //$NON-NLS-1$ //$NON-NLS-2$ + frameLayout.addView(editText, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.FILL_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT)); + + final Runnable createShortcut = new Runnable() { + @Override + public void run() { + String label = editText.getText().toString(); + createShortcut(filter, shortcutIntent, label); + } + }; + editText.setOnEditorActionListener(new OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if(actionId == EditorInfo.IME_NULL) { + createShortcut.run(); + return true; + } + return false; + } + }); + + new AlertDialog.Builder(this) + .setTitle(R.string.FLA_shortcut_dialog_title) + .setMessage(R.string.FLA_shortcut_dialog) + .setView(frameLayout) + .setIcon(android.R.drawable.ic_dialog_info) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + createShortcut.run(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + } diff --git a/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java b/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java index a0db742f7..756aaa751 100644 --- a/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java @@ -19,6 +19,8 @@ */ package com.todoroo.astrid.activity; +import java.util.Map.Entry; + import android.app.Activity; import android.content.ContentValues; import android.content.Intent; @@ -46,14 +48,13 @@ public class ShortcutActivity extends Activity { /** token for passing a {@link Filter}'s sql through extras */ public static final String TOKEN_FILTER_SQL = "sql"; //$NON-NLS-1$ - /** token for passing a {@link Filter}'s values for new tasks through extras */ + /** token for passing a {@link Filter}'s values for new tasks through extras as string */ + @Deprecated public static final String TOKEN_FILTER_VALUES = "v4nt"; //$NON-NLS-1$ - /** token for passing a {@link Filter}'s values for new tasks through extras (keys) */ - public static final String TOKEN_FILTER_VALUES_KEYS = "v4ntk"; //$NON-NLS-1$ + /** token for passing a {@link Filter}'s values for new tasks through extras as exploded ContentValues */ + public static final String TOKEN_FILTER_VALUES_ITEM = "v4ntp_"; //$NON-NLS-1$ - /** token for passing a {@link Filter}'s values for new tasks through extras (values) */ - public static final String TOKEN_FILTER_VALUES_VALUES = "v4ntv"; //$NON-NLS-1$ // --- implementation @@ -81,6 +82,28 @@ public class ShortcutActivity extends Activity { ContentValues values = null; if(extras.containsKey(TOKEN_FILTER_VALUES)) values = AndroidUtilities.contentValuesFromString(extras.getString(TOKEN_FILTER_VALUES)); + else { + values = new ContentValues(); + for(String key : extras.keySet()) { + if(!key.startsWith(TOKEN_FILTER_VALUES_ITEM)) + continue; + + Object value = extras.get(key); + key = key.substring(TOKEN_FILTER_VALUES_ITEM.length()); + + // assume one of the big 4... + if(value instanceof String) + values.put(key, (String) value); + else if(value instanceof Integer) + values.put(key, (Integer) value); + else if(value instanceof Double) + values.put(key, (Double) value); + else if(value instanceof Long) + values.put(key, (Long) value); + else + throw new IllegalStateException("Unsupported bundle type " + value.getClass()); //$NON-NLS-1$ + } + } Filter filter = new Filter("", title, new QueryTemplate(), values); //$NON-NLS-1$ filter.sqlQuery = sql; @@ -95,12 +118,26 @@ public class ShortcutActivity extends Activity { public static Intent createIntent(Filter filter) { Intent shortcutIntent = new Intent(ContextManager.getContext(), ShortcutActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); + shortcutIntent.setAction(Intent.ACTION_VIEW); shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_TITLE, filter.title); shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_SQL, filter.sqlQuery); if(filter.valuesForNewTasks != null) { - shortcutIntent.putExtra(ShortcutActivity.TOKEN_FILTER_VALUES, - filter.valuesForNewTasks.toString()); + for(Entry item : filter.valuesForNewTasks.valueSet()) { + String key = TOKEN_FILTER_VALUES_ITEM + item.getKey(); + Object value = item.getValue(); + + // assume one of the big 4... + if(value instanceof String) + shortcutIntent.putExtra(key, (String) value); + else if(value instanceof Integer) + shortcutIntent.putExtra(key, (Integer) value); + else if(value instanceof Double) + shortcutIntent.putExtra(key, (Double) value); + else if(value instanceof Long) + shortcutIntent.putExtra(key, (Long) value); + else + throw new IllegalStateException("Unsupported bundle type " + value.getClass()); //$NON-NLS-1$ + } } return shortcutIntent; } diff --git a/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java b/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java index 0ec03334d..f0a81a33f 100644 --- a/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/FilterAdapter.java @@ -95,7 +95,7 @@ public class FilterAdapter extends BaseExpandableListAdapter { return convertView; } - private static class ViewHolder { + public static class ViewHolder { public FilterListItem item; public ImageView expander; public ImageView icon; @@ -132,11 +132,7 @@ public class FilterAdapter extends BaseExpandableListAdapter { convertView = newView(convertView, parent); ViewHolder viewHolder = (ViewHolder) convertView.getTag(); - Object item = getChild(groupPosition, childPosition); - if(!(item instanceof FilterListItem)) - return convertView; - - viewHolder.item = (FilterListItem) item; + viewHolder.item = (FilterListItem) getChild(groupPosition, childPosition); populateView(viewHolder, true, false); return convertView; @@ -162,11 +158,7 @@ public class FilterAdapter extends BaseExpandableListAdapter { ViewGroup parent) { convertView = newView(convertView, parent); ViewHolder viewHolder = (ViewHolder) convertView.getTag(); - Object groupItem = getGroup(groupPosition); - if(!(groupItem instanceof FilterListItem)) - return convertView; - - viewHolder.item = (FilterListItem) groupItem; + viewHolder.item = (FilterListItem) getGroup(groupPosition); populateView(viewHolder, false, isExpanded); return convertView; } From e7447982b11f2800eb5a7eaa2c045693ac942cb9 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 02:16:59 -0700 Subject: [PATCH 04/40] Fix for AST-164 - repeat completion clears timer, makes old task not have recurrence --- .../todoroo/astrid/repeats/RepeatTaskCompleteListener.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java index ac17ffba7..64e37d979 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java @@ -107,10 +107,17 @@ public class RepeatTaskCompleteListener extends BroadcastReceiver { hideUntil += newDueDate - task.getValue(Task.DUE_DATE); } + // clear recurrence from completed task so it can be re-commpleted + task.setValue(Task.RECURRENCE, ""); //$NON-NLS-1$ + taskService.save(task, false); + + // clone to create new task task = taskService.clone(task); task.setValue(Task.DUE_DATE, newDueDate); task.setValue(Task.HIDE_UNTIL, hideUntil); task.setValue(Task.COMPLETION_DATE, 0L); + task.setValue(Task.TIMER_START, 0L); + task.setValue(Task.ELAPSED_SECONDS, 0); taskService.save(task, false); } catch (ParseException e) { exceptionService.reportError("recurrence-rule: " + recurrence, e); //$NON-NLS-1$ From 4a269279a4fef3c7e24148e25ed877ff331e78e2 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 02:40:34 -0700 Subject: [PATCH 05/40] Making the timer controls easier to use for rapid changing. --- .../astrid/timers/TimerControlSet.java | 13 ++- .../astrid/ui/NNumberPickerDialog.java | 6 +- .../com/todoroo/astrid/ui/NumberPicker.java | 13 +-- .../astrid/ui/TimeDurationControlSet.java | 85 ++++++++----------- 4 files changed, 56 insertions(+), 61 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerControlSet.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerControlSet.java index d87b3b3e1..866fee76d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/timers/TimerControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerControlSet.java @@ -10,7 +10,6 @@ import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.astrid.activity.TaskEditActivity.TaskEditControlSet; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.ui.TimeDurationControlSet; -import com.todoroo.astrid.ui.TimeDurationControlSet.TimeDurationType; /** * Control Set for managing repeats @@ -31,11 +30,11 @@ public class TimerControlSet implements TaskEditControlSet { LayoutInflater.from(activity).inflate(R.layout.timer_control, parent, true); estimated = new TimeDurationTaskEditControlSet(Task.ESTIMATED_SECONDS, - R.id.estimatedDuration, 0, R.string.DLG_hour_minutes, - TimeDurationType.HOURS_MINUTES); + R.id.estimatedDuration, 0, R.string.DLG_hour_minutes + ); elapsed = new TimeDurationTaskEditControlSet(Task.ELAPSED_SECONDS, R.id.elapsedDuration, - 0, R.string.DLG_hour_minutes, - TimeDurationType.HOURS_MINUTES); + 0, R.string.DLG_hour_minutes + ); } @Override @@ -62,10 +61,10 @@ public class TimerControlSet implements TaskEditControlSet { private final IntegerProperty property; public TimeDurationTaskEditControlSet(IntegerProperty property, int timeButtonId, - int prefixResource, int titleResource, TimeDurationType type) { + int prefixResource, int titleResource) { this.property = property; this.controlSet = new TimeDurationControlSet(activity, - timeButtonId, prefixResource, titleResource, type); + timeButtonId, prefixResource, titleResource); } @Override diff --git a/astrid/src/com/todoroo/astrid/ui/NNumberPickerDialog.java b/astrid/src/com/todoroo/astrid/ui/NNumberPickerDialog.java index 5c1f80cf6..3ac68cca8 100644 --- a/astrid/src/com/todoroo/astrid/ui/NNumberPickerDialog.java +++ b/astrid/src/com/todoroo/astrid/ui/NNumberPickerDialog.java @@ -29,9 +29,9 @@ import android.content.DialogInterface.OnClickListener; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.widget.FrameLayout.LayoutParams; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.FrameLayout.LayoutParams; import com.timsu.astrid.R; @@ -102,6 +102,10 @@ public class NNumberPickerDialog extends AlertDialog implements OnClickListener } } + public NumberPicker getPicker(int index) { + return pickers.get(index); + } + public void setInitialValues(int[] values) { for(int i = 0; i < pickers.size(); i++) pickers.get(i).setCurrent(values[i]); diff --git a/astrid/src/com/todoroo/astrid/ui/NumberPicker.java b/astrid/src/com/todoroo/astrid/ui/NumberPicker.java index 3a5e4673b..f0db4d326 100644 --- a/astrid/src/com/todoroo/astrid/ui/NumberPicker.java +++ b/astrid/src/com/todoroo/astrid/ui/NumberPicker.java @@ -44,7 +44,8 @@ public class NumberPicker extends LinearLayout implements OnClickListener, OnFocusChangeListener, OnLongClickListener { public interface OnChangedListener { - void onChanged(NumberPicker picker, int oldVal, int newVal); + /** return new value */ + int onChanged(NumberPicker picker, int oldVal, int newVal); } public interface Formatter { @@ -253,6 +254,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, } private void changeCurrent(int current, Animation in, Animation out) { + current = notifyChange(); // Wrap around the values if we go past the start or end if (current > mEnd) { @@ -262,14 +264,15 @@ public class NumberPicker extends LinearLayout implements OnClickListener, } mPrevious = mCurrent; mCurrent = current; - notifyChange(); updateView(); } - private void notifyChange() { + private int notifyChange() { if (mListener != null) { - mListener.onChanged(this, mPrevious, mCurrent); - } + return mListener.onChanged(this, mPrevious, mCurrent); + } else + return mCurrent; + } private void updateView() { diff --git a/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java b/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java index dfb30ded8..c1324c3f7 100644 --- a/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java +++ b/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java @@ -37,47 +37,49 @@ public class TimeDurationControlSet implements OnNNumberPickedListener, @Autowired DateUtilities dateUtilities; - public enum TimeDurationType { - DAYS_HOURS, - HOURS_MINUTES; - } - private final Activity activity; private final Button timeButton; private final NNumberPickerDialog dialog; private final int prefixResource; - private final TimeDurationType type; private int timeDuration; public TimeDurationControlSet(Activity activity, int timeButtonId, - int prefixResource, int titleResource, TimeDurationType type) { + int prefixResource, int titleResource) { DependencyInjectionService.getInstance().inject(this); this.activity = activity; this.prefixResource = prefixResource; - this.type = type; timeButton = (Button)activity.findViewById(timeButtonId); timeButton.setOnClickListener(this); - switch(type) { - case DAYS_HOURS: - dialog = new NNumberPickerDialog(activity, this, - activity.getResources().getString(titleResource), - new int[] {0, 0}, new int[] {1, 1}, new int[] {0, 0}, - new int[] {31, 23}, new String[] { - "d\na\ny\ns", - "h\nr\ns" - }); - break; - case HOURS_MINUTES: - default: - dialog = new NNumberPickerDialog(activity, this, - activity.getResources().getString(titleResource), - new int[] {0, 0}, new int[] {1, 5}, new int[] {0, 0}, - new int[] {99, 59}, new String[] {":", null}); - break; - } + dialog = new NNumberPickerDialog(activity, this, + activity.getResources().getString(titleResource), + new int[] {0, 0}, new int[] {1, 5}, new int[] {0, 0}, + new int[] {99, 59}, new String[] {":", null}); + final NumberPicker hourPicker = dialog.getPicker(0); + final NumberPicker minutePicker = dialog.getPicker(1); + minutePicker.setFormatter(new NumberPicker.Formatter() { + @Override + public String toString(int value) { + return String.format("%02d", value); + } + }); + minutePicker.setOnChangeListener(new NumberPicker.OnChangedListener() { + @Override + public int onChanged(NumberPicker picker, int oldVal, int newVal) { + if(newVal < 0) { + if(hourPicker.getCurrent() == 0) + return 0; + hourPicker.setCurrent(hourPicker.getCurrent() - 1); + return 60 - newVal; + } else if(newVal > 59) { + hourPicker.setCurrent(hourPicker.getCurrent() + 1); + return newVal % 60; + } + return newVal; + } + }); } public int getTimeDurationInSeconds() { @@ -97,33 +99,20 @@ public class TimeDurationControlSet implements OnNNumberPickedListener, } String prefix = ""; - if(prefixResource != 0) + if (prefixResource != 0) prefix = r.getString(prefixResource); - timeButton.setText(prefix + " " + dateUtilities.getDurationString( - timeDurationInSeconds * 1000L, 2)); - switch(type) { - case DAYS_HOURS: - int days = timeDuration / 24 / 3600; - int hours = timeDuration / 3600 - 24 * days; - dialog.setInitialValues(new int[] {days, hours}); - break; - case HOURS_MINUTES: - hours = timeDuration / 3600; - int minutes = timeDuration/60 - 60 * hours; - dialog.setInitialValues(new int[] {hours, minutes}); - } + timeButton.setText(prefix + + " " + + dateUtilities.getDurationString( + timeDurationInSeconds * 1000L, 2)); + int hours = timeDuration / 3600; + int minutes = timeDuration / 60 - 60 * hours; + dialog.setInitialValues(new int[] { hours, minutes }); } /** Called when NumberPicker activity is completed */ public void onNumbersPicked(int[] values) { - switch(type) { - case DAYS_HOURS: - setTimeDuration(values[0] * 24 * 3600 + values[1] * 3600); - break; - case HOURS_MINUTES: - setTimeDuration(values[0] * 3600 + values[1] * 60); - break; - } + setTimeDuration(values[0] * 3600 + values[1] * 60); } /** Called when time button is clicked */ From 9b6cbaf1b56a97246fc55dae685aa289159f4c9f Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 02:44:12 -0700 Subject: [PATCH 06/40] Fix for AST-146 - moved priority bar to left side --- astrid/res/layout/task_adapter_row.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/astrid/res/layout/task_adapter_row.xml b/astrid/res/layout/task_adapter_row.xml index 7d22cdf53..de54bce1c 100644 --- a/astrid/res/layout/task_adapter_row.xml +++ b/astrid/res/layout/task_adapter_row.xml @@ -10,13 +10,19 @@ android:paddingRight="4dip" android:minHeight="40dip" android:orientation="vertical"> - + + + + @@ -52,11 +57,6 @@ - - - Date: Fri, 6 Aug 2010 03:47:15 -0700 Subject: [PATCH 07/40] fix for AST-167: allowing 'by day' type functionality for all repeats --- .../astrid/repeats/RepeatControlSet.java | 54 ++++-- .../astrid/repeats/RepeatDetailExposer.java | 4 +- .../repeats/RepeatTaskCompleteListener.java | 180 +++++++++++------- .../todoroo/astrid/adapter/TaskAdapter.java | 9 +- .../src/com/todoroo/astrid/dao/TaskDao.java | 21 +- .../com/todoroo/astrid/ui/NumberPicker.java | 8 +- .../astrid/repeats/AdvancedRepeatTests.java | 113 +++++++++++ 7 files changed, 278 insertions(+), 111 deletions(-) create mode 100644 tests/src/com/todoroo/astrid/repeats/AdvancedRepeatTests.java diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java index 8b81b7f17..2b1e69d7b 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatControlSet.java @@ -4,6 +4,7 @@ import java.text.DateFormatSymbols; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import android.app.Activity; import android.view.LayoutInflater; @@ -65,6 +66,8 @@ public class RepeatControlSet implements TaskEditControlSet { @Autowired ExceptionService exceptionService; + boolean setInterval = false; + // --- implementation public RepeatControlSet(final Activity activity, ViewGroup parent) { @@ -111,10 +114,22 @@ public class RepeatControlSet implements TaskEditControlSet { repeatValueClick(); } }); + interval.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parentView, View view, int position, long id) { - daysOfWeekContainer.setVisibility(position == INTERVAL_WEEKS ? View.VISIBLE : View.GONE); + if(setInterval) { + setInterval = false; + return; + } + if(position == INTERVAL_WEEKS) { + int dayOfWeek = new Date().getDay(); + for(int i = 0; i < 7; i++) + daysOfWeek[i].setChecked(i == dayOfWeek); + } else { + for(int i = 0; i < 7; i++) + daysOfWeek[i].setChecked(true); + } } @Override @@ -122,6 +137,7 @@ public class RepeatControlSet implements TaskEditControlSet { // } }); + daysOfWeekContainer.setVisibility(View.VISIBLE); } /** Set up the repeat value button */ @@ -172,17 +188,6 @@ public class RepeatControlSet implements TaskEditControlSet { break; case WEEKLY: { interval.setSelection(INTERVAL_WEEKS); - - // clear all day of week checks, then update them - for(int i = 0; i < 7; i++) - daysOfWeek[i].setChecked(false); - - for(WeekdayNum day : rrule.getByDay()) { - for(int i = 0; i < 7; i++) - if(daysOfWeek[i].getTag() == day.wday) - daysOfWeek[i].setChecked(true); - } - break; } case MONTHLY: @@ -196,6 +201,19 @@ public class RepeatControlSet implements TaskEditControlSet { exceptionService.reportError("repeat-unhandled-rule", //$NON-NLS-1$ new Exception("Unhandled rrule frequency: " + recurrence)); //$NON-NLS-1$ } + + // clear all day of week checks, then update them + for(int i = 0; i < 7; i++) + daysOfWeek[i].setChecked(false); + + for(WeekdayNum day : rrule.getByDay()) { + for(int i = 0; i < 7; i++) + if(daysOfWeek[i].getTag() == day.wday) + daysOfWeek[i].setChecked(true); + } + + // suppress first call to interval.onItemSelected + setInterval = true; } catch (ParseException e) { recurrence = ""; //$NON-NLS-1$ exceptionService.reportError("repeat-parse-exception", e); //$NON-NLS-1$ @@ -226,11 +244,6 @@ public class RepeatControlSet implements TaskEditControlSet { break; case INTERVAL_WEEKS: { rrule.setFreq(Frequency.WEEKLY); - ArrayList days = new ArrayList(); - for(int i = 0; i < daysOfWeek.length; i++) - if(daysOfWeek[i].isChecked()) - days.add(new WeekdayNum(0, (Weekday)daysOfWeek[i].getTag())); - rrule.setByDay(days); break; } case INTERVAL_MONTHS: @@ -239,6 +252,13 @@ public class RepeatControlSet implements TaskEditControlSet { case INTERVAL_HOURS: rrule.setFreq(Frequency.HOURLY); } + + ArrayList days = new ArrayList(); + for(int i = 0; i < daysOfWeek.length; i++) + if(daysOfWeek[i].isChecked()) + days.add(new WeekdayNum(0, (Weekday)daysOfWeek[i].getTag())); + rrule.setByDay(days); + result = rrule.toIcal(); } task.setValue(Task.RECURRENCE, result); diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java index 46d3af6f7..4d332b583 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatDetailExposer.java @@ -92,8 +92,8 @@ public class RepeatDetailExposer extends BroadcastReceiver implements DetailExpo } interval = "" + interval + ""; //$NON-NLS-1$//$NON-NLS-2$ - if(rrule.getFreq() == Frequency.WEEKLY) { - List byDay = rrule.getByDay(); + List byDay = rrule.getByDay(); + if(rrule.getFreq() == Frequency.WEEKLY || byDay.size() != 7) { if(byDay.size() > 0) { StringBuilder byDayString = new StringBuilder(); DateFormatSymbols dfs = new DateFormatSymbols(); diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java index 64e37d979..ea249635a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java @@ -1,7 +1,9 @@ package com.todoroo.astrid.repeats; import java.text.ParseException; +import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.TimeZone; import android.content.BroadcastReceiver; @@ -15,6 +17,7 @@ import com.google.ical.values.DateValue; import com.google.ical.values.DateValueImpl; import com.google.ical.values.Frequency; import com.google.ical.values.RRule; +import com.google.ical.values.WeekdayNum; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; @@ -46,83 +49,116 @@ public class RepeatTaskCompleteListener extends BroadcastReceiver { String recurrence = task.getValue(Task.RECURRENCE); if(recurrence != null && recurrence.length() > 0) { - DateValue repeatFrom; - Date repeatFromDate = new Date(); - - DateValue today = new DateValueImpl(repeatFromDate.getYear() + 1900, - repeatFromDate.getMonth() + 1, repeatFromDate.getDate()); - if(task.hasDueDate() && !task.getFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION)) { - repeatFromDate = new Date(task.getValue(Task.DUE_DATE)); - if(task.hasDueTime()) { - repeatFrom = new DateTimeValueImpl(repeatFromDate.getYear() + 1900, - repeatFromDate.getMonth() + 1, repeatFromDate.getDate(), - repeatFromDate.getHours(), repeatFromDate.getMinutes(), repeatFromDate.getSeconds()); - } else { - repeatFrom = new DateValueImpl(repeatFromDate.getYear() + 1900, - repeatFromDate.getMonth() + 1, repeatFromDate.getDate()); - } + long newDueDate; + try { + newDueDate = computeNextDueDate(task, recurrence); + if(newDueDate == -1) + return; + } catch (ParseException e) { + exceptionService.reportError("repeat-parse", e); //$NON-NLS-1$ + return; + } + + long hideUntil = task.getValue(Task.HIDE_UNTIL); + if(hideUntil > 0 && task.getValue(Task.DUE_DATE) > 0) { + hideUntil += newDueDate - task.getValue(Task.DUE_DATE); + } + + // clear recurrence from completed task so it can be re-commpleted + task.setValue(Task.RECURRENCE, ""); //$NON-NLS-1$ + taskService.save(task, false); + + // clone to create new task + task = taskService.clone(task); + task.setValue(Task.DUE_DATE, newDueDate); + task.setValue(Task.HIDE_UNTIL, hideUntil); + task.setValue(Task.COMPLETION_DATE, 0L); + task.setValue(Task.TIMER_START, 0L); + task.setValue(Task.ELAPSED_SECONDS, 0); + taskService.save(task, false); + } + } + + public static long computeNextDueDate(Task task, String recurrence) throws ParseException { + DateValue repeatFrom; + Date repeatFromDate = new Date(); + + DateValue today = new DateValueImpl(repeatFromDate.getYear() + 1900, + repeatFromDate.getMonth() + 1, repeatFromDate.getDate()); + if(task.hasDueDate() && !task.getFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION)) { + repeatFromDate = new Date(task.getValue(Task.DUE_DATE)); + if(task.hasDueTime()) { + repeatFrom = new DateTimeValueImpl(repeatFromDate.getYear() + 1900, + repeatFromDate.getMonth() + 1, repeatFromDate.getDate(), + repeatFromDate.getHours(), repeatFromDate.getMinutes(), repeatFromDate.getSeconds()); } else { - repeatFrom = today; + repeatFrom = new DateValueImpl(repeatFromDate.getYear() + 1900, + repeatFromDate.getMonth() + 1, repeatFromDate.getDate()); } + } else { + repeatFrom = today; + } - // invoke the recurrence iterator - try { - long newDueDate; - RRule rrule = new RRule(recurrence); - if(rrule.getFreq() == Frequency.HOURLY) { - newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, - repeatFromDate.getTime() + DateUtilities.ONE_HOUR * rrule.getInterval()); - } else { - RecurrenceIterator iterator = RecurrenceIteratorFactory.createRecurrenceIterator(rrule, - repeatFrom, TimeZone.getDefault()); - DateValue nextDate; - if(repeatFrom.compareTo(today) < 0) { - iterator.advanceTo(today); - if(!iterator.hasNext()) - return; - nextDate = iterator.next(); - } else { - iterator.advanceTo(repeatFrom); - if(!iterator.hasNext()) - return; - nextDate = iterator.next(); - nextDate = iterator.next(); - } - - if(nextDate instanceof DateTimeValueImpl) { - DateTimeValueImpl newDateTime = (DateTimeValueImpl)nextDate; - newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, - Date.UTC(newDateTime.year() - 1900, newDateTime.month() - 1, - newDateTime.day(), newDateTime.hour(), - newDateTime.minute(), newDateTime.second())); - } else { - newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, - new Date(nextDate.year() - 1900, nextDate.month() - 1, - nextDate.day()).getTime()); - } - } - - long hideUntil = task.getValue(Task.HIDE_UNTIL); - if(hideUntil > 0 && task.getValue(Task.DUE_DATE) > 0) { - hideUntil += newDueDate - task.getValue(Task.DUE_DATE); - } - - // clear recurrence from completed task so it can be re-commpleted - task.setValue(Task.RECURRENCE, ""); //$NON-NLS-1$ - taskService.save(task, false); - - // clone to create new task - task = taskService.clone(task); - task.setValue(Task.DUE_DATE, newDueDate); - task.setValue(Task.HIDE_UNTIL, hideUntil); - task.setValue(Task.COMPLETION_DATE, 0L); - task.setValue(Task.TIMER_START, 0L); - task.setValue(Task.ELAPSED_SECONDS, 0); - taskService.save(task, false); - } catch (ParseException e) { - exceptionService.reportError("recurrence-rule: " + recurrence, e); //$NON-NLS-1$ + // invoke the recurrence iterator + long newDueDate; + RRule rrule = new RRule(recurrence); + + // handle the iCalendar "byDay" field differently depending on if + // we are weekly or otherwise + + List byDay = null; + if(rrule.getFreq() != Frequency.WEEKLY) { + byDay = rrule.getByDay(); + rrule.setByDay(Collections.EMPTY_LIST); + } + + if(rrule.getFreq() == Frequency.HOURLY) { + newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, + repeatFromDate.getTime() + DateUtilities.ONE_HOUR * rrule.getInterval()); + } else { + RecurrenceIterator iterator = RecurrenceIteratorFactory.createRecurrenceIterator(rrule, + repeatFrom, TimeZone.getDefault()); + DateValue nextDate = repeatFrom; + if(repeatFrom.compareTo(today) < 0) + iterator.advanceTo(today); + + for(int i = 0; i < 10; i++) { // ten tries then we give up + if(!iterator.hasNext()) + return -1; + nextDate = iterator.next(); + if(nextDate.compareTo(repeatFrom) != 0) + break; + } + System.err.println("REPEAT started " + repeatFrom + ", ended " + nextDate); //$NON-NLS-1$ //$NON-NLS-2$ + + if(nextDate instanceof DateTimeValueImpl) { + DateTimeValueImpl newDateTime = (DateTimeValueImpl)nextDate; + newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, + Date.UTC(newDateTime.year() - 1900, newDateTime.month() - 1, + newDateTime.day(), newDateTime.hour(), + newDateTime.minute(), newDateTime.second())); + } else { + newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, + new Date(nextDate.year() - 1900, nextDate.month() - 1, + nextDate.day()).getTime()); } } + + // what we do with the by day information is to add days until + // weekday equals one of this list + if(byDay != null && byDay.size() > 0) { + Date newDueDateDate = new Date(newDueDate); + outer: for(int i = 0; i < 7; i++) { + int weekday = newDueDateDate.getDay(); + for(WeekdayNum wdn : byDay) + if(wdn.wday.jsDayNum == weekday) + break outer; + newDueDateDate.setDate(newDueDateDate.getDate() + 1); + } + newDueDate = newDueDateDate.getTime(); + } + + return newDueDate; } } diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index 2542eac1c..0c26153f1 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -15,12 +15,12 @@ import android.graphics.Paint; import android.text.Html; import android.text.util.Linkify; import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.view.ContextMenu.ContextMenuInfo; import android.view.View.OnClickListener; import android.view.View.OnCreateContextMenuListener; -import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.CheckBox; @@ -308,7 +308,10 @@ public class TaskAdapter extends CursorAdapter implements Filterable { // importance bar final View importanceView = viewHolder.importance; { int value = task.getValue(Task.IMPORTANCE); - importanceView.setBackgroundColor(IMPORTANCE_COLORS[value]); + if(value < IMPORTANCE_COLORS.length) + importanceView.setBackgroundColor(IMPORTANCE_COLORS[value]); + else + importanceView.setBackgroundColor(0); } // details and decorations, expanded diff --git a/astrid/src/com/todoroo/astrid/dao/TaskDao.java b/astrid/src/com/todoroo/astrid/dao/TaskDao.java index 07f626727..cd94a9cd6 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskDao.java @@ -153,7 +153,7 @@ public class TaskDao extends GenericDao { if(saveSuccessful) { task.markSaved(); - afterSave(task, values, skipHooks); + afterSave(task, values); } return saveSuccessful; @@ -208,9 +208,9 @@ public class TaskDao extends GenericDao { * @param values values to be persisted to the database * @param skipHooks whether this save occurs as part of a sync */ - private void afterSave(Task task, ContentValues values, boolean skipHooks) { + private void afterSave(Task task, ContentValues values) { if(values.containsKey(Task.COMPLETION_DATE.name) && task.isCompleted()) - afterComplete(task, values, skipHooks); + afterComplete(task, values); else { ReminderService.getInstance().scheduleAlarm(task); } @@ -218,9 +218,6 @@ public class TaskDao extends GenericDao { Astrid2TaskProvider.notifyDatabaseModification(); ContextManager.getContext().startService(new Intent(ContextManager.getContext(), TasksWidget.UpdateService.class)); - - if(skipHooks) - return; } /** @@ -230,14 +227,12 @@ public class TaskDao extends GenericDao { * @param values * @param duringSync */ - private void afterComplete(Task task, ContentValues values, boolean duringSync) { + private void afterComplete(Task task, ContentValues values) { // send broadcast - if(!duringSync) { - Context context = ContextManager.getContext(); - Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_COMPLETED); - broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); - context.sendOrderedBroadcast(broadcastIntent, null); - } + Context context = ContextManager.getContext(); + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_COMPLETED); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); + context.sendOrderedBroadcast(broadcastIntent, null); } } diff --git a/astrid/src/com/todoroo/astrid/ui/NumberPicker.java b/astrid/src/com/todoroo/astrid/ui/NumberPicker.java index f0db4d326..932cf9e4e 100644 --- a/astrid/src/com/todoroo/astrid/ui/NumberPicker.java +++ b/astrid/src/com/todoroo/astrid/ui/NumberPicker.java @@ -254,7 +254,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, } private void changeCurrent(int current, Animation in, Animation out) { - current = notifyChange(); + current = notifyChange(current); // Wrap around the values if we go past the start or end if (current > mEnd) { @@ -267,9 +267,9 @@ public class NumberPicker extends LinearLayout implements OnClickListener, updateView(); } - private int notifyChange() { + private int notifyChange(int current) { if (mListener != null) { - return mListener.onChanged(this, mPrevious, mCurrent); + return mListener.onChanged(this, mCurrent, current); } else return mCurrent; @@ -294,7 +294,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, if ((val >= mStart) && (val <= mEnd)) { mPrevious = mCurrent; mCurrent = val; - notifyChange(); + notifyChange(mCurrent); } updateView(); } diff --git a/tests/src/com/todoroo/astrid/repeats/AdvancedRepeatTests.java b/tests/src/com/todoroo/astrid/repeats/AdvancedRepeatTests.java new file mode 100644 index 000000000..fd78a034a --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/AdvancedRepeatTests.java @@ -0,0 +1,113 @@ +package com.todoroo.astrid.repeats; + +import java.text.ParseException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; + +import com.google.ical.values.Frequency; +import com.google.ical.values.RRule; +import com.google.ical.values.Weekday; +import com.google.ical.values.WeekdayNum; +import com.todoroo.andlib.test.TodorooTestCase; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.model.Task; + +public class AdvancedRepeatTests extends TodorooTestCase { + + + public static void assertDatesEqual(long date, long other) { + assertEquals("Expected: " + new Date(date) + ", Actual: " + new Date(other), + date, other); + } + + public void testDailyWithDaysOfWeek() throws ParseException { + RRule rrule = new RRule(); + rrule.setInterval(1); + rrule.setFreq(Frequency.DAILY); + rrule.setByDay(Collections.singletonList(new WeekdayNum(0, Weekday.FR))); + + Task task = new Task(); + long thursday = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(113, 7, 1).getTime()); + task.setValue(Task.DUE_DATE, thursday); + + // repeat once => due date should become friday + long friday = thursday + DateUtilities.ONE_DAY; + long nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(friday, nextDueDate); + + // repeat again => due date should be one week from friday + long nextFriday = friday + DateUtilities.ONE_WEEK; + task.setValue(Task.DUE_DATE, friday); + nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(nextFriday, nextDueDate); + + // now try with thursday, and repeat every 2 days. expect next friday + rrule.setInterval(2); + task.setValue(Task.DUE_DATE, thursday); + nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(nextFriday, nextDueDate); + + // again with friday, expect next friday + task.setValue(Task.DUE_DATE, friday); + nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(nextFriday, nextDueDate); + } + + public void testMonthlyWithDaysOfWeek() throws ParseException { + RRule rrule = new RRule(); + rrule.setInterval(1); + rrule.setFreq(Frequency.MONTHLY); + rrule.setByDay(Arrays.asList(new WeekdayNum[] { + new WeekdayNum(0, Weekday.SU), + new WeekdayNum(0, Weekday.MO), + new WeekdayNum(0, Weekday.TU), + new WeekdayNum(0, Weekday.WE), + new WeekdayNum(0, Weekday.TH), + new WeekdayNum(0, Weekday.FR), + new WeekdayNum(0, Weekday.SA), + })); + + Task task = new Task(); + long thursday = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(113, 7, 1).getTime()); + task.setValue(Task.DUE_DATE, thursday); + + // repeat once => due date should become next month on the first + long nextMonth = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(113, 8, 1).getTime()); + long nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(nextMonth, nextDueDate); + + // only allow thursdays + rrule.setByDay(Arrays.asList(new WeekdayNum[] { + new WeekdayNum(0, Weekday.TH), + })); + long nextMonthOnThursday = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(113, 8, 5).getTime()); + nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(nextMonthOnThursday, nextDueDate); + } + + public void testDueDateInPast() throws ParseException { + RRule rrule = new RRule(); + rrule.setInterval(1); + rrule.setFreq(Frequency.DAILY); + + Task task = new Task(); + + // repeat once => due date should become tomorrow + long past = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(110, 7, 1).getTime()); + task.setValue(Task.DUE_DATE, past); + long today = task.createDueDate(Task.URGENCY_SPECIFIC_DAY, DateUtilities.now()); + long nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(today, nextDueDate); + + // test specific day & time + long pastWithTime = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new Date(110, 7, 1, 10, 4).getTime()); + task.setValue(Task.DUE_DATE, pastWithTime); + Date date = new Date(DateUtilities.now() / 1000L * 1000L); + date.setHours(10); + date.setMinutes(4); + long todayWithTime = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, date.getTime()); + nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); + assertDatesEqual(todayWithTime + DateUtilities.ONE_DAY, nextDueDate); + } +} From 4bcfc48f4d29cf688fe3407467271ae168df857b Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 04:02:16 -0700 Subject: [PATCH 08/40] Fix for cloning original task without recurrence --- .../repeats/RepeatTaskCompleteListener.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java index ea249635a..9bcf77d58 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java @@ -64,17 +64,17 @@ public class RepeatTaskCompleteListener extends BroadcastReceiver { hideUntil += newDueDate - task.getValue(Task.DUE_DATE); } - // clear recurrence from completed task so it can be re-commpleted - task.setValue(Task.RECURRENCE, ""); //$NON-NLS-1$ - taskService.save(task, false); - // clone to create new task - task = taskService.clone(task); - task.setValue(Task.DUE_DATE, newDueDate); - task.setValue(Task.HIDE_UNTIL, hideUntil); - task.setValue(Task.COMPLETION_DATE, 0L); - task.setValue(Task.TIMER_START, 0L); - task.setValue(Task.ELAPSED_SECONDS, 0); + Task clone = taskService.clone(task); + clone.setValue(Task.DUE_DATE, newDueDate); + clone.setValue(Task.HIDE_UNTIL, hideUntil); + clone.setValue(Task.COMPLETION_DATE, 0L); + clone.setValue(Task.TIMER_START, 0L); + clone.setValue(Task.ELAPSED_SECONDS, 0); + taskService.save(clone, false); + + // clear recurrence from completed task so it can be re-completed + task.setValue(Task.RECURRENCE, ""); //$NON-NLS-1$ taskService.save(task, false); } } From 7547767d67644c2f6c9c0796ed0971028bea4f2b Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 15:51:23 -0700 Subject: [PATCH 09/40] Made old shortcuts work --- .../andlib/utility/AndroidUtilities.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java index c6719d4d9..b4956414c 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java @@ -23,8 +23,8 @@ import android.text.InputType; import android.util.Log; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.View.OnTouchListener; +import android.view.ViewGroup; import android.widget.TextView; import com.todoroo.andlib.service.Autowired; @@ -195,9 +195,19 @@ public class AndroidUtilities { String[] pairs = string.split("="); ContentValues result = new ContentValues(); - for(String item : pairs) { - String[] keyValue = item.split("="); - result.put(keyValue[0].trim(), keyValue[1].trim()); + String key = null; + for(int i = 0; i < pairs.length; i++) { + String newKey = null; + int lastSpace = pairs[i].lastIndexOf(' '); + if(lastSpace != -1) { + newKey = pairs[i].substring(lastSpace + 1); + pairs[i] = pairs[i].substring(0, lastSpace); + } else { + newKey = pairs[i]; + } + if(key != null) + result.put(key.trim(), pairs[i].trim()); + key = newKey; } return result; } From 07bd6f099e87d5a5764adfe389afe145f528b47e Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 16:02:52 -0700 Subject: [PATCH 10/40] Set owner of task import and export progress dialog boxes so they get collected --- .../com/todoroo/astrid/backup/TasksXmlExporter.java | 5 ++++- .../com/todoroo/astrid/backup/TasksXmlImporter.java | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java index a779358be..1829d1c4f 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlExporter.java @@ -6,6 +6,7 @@ import java.io.IOException; import org.xmlpull.v1.XmlSerializer; +import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; import android.os.Handler; @@ -16,8 +17,8 @@ import android.widget.Toast; import com.timsu.astrid.R; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; -import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.Property.PropertyVisitor; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Query; @@ -82,6 +83,8 @@ public class TasksXmlExporter { progressDialog.setCancelable(false); progressDialog.setIndeterminate(false); progressDialog.show(); + if(context instanceof Activity) + progressDialog.setOwnerActivity((Activity)context); } new Thread(new Runnable() { diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java index 8a557692e..4f17af28c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java @@ -10,6 +10,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; +import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; @@ -23,8 +24,8 @@ import com.google.ical.values.RRule; import com.timsu.astrid.R; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; -import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.Property.PropertyVisitor; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.sql.Criterion; @@ -33,8 +34,8 @@ import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.legacy.LegacyImportance; import com.todoroo.astrid.legacy.LegacyRepeatInfo; -import com.todoroo.astrid.legacy.LegacyTaskModel; import com.todoroo.astrid.legacy.LegacyRepeatInfo.LegacyRepeatInterval; +import com.todoroo.astrid.legacy.LegacyTaskModel; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.rmilk.data.MilkTask; @@ -97,6 +98,8 @@ public class TasksXmlImporter { progressDialog.setCancelable(false); progressDialog.setIndeterminate(true); progressDialog.show(); + if(context instanceof Activity) + progressDialog.setOwnerActivity((Activity)context); new Thread(new Runnable() { @Override From 6bb677ca5cbbede6d6332c6bdd6fb2a6f2dd364a Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 16:07:32 -0700 Subject: [PATCH 11/40] Fix for time duration control set not working --- astrid/src/com/todoroo/astrid/ui/NumberPicker.java | 5 +++-- astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/astrid/src/com/todoroo/astrid/ui/NumberPicker.java b/astrid/src/com/todoroo/astrid/ui/NumberPicker.java index 932cf9e4e..68cab4fd0 100644 --- a/astrid/src/com/todoroo/astrid/ui/NumberPicker.java +++ b/astrid/src/com/todoroo/astrid/ui/NumberPicker.java @@ -106,7 +106,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, private int mPrevious; private OnChangedListener mListener; private Formatter mFormatter; - private long mSpeed = 500; + private long mSpeed = 300; private boolean mIncrement; private boolean mDecrement; @@ -179,6 +179,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, public void setFormatter(Formatter formatter) { mFormatter = formatter; + updateView(); } /** @@ -271,7 +272,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, if (mListener != null) { return mListener.onChanged(this, mCurrent, current); } else - return mCurrent; + return current; } diff --git a/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java b/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java index c1324c3f7..49bffa78e 100644 --- a/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java +++ b/astrid/src/com/todoroo/astrid/ui/TimeDurationControlSet.java @@ -72,7 +72,7 @@ public class TimeDurationControlSet implements OnNNumberPickedListener, if(hourPicker.getCurrent() == 0) return 0; hourPicker.setCurrent(hourPicker.getCurrent() - 1); - return 60 - newVal; + return 60 + newVal; } else if(newVal > 59) { hourPicker.setCurrent(hourPicker.getCurrent() + 1); return newVal % 60; From e8ee04a232c34f4dd9bd498643b76c9cab79d0dd Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 16:09:52 -0700 Subject: [PATCH 12/40] fix for task decoration being to the left of the checkbox --- astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java index 0c26153f1..0f9d2efbc 100644 --- a/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java +++ b/astrid/src/com/todoroo/astrid/adapter/TaskAdapter.java @@ -15,12 +15,12 @@ import android.graphics.Paint; import android.text.Html; import android.text.util.Linkify; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.view.ContextMenu.ContextMenuInfo; import android.view.View.OnClickListener; import android.view.View.OnCreateContextMenuListener; +import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.CheckBox; @@ -484,10 +484,10 @@ public class TaskAdapter extends CursorAdapter implements Filterable { viewHolder.decorations[i] = view; switch(decoration.position) { case TaskDecoration.POSITION_LEFT: - viewHolder.taskRow.addView(view, 1); + viewHolder.taskRow.addView(view, 2); break; case TaskDecoration.POSITION_RIGHT: - viewHolder.taskRow.addView(view, viewHolder.taskRow.getChildCount() - 2); + viewHolder.taskRow.addView(view, viewHolder.taskRow.getChildCount() - 1); } } i++; From 79933aaed59edecf6a506dfac4ee2f3aca8704e0 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 16:22:38 -0700 Subject: [PATCH 13/40] Fix for AST-171 - sorting by alpha takes capitalization into account --- .../plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java index 1cf0b2df4..fe8f5e3bb 100644 --- a/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/core/CoreFilterExposer.java @@ -86,7 +86,7 @@ public final class CoreFilterExposer extends BroadcastReceiver { r.getString(R.string.BFE_Alphabetical), new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), TaskCriteria.isVisible())). - orderBy(Order.asc(Task.TITLE)), + orderBy(Order.asc(Functions.upper(Task.TITLE))), null); alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_alpha)).getBitmap(); From db599d81f801051eaad2fb12ebd4627de89ac06a Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 16:22:49 -0700 Subject: [PATCH 14/40] Crash during notification randomly --- .../com/todoroo/astrid/reminders/Notifications.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java index ce9adf6d3..0a55040ce 100644 --- a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java @@ -81,6 +81,11 @@ public class Notifications extends BroadcastReceiver { else reminder = ""; //$NON-NLS-1$ + synchronized(Notifications.class) { + if(notificationManager == null) + notificationManager = new AndroidNotificationManager(context); + } + if(!showTaskNotification(id, type, reminder)) { notificationManager.cancel((int)id); } From feb6b828a52d40cddae574220a2a5e204cc8a439 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 17:24:19 -0700 Subject: [PATCH 15/40] Added a category for completed tasks --- .../todoroo/astrid/tags/TagFilterExposer.java | 25 ++++++++++++++----- .../com/todoroo/astrid/tags/TagService.java | 20 +++++++++------ astrid/res/values/strings-tags.xml | 3 +++ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java index e75248814..86bbecada 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java @@ -31,11 +31,11 @@ public class TagFilterExposer extends BroadcastReceiver { private TagService tagService; @SuppressWarnings("nls") - private Filter filterFromTag(Context context, Tag tag) { + private Filter filterFromTag(Context context, Tag tag, boolean completed) { String listTitle = context.getString(R.string.tag_FEx_tag_w_size). replace("$T", tag.tag).replace("$C", Integer.toString(tag.count)); String title = context.getString(R.string.tag_FEx_name, tag.tag); - QueryTemplate tagTemplate = tag.queryTemplate(); + QueryTemplate tagTemplate = tag.queryTemplate(completed); ContentValues contentValues = new ContentValues(); contentValues.put(Metadata.KEY.name, TagService.KEY); contentValues.put(TagService.TAG.name, tag.tag); @@ -70,31 +70,44 @@ public class TagFilterExposer extends BroadcastReceiver { Tag[] tagsBySize = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE); Filter[] filtersByAlpha = new Filter[tagsByAlpha.length]; for(int i = 0; i < tagsByAlpha.length; i++) - filtersByAlpha[i] = filterFromTag(context, tagsByAlpha[i]); + filtersByAlpha[i] = filterFromTag(context, tagsByAlpha[i], false); Filter[] filtersBySize = new Filter[tagsBySize.length]; for(int i = 0; i < tagsBySize.length; i++) - filtersBySize[i] = filterFromTag(context, tagsBySize[i]); + filtersBySize[i] = filterFromTag(context, tagsBySize[i], false); + + Tag[] completed = tagService.getGroupedTags(TagService.GROUPED_TAGS_COMPLETED); + Filter[] filtersCompleted = new Filter[completed.length]; + for(int i = 0; i < completed.length; i++) + filtersCompleted[i] = filterFromTag(context, completed[i], true); FilterListHeader tagsHeader = new FilterListHeader(context.getString(R.string.tag_FEx_header)); + Filter untagged = new Filter(r.getString(R.string.tag_FEx_untagged), r.getString(R.string.tag_FEx_untagged), tagService.untaggedTemplate(), null); untagged.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_untagged)).getBitmap(); + FilterCategory tagsCategoryBySize = new FilterCategory(context.getString(R.string.tag_FEx_by_size), filtersBySize); tagsCategoryBySize.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap(); + FilterCategory tagsCategoryByAlpha = new FilterCategory(context.getString(R.string.tag_FEx_alpha), filtersByAlpha); - tagsCategoryByAlpha.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags2)).getBitmap(); + tagsCategoryByAlpha.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap(); + + FilterCategory tagsCategoryCompleted = new FilterCategory(context.getString(R.string.tag_FEx_completed), + filtersCompleted); + tagsCategoryCompleted.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags2)).getBitmap(); // transmit filter list - FilterListItem[] list = new FilterListItem[4]; + FilterListItem[] list = new FilterListItem[5]; list[0] = tagsHeader; list[1] = untagged; list[2] = tagsCategoryBySize; list[3] = tagsCategoryByAlpha; + list[4] = tagsCategoryCompleted; Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java index 9f73ba6fc..09499248a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java @@ -2,9 +2,9 @@ package com.todoroo.astrid.tags; import java.util.ArrayList; -import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.Property.CountProperty; import com.todoroo.andlib.data.Property.StringProperty; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; @@ -58,8 +58,12 @@ public final class TagService { * Property for retrieving count of aggregated rows */ private static final CountProperty COUNT = new CountProperty(); - public static final Order GROUPED_TAGS_BY_ALPHA = Order.asc(TAG); - public static final Order GROUPED_TAGS_BY_SIZE = Order.desc(COUNT); + public static final QueryTemplate GROUPED_TAGS_BY_ALPHA = new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), MetadataCriteria.withKey(KEY))). + orderBy(Order.asc(TAG)); + public static final QueryTemplate GROUPED_TAGS_BY_SIZE = new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), MetadataCriteria.withKey(KEY))). + orderBy(Order.desc(COUNT)); + public static final QueryTemplate GROUPED_TAGS_COMPLETED = new QueryTemplate().where(Criterion.and(TaskCriteria.completed(), MetadataCriteria.withKey(KEY))). + orderBy(Order.desc(COUNT)); /** * Helper class for returning a tag/task count pair @@ -82,11 +86,12 @@ public final class TagService { * @param tag * @return */ - public QueryTemplate queryTemplate() { + public QueryTemplate queryTemplate(boolean completed) { + Criterion activeStatus = completed ? TaskCriteria.completed() : TaskCriteria.isActive(); return new QueryTemplate().join(Join.inner(Metadata.TABLE, Task.ID.eq(Metadata.TASK))).where(Criterion.and( MetadataCriteria.withKey(KEY), TAG.eq(tag), - TaskCriteria.isActive())); + activeStatus)); } } @@ -103,11 +108,10 @@ public final class TagService { * @param taskId * @return empty array if no tags, otherwise array */ - public Tag[] getGroupedTags(Order order) { + public Tag[] getGroupedTags(QueryTemplate template) { Query query = Query.select(TAG.as(TAG.name), COUNT). join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))). - where(Criterion.and(TaskCriteria.isActive(), MetadataCriteria.withKey(KEY))). - orderBy(order).groupBy(TAG); + withQueryTemplate(template.toString()).groupBy(TAG); TodorooCursor cursor = metadataDao.query(query); try { Tag[] array = new Tag[cursor.getCount()]; diff --git a/astrid/res/values/strings-tags.xml b/astrid/res/values/strings-tags.xml index c2632f136..b894067f2 100644 --- a/astrid/res/values/strings-tags.xml +++ b/astrid/res/values/strings-tags.xml @@ -28,6 +28,9 @@ Alphabetical + + Completed + Untagged From 8fee29ef5567321db14b074ddef6f7c22b1d5524 Mon Sep 17 00:00:00 2001 From: Tim Su Date: Fri, 6 Aug 2010 17:59:58 -0700 Subject: [PATCH 16/40] Completed step 1 of alarms - the control set --- .../com/todoroo/andlib/sql/Query.java | 4 +- .../com/todoroo/astrid/alarms/Alarm.java | 99 ++--------------- .../astrid/alarms/AlarmControlSet.java | 101 +++++++++++++++++ .../todoroo/astrid/alarms/AlarmDatabase.java | 17 +-- .../todoroo/astrid/alarms/AlarmService.java | 57 +++++----- .../astrid/alarms/TransitionalAlarm.java | 102 ++++++++++++++++++ .../astrid/backup/TasksXmlImporter.java | 4 +- .../todoroo/astrid/tags/TagFilterExposer.java | 8 +- .../com/todoroo/astrid/tags/TagService.java | 20 ++-- .../todoroo/astrid/tags/TagsControlSet.java | 7 +- astrid/res/layout/alarm_control.xml | 23 ++++ astrid/res/layout/alarm_edit_row.xml | 42 ++++++++ astrid/res/values/strings-alarms.xml | 16 +++ .../astrid/activity/TaskEditActivity.java | 12 ++- .../astrid/provider/Astrid2TaskProvider.java | 3 +- .../service/Astrid2To3UpgradeHelper.java | 10 +- .../todoroo/astrid/widget/TasksWidget.java | 4 +- .../upgrade/Astrid2To3UpgradeTests.java | 10 +- 18 files changed, 376 insertions(+), 163 deletions(-) create mode 100644 astrid/plugin-src/com/todoroo/astrid/alarms/AlarmControlSet.java create mode 100644 astrid/plugin-src/com/todoroo/astrid/alarms/TransitionalAlarm.java create mode 100644 astrid/res/layout/alarm_control.xml create mode 100644 astrid/res/layout/alarm_edit_row.xml create mode 100644 astrid/res/values/strings-alarms.xml diff --git a/astrid/common-src/com/todoroo/andlib/sql/Query.java b/astrid/common-src/com/todoroo/andlib/sql/Query.java index 620087ced..7cc16c459 100644 --- a/astrid/common-src/com/todoroo/andlib/sql/Query.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Query.java @@ -89,14 +89,14 @@ public final class Query { visitSelectClause(sql); visitFromClause(sql); + visitJoinClause(sql); if(queryTemplate == null) { - visitJoinClause(sql); visitWhereClause(sql); visitGroupByClause(sql); visitOrderByClause(sql); visitLimitClause(sql); } else { - if(joins.size() > 0 || groupBies.size() > 0 || orders.size() > 0 || + if(groupBies.size() > 0 || orders.size() > 0 || havings.size() > 0) throw new IllegalStateException("Can't have extras AND query template"); //$NON-NLS-1$ sql.append(queryTemplate); diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java b/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java index 47b1fba67..29e0e7d0a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java @@ -1,111 +1,34 @@ -/** - * See the file "LICENSE" for the full license governing this code. - */ package com.todoroo.astrid.alarms; - -import android.content.ContentValues; - -import com.todoroo.andlib.data.AbstractModel; -import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property.IntegerProperty; import com.todoroo.andlib.data.Property.LongProperty; -import com.todoroo.andlib.data.Property.StringProperty; -import com.todoroo.andlib.data.Table; -import com.todoroo.andlib.data.TodorooCursor; -import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.model.Metadata; /** - * Data Model which represents an alarm + * Metadata entry for a task alarm * * @author Tim Su * */ -@SuppressWarnings("nls") -public class Alarm extends AbstractModel { - - // --- table - - public static final Table TABLE = new Table("alarm", Alarm.class); +public class Alarm { - // --- properties + /** metadata key */ + public static final String METADATA_KEY = "alarm"; //$NON-NLS-1$ - /** ID */ - public static final LongProperty ID = new LongProperty( - TABLE, ID_PROPERTY_NAME); + /** time of alarm */ + public static final LongProperty TIME = new LongProperty(Metadata.TABLE, + Metadata.VALUE1.name); - /** Associated Task */ - public static final LongProperty TASK = new LongProperty( - TABLE, "task"); - - /** Alarm Time */ - public static final LongProperty TIME = new LongProperty( - TABLE, "time"); - - /** Alarm Type (see constants) */ - public static final IntegerProperty TYPE = new IntegerProperty( - TABLE, "type"); - - /** Alarm Ringtone */ - public static final StringProperty RINGTONE = new StringProperty( - TABLE, "ringtone"); - - /** List of all properties for this model */ - public static final Property[] PROPERTIES = generateProperties(Alarm.class); + /** alarm type */ + public static final IntegerProperty TYPE = new IntegerProperty(Metadata.TABLE, + Metadata.VALUE2.name); // --- constants - /** this alarm was already triggered */ - public static final int TYPE_TRIGGERED = 0; - /** this alarm is single-shot */ public static final int TYPE_SINGLE = 1; /** this alarm repeats itself until turned off */ public static final int TYPE_REPEATING = 2; - // --- defaults - - /** Default values container */ - private static final ContentValues defaultValues = new ContentValues(); - - static { - defaultValues.put(TYPE.name, TYPE_SINGLE); - defaultValues.put(RINGTONE.name, ""); - } - - @Override - public ContentValues getDefaultValues() { - return defaultValues; - } - - // --- data access boilerplate - - public Alarm() { - super(); - } - - public Alarm(TodorooCursor cursor) { - this(); - readPropertiesFromCursor(cursor); - } - - public void readFromCursor(TodorooCursor cursor) { - super.readPropertiesFromCursor(cursor); - } - - @Override - public long getId() { - return getIdHelper(ID); - }; - - // --- parcelable helpers - - private static final Creator CREATOR = new ModelCreator(Task.class); - - @Override - protected Creator getCreator() { - return CREATOR; - } - } diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmControlSet.java b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmControlSet.java new file mode 100644 index 000000000..b9c54eb82 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmControlSet.java @@ -0,0 +1,101 @@ +package com.todoroo.astrid.alarms; + +import java.util.Date; +import java.util.LinkedHashSet; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +import com.timsu.astrid.R; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.widget.DateControlSet; +import com.todoroo.astrid.activity.TaskEditActivity.TaskEditControlSet; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.Task; + +/** + * Control set to manage adding and removing tags + * + * @author Tim Su + * + */ +public final class AlarmControlSet implements TaskEditControlSet { + + // --- constants + + /** Number of alarms a task can have */ + static final int MAX_ALARMS = 10; + + // --- instance variables + + private final LinearLayout alertsContainer; + private final Activity activity; + + public AlarmControlSet(Activity activity, ViewGroup parent) { + View v = LayoutInflater.from(activity).inflate(R.layout.alarm_control, parent, true); + + this.activity = activity; + this.alertsContainer = (LinearLayout) v.findViewById(R.id.alert_container); + v.findViewById(R.id.alarms_add).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View arg0) { + addAlarm(new Date()); + } + }); + } + + @Override + public void readFromTask(Task task) { + if(alertsContainer.getChildCount() == 0) { + TodorooCursor cursor = AlarmService.getInstance().getAlarms(task.getId()); + try { + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) + addAlarm(new Date(cursor.get(Alarm.TIME))); + } finally { + cursor.close(); + } + } + } + + @Override + public void writeToModel(Task task) { + LinkedHashSet alarms = new LinkedHashSet(); + for(int i = 0; i < alertsContainer.getChildCount(); i++) { + DateControlSet set = (DateControlSet) alertsContainer.getChildAt(i).getTag(); + if(set == null) + continue; + Date date = set.getDate(); + if(date != null) + alarms.add(set.getDate().getTime()); + } + AlarmService.getInstance().synchronizeAlarms(task.getId(), alarms); + } + + private boolean addAlarm(Date alert) { + if(alertsContainer.getChildCount() >= MAX_ALARMS) + return false; + + final View alertItem = LayoutInflater.from(activity).inflate(R.layout.alarm_edit_row, null); + alertsContainer.addView(alertItem); + + DateControlSet dcs = new DateControlSet(activity, (Button)alertItem.findViewById(R.id.date), + (Button)alertItem.findViewById(R.id.time)); + dcs.setDate(alert); + alertItem.setTag(dcs); + + ImageButton reminderRemoveButton; + reminderRemoveButton = (ImageButton)alertItem.findViewById(R.id.button1); + reminderRemoveButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + alertsContainer.removeView(alertItem); + } + }); + + return true; + } +} \ No newline at end of file diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDatabase.java b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDatabase.java index 624eadc65..7e5c4d7f8 100644 --- a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDatabase.java +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDatabase.java @@ -36,12 +36,12 @@ public class AlarmDatabase extends AbstractDatabase { * also make sure that our SQLite helper does the right thing. */ public static final Table[] TABLES = new Table[] { - Alarm.TABLE + TransitionalAlarm.TABLE }; // --- implementation - private final GenericDao dao = new GenericDao(Alarm.class, this); + private final GenericDao dao = new GenericDao(TransitionalAlarm.class, this); @Override protected String getName() { @@ -58,7 +58,7 @@ public class AlarmDatabase extends AbstractDatabase { return TABLES; } - public GenericDao getDao() { + public GenericDao getDao() { return dao; } @@ -66,15 +66,8 @@ public class AlarmDatabase extends AbstractDatabase { protected synchronized void onCreateTables() { StringBuilder sql = new StringBuilder(); sql.append("CREATE INDEX IF NOT EXISTS a_task ON "). - append(Alarm.TABLE).append('('). - append(Alarm.TASK.name). - append(')'); - database.execSQL(sql.toString()); - - sql.setLength(0); - sql.append("CREATE INDEX IF NOT EXISTS a_type ON "). - append(Alarm.TABLE).append('('). - append(Alarm.TYPE.name). + append(TransitionalAlarm.TABLE).append('('). + append(TransitionalAlarm.TASK.name). append(')'); database.execSQL(sql.toString()); } diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java index c90c13cf2..6ae73b15e 100644 --- a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java @@ -1,11 +1,15 @@ package com.todoroo.astrid.alarms; -import java.util.ArrayList; +import java.util.LinkedHashSet; -import com.todoroo.andlib.data.GenericDao; import com.todoroo.andlib.data.TodorooCursor; -import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Query; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.service.MetadataService; /** * Provides operations for working with alerts @@ -16,29 +20,27 @@ import com.todoroo.andlib.sql.Query; @SuppressWarnings("nls") public class AlarmService { - AlarmDatabase database = new AlarmDatabase(); + // --- singleton - GenericDao dao = new GenericDao(Alarm.class, database); + private static AlarmService instance = null; - /** - * Metadata key for # of alarms - */ - public static final String ALARM_COUNT = "alarms-count"; - - public AlarmService() { - DependencyInjectionService.getInstance().inject(this); + public static synchronized AlarmService getInstance() { + if(instance == null) + instance = new AlarmService(); + return instance; } + // --- interface /** - * Return alarms for the given task + * Return alarms for the given task. PLEASE CLOSE THE CURSOR! * * @param taskId */ - public TodorooCursor getAlarms(long taskId) { - database.openForReading(); - Query query = Query.select(Alarm.PROPERTIES).where(Alarm.TASK.eq(taskId)); - return dao.query(query); + public TodorooCursor getAlarms(long taskId) { + return PluginServices.getMetadataService().query(Query.select( + Metadata.PROPERTIES).where(MetadataCriteria.byTaskAndwithKey( + taskId, Alarm.METADATA_KEY)).orderBy(Order.asc(Alarm.TIME))); } /** @@ -46,14 +48,19 @@ public class AlarmService { * @param taskId * @param tags */ - public void synchronizeAlarms(long taskId, ArrayList alarms) { - database.openForWriting(); - dao.deleteWhere(Alarm.TASK.eq(taskId)); - - for(Alarm alarm : alarms) { - alarm.setId(Alarm.NO_ID); - alarm.setValue(Alarm.TASK, taskId); - dao.saveExisting(alarm); + public void synchronizeAlarms(long taskId, LinkedHashSet alarms) { + MetadataService service = PluginServices.getMetadataService(); + service.deleteWhere(Criterion.and(MetadataCriteria.byTask(taskId), + MetadataCriteria.withKey(Alarm.METADATA_KEY))); + + Metadata metadata = new Metadata(); + metadata.setValue(Metadata.KEY, Alarm.METADATA_KEY); + metadata.setValue(Metadata.TASK, taskId); + for(Long alarm : alarms) { + metadata.clearValue(Metadata.ID); + metadata.setValue(Alarm.TIME, alarm); + metadata.setValue(Alarm.TYPE, Alarm.TYPE_SINGLE); + service.save(metadata); } } } diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/TransitionalAlarm.java b/astrid/plugin-src/com/todoroo/astrid/alarms/TransitionalAlarm.java new file mode 100644 index 000000000..d8a93b9cc --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/TransitionalAlarm.java @@ -0,0 +1,102 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.alarms; + + +import android.content.ContentValues; + +import com.todoroo.andlib.data.AbstractModel; +import com.todoroo.andlib.data.Property; +import com.todoroo.andlib.data.Property.LongProperty; +import com.todoroo.andlib.data.Table; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.astrid.model.Task; + +/** + * Data Model which represents an alarm. This is a transitional class - + * Alarms are moved over to metadata + * + * @author Tim Su + * + */ +@SuppressWarnings("nls") +@Deprecated +public class TransitionalAlarm extends AbstractModel { + + // --- table + + public static final Table TABLE = new Table("alarm", TransitionalAlarm.class); + + // --- properties + + /** ID */ + public static final LongProperty ID = new LongProperty( + TABLE, ID_PROPERTY_NAME); + + /** Associated Task */ + public static final LongProperty TASK = new LongProperty( + TABLE, "task"); + + /** Alarm Time */ + public static final LongProperty TIME = new LongProperty( + TABLE, "time"); + + /** List of all properties for this model */ + public static final Property[] PROPERTIES = generateProperties(TransitionalAlarm.class); + + // --- constants + + /** this alarm was already triggered */ + public static final int TYPE_TRIGGERED = 0; + + /** this alarm is single-shot */ + public static final int TYPE_SINGLE = 1; + + /** this alarm repeats itself until turned off */ + public static final int TYPE_REPEATING = 2; + + // --- defaults + + /** Default values container */ + private static final ContentValues defaultValues = new ContentValues(); + + static { + // + } + + @Override + public ContentValues getDefaultValues() { + return defaultValues; + } + + // --- data access boilerplate + + public TransitionalAlarm() { + super(); + } + + public TransitionalAlarm(TodorooCursor cursor) { + this(); + readPropertiesFromCursor(cursor); + } + + public void readFromCursor(TodorooCursor cursor) { + super.readPropertiesFromCursor(cursor); + } + + @Override + public long getId() { + return getIdHelper(ID); + }; + + // --- parcelable helpers + + private static final Creator CREATOR = new ModelCreator(Task.class); + + @Override + protected Creator getCreator() { + return CREATOR; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java index 4f17af28c..dc9839dce 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java @@ -2,8 +2,8 @@ package com.todoroo.astrid.backup; import java.io.FileReader; import java.io.IOException; -import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashSet; import java.util.StringTokenizer; import org.xmlpull.v1.XmlPullParser; @@ -329,7 +329,7 @@ public class TasksXmlImporter { private String upgradeNotes = null; private boolean syncOnComplete = false; - private final ArrayList tags = new ArrayList(); + private final LinkedHashSet tags = new LinkedHashSet(); public Format1TaskImporter(XmlPullParser xpp) throws XmlPullParserException, IOException { this.xpp = xpp; diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java index 86bbecada..b8251c340 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java @@ -11,12 +11,14 @@ import android.content.res.Resources; import android.graphics.drawable.BitmapDrawable; import com.timsu.astrid.R; +import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.FilterCategory; import com.todoroo.astrid.api.FilterListHeader; import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.tags.TagService.Tag; @@ -59,7 +61,7 @@ public class TagFilterExposer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { tagService = TagService.getInstance(); - Tag[] tagsByAlpha = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_ALPHA); + Tag[] tagsByAlpha = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_ALPHA, Criterion.all); // If user does not have any tags, don't show this section at all if(tagsByAlpha.length == 0) @@ -67,7 +69,7 @@ public class TagFilterExposer extends BroadcastReceiver { Resources r = context.getResources(); - Tag[] tagsBySize = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE); + Tag[] tagsBySize = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE, TaskCriteria.isActive()); Filter[] filtersByAlpha = new Filter[tagsByAlpha.length]; for(int i = 0; i < tagsByAlpha.length; i++) filtersByAlpha[i] = filterFromTag(context, tagsByAlpha[i], false); @@ -76,7 +78,7 @@ public class TagFilterExposer extends BroadcastReceiver { for(int i = 0; i < tagsBySize.length; i++) filtersBySize[i] = filterFromTag(context, tagsBySize[i], false); - Tag[] completed = tagService.getGroupedTags(TagService.GROUPED_TAGS_COMPLETED); + Tag[] completed = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE, TaskCriteria.completed()); Filter[] filtersCompleted = new Filter[completed.length]; for(int i = 0; i < completed.length; i++) filtersCompleted[i] = filterFromTag(context, completed[i], true); diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java index 09499248a..2e9fde24e 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java @@ -1,6 +1,6 @@ package com.todoroo.astrid.tags; -import java.util.ArrayList; +import java.util.LinkedHashSet; import com.todoroo.andlib.data.Property.CountProperty; import com.todoroo.andlib.data.Property.StringProperty; @@ -58,12 +58,8 @@ public final class TagService { * Property for retrieving count of aggregated rows */ private static final CountProperty COUNT = new CountProperty(); - public static final QueryTemplate GROUPED_TAGS_BY_ALPHA = new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), MetadataCriteria.withKey(KEY))). - orderBy(Order.asc(TAG)); - public static final QueryTemplate GROUPED_TAGS_BY_SIZE = new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), MetadataCriteria.withKey(KEY))). - orderBy(Order.desc(COUNT)); - public static final QueryTemplate GROUPED_TAGS_COMPLETED = new QueryTemplate().where(Criterion.and(TaskCriteria.completed(), MetadataCriteria.withKey(KEY))). - orderBy(Order.desc(COUNT)); + public static final Order GROUPED_TAGS_BY_ALPHA = Order.asc(TAG); + public static final Order GROUPED_TAGS_BY_SIZE = Order.desc(COUNT); /** * Helper class for returning a tag/task count pair @@ -105,13 +101,15 @@ public final class TagService { /** * Return all tags ordered by given clause * - * @param taskId + * @param order ordering + * @param activeStatus criterion for specifying completed or uncompleted * @return empty array if no tags, otherwise array */ - public Tag[] getGroupedTags(QueryTemplate template) { + public Tag[] getGroupedTags(Order order, Criterion activeStatus) { Query query = Query.select(TAG.as(TAG.name), COUNT). join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))). - withQueryTemplate(template.toString()).groupBy(TAG); + where(Criterion.and(activeStatus, MetadataCriteria.withKey(KEY))). + orderBy(order).groupBy(TAG); TodorooCursor cursor = metadataDao.query(query); try { Tag[] array = new Tag[cursor.getCount()]; @@ -179,7 +177,7 @@ public final class TagService { * @param taskId * @param tags */ - public void synchronizeTags(long taskId, ArrayList tags) { + public void synchronizeTags(long taskId, LinkedHashSet tags) { metadataDao.deleteWhere(Criterion.and(MetadataCriteria.byTask(taskId), MetadataCriteria.withKey(KEY))); diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagsControlSet.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagsControlSet.java index 0081a545a..469b6c03d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagsControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagsControlSet.java @@ -1,6 +1,6 @@ package com.todoroo.astrid.tags; -import java.util.ArrayList; +import java.util.LinkedHashSet; import android.app.Activity; import android.text.Editable; @@ -15,6 +15,7 @@ import android.widget.TextView; import com.timsu.astrid.R; import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.sql.Criterion; import com.todoroo.astrid.activity.TaskEditActivity.TaskEditControlSet; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Task; @@ -41,7 +42,7 @@ public final class TagsControlSet implements TaskEditControlSet { private final Activity activity; public TagsControlSet(Activity activity, int tagsContainer) { - allTags = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE); + allTags = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE, Criterion.all); this.activity = activity; this.tagsContainer = (LinearLayout) activity.findViewById(tagsContainer); } @@ -64,7 +65,7 @@ public final class TagsControlSet implements TaskEditControlSet { @Override public void writeToModel(Task task) { - ArrayList tags = new ArrayList(); + LinkedHashSet tags = new LinkedHashSet(); for(int i = 0; i < tagsContainer.getChildCount(); i++) { TextView tagName = (TextView)tagsContainer.getChildAt(i).findViewById(R.id.text1); diff --git a/astrid/res/layout/alarm_control.xml b/astrid/res/layout/alarm_control.xml new file mode 100644 index 000000000..ba11307fc --- /dev/null +++ b/astrid/res/layout/alarm_control.xml @@ -0,0 +1,23 @@ + + + + + + + + + +