/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.data;
import java.util.Date;
import android.content.ContentValues;
import com.todoroo.andlib.DateUtilities;
import com.todoroo.astrid.data.Property.IntegerProperty;
import com.todoroo.astrid.data.Property.LongProperty;
import com.todoroo.astrid.data.Property.StringProperty;
/**
* Data Model which represents a task users need to accomplish.
*
* @author Tim Su <tim@todoroo.com>
*
*/
@SuppressWarnings("nls")
public final class Task extends AbstractModel {
// --- table
public static final Table TABLE = new Table("tasks", Task.class);
// --- properties
/** ID */
public static final LongProperty ID = new LongProperty(
TABLE, ID_PROPERTY_NAME);
/** Name of Task */
public static final StringProperty TITLE = new StringProperty(
TABLE, "title");
/** Importance of Task (see importance flags) */
public static final IntegerProperty IMPORTANCE = new IntegerProperty(
TABLE, "importance");
/** Unixtime Task is due, 0 if not set */
public static final LongProperty DUE_DATE = new LongProperty(
TABLE, "dueDate");
/** Unixtime Task should be hidden until, 0 if not set */
public static final LongProperty HIDE_UNTIL = new LongProperty(
TABLE, "hideUntil");
/** Unixtime Task was created */
public static final LongProperty CREATION_DATE = new LongProperty(
TABLE, "created");
/** Unixtime Task was last touched */
public static final LongProperty MODIFICATION_DATE = new LongProperty(
TABLE, "modified");
/** Unixtime Task was completed. 0 means active */
public static final LongProperty COMPLETION_DATE = new LongProperty(
TABLE, "completed");
/** Unixtime Task was deleted. 0 means not deleted */
public static final LongProperty DELETION_DATE = new LongProperty(
TABLE, "deleted");
// --- for migration purposes from astrid 2 (eventually we will want to
// move these into the metadata table and treat them as plug-ins
public static final StringProperty NOTES = new StringProperty(
TABLE, "notes");
public static final IntegerProperty ESTIMATED_SECONDS = new IntegerProperty(
TABLE, "estimatedSeconds");
public static final IntegerProperty ELAPSED_SECONDS = new IntegerProperty(
TABLE, "elapsedSeconds");
public static final LongProperty TIMER_START = new LongProperty(
TABLE, "timerStart");
public static final IntegerProperty POSTPONE_COUNT = new IntegerProperty(
TABLE, "postponeCount");
/** Flags for when to send reminders */
public static final IntegerProperty REMINDER_FLAGS = new IntegerProperty(
TABLE, "notificationFlags");
/** Reminder period, in milliseconds. 0 means disabled */
public static final LongProperty REMINDER_PERIOD = new LongProperty(
TABLE, "notifications");
/** Unixtime the last reminder was triggered */
public static final LongProperty REMINDER_LAST = new LongProperty(
TABLE, "lastNotified");
public static final StringProperty RECURRENCE = new StringProperty(
TABLE, "recurrence");
public static final IntegerProperty FLAGS = new IntegerProperty(
TABLE, "flags");
public static final StringProperty CALENDAR_URI = new StringProperty(
TABLE, "calendarUri");
/** List of all properties for this model */
public static final Property<?>[] PROPERTIES = generateProperties(Task.class);
// --- flags
/** whether repeat occurs relative to completion date instead of due date */
public static final int FLAG_REPEAT_AFTER_COMPLETION = 1 << 1;
// --- notification flags
/** whether to send a reminder at deadline */
public static final int NOTIFY_AT_DEADLINE = 1 << 1;
/** whether to send reminders while task is overdue */
public static final int NOTIFY_AFTER_DEADLINE = 1 << 2;
/** reminder mode non-stop */
public static final int NOTIFY_NONSTOP = 1 << 3;
// --- importance settings
public static final int IMPORTANCE_DO_OR_DIE = 0;
public static final int IMPORTANCE_MUST_DO = 1;
public static final int IMPORTANCE_SHOULD_DO = 2;
public static final int IMPORTANCE_NONE = 3;
public static final int IMPORTANCE_MOST = IMPORTANCE_DO_OR_DIE;
public static final int IMPORTANCE_LEAST = IMPORTANCE_NONE;
// --- defaults
/** Default values container */
private static final ContentValues defaultValues = new ContentValues();
static {
defaultValues.put(TITLE.name, "");
defaultValues.put(DUE_DATE.name, 0);
defaultValues.put(HIDE_UNTIL.name, 0);
defaultValues.put(COMPLETION_DATE.name, 0);
defaultValues.put(DELETION_DATE.name, 0);
defaultValues.put(IMPORTANCE.name, IMPORTANCE_NONE);
defaultValues.put(CALENDAR_URI.name, "");
defaultValues.put(RECURRENCE.name, "");
defaultValues.put(REMINDER_PERIOD.name, 0);
defaultValues.put(REMINDER_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(FLAGS.name, 0);
defaultValues.put(TIMER_START.name, 0);
}
@Override
public ContentValues getDefaultValues() {
return defaultValues;
}
// --- data access boilerplate
public Task() {
super();
}
public Task(TodorooCursor<Task> cursor) {
this();
readPropertiesFromCursor(cursor);
}
public void readFromCursor(TodorooCursor<Task> cursor) {
super.readPropertiesFromCursor(cursor);
}
@Override
public long getId() {
return getIdHelper(ID);
}
// --- parcelable helpers
private static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class);
@Override
protected Creator<? extends AbstractModel> getCreator() {
return CREATOR;
}
// --- data access methods
/** Checks whether task is done. Requires COMPLETION_DATE */
public boolean isCompleted() {
return getValue(COMPLETION_DATE) > 0;
}
/** Checks whether task is deleted. Will return false if DELETION_DATE not read */
public boolean isDeleted() {
// assume false if we didn't load deletion date
if(!containsValue(DELETION_DATE))
return false;
else
return getValue(DELETION_DATE) > 0;
}
/** Checks whether task is hidden. Requires HIDDEN_UNTIL */
public boolean isHidden() {
return getValue(HIDE_UNTIL) > DateUtilities.now();
}
/** Checks whether task is done. Requires DUE_DATE */
public boolean hasDueDate() {
return getValue(DUE_DATE) > 0;
}
/**
* Returns the set state of the given flag on the given property
* @param property
* @param flag
* @return
*/
public boolean getFlag(IntegerProperty property, int flag) {
return (getValue(property) & flag) > 0;
}
/**
* Sets the state of the given flag on the given property
* @param property
* @param flag
* @param value
*/
public void setFlag(IntegerProperty property, int flag, boolean value) {
if(value)
setValue(property, getValue(property) | flag);
else
setValue(property, getValue(property) & ~flag);
}
// --- due and hide until date management
/** urgency array index -> significance */
public static final int URGENCY_NONE = 0;
public static final int URGENCY_TODAY = 1;
public static final int URGENCY_TOMORROW = 2;
public static final int URGENCY_DAY_AFTER = 3;
public static final int URGENCY_NEXT_WEEK = 4;
public static final int URGENCY_NEXT_MONTH = 5;
public static final int URGENCY_SPECIFIC_DAY = 6;
public static final int URGENCY_SPECIFIC_DAY_TIME = 7;
/** hide until array index -> significance */
public static final int HIDE_UNTIL_NONE = 0;
public static final int HIDE_UNTIL_DUE = 1;
public static final int HIDE_UNTIL_DAY_BEFORE = 2;
public static final int HIDE_UNTIL_WEEK_BEFORE = 3;
public static final int HIDE_UNTIL_SPECIFIC_DAY = 4;
/**
* Creates due date for this task. If this due date has no time associated,
* we move it to the last millisecond of the day.
*
* @param setting
* one of the URGENCY_* constants
* @param customDate
* if specific day or day & time is set, this value
*/
public long createDueDate(int setting, long customDate) {
long date;
switch(setting) {
case URGENCY_NONE:
date = 0;
break;
case URGENCY_TODAY:
date = DateUtilities.now();
break;
case URGENCY_TOMORROW:
date = DateUtilities.now() + DateUtilities.ONE_DAY;
break;
case URGENCY_DAY_AFTER:
date = DateUtilities.now() + 2 * DateUtilities.ONE_DAY;
break;
case URGENCY_NEXT_WEEK:
date = DateUtilities.now() + DateUtilities.ONE_WEEK;
break;
case URGENCY_NEXT_MONTH:
date = DateUtilities.oneMonthFromNow();
break;
case URGENCY_SPECIFIC_DAY:
case URGENCY_SPECIFIC_DAY_TIME:
date = customDate;
break;
default:
throw new IllegalArgumentException("Unknown setting " + setting);
}
if(date <= 0)
return date;
Date dueDate = new Date(date / 1000L * 1000L); // get rid of millis
if(setting != URGENCY_SPECIFIC_DAY_TIME) {
dueDate.setHours(23);
dueDate.setMinutes(59);
dueDate.setSeconds(59);
} else if(isEndOfDay(dueDate)) {
dueDate.setSeconds(58);
}
return dueDate.getTime();
}
/**
* Create hide until for this task.
*
* @param setting
* one of the HIDE_UNTIL_* constants
* @param customDate
* if specific day is set, this value
* @return
*/
public long createHideUntil(int setting, long customDate) {
long date;
switch(setting) {
case HIDE_UNTIL_NONE:
return 0;
case HIDE_UNTIL_DUE:
date = getValue(DUE_DATE);
break;
case HIDE_UNTIL_DAY_BEFORE:
date = getValue(DUE_DATE) - DateUtilities.ONE_DAY;
break;
case HIDE_UNTIL_WEEK_BEFORE:
date = getValue(DUE_DATE) - DateUtilities.ONE_WEEK;
break;
case HIDE_UNTIL_SPECIFIC_DAY:
date = customDate;
break;
default:
throw new IllegalArgumentException("Unknown setting " + setting);
}
if(date <= 0)
return date;
Date hideUntil = new Date(date / 1000L * 1000L); // get rid of millis
hideUntil.setHours(0);
hideUntil.setMinutes(0);
hideUntil.setSeconds(0);
return hideUntil.getTime();
}
/**
* @return true if hours, minutes, and seconds indicate end of day
*/
private static boolean isEndOfDay(Date date) {
int hours = date.getHours();
int minutes = date.getMinutes();
int seconds = date.getSeconds();
return hours == 23 && minutes == 59 && seconds == 59;
}
/**
* Checks whether this due date has a due time or only a date
*/
public boolean hasDueTime() {
return hasDueTime(getValue(Task.DUE_DATE));
}
/**
* Checks whether provided due date has a due time or only a date
*/
public static boolean hasDueTime(long dueDate) {
return !isEndOfDay(new Date(dueDate));
}
}