/** * 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)); } }