From e06f60b95f99c43b2657eb6c74a365eb7ed2acaf Mon Sep 17 00:00:00 2001 From: Tim Su Date: Tue, 29 Jun 2010 20:43:37 -0700 Subject: [PATCH] Now got edit to work. yay! no saving tags, and no alerts. Still to do: due date stuff, urgency stuff --- astrid/AndroidManifest.xml | 2 +- .../todoroo/andlib/data/AbstractModel.java | 19 +- .../andlib/utility/AndroidUtilities.java | 21 + .../todoroo/andlib/utility/DateUtilities.java | 18 +- .../{btn_check0.xml => btn_check.xml} | 2 +- astrid/res/drawable/btn_check_0.png | Bin 1491 -> 0 bytes astrid/res/layout/task_list_activity.xml | 13 +- astrid/res/layout/task_row.xml | 8 +- astrid/res/values/arrays.xml | 2 +- astrid/res/values/colors.xml | 20 - .../astrid/activities/TaskListAdapter.java | 4 +- .../astrid/data/task/AbstractTaskModel.java | 24 +- .../timsu/astrid/widget/DateControlSet.java | 13 + .../activity/AbstractModelTabActivity.java | 104 +++ .../astrid/activity/TaskEditActivity.java | 868 ++++++++++++++++++ .../astrid/activity/TaskListActivity.java | 69 +- astrid/src/com/todoroo/astrid/model/Task.java | 48 +- .../astrid/service/FlurryReporter.java | 3 + .../astrid/service/MetadataService.java | 8 + .../todoroo/astrid/service/TaskService.java | 16 +- 20 files changed, 1179 insertions(+), 83 deletions(-) rename astrid/res/drawable/{btn_check0.xml => btn_check.xml} (96%) delete mode 100644 astrid/res/drawable/btn_check_0.png create mode 100644 astrid/src/com/todoroo/astrid/activity/AbstractModelTabActivity.java create mode 100644 astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 386dcf36e..4ae57f730 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -83,7 +83,7 @@ - diff --git a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java index b28e296d1..d8e61e69c 100644 --- a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java +++ b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java @@ -176,6 +176,13 @@ public abstract class AbstractModel implements Parcelable { setValues.put(ID_PROPERTY_NAME, id); } + /** + * @return true if this model has found Jesus (i.e. the database) + */ + public boolean isSaved() { + return getId() != NO_ID; + } + /** * @param property * @return true if setValues or values contains this property @@ -228,6 +235,16 @@ public abstract class AbstractModel implements Parcelable { saver.save(property, setValues, value); } + /** + * Merges content values with those coming from another source + */ + public synchronized void mergeWith(ContentValues other) { + if (setValues == null) + setValues = other; + else + setValues.putAll(other); + } + /** * Clear the key for the given property * @param property @@ -332,7 +349,7 @@ public abstract class AbstractModel implements Parcelable { protected static final class ModelCreator implements Parcelable.Creator { - private Class cls; + private final Class cls; public ModelCreator(Class cls) { super(); diff --git a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java index b6b7c8dec..0d3ac90c5 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java @@ -7,6 +7,7 @@ import java.net.URL; import java.net.URLConnection; import android.app.Activity; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -106,4 +107,24 @@ public class AndroidUtilities { } } + /** + * Put an arbitrary object into a {@link ContentValues} + * @param target + * @param key + * @param value + */ + public static void putInto(ContentValues target, String key, Object value) { + if(value instanceof String) + target.put(key, (String) value); + else if(value instanceof Long) + target.put(key, (Long) value); + else if(value instanceof Integer) + target.put(key, (Integer) value); + else if(value instanceof Double) + target.put(key, (Double) value); + else + throw new UnsupportedOperationException("Could not handle type " + //$NON-NLS-1$ + value.getClass()); + } + } diff --git a/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java index bf14db575..17893516d 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java @@ -10,6 +10,7 @@ import java.util.Date; import android.content.res.Resources; import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; @@ -85,9 +86,8 @@ public class DateUtilities { /** * Convenience method for dropping the preposition argument. */ - public String getDurationString(Resources r, int timeInSeconds, - int unitsToShow) { - return getDurationString(r, timeInSeconds, unitsToShow, false); + public String getDurationString(long duration, int unitsToShow) { + return getDurationString(duration, unitsToShow, false); } /** @@ -100,17 +100,17 @@ public class DateUtilities { * @param withPreposition whether there is a preceding preposition * @return */ - public String getDurationString(Resources r, int timeInSeconds, - int unitsToShow, boolean withPreposition) { + public String getDurationString(long duration, int unitsToShow, boolean withPreposition) { + Resources r = ContextManager.getContext().getResources(); int years, months, days, hours, minutes, seconds; short unitsDisplayed = 0; - timeInSeconds = Math.abs(timeInSeconds); + duration = Math.abs(duration); - if(timeInSeconds == 0) + if(duration == 0) return r.getQuantityString(secondsResource, 0, 0); - Date now = new Date(80, 0, 1); - Date then = unixtimeToDate((int)(now.getTime() / 1000L) + timeInSeconds); + Date now = new Date(); + Date then = new Date(DateUtilities.now() + duration); years = then.getYear() - now.getYear(); months = then.getMonth() - now.getMonth(); diff --git a/astrid/res/drawable/btn_check0.xml b/astrid/res/drawable/btn_check.xml similarity index 96% rename from astrid/res/drawable/btn_check0.xml rename to astrid/res/drawable/btn_check.xml index ec1956320..a6dcbb432 100644 --- a/astrid/res/drawable/btn_check0.xml +++ b/astrid/res/drawable/btn_check.xml @@ -27,7 +27,7 @@ + android:drawable="@drawable/btn_check_off" /> diff --git a/astrid/res/drawable/btn_check_0.png b/astrid/res/drawable/btn_check_0.png deleted file mode 100644 index 8bd6db1f134344147e4a8ebeac5fd3d308013051..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1491 zcmV;^1uXiBP)Px#24YJ`L;wH)0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOcA z6(lwKIlWN;00mP?L_t(o!|j+$ZxdG-hM&1Owz2Hu1Us^WamKlslw6>wq9BS?sf(6< zS$2u~3o3pA>M!WJn@XTog;Infqe@V4=b87s=li}hf@N8}i%sMmZAV~7VEt~g^F|N^`{xi41wz1keVl(8P=PFf zGeXF1I>&*TM~@zTl}IGs9~>NvA9hF)1OdY^>d&4%OI^8ga9)5I_g48y=Q%|pNJ zal_&8L@JfK4ovRrF;bbqaM)m4N#ow#pC}fKD9RY0eSXy$o(=}H{nZWBL4E9r zOPah-#FDW$H!-gX1rzjqgxERviWXK-+k ze4)t3#wN+>IXqr3l}e?#BYOsOLg~8Bix)45L?S4P(h=&xg9i)@ct}nr2%QL0)HJkW zk;q7#k&`DWm&=%@*;K0=FxgH+&ZT_y>J^V4KgR8Lv$V8CCX?aZxpQry78Vw8yWJ#{ zNy4EJdMQsat770Id3u_1xlE(cX!_7JO(aP=cvrRqTU=aZW@d&&BEjb7CMzo|Jbn6< zxw*Mr4?leP5Rb?68YTCI{V=oBk(>GFqEt5s^X+O7@ah-FfzLQ!@mS^EkGgN%%f z;PrZm$K#BRjgiS@c>er(bJ&T0Q1+ zxhR!NxLhv$en0Ve9Dvo;Rh~R~LZMK=@Ao&M8jS{;rm?ZHK|CJE?eU=N`ksyCOymc8 ztR2|Y)D-D-`gI3c=Gd`g#N%-!Ng|ug5(oqs9v&tf4r7`o<#HKK(-;~WA{vdN>pFH} z&;hKoV$P`VEPay6B#VoSh@wa+6vF585sSr$L?TF%ge=RLrb)G0rBEnPsZ=-8dvB0D=fRI62Txg6PSmJ1gy>@j;Q zTu+L%R+`S{@puRX0;sCWz`y{qEaUZhsn_dNDi!kiJX>2^oH=ubTCIkz>z$8iuTxR) z9%}^y2!%rEr4pK^AxRRVC}Nr>x~`+DD$!^Zx7$szSnN7$^sLx^<#l{?lz&!N5k(PM zmN5(iP1ErCd_*D%Iy!p%uxtNz$F{Y#wZgS)*X{$~S(cUB4=ezD0eo`v=FN+ + + + - - + + android:src="@drawable/btn_add_extended"/> diff --git a/astrid/res/layout/task_row.xml b/astrid/res/layout/task_row.xml index 382cb2e36..f8205bae9 100644 --- a/astrid/res/layout/task_row.xml +++ b/astrid/res/layout/task_row.xml @@ -14,10 +14,12 @@ + android:paddingLeft="10px" + android:button="@drawable/btn_check" + android:scaleType="center"/> 07:00 08:00 - + diff --git a/astrid/res/values/colors.xml b/astrid/res/values/colors.xml index 9cf351f88..42220ddd1 100644 --- a/astrid/res/values/colors.xml +++ b/astrid/res/values/colors.xml @@ -1,24 +1,4 @@ - #FFFB6666 #FFFFFFFF diff --git a/astrid/src-legacy/com/timsu/astrid/activities/TaskListAdapter.java b/astrid/src-legacy/com/timsu/astrid/activities/TaskListAdapter.java index 29e5bd588..dea2af666 100644 --- a/astrid/src-legacy/com/timsu/astrid/activities/TaskListAdapter.java +++ b/astrid/src-legacy/com/timsu/astrid/activities/TaskListAdapter.java @@ -729,12 +729,12 @@ public class TaskListAdapter extends ArrayAdapter { if(task.isTaskCompleted() || task == recentlyCompleted) { name.setPaintFlags(name.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); name.setTextColor(r.getColor(R.color.task_list_done)); - progress.setButtonDrawable(R.drawable.btn_check0); + progress.setButtonDrawable(R.drawable.btn_check); progress.setChecked(true); } else { name.setPaintFlags(name.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); name.setTextColor(r.getColor(task.getTaskColorResource(getContext()))); - progress.setButtonDrawable(R.drawable.btn_check0); + progress.setButtonDrawable(R.drawable.btn_check); } } diff --git a/astrid/src-legacy/com/timsu/astrid/data/task/AbstractTaskModel.java b/astrid/src-legacy/com/timsu/astrid/data/task/AbstractTaskModel.java index 438c79009..e3b206038 100644 --- a/astrid/src-legacy/com/timsu/astrid/data/task/AbstractTaskModel.java +++ b/astrid/src-legacy/com/timsu/astrid/data/task/AbstractTaskModel.java @@ -340,8 +340,8 @@ public abstract class AbstractTaskModel extends AbstractModel { // --- helper classes public static class RepeatInfo { - private RepeatInterval interval; - private int value; + private final RepeatInterval interval; + private final int value; public RepeatInfo(RepeatInterval repeatInterval, int value) { this.interval = repeatInterval; @@ -362,6 +362,26 @@ public abstract class AbstractTaskModel extends AbstractModel { return value; } + public static int toSingleField(RepeatInfo repeatInfo) { + int repeat; + if(repeatInfo == null) + repeat = 0; + else + repeat = (repeatInfo.value << REPEAT_VALUE_OFFSET) + + repeatInfo.interval.ordinal(); + return repeat; + } + + public static RepeatInfo fromSingleField(int repeat) { + if(repeat == 0) + return null; + int value = repeat >> REPEAT_VALUE_OFFSET; + RepeatInterval interval = RepeatInterval.values() + [repeat - (value << REPEAT_VALUE_OFFSET)]; + + return new RepeatInfo(interval, value); + } + } // --- task identifier diff --git a/astrid/src-legacy/com/timsu/astrid/widget/DateControlSet.java b/astrid/src-legacy/com/timsu/astrid/widget/DateControlSet.java index aaa5b4d7d..9e49e9269 100644 --- a/astrid/src-legacy/com/timsu/astrid/widget/DateControlSet.java +++ b/astrid/src-legacy/com/timsu/astrid/widget/DateControlSet.java @@ -66,6 +66,12 @@ public class DateControlSet implements OnTimeSetListener, return date; } + public long getMillis() { + if(date == null) + return 0; + return date.getTime(); + } + /** Initialize the components for the given date field */ public void setDate(Date newDate) { if(newDate == null) { @@ -82,6 +88,13 @@ public class DateControlSet implements OnTimeSetListener, updateTime(); } + public void setDate(long newDate) { + if(newDate == 0L) + setDate(null); + else + setDate(new Date(newDate)); + } + public void onDateSet(DatePicker view, int year, int month, int monthDay) { date.setYear(year - 1900); date.setMonth(month); diff --git a/astrid/src/com/todoroo/astrid/activity/AbstractModelTabActivity.java b/astrid/src/com/todoroo/astrid/activity/AbstractModelTabActivity.java new file mode 100644 index 000000000..0cccf2255 --- /dev/null +++ b/astrid/src/com/todoroo/astrid/activity/AbstractModelTabActivity.java @@ -0,0 +1,104 @@ +package com.todoroo.astrid.activity; + +import android.app.TabActivity; +import android.content.Intent; +import android.os.Bundle; + +import com.flurry.android.FlurryAgent; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.service.ExceptionService; +import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.service.AstridDependencyInjector; +import com.todoroo.astrid.utility.Constants; + +abstract public class AbstractModelTabActivity extends TabActivity { + + // from AstridActivity + + static { + AstridDependencyInjector.initialize(); + } + + @Override + protected void onStart() { + super.onStart(); + FlurryAgent.onStartSession(this, Constants.FLURRY_KEY); + } + + @Override + protected void onStop() { + super.onStop(); + FlurryAgent.onEndSession(this); + } + + // from AbstractModelActivity + + // --- bundle arguments + + /** + * Action Item ID + */ + public static final String ID_TOKEN = "i"; //$NON-NLS-1$ + + // --- instance variables + + @Autowired + protected ExceptionService exceptionService; + + @Autowired + protected Database database; + + protected TYPE model = null; + + // --- abstract methods + + abstract protected TYPE fetchModel(long id); + + /** + * Load Bente Dependency Injector + */ + static { + AstridDependencyInjector.initialize(); + } + + public AbstractModelTabActivity() { + DependencyInjectionService.getInstance().inject(this); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ContextManager.setContext(this); + loadItem(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + loadItem(intent); + } + + /** + * Loads action item from the given intent + * @param intent + */ + protected void loadItem(Intent intent) { + long idParam = intent.getLongExtra(ID_TOKEN, -1L); + if(idParam == -1) { + exceptionService.reportError("AMA-no-token", null); //$NON-NLS-1$ + finish(); + return; + } + + database.openForReading(); + model = fetchModel(idParam); + + if(model == null) { + exceptionService.reportError("AMA-no-task", new NullPointerException("model")); //$NON-NLS-1$ //$NON-NLS-2$ + finish(); + return; + } + } +} diff --git a/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java new file mode 100644 index 000000000..de6554798 --- /dev/null +++ b/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java @@ -0,0 +1,868 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.activity; + +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import android.app.AlertDialog; +import android.content.ContentValues; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TabHost; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ToggleButton; + +import com.flurry.android.FlurryAgent; +import com.timsu.astrid.R; +import com.timsu.astrid.data.enums.RepeatInterval; +import com.timsu.astrid.data.tag.TagIdentifier; +import com.timsu.astrid.data.task.TaskModelForEdit; +import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo; +import com.timsu.astrid.utilities.AstridUtilities; +import com.timsu.astrid.utilities.Constants; +import com.timsu.astrid.utilities.Preferences; +import com.timsu.astrid.widget.DateControlSet; +import com.timsu.astrid.widget.DateWithNullControlSet; +import com.timsu.astrid.widget.NumberPicker; +import com.timsu.astrid.widget.NumberPickerDialog; +import com.timsu.astrid.widget.TimeDurationControlSet; +import com.timsu.astrid.widget.NumberPickerDialog.OnNumberPickedListener; +import com.timsu.astrid.widget.TimeDurationControlSet.TimeDurationType; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.service.TaskService; +import com.todoroo.astrid.tags.TagService; +import com.todoroo.astrid.tags.TagService.Tag; + +/** + * This activity is responsible for creating new tasks and editing existing + * ones. It saves a task when it is paused (screen rotated, back button + * pressed) as long as the task has a title. + * + * @author timsu + * + */ +public final class TaskEditActivity extends AbstractModelTabActivity { + + // --- request codes + + @SuppressWarnings("unused") + private static final int REQUEST_CODE_OPERATION = 0; + + // --- menu codes + + private static final int MENU_SAVE_ID = Menu.FIRST; + private static final int MENU_DISCARD_ID = Menu.FIRST + 1; + private static final int MENU_DELETE_ID = Menu.FIRST + 2; + + // --- result codes + + public static final int RESULT_CODE_SAVED = RESULT_FIRST_USER; + public static final int RESULT_CODE_DISCARDED = RESULT_FIRST_USER + 1; + public static final int RESULT_CODE_DELETED = RESULT_FIRST_USER + 2; + + // --- other constants + + private static final int MAX_TAGS = 5; + private static final int MAX_ALERTS = 5; + private static final String TAB_BASIC = "basic"; //$NON-NLS-1$ + private static final String TAB_DATES = "dates"; //$NON-NLS-1$ + private static final String TAB_ALERTS = "alerts"; //$NON-NLS-1$ + private static final int DEFAULT_CAL_TIME = 3600; + + // --- autowired + + @Autowired + TaskService taskService; + + @Autowired + DateUtilities dateUtilities; + + // --- UI components + EditText title; + ImportanceControlSet importance; + TimeDurationControlSet estimatedDuration; + TimeDurationControlSet elapsedDuration; + TimeDurationControlSet notification; + DateControlSet dueDate; + DateControlSet preferredDueDate; + DateControlSet hiddenUntil; + EditText notes; + LinearLayout tagsContainer; + NotifyFlagControlSet flags; + LinearLayout alertsContainer; + Button repeatValue; + Spinner repeatInterval; + CheckBox addToCalendar; + + // --- other instance variables + + /** whether task should be saved when this activity exits */ + boolean shouldSaveState = true; + + /** whether help should be shown when setting repeat */ + boolean repeatHelpShown = false; + + /** list of all tags */ + Tag[] tags; + + /* ====================================================================== + * ======================================================= initialization + * ====================================================================== */ + + @Override + protected Task fetchModel(long id) { + database.openForWriting(); + + if(id == Task.NO_ID) { + FlurryAgent.onEvent("create-task"); //$NON-NLS-1$ + Task task = new Task(); + taskService.save(task, false); + return task; + } + + FlurryAgent.onEvent("edit-task"); //$NON-NLS-1$ + return taskService.fetchById(id, Task.PROPERTIES); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TabHost tabHost = getTabHost(); + tabHost.setPadding(0, 4, 0, 0); + Resources r = getResources(); + + LayoutInflater.from(this).inflate(R.layout.task_edit, tabHost.getTabContentView(), true); + + tabHost.addTab(tabHost.newTabSpec(TAB_BASIC).setIndicator(r.getString(R.string.taskEdit_tab_basic), + r.getDrawable(R.drawable.ic_dialog_info_c)).setContent(R.id.tab_basic)); + tabHost.addTab(tabHost.newTabSpec(TAB_DATES).setIndicator(r.getString(R.string.taskEdit_tab_dates), + r.getDrawable(R.drawable.ic_dialog_time_c)).setContent(R.id.tab_dates)); + tabHost.addTab(tabHost.newTabSpec(TAB_ALERTS).setIndicator(r.getString(R.string.taskEdit_tab_alerts), + r.getDrawable(R.drawable.ic_dialog_alert_c)).setContent(R.id.tab_notification)); + + // weird case that has been hit before. + if(model == null) + model = new Task(); + + setUpUIComponents(); + setUpListeners(); + + // disable name input box until user requests it + AstridUtilities.suppressVirtualKeyboard(title); + } + + /* ====================================================================== + * ==================================================== UI initialization + * ====================================================================== */ + + /** Initialize UI components */ + private void setUpUIComponents() { + Resources r = getResources(); + setTitle(new StringBuilder().append(r.getString(R.string.taskEdit_titleGeneric))); + + // populate instance variables + title = (EditText)findViewById(R.id.name); + importance = new ImportanceControlSet(R.id.importance_container); + tagsContainer = (LinearLayout)findViewById(R.id.tags_container); + estimatedDuration = new TimeDurationControlSet(this, + R.id.estimatedDuration, 0, R.string.hour_minutes_dialog, + TimeDurationType.HOURS_MINUTES); + elapsedDuration = new TimeDurationControlSet(this, R.id.elapsedDuration, + 0, R.string.hour_minutes_dialog, + TimeDurationType.HOURS_MINUTES); + notification = new TimeDurationControlSet(this, R.id.notification, + R.string.notification_prefix, R.string.notification_dialog, + TimeDurationType.DAYS_HOURS); + dueDate = new DateWithNullControlSet(this, R.id.definiteDueDate_notnull, + R.id.definiteDueDate_date, R.id.definiteDueDate_time); + preferredDueDate = new DateWithNullControlSet(this, R.id.preferredDueDate_notnull, + R.id.preferredDueDate_date, R.id.preferredDueDate_time); + hiddenUntil = new DateWithNullControlSet(this, R.id.hiddenUntil_notnull, + R.id.hiddenUntil_date, R.id.hiddenUntil_time); + notes = (EditText)findViewById(R.id.notes); + flags = new NotifyFlagControlSet(R.id.flag_before, + R.id.flag_during, R.id.flag_after, R.id.flag_nonstop); + 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( + this, android.R.layout.simple_spinner_item, + RepeatInterval.getLabels(getResources())); + repeatAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + repeatInterval.setAdapter(repeatAdapter); + + // load tags + TagService tagService = new TagService(this); + tags = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE); + + // read data + populateFields(); + } + + /** Set up button listeners */ + private void setUpListeners() { + Button saveButtonGeneral = (Button) findViewById(R.id.save_general); + saveButtonGeneral.setOnClickListener(mSaveListener); + + Button saveButtonDates = (Button) findViewById(R.id.save_dates); + saveButtonDates.setOnClickListener(mSaveListener); + + Button saveButtonNotify = (Button) findViewById(R.id.save_notify); + saveButtonNotify.setOnClickListener(mSaveListener); + + Button discardButtonGeneral = (Button) findViewById(R.id.discard_general); + discardButtonGeneral.setOnClickListener(mDiscardListener); + + Button discardButtonDates = (Button) findViewById(R.id.discard_dates); + discardButtonDates.setOnClickListener(mDiscardListener); + + Button discardButtonNotify = (Button) findViewById(R.id.discard_notify); + discardButtonNotify.setOnClickListener(mDiscardListener); + + Button deleteButtonGeneral = (Button) findViewById(R.id.delete_general); + Button deleteButtonDates = (Button) findViewById(R.id.delete_dates); + Button deleteButtonNotify = (Button) findViewById(R.id.delete_notify); + if(model.getId() == Task.NO_ID) { + deleteButtonGeneral.setVisibility(View.GONE); + deleteButtonDates.setVisibility(View.GONE); + deleteButtonNotify.setVisibility(View.GONE); + } else { + deleteButtonGeneral.setOnClickListener(mDeleteListener); + deleteButtonDates.setOnClickListener(mDeleteListener); + deleteButtonNotify.setOnClickListener(mDeleteListener); + } + + Button addAlertButton = (Button) findViewById(R.id.addAlert); + addAlertButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + addAlert(null); + } + }); + + repeatValue.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + repeatValueClick(); + } + }); + } + + private final View.OnClickListener mSaveListener = new View.OnClickListener() { + public void onClick(View v) { + saveButtonClick(); + } + }; + private final View.OnClickListener mDiscardListener = new View.OnClickListener() { + public void onClick(View v) { + discardButtonClick(); + } + }; + private final View.OnClickListener mDeleteListener = new View.OnClickListener() { + public void onClick(View v) { + deleteButtonClick(); + } + }; + + /** Set up the repeat value button */ + private void setRepeatValue(int value) { + if(value == 0) + repeatValue.setText(R.string.repeat_value_unset); + else + repeatValue.setText(Integer.toString(value)); + repeatValue.setTag(value); + } + + private RepeatInfo getRepeatValue() { + if(repeatValue.getTag().equals(0)) + return null; + return new RepeatInfo(RepeatInterval.values() + [repeatInterval.getSelectedItemPosition()], + (Integer)repeatValue.getTag()); + } + + /** Adds an alert to the alert field */ + protected boolean addAlert(Date alert) { + if(alertsContainer.getChildCount() >= MAX_ALERTS) + return false; + + LayoutInflater inflater = getLayoutInflater(); + final View alertItem = inflater.inflate(R.layout.edit_alert_item, null); + alertsContainer.addView(alertItem); + + DateControlSet dcs = new DateControlSet(this, + (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; + } + + /* ====================================================================== + * =============================================== model reading / saving + * ====================================================================== */ + + /** Populate UI component values from the model */ + private void populateFields() { + Resources r = getResources(); + + title.setText(model.getValue(Task.TITLE)); + if(title.getText().length() > 0) { + setTitle(new StringBuilder(). + append(r.getString(R.string.taskEdit_titlePrefix)). + append(" "). //$NON-NLS-1$ + append(title.getText())); + } + estimatedDuration.setTimeDuration(model.getValue(Task.ESTIMATED_SECONDS)); + elapsedDuration.setTimeDuration(model.getValue(Task.ELAPSED_SECONDS)); + importance.setImportance(model.getValue(Task.IMPORTANCE)); + dueDate.setDate(model.getValue(Task.DUE_DATE)); + hiddenUntil.setDate(model.getValue(Task.HIDE_UNTIL)); + notification.setTimeDuration(model.getValue(Task.NOTIFICATIONS)); + flags.setValue(model.getValue(Task.NOTIFICATION_FLAGS)); + notes.setText(model.getValue(Task.NOTES)); + if(model.getValue(Task.CALENDAR_URI).length() > 0) + addToCalendar.setText(r.getString(R.string.showCalendar_label)); + + // tags (only configure if not already set) + if(tagsContainer.getChildCount() == 0) { + TagService tagService = new TagService(this); + TodorooCursor cursor = tagService.getTags(model.getId()); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) + addTag(cursor.get(Metadata.VALUE)); + addTag(""); //$NON-NLS-1$ + } + + /* // alerts + if(model.getTaskIdentifier() != null) { + List alerts = alertController.getTaskAlerts(model.getTaskIdentifier()); + for(Date alert : alerts) { + addAlert(alert); + } + } + + // repeats + RepeatInfo repeatInfo = model.getRepeat(); + if(repeatInfo != null) { + setRepeatValue(repeatInfo.getValue()); + repeatInterval.setSelection(repeatInfo.getInterval().ordinal()); + } else + setRepeatValue(0);*/ // TODO + + } + + /** Save task model from values in UI components */ + private void save() { + // don't save if user accidentally created a new task + if(title.getText().length() == 0) { + if(model.isSaved()) + taskService.delete(model); + return; + } + + model.setValue(Task.TITLE, title.getText().toString()); + model.setValue(Task.ESTIMATED_SECONDS, estimatedDuration.getTimeDurationInSeconds()); + model.setValue(Task.ELAPSED_SECONDS, elapsedDuration.getTimeDurationInSeconds()); + model.setValue(Task.IMPORTANCE, importance.getImportance()); + model.setValue(Task.DUE_DATE, dueDate.getMillis()); + model.setValue(Task.HIDE_UNTIL, hiddenUntil.getMillis()); + model.setValue(Task.NOTIFICATION_FLAGS, flags.getValue()); + model.setValue(Task.NOTES, notes.getText().toString()); + model.setValue(Task.NOTIFICATIONS, notification.getTimeDurationInSeconds()); + model.setValue(Task.REPEAT, RepeatInfo.toSingleField(getRepeatValue())); + + taskService.save(model, false); + + long due = model.getValue(Task.DUE_DATE); + if (due != 0) { + showSaveToast(due); + } else { + showSaveToast(); + } + } + + /** + * Displays a Toast reporting that the selected task has been saved and is + * due in 'x' amount of time, to 2 time-units of precision (e.g. Days + Hours). + * @param dueDate the Date when the task is due + */ + private void showSaveToast(long dueDate) { + int stringResource; + + int timeInSeconds = (int)((dueDate - System.currentTimeMillis())/1000L); + + if (timeInSeconds < 0) { + timeInSeconds *= -1; // DateUtilities.getDurationString() requires positive integer + stringResource = R.string.taskEdit_onTaskSave_Overdue; + } else { + stringResource = R.string.taskEdit_onTaskSave_Due; + } + String formattedDate = dateUtilities.getDurationString(timeInSeconds, 2); + Toast.makeText(this, + getResources().getString(stringResource, formattedDate), + Toast.LENGTH_SHORT).show(); + } + + /** + * Displays a Toast reporting that the selected task has been saved. + * Use this version when no due Date has been set. + */ + private void showSaveToast() { + Toast.makeText(this, R.string.taskEdit_onTaskSave_notDue, Toast.LENGTH_SHORT).show(); + } + + /** Save task tags. Must be called after task already has an ID */ + private void saveTags() { + Set tagsToDelete; + Set tagsToAdd; + + HashSet tagNames = new HashSet(); + for(int i = 0; i < tagsContainer.getChildCount(); i++) { + TextView tagName = (TextView)tagsContainer.getChildAt(i).findViewById(R.id.text1); + if(tagName.getText().length() == 0) + continue; + tagNames.add(tagName.getText().toString()); + } + + // map names to tag identifiers, creating them if necessary + /*HashSet tagIds = new HashSet(); + HashMap tagsByName = new HashMap(); + for(TagModelForView tag : tags) + tagsByName.put(tag.getName(), tag.getTagIdentifier()); + for(String tagName : tagNames) { + if(tagsByName.containsKey(tagName)) + tagIds.add(tagsByName.get(tagName)); + else { + TagIdentifier newTagId = tagController.createTag(tagName); + tagIds.add(newTagId); + } + } + + // intersect tags to figure out which we need to add / remove + tagsToDelete = new HashSet(taskTags); + tagsToDelete.removeAll(tagIds); + tagsToAdd = tagIds; + tagsToAdd.removeAll(taskTags); + + // perform the database updates + for(TagIdentifier tagId : tagsToDelete) + tagController.removeTag(model.getTaskIdentifier(), tagId); + for(TagIdentifier tagId : tagsToAdd) + tagController.addTag(model.getTaskIdentifier(), tagId); + + if(tagsToDelete.size() > 0 || tagsToAdd.size() > 0) + SyncDataController.taskUpdated(this, model);*/ + } + + /** Helper method to save alerts for this task */ + private void saveAlerts() { + /*alertController.removeAlerts(model.getTaskIdentifier()); + + for(int i = 0; i < alertsContainer.getChildCount(); i++) { + DateControlSet dateControlSet = (DateControlSet)alertsContainer. + getChildAt(i).getTag(); + Date date = dateControlSet.getDate(); + alertController.addAlert(model.getTaskIdentifier(), date); + }*/ + } + + + /** Adds a tag to the tag field */ + boolean addTag(String tagName) { + if (tagsContainer.getChildCount() >= MAX_TAGS) { + return false; + } + + LayoutInflater inflater = getLayoutInflater(); + final View tagItem = inflater.inflate(R.layout.edit_tag_item, null); + tagsContainer.addView(tagItem); + + AutoCompleteTextView textView = (AutoCompleteTextView)tagItem. + findViewById(R.id.text1); + textView.setText(tagName); + ArrayAdapter tagsAdapter = + new ArrayAdapter(this, + android.R.layout.simple_dropdown_item_1line, tags); + textView.setAdapter(tagsAdapter); + textView.addTextChangedListener(new TextWatcher() { + public void onTextChanged(CharSequence s, int start, int before, + int count) { + if(start == 0 && tagsContainer.getChildAt( + tagsContainer.getChildCount()-1) == tagItem) { + addTag(""); //$NON-NLS-1$ + } + } + + public void afterTextChanged(Editable s) { + // + } + + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + // + } + }); + + ImageButton reminderRemoveButton; + reminderRemoveButton = (ImageButton)tagItem.findViewById(R.id.button1); + reminderRemoveButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + tagsContainer.removeView(tagItem); + } + }); + + return true; + } + + /* ====================================================================== + * ======================================================= event handlers + * ====================================================================== */ + + protected void saveButtonClick() { + setResult(RESULT_OK); + finish(); + } + + protected void discardButtonClick() { + shouldSaveState = false; + setResult(Constants.RESULT_DISCARD); + finish(); + } + + protected void deleteButtonClick() { + new AlertDialog.Builder(this) + .setTitle(R.string.delete_title) + .setMessage(R.string.delete_this_task_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + taskService.delete(model); + shouldSaveState = false; + setResult(Constants.RESULT_GO_HOME); + finish(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + protected void repeatValueClick() { + final int tagValue = (Integer)repeatValue.getTag(); + if(tagValue > 0) + repeatHelpShown = true; + + final Runnable openDialogRunnable = new Runnable() { + public void run() { + repeatHelpShown = true; + + int dialogValue = tagValue; + if(dialogValue == 0) + dialogValue = 1; + + new NumberPickerDialog(TaskEditActivity.this, new OnNumberPickedListener() { + public void onNumberPicked(NumberPicker view, int number) { + setRepeatValue(number); + } + }, getResources().getString(R.string.repeat_picker_title), + dialogValue, 1, 0, 31).show(); + } + }; + + if(repeatHelpShown || !Preferences.shouldShowRepeatHelp(this)) { + openDialogRunnable.run(); + return; + } + + new AlertDialog.Builder(this) + .setTitle(R.string.repeat_help_dialog_title) + .setMessage(R.string.repeat_help_dialog) + .setIcon(android.R.drawable.ic_dialog_info) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + openDialogRunnable.run(); + } + }) + .setNeutralButton(R.string.repeat_help_hide, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Preferences.setShowRepeatHelp(TaskEditActivity.this, false); + openDialogRunnable.run(); + } + }) + .show(); + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + switch(item.getItemId()) { + case MENU_SAVE_ID: + saveButtonClick(); + return true; + case MENU_DISCARD_ID: + discardButtonClick(); + return true; + case MENU_DELETE_ID: + deleteButtonClick(); + return true; + } + + return super.onMenuItemSelected(featureId, item); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuItem item; + + item = menu.add(Menu.NONE, MENU_SAVE_ID, 0, R.string.save_label); + item.setIcon(android.R.drawable.ic_menu_save); + item.setAlphabeticShortcut('s'); + + item = menu.add(Menu.NONE, MENU_DISCARD_ID, 0, R.string.discard_label); + item.setIcon(android.R.drawable.ic_menu_close_clear_cancel); + item.setAlphabeticShortcut('c'); + + item = menu.add(Menu.NONE, MENU_DELETE_ID, 0, R.string.delete_label); + item.setIcon(android.R.drawable.ic_menu_delete); + item.setAlphabeticShortcut('d'); + + return true; + } + + /** Take the values from the model and set the calendar start and end times + * based on these. Sets keys 'dtstart' and 'dtend'. + * + * @param preferred preferred due date or null + * @param definite definite due date or null + * @param estimatedSeconds estimated duration or null + * @param values + */ + public static void createCalendarStartEndTimes(Date preferred, Date definite, + Integer estimatedSeconds, ContentValues values) { + FlurryAgent.onEvent("create-calendar-event"); + + Long deadlineDate = null; + if (preferred != null && preferred.after(new Date())) + deadlineDate = preferred.getTime(); + else if (definite != null) + deadlineDate = definite.getTime(); + else + deadlineDate = System.currentTimeMillis() + 24*3600*1000L; + + int estimatedTime = DEFAULT_CAL_TIME; + if(estimatedSeconds != null && estimatedSeconds > 0) { + estimatedTime = estimatedSeconds; + } + values.put("dtstart", deadlineDate - estimatedTime * 1000L); + values.put("dtend", deadlineDate); + } + + @Override + protected void onPause() { + // create calendar event + /*if(addToCalendar.isChecked() && model.getCalendarUri() == null) { + + Uri uri = Uri.parse("content://calendar/events"); + ContentResolver cr = getContentResolver(); + + ContentValues values = new ContentValues(); + values.put("title", title.getText().toString()); + values.put("calendar_id", Preferences.getDefaultCalendarIDSafe(this)); + values.put("description", notes.getText().toString()); + values.put("hasAlarm", 0); + values.put("transparency", 0); + values.put("visibility", 0); + + createCalendarStartEndTimes(model.getPreferredDueDate(), + model.getDefiniteDueDate(), model.getEstimatedSeconds(), + values); + + Uri result = null; + try{ + result = cr.insert(uri, values); + model.setCalendarUri(result.toString()); + } catch (IllegalArgumentException e) { + Log.e("astrid", "Error creating calendar event!", e); + } + } + + if(shouldSaveState) + save(); + + if(addToCalendar.isChecked() && model.getCalendarUri() != null) { + Uri result = Uri.parse(model.getCalendarUri()); + Intent intent = new Intent(Intent.ACTION_EDIT, result); + + ContentValues values = new ContentValues(); + createCalendarStartEndTimes(model.getPreferredDueDate(), + model.getDefiniteDueDate(), model.getEstimatedSeconds(), + values); + + intent.putExtra("beginTime", values.getAsLong("dtstart")); + intent.putExtra("endTime", values.getAsLong("dtend")); + + startActivity(intent); + }*/ + + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + // populateFields(); + } + + /* ====================================================================== + * ========================================== UI component helper classes + * ====================================================================== */ + + /** Control set dealing with notification flags */ + public class NotifyFlagControlSet { + private final CheckBox before, during, after, nonstop; + + public NotifyFlagControlSet(int beforeId, int duringId, + int afterId, int nonstopId) { + before = (CheckBox)findViewById(beforeId); + during = (CheckBox)findViewById(duringId); + after = (CheckBox)findViewById(afterId); + nonstop = (CheckBox)findViewById(nonstopId); + } + + public void setValue(int flags) { + before.setChecked((flags & + TaskModelForEdit.NOTIFY_BEFORE_DEADLINE) > 0); + during.setChecked((flags & + TaskModelForEdit.NOTIFY_AT_DEADLINE) > 0); + after.setChecked((flags & + TaskModelForEdit.NOTIFY_AFTER_DEADLINE) > 0); + nonstop.setChecked((flags & + TaskModelForEdit.NOTIFY_NONSTOP) > 0); + } + + public int getValue() { + int value = 0; + if(before.isChecked()) + value |= TaskModelForEdit.NOTIFY_BEFORE_DEADLINE; + if(during.isChecked()) + value |= TaskModelForEdit.NOTIFY_AT_DEADLINE; + if(after.isChecked()) + value |= TaskModelForEdit.NOTIFY_AFTER_DEADLINE; + if(nonstop.isChecked()) + value |= TaskModelForEdit.NOTIFY_NONSTOP; + return value; + } + } + + /** Control set dealing with importance */ + public class ImportanceControlSet { + private final List buttons = new LinkedList(); + + private final int[] colors = Task.getImportanceColors(getResources()); + + public ImportanceControlSet(int containerId) { + LinearLayout layout = (LinearLayout)findViewById(containerId); + + for(int i = Task.IMPORTANCE_MOST; i <= Task.IMPORTANCE_LEAST; i++) { + final ToggleButton button = new ToggleButton(TaskEditActivity.this); + button.setLayoutParams(new LinearLayout.LayoutParams( + LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1)); + + StringBuilder label = new StringBuilder(); + for(int j = Task.IMPORTANCE_LEAST; j >= 0; j--) + label.append('!'); + + + button.setTextColor(colors[i]); + button.setTextOff(label); + button.setTextOn(label); + + button.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + setImportance((Integer)button.getTag()); + } + }); + button.setTag(i); + + buttons.add(button); + layout.addView(button); + } + } + + public void setImportance(Integer i) { + for(CompoundButton b : buttons) { + if(b.getTag() == i) { + b.setTextSize(24); + b.setChecked(true); + } else { + b.setTextSize(16); + b.setChecked(false); + } + } + } + + public int getImportance() { + for(CompoundButton b : buttons) + if(b.isChecked()) + return (Integer) b.getTag(); + return Task.getStaticDefaultValues().getAsInteger(Task.IMPORTANCE.name); + } + } + +} diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index 87705630c..803f285bf 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -1,10 +1,12 @@ package com.todoroo.astrid.activity; import java.util.List; +import java.util.Map.Entry; import android.app.AlertDialog; import android.app.ListActivity; import android.content.BroadcastReceiver; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -32,7 +34,6 @@ import android.widget.AdapterView.AdapterContextMenuInfo; import com.flurry.android.FlurryAgent; import com.timsu.astrid.R; import com.timsu.astrid.activities.EditPreferences; -import com.timsu.astrid.activities.TaskEdit; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; @@ -47,8 +48,10 @@ import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.TaskDetail; import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.filters.CoreFilterExposer; +import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.service.AstridDependencyInjector; +import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.utility.Constants; @@ -91,6 +94,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { @Autowired protected TaskService taskService; + @Autowired + protected MetadataService metadataService; + @Autowired protected DialogUtilities dialogUtilities; @@ -131,6 +137,9 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { filter = CoreFilterExposer.buildInboxFilter(getResources()); } + if(database == null) + return; + database.openForWriting(); setUpUiComponents(); setUpTaskList(); @@ -205,17 +214,26 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { ((ImageButton)findViewById(R.id.quickAddButton)).setOnClickListener(new OnClickListener() { public void onClick(View v) { TextView quickAdd = (TextView)findViewById(R.id.quickAddText); - Task task = quickAddTask(quickAdd.getText().toString()); if(quickAdd.getText().length() > 0) { + Task task = quickAddTask(quickAdd.getText().toString()); quickAdd.setText(""); //$NON-NLS-1$ loadTaskListContent(true); } else { - Intent intent = new Intent(TaskListActivity.this, TaskEdit.class); - intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, task.getId()); + Intent intent = new Intent(TaskListActivity.this, TaskEditActivity.class); startActivityForResult(intent, ACTIVITY_EDIT_TASK); } } }); + + ((ImageButton)findViewById(R.id.extendedAddButton)).setOnClickListener(new OnClickListener() { + public void onClick(View v) { + TextView quickAdd = (TextView)findViewById(R.id.quickAddText); + Task task = quickAddTask(quickAdd.getText().toString()); + Intent intent = new Intent(TaskListActivity.this, TaskEditActivity.class); + intent.putExtra(TaskEditActivity.ID_TOKEN, task.getId()); + startActivityForResult(intent, ACTIVITY_EDIT_TASK); + } + }); } /** @@ -228,11 +246,30 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { try { Task task = new Task(); task.setValue(Task.TITLE, title); - /*task.setValue(Task.DUE_DATE, Task.initializeDueDate( - task.getValue(Task.URGENCY))); + ContentValues forMetadata = null; + if(filter.valuesForNewTasks != null && filter.valuesForNewTasks.size() > 0) { + ContentValues forTask = new ContentValues(); + forMetadata = new ContentValues(); + for(Entry item : filter.valuesForNewTasks.valueSet()) { + if(item.getKey().startsWith(Task.TABLE.name)) + AndroidUtilities.putInto(forTask, item.getKey(), item.getValue()); + else + AndroidUtilities.putInto(forMetadata, item.getKey(), item.getValue()); + } + task.mergeWith(forTask); + } taskService.save(task, false); - if(filter.sqlForNewTasks != null) - taskService.invokeSqlForNewTask(filter, task); */ // TODO + if(forMetadata != null && forMetadata.size() > 0) { + Metadata metadata = new Metadata(); + for(Entry item : forMetadata.valueSet()) { + metadata.setValue(Metadata.TASK, task.getId()); + metadata.setValue(Metadata.KEY, item.getKey()); + metadata.setValue(Metadata.VALUE, item.toString()); + metadataService.save(metadata); + metadata.clear(); + } + } + return task; } catch (Exception e) { exceptionService.displayAndReportError(this, "quick-add-task", e); @@ -417,7 +454,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { } /** Show a dialog box and delete the task specified */ - private void deleteTask(final long id) { + private void deleteTask(final Task task) { new AlertDialog.Builder(this).setTitle(R.string.DLG_confirm_title) .setMessage(R.string.DLG_delete_this_task_question).setIcon( android.R.drawable.ic_dialog_alert).setPositiveButton( @@ -425,7 +462,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - taskService.delete(id); + taskService.delete(task); loadTaskListContent(true); } }).setNegativeButton(android.R.string.cancel, null) @@ -440,8 +477,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { // handle my own menus switch (item.getItemId()) { case MENU_ADD_TASK_ID: - intent = new Intent(TaskListActivity.this, TaskEdit.class); - intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, Task.NO_ID); + intent = new Intent(TaskListActivity.this, TaskEditActivity.class); + intent.putExtra(TaskEditActivity.ID_TOKEN, Task.NO_ID); startActivityForResult(intent, ACTIVITY_EDIT_TASK); return true; case MENU_PLUGINS_ID: @@ -469,15 +506,17 @@ public class TaskListActivity extends ListActivity implements OnScrollListener { case CONTEXT_MENU_EDIT_TASK_ID: { itemId = item.getGroupId(); - intent = new Intent(TaskListActivity.this, TaskEdit.class); - intent.putExtra(TaskEdit.LOAD_INSTANCE_TOKEN, itemId); + intent = new Intent(TaskListActivity.this, TaskEditActivity.class); + intent.putExtra(TaskEditActivity.ID_TOKEN, itemId); startActivityForResult(intent, ACTIVITY_EDIT_TASK); return true; } case CONTEXT_MENU_DELETE_TASK_ID: itemId = item.getGroupId(); - deleteTask(itemId); + Task task = new Task(); + task.setId(itemId); + deleteTask(task); return true; } diff --git a/astrid/src/com/todoroo/astrid/model/Task.java b/astrid/src/com/todoroo/astrid/model/Task.java index 14a797adc..75bf345b6 100644 --- a/astrid/src/com/todoroo/astrid/model/Task.java +++ b/astrid/src/com/todoroo/astrid/model/Task.java @@ -7,9 +7,9 @@ package com.todoroo.astrid.model; import android.content.ContentValues; +import android.content.res.Resources; -import com.timsu.astrid.data.enums.RepeatInterval; -import com.timsu.astrid.data.task.AbstractTaskModel.RepeatInfo; +import com.timsu.astrid.R; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Table; @@ -135,6 +135,21 @@ public final class Task extends AbstractModel { public static final int IMPORTANCE_SHOULD_DO = 2; public static final int IMPORTANCE_NONE = 3; + /** + * Get colors that correspond to importance values + */ + public static int[] getImportanceColors(Resources r) { + return new int[] { + r.getColor(R.color.importance_1), + r.getColor(R.color.importance_2), + r.getColor(R.color.importance_3), + r.getColor(R.color.importance_4), + }; + } + + public static final int IMPORTANCE_MOST = IMPORTANCE_DO_OR_DIE; + public static final int IMPORTANCE_LEAST = IMPORTANCE_NONE; + // --- defaults /** Default values container */ @@ -148,6 +163,16 @@ public final class Task extends AbstractModel { defaultValues.put(DELETION_DATE.name, 0); defaultValues.put(URGENCY.name, URGENCY_NONE); defaultValues.put(IMPORTANCE.name, IMPORTANCE_NONE); + + defaultValues.put(CALENDAR_URI.name, ""); + defaultValues.put(REPEAT.name, 0); + defaultValues.put(NOTIFICATIONS.name, 0); + defaultValues.put(NOTIFICATION_FLAGS.name, 0); + defaultValues.put(ESTIMATED_SECONDS.name, 0); + defaultValues.put(ELAPSED_SECONDS.name, 0); + defaultValues.put(POSTPONE_COUNT.name, 0); + defaultValues.put(NOTES.name, ""); + defaultValues.put(TIMER_START.name, 0); } private static boolean defaultValuesLoaded = false; @@ -232,23 +257,4 @@ public final class Task extends AbstractModel { return getValue(DUE_DATE) > 0; } - // --- data access methods for migration properties - - /** Number of bits to shift repeat value by */ - public static final int REPEAT_VALUE_OFFSET = 3; - - /** - * @return RepeatInfo corresponding to - */ - public RepeatInfo getRepeatInfo() { - int repeat = getValue(REPEAT); - if(repeat == 0) - return null; - int value = repeat >> REPEAT_VALUE_OFFSET; - RepeatInterval interval = RepeatInterval.values() - [repeat - (value << REPEAT_VALUE_OFFSET)]; - - return new RepeatInfo(interval, value); - } - } \ No newline at end of file diff --git a/astrid/src/com/todoroo/astrid/service/FlurryReporter.java b/astrid/src/com/todoroo/astrid/service/FlurryReporter.java index ace1af9e8..570f099d9 100644 --- a/astrid/src/com/todoroo/astrid/service/FlurryReporter.java +++ b/astrid/src/com/todoroo/astrid/service/FlurryReporter.java @@ -16,6 +16,9 @@ public class FlurryReporter implements ErrorReporter { @SuppressWarnings("nls") public void handleError(String name, Throwable error) { + if(error == null) + return; + String message = error.toString(); StringWriter writer = new StringWriter(); diff --git a/astrid/src/com/todoroo/astrid/service/MetadataService.java b/astrid/src/com/todoroo/astrid/service/MetadataService.java index 5dd138d1d..0425db804 100644 --- a/astrid/src/com/todoroo/astrid/service/MetadataService.java +++ b/astrid/src/com/todoroo/astrid/service/MetadataService.java @@ -82,4 +82,12 @@ public class MetadataService { public void deleteWhere(Criterion where) { metadataDao.deleteWhere(where); } + + /** + * Save a single piece of metadata + * @param metadata + */ + public void save(Metadata metadata) { + metadataDao.saveItem(metadata); + } } diff --git a/astrid/src/com/todoroo/astrid/service/TaskService.java b/astrid/src/com/todoroo/astrid/service/TaskService.java index b76c373bf..cf0dee658 100644 --- a/astrid/src/com/todoroo/astrid/service/TaskService.java +++ b/astrid/src/com/todoroo/astrid/service/TaskService.java @@ -39,7 +39,7 @@ public class TaskService { } /** - * Mark the given action item as completed and save it. + * Mark the given task as completed and save it. * * @param item */ @@ -65,12 +65,20 @@ public class TaskService { } /** - * Delete the given action item + * Delete the given task. If this task had a title, instead of deleting + * from the database, we set the deleted flag. * * @param model */ - public void delete(long itemId) { - taskDao.delete(itemId); + public void delete(Task item) { + if(!item.isSaved()) + return; + if(item.getValue(Task.TITLE).length() == 0) + taskDao.delete(item.getId()); + else { + item.setValue(Task.DELETION_DATE, DateUtilities.now()); + taskDao.save(item, false); + } } /**