diff --git a/astrid/.settings/org.eclipse.jdt.core.prefs b/astrid/.settings/org.eclipse.jdt.core.prefs index f5dd01cd3..ca4026d0e 100644 --- a/astrid/.settings/org.eclipse.jdt.core.prefs +++ b/astrid/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,4 @@ -#Mon Jan 04 09:25:09 PST 2010 +#Wed Jun 30 18:54:52 PDT 2010 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 @@ -10,6 +10,8 @@ org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning org.eclipse.jdt.core.compiler.problem.deprecation=warning org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled @@ -29,8 +31,10 @@ org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning @@ -45,7 +49,7 @@ org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning diff --git a/astrid/.settings/org.eclipse.jdt.ui.prefs b/astrid/.settings/org.eclipse.jdt.ui.prefs index 94bdf85fb..ace644cbc 100644 --- a/astrid/.settings/org.eclipse.jdt.ui.prefs +++ b/astrid/.settings/org.eclipse.jdt.ui.prefs @@ -1,4 +1,4 @@ -#Thu Jul 02 10:50:56 PDT 2009 +#Tue Jun 29 14:53:46 PDT 2010 cleanup.add_default_serial_version_id=true cleanup.add_generated_serial_version_id=false cleanup.add_missing_annotations=true @@ -52,6 +52,58 @@ cleanup.use_this_for_non_static_method_access_only_if_necessary=true cleanup_profile=_Astrid cleanup_settings_version=2 eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true formatter_profile=_Astrid formatter_settings_version=11 org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=false +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=true +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 202dfd9f1..8f27e506d 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -1,137 +1,208 @@ + package="com.timsu.astrid" + android:versionName="3.0.0-beta" android:versionCode="136"> - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java b/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java index b8b73a6ab..8749368df 100644 --- a/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java +++ b/astrid/api-src/com/todoroo/astrid/api/AstridApiConstants.java @@ -39,10 +39,22 @@ public class AstridApiConstants { public static final String EXTRAS_ITEMS = "items"; /** - * Extras name for your plug-in name, used for logging errors to LogCat + * Extras name for plug-in object */ public static final String EXTRAS_PLUGIN = "plugin"; + // --- Plug-ins API + + /** + * Action name for broadcast intent requesting filters + */ + public static final String BROADCAST_REQUEST_PLUGINS = PACKAGE + ".REQUEST_PLUGINS"; + + /** + * Action name for broadcast intent sending filters back to Astrid + */ + public static final String BROADCAST_SEND_PLUGINS = PACKAGE + ".SEND_PLUGINS"; + // --- Filters API /** diff --git a/astrid/api-src/com/todoroo/astrid/api/AstridContentProvider.java b/astrid/api-src/com/todoroo/astrid/api/AstridContentProvider.java deleted file mode 100644 index 61b4bf35f..000000000 --- a/astrid/api-src/com/todoroo/astrid/api/AstridContentProvider.java +++ /dev/null @@ -1,184 +0,0 @@ -/** - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.api; - -import android.net.Uri; - -/** - * Constants for interfacing with Astrid's Content Providers. - * - * @author Tim Su - */ -@SuppressWarnings("nls") -public class AstridContentProvider { - - /** - * Content Provider - */ - public static final String PROVIDER = "com.todoroo.astrid.provider"; - - // --- methods for generating URI's for accessing Astrid data - - /** - * URI for: - * - * If your selection clause contains metadata columns, you need to use - * allItemsWithMetadataUri instead of this one. - */ - public static Uri allItemsUri() { - return Uri.parse("content://" + PROVIDER + "/items"); - } - - /** - * URI for: - * - * If, for example, you have defined metadata key 'tag' and wish to delete - * all tasks where 'tag' = 'deleteme', you would use this URI. For querying - * or insertion, use allItemsUri. - *

- * For queries, allItemsUri will be more efficient, but will - * not work if your selection clause contains columns not mentioned in your - * projection - * - * @param metadata - * array of metadata columns you wish to select using - * - */ - public static Uri allItemsWithMetadataUri(String[] metadata) { - if(metadata == null || metadata.length == 0) - throw new IllegalArgumentException("You must provide metadata"); - StringBuilder builder = new StringBuilder(); - for(int i = 0; i < metadata.length; i++) - builder.append(escapeUriSubComponent(metadata[i])).append( - SUB_COMPONENT_SEPARATOR); - - return Uri.parse("content://" + PROVIDER + "/itemsWith/" + - escapeUriComponent(builder.toString())); - } - - /** - * URI for: - *

- * - * @param id - * id of task to fetch - */ - public static Uri singleItemUri(long id) { - return Uri.parse("content://" + PROVIDER + "/" + id); - } - - /** - * URI for: - * - * @param groupBy - * column name to group by - */ - public static Uri groupByUri(String groupBy) { - groupBy = escapeUriComponent(groupBy); - return Uri.parse("content://" + PROVIDER + "/groupby/" + groupBy); - } - - // --- task built-in columns and constnats - - /** - * A task in Astrid represents a single item in a user's task list - * - * @author Tim Su - */ - public static class AstridTask { - - // --- columns - - /** long: Task id */ - public static final String ID = "_id"; - - /** String: name of Task */ - public static final String TITLE = "title"; - - /** int: Task Urgency setting (see Task.URGENCY_*) */ - public static final String URGENCY = "urgency"; - - /** int: Task Importance setting (see Task.IMPORTANCE_*) */ - public static final String IMPORTANCE = "importance"; - - /** int: unixtime Task is due, 0 if not set */ - public static final String DUE_DATE = "dueDate"; - - /** int: unixtime Task should be hidden until, 0 if not set */ - public static final String HIDDEN_UNTIL = "hiddenUntil"; - - /** int: unixtime Task was created */ - public static final String CREATION_DATE = "creationDate"; - - /** int: unixtime Task was completed, 0 if task not completed */ - public static final String COMPLETION_DATE = "completionDate"; - - /** int: unixtime Task was deleted, 0 if task not deleted */ - public static final String DELETION_DATE = "deletionDate"; - - /** int: unixtime Task was modified */ - public static final String MODIFICATION_DATE = "modificationDate"; - - // --- urgency settings - - public static final int URGENCY_NONE = 0; - public static final int URGENCY_TODAY = 1; - public static final int URGENCY_THIS_WEEK = 2; - public static final int URGENCY_THIS_MONTH = 3; - public static final int URGENCY_WITHIN_THREE_MONTHS = 4; - public static final int URGENCY_WITHIN_SIX_MONTHS = 5; - public static final int URGENCY_WITHIN_A_YEAR = 6; - public static final int URGENCY_SPECIFIC_DAY = 7; - public static final int URGENCY_SPECIFIC_DAY_TIME = 8; - - // --- 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; - - - } - - // --- internal methods - - /** - * Escapes a string for use in a URI. Used internally to pass extra data - * to the content provider. - * @param component - * @return - */ - private static String escapeUriComponent(String component) { - return component.replace("%", "%o").replace("/", "%s"); - } - - private static final String SUB_COMPONENT_SEPARATOR = "|"; - - /** - * Escapes a string for use as part of a URI string. Used internally to pass extra data - * to the content provider. - * @param component - * @return - */ - private static String escapeUriSubComponent(String component) { - return component.replace("$", "$o").replace(SUB_COMPONENT_SEPARATOR, "$s"); - } - -} diff --git a/astrid/api-src/com/todoroo/astrid/api/EditOperation.java b/astrid/api-src/com/todoroo/astrid/api/EditOperation.java index 779b40689..84af715dd 100644 --- a/astrid/api-src/com/todoroo/astrid/api/EditOperation.java +++ b/astrid/api-src/com/todoroo/astrid/api/EditOperation.java @@ -13,12 +13,12 @@ import android.os.Parcelable; * @author Tim Su * */ -public class EditOperation implements Parcelable { +public final class EditOperation implements Parcelable { /** * Plugin Id */ - public String plugin = null; + public final String plugin; /** * Label @@ -33,13 +33,15 @@ public class EditOperation implements Parcelable { /** * Create an EditOperation object * + * @param plugin + * {@link Plugin} identifier that encompasses object * @param text * label to display * @param intent * intent to invoke. {@link EXTRAS_TASK_ID} will be passed */ - public EditOperation(String text, Intent intent) { - super(); + public EditOperation(String plugin, String text, Intent intent) { + this.plugin = plugin; this.text = text; this.intent = intent; } @@ -57,6 +59,7 @@ public class EditOperation implements Parcelable { * {@inheritDoc} */ public void writeToParcel(Parcel dest, int flags) { + dest.writeString(plugin); dest.writeString(text); dest.writeParcelable(intent, 0); } @@ -69,8 +72,8 @@ public class EditOperation implements Parcelable { * {@inheritDoc} */ public EditOperation createFromParcel(Parcel source) { - return new EditOperation(source.readString(), (Intent)source.readParcelable( - Intent.class.getClassLoader())); + return new EditOperation(source.readString(), source.readString(), + (Intent)source.readParcelable(Intent.class.getClassLoader())); } /** diff --git a/astrid/api-src/com/todoroo/astrid/api/Filter.java b/astrid/api-src/com/todoroo/astrid/api/Filter.java index bd2e45c9e..0ae269f25 100644 --- a/astrid/api-src/com/todoroo/astrid/api/Filter.java +++ b/astrid/api-src/com/todoroo/astrid/api/Filter.java @@ -3,10 +3,11 @@ */ package com.todoroo.astrid.api; +import android.content.ContentValues; import android.os.Parcel; import android.os.Parcelable; -import com.todoroo.astrid.api.AstridContentProvider.AstridTask; +import com.todoroo.andlib.sql.QueryTemplate; /** * A FilterListFilter allows users to display tasks that have @@ -19,7 +20,12 @@ import com.todoroo.astrid.api.AstridContentProvider.AstridTask; * @author Tim Su * */ -public class Filter extends FilterListItem { +public final class Filter extends FilterListItem { + + /** + * Plug-in Identifier + */ + public final String plugin; /** * Expanded title of this filter. This is displayed at the top @@ -31,15 +37,14 @@ public class Filter extends FilterListItem { /** * SQL query for this filter. The query will be appended to the select - * statement after "SELECT fields FROM table %s". Use - * {@link AstridApiConstants.TASK_TABLE} and - * {@link AstridApiConstants.METADATA_TABLE} as table names, - * {@link AstridTask} for field names. + * statement after "SELECT fields FROM table %s". It is + * recommended that you use a {@link QueryTemplate} to construct your + * query. *

* Examples: *

    - *
  • " WHERE completionDate = 0" - *
  • " INNER JOIN " + + *
  • "WHERE completionDate = 0" + *
  • "INNER JOIN " + * Constants.TABLE_METADATA + " ON metadata.task = tasks.id WHERE * metadata.namespace = " + NAMESPACE + " AND metadata.key = 'a' AND * metadata.value = 'b' GROUP BY tasks.id ORDER BY tasks.title" @@ -48,25 +53,18 @@ public class Filter extends FilterListItem { public String sqlQuery; /** - * SQL query to execute on a task when quick-creating a new task while viewing - * this filter. For example, when a user views tasks tagged 'ABC', the + * Values to apply to a task when quick-adding a task from this filter. + * For example, when a user views tasks tagged 'ABC', the * tasks they create should also be tagged 'ABC'. If set to null, no - * query will be executed. In this string, $ID will be replaced with the - * task id. - *

    - * Examples: - *

      - *
    • "INSERT INTO " + Constants.TABLE_METADATA + " (task, - * namespace, key, string) VALUES ($ID, " + ... + ")" - *
    • "UPDATE " + Constants.TABLE_TASK + " SET urgency = 0 - * WHERE _id = $ID" - *
    + * additional values will be stored for a task. */ - public String sqlForNewTasks = null; + public ContentValues valuesForNewTasks = null; /** * Utility constructor for creating a TaskList object * + * @param plugin + * {@link Plugin} identifier that encompasses object * @param listingTitle * Title of this item as displayed on the lists page, e.g. Inbox * @param title @@ -74,22 +72,26 @@ public class Filter extends FilterListItem { * filter, e.g. Inbox (20 tasks) * @param sqlQuery * SQL query for this list (see {@link sqlQuery} for examples). - * @param sqlForNewTasks + * @param valuesForNewTasks * see {@link sqlForNewTasks} */ - public Filter(String listingTitle, - String title, String sqlQuery, String sqlForNewTasks) { + public Filter(String plugin, String listingTitle, + String title, QueryTemplate sqlQuery, ContentValues valuesForNewTasks) { + this.plugin = plugin; this.listingTitle = listingTitle; this.title = title; - this.sqlQuery = sqlQuery; - this.sqlForNewTasks = sqlForNewTasks; + this.sqlQuery = sqlQuery.toString(); + this.valuesForNewTasks = valuesForNewTasks; } /** - * Blank constructor + * Utility constructor + * + * @param plugin + * {@link Plugin} identifier that encompasses object */ - public Filter() { - // + protected Filter(String plugin) { + this.plugin = plugin; } // --- parcelable @@ -106,10 +108,11 @@ public class Filter extends FilterListItem { */ @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(plugin); super.writeToParcel(dest, flags); dest.writeString(title); dest.writeString(sqlQuery); - dest.writeString(sqlForNewTasks); + dest.writeParcelable(valuesForNewTasks, 0); } /** @@ -121,11 +124,11 @@ public class Filter extends FilterListItem { * {@inheritDoc} */ public Filter createFromParcel(Parcel source) { - Filter item = new Filter(); + Filter item = new Filter(source.readString()); item.readFromParcel(source); item.title = source.readString(); item.sqlQuery = source.readString(); - item.sqlForNewTasks = source.readString(); + item.valuesForNewTasks = source.readParcelable(ContentValues.class.getClassLoader()); return item; } diff --git a/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java b/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java index a95ffb802..ffaa9cb35 100644 --- a/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java +++ b/astrid/api-src/com/todoroo/astrid/api/FilterCategory.java @@ -15,6 +15,11 @@ import android.os.Parcelable; */ public class FilterCategory extends FilterListItem { + /** + * Plug-in Identifier + */ + public final String plugin; + /** * {@link Filter}s contained by this category */ @@ -23,22 +28,28 @@ public class FilterCategory extends FilterListItem { /** * Constructor for creating a new FilterCategory * + * @param plugin + * {@link Plugin} identifier that encompasses object * @param listingTitle * Title of this item as displayed on the lists page, e.g. Inbox * @param children * filters belonging to this category */ - public FilterCategory(String listingTitle, + public FilterCategory(String plugin, String listingTitle, Filter[] children) { + this.plugin = plugin; this.listingTitle = listingTitle; this.children = children; } /** - * Blank constructor + * Constructor for creating a new FilterCategory + * + * @param plugin + * {@link Plugin} identifier that encompasses object */ - public FilterCategory() { - // + protected FilterCategory(String plugin) { + this.plugin = plugin; } // --- parcelable @@ -55,6 +66,7 @@ public class FilterCategory extends FilterListItem { */ @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(plugin); super.writeToParcel(dest, flags); dest.writeParcelableArray(children, 0); } @@ -68,7 +80,7 @@ public class FilterCategory extends FilterListItem { * {@inheritDoc} */ public FilterCategory createFromParcel(Parcel source) { - FilterCategory item = new FilterCategory(); + FilterCategory item = new FilterCategory(source.readString()); item.readFromParcel(source); Parcelable[] parcelableChildren = source.readParcelableArray( diff --git a/astrid/api-src/com/todoroo/astrid/api/FilterListHeader.java b/astrid/api-src/com/todoroo/astrid/api/FilterListHeader.java index b980a49e8..e1d38b87b 100644 --- a/astrid/api-src/com/todoroo/astrid/api/FilterListHeader.java +++ b/astrid/api-src/com/todoroo/astrid/api/FilterListHeader.java @@ -8,44 +8,57 @@ import android.os.Parcelable; /** * Section Header for Filter List - * + * * @author Tim Su * */ public class FilterListHeader extends FilterListItem { + /** + * Plug-in Identifier + */ + public final String plugin; + /** * Constructor for creating a new FilterListHeader - * + * + * @param plugin + * {@link Plugin} identifier that encompasses object * @param listingTitle * @param listingIconResource * @param priority */ - public FilterListHeader(String listingTitle) { + public FilterListHeader(String plugin, String listingTitle) { + this.plugin = plugin; this.listingTitle = listingTitle; } - + /** - * Empty constructor + * Constructor for creating a new FilterListHeader + * + * @param plugin + * {@link Plugin} identifier that encompasses object */ - public FilterListHeader() { - // + protected FilterListHeader(String plugin) { + this.plugin = plugin; } - + // --- parcelable - + public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(plugin); super.writeToParcel(dest, flags); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public FilterListHeader createFromParcel(Parcel source) { - FilterListHeader item = new FilterListHeader(); + FilterListHeader item = new FilterListHeader(source.readString()); item.readFromParcel(source); return item; } @@ -53,6 +66,6 @@ public class FilterListHeader extends FilterListItem { public FilterListHeader[] newArray(int size) { return new FilterListHeader[size]; } - + }; } diff --git a/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java b/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java index f0eab9ec8..747a22cf3 100644 --- a/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java +++ b/astrid/api-src/com/todoroo/astrid/api/FilterListItem.java @@ -57,6 +57,7 @@ abstract public class FilterListItem implements Parcelable { /** * Utility method to read FilterListItem properties from a parcel. + * * @param source */ public void readFromParcel(Parcel source) { diff --git a/astrid/api-src/com/todoroo/astrid/api/Plugin.java b/astrid/api-src/com/todoroo/astrid/api/Plugin.java new file mode 100644 index 000000000..81f280ff7 --- /dev/null +++ b/astrid/api-src/com/todoroo/astrid/api/Plugin.java @@ -0,0 +1,93 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.api; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a plug-in for Astrid. Users can enable or disable plug-ins, + * which affect all other extension points that share the same identifier. + * + * @author Tim Su + * + */ +public class Plugin implements Parcelable { + + /** + * Plug-in Identifier + */ + public String plugin = null; + + /** + * Plug-in Title + */ + public String title = null; + + /** + * Plug-in Author + */ + public String author = null; + + /** + * Plug-in Description + */ + public String description = null; + + /** + * Convenience constructor to generate a plug-in object + * + * @param plugin + * @param title + * @param author + * @param description + */ + public Plugin(String plugin, String title, String author, String description) { + this.plugin = plugin; + this.title = title; + this.author = author; + this.description = description; + } + + // --- parcelable helpers + + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(plugin); + dest.writeString(title); + dest.writeString(author); + dest.writeString(description); + } + + /** + * Parcelable creator + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + /** + * {@inheritDoc} + */ + public Plugin createFromParcel(Parcel source) { + return new Plugin(source.readString(), source.readString(), + source.readString(), source.readString()); + } + + /** + * {@inheritDoc} + */ + public Plugin[] newArray(int size) { + return new Plugin[size]; + }; + }; + +} diff --git a/astrid/api-src/com/todoroo/astrid/api/TaskDetail.java b/astrid/api-src/com/todoroo/astrid/api/TaskDetail.java index 573a7b35c..2fe01fd66 100644 --- a/astrid/api-src/com/todoroo/astrid/api/TaskDetail.java +++ b/astrid/api-src/com/todoroo/astrid/api/TaskDetail.java @@ -12,7 +12,12 @@ import android.os.Parcelable; * @author Tim Su * */ -public class TaskDetail implements Parcelable { +public final class TaskDetail implements Parcelable { + + /** + * Plug-in Identifier + */ + public final String plugin; /** * Text of detail @@ -27,13 +32,15 @@ public class TaskDetail implements Parcelable { /** * Creates a TaskDetail object * + * @param plugin + * {@link Plugin} identifier that encompasses object * @param text * text to display * @param color * color to use for text. Use 0 for default color */ - public TaskDetail(String text, int color) { - super(); + public TaskDetail(String plugin, String text, int color) { + this.plugin = plugin; this.text = text; this.color = color; } @@ -44,8 +51,8 @@ public class TaskDetail implements Parcelable { * @param text * text to display */ - public TaskDetail(String text) { - this(text, 0); + public TaskDetail(String plugin, String text) { + this(plugin, text, 0); } @@ -63,6 +70,7 @@ public class TaskDetail implements Parcelable { * {@inheritDoc} */ public void writeToParcel(Parcel dest, int flags) { + dest.writeString(plugin); dest.writeString(text); dest.writeInt(color); } @@ -75,7 +83,7 @@ public class TaskDetail implements Parcelable { * {@inheritDoc} */ public TaskDetail createFromParcel(Parcel source) { - return new TaskDetail(source.readString(), source.readInt()); + return new TaskDetail(source.readString(), source.readString(), source.readInt()); } /** diff --git a/astrid/astrid.launch b/astrid/astrid.launch new file mode 100644 index 000000000..cc219d422 --- /dev/null +++ b/astrid/astrid.launch @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java b/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java index e5d268a57..197356893 100644 --- a/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java +++ b/astrid/common-src/com/todoroo/andlib/data/AbstractDatabase.java @@ -148,11 +148,12 @@ abstract public class AbstractDatabase { } /** - * @return sql database. throws error if database was not opened + * @return sql database. opens database if not yet open */ public final SQLiteDatabase getDatabase() { + // open database if requested if(database == null) - throw new IllegalStateException("Database was not opened!"); + openForWriting(); return database; } diff --git a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java index b28e296d1..b70d86d1b 100644 --- a/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java +++ b/astrid/common-src/com/todoroo/andlib/data/AbstractModel.java @@ -99,6 +99,18 @@ public abstract class AbstractModel implements Parcelable { setValues = null; } + /** + * Transfers all set values into values. This occurs when a task is + * saved - future saves will not need to write all the data as before. + */ + public void markSaved() { + if(values == null) + values = setValues; + else if(setValues != null) + values.putAll(setValues); + setValues = null; + } + /** * Use merged values to compare two models to each other. Must be of * exactly the same class. @@ -176,6 +188,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 +247,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 +361,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/data/GenericDao.java b/astrid/common-src/com/todoroo/andlib/data/GenericDao.java index 7fe7b38f5..f2d3a53c2 100644 --- a/astrid/common-src/com/todoroo/andlib/data/GenericDao.java +++ b/astrid/common-src/com/todoroo/andlib/data/GenericDao.java @@ -11,8 +11,8 @@ import java.lang.reflect.InvocationTargetException; import android.content.ContentValues; import android.database.Cursor; -import com.todoroo.andlib.data.sql.Criterion; -import com.todoroo.andlib.data.sql.Query; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Query; @@ -24,7 +24,7 @@ import com.todoroo.andlib.data.sql.Query; */ public class GenericDao { - private Class modelClass; + private final Class modelClass; private Table table; @@ -76,7 +76,7 @@ public class GenericDao { * properties to read * @param id * id of item - * @return + * @return null if no item found */ public TYPE fetch(long id, Property... properties) { TodorooCursor cursor = fetchItem(id, properties); @@ -133,14 +133,14 @@ public class GenericDao { */ public boolean persist(AbstractModel item) { if (item.getId() == AbstractModel.NO_ID) { - return createItem(item); + return createNew(item); } else { ContentValues values = item.getSetValues(); if (values.size() == 0) // nothing changed return true; - return saveItem(item); + return saveExisting(item); } } @@ -154,7 +154,7 @@ public class GenericDao { * item model * @return returns true on success. */ - public boolean createItem(AbstractModel item) { + public boolean createNew(AbstractModel item) { long newRow = database.getDatabase().insert(table.name, AbstractModel.ID_PROPERTY.name, item.getMergedValues()); item.setId(newRow); @@ -162,7 +162,7 @@ public class GenericDao { } /** - * Saves the given item. + * Saves the given item. Will not create a new item! * * @param database * @param table @@ -171,7 +171,7 @@ public class GenericDao { * item model * @return returns true on success. */ - public boolean saveItem(AbstractModel item) { + public boolean saveExisting(AbstractModel item) { ContentValues values = item.getSetValues(); if(values.size() == 0) // nothing changed return true; diff --git a/astrid/common-src/com/todoroo/andlib/data/Property.java b/astrid/common-src/com/todoroo/andlib/data/Property.java index ad51e5353..9a4665bf2 100644 --- a/astrid/common-src/com/todoroo/andlib/data/Property.java +++ b/astrid/common-src/com/todoroo/andlib/data/Property.java @@ -5,7 +5,7 @@ */ package com.todoroo.andlib.data; -import com.todoroo.andlib.data.sql.Field; +import com.todoroo.andlib.sql.Field; /** * Property represents a typed column in a database. diff --git a/astrid/common-src/com/todoroo/andlib/data/Table.java b/astrid/common-src/com/todoroo/andlib/data/Table.java index ae8d59274..3d2cb8880 100644 --- a/astrid/common-src/com/todoroo/andlib/data/Table.java +++ b/astrid/common-src/com/todoroo/andlib/data/Table.java @@ -1,6 +1,7 @@ package com.todoroo.andlib.data; -import com.todoroo.andlib.data.sql.Field; +import com.todoroo.andlib.sql.Field; +import com.todoroo.andlib.sql.SqlTable; /** * Table class. Most fields are final, so methods such as as will @@ -9,7 +10,7 @@ import com.todoroo.andlib.data.sql.Field; * @author Tim Su * */ -public final class Table extends com.todoroo.andlib.data.sql.Table { +public final class Table extends SqlTable { public final String name; public final Class modelClass; diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/OrderType.java b/astrid/common-src/com/todoroo/andlib/data/sql/OrderType.java deleted file mode 100644 index b3b8fa5af..000000000 --- a/astrid/common-src/com/todoroo/andlib/data/sql/OrderType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.todoroo.andlib.data.sql; - -public enum OrderType { - DESC, ASC -} diff --git a/astrid/common-src/com/todoroo/andlib/service/DependencyInjectionService.java b/astrid/common-src/com/todoroo/andlib/service/DependencyInjectionService.java index f347bd371..feaa2a4c8 100644 --- a/astrid/common-src/com/todoroo/andlib/service/DependencyInjectionService.java +++ b/astrid/common-src/com/todoroo/andlib/service/DependencyInjectionService.java @@ -3,8 +3,6 @@ package com.todoroo.andlib.service; import java.lang.reflect.Field; import java.util.Arrays; -import android.util.Log; - /** * Simple Dependency Injection Service for Android. *

    @@ -138,6 +136,5 @@ public class DependencyInjectionService { */ public synchronized void setInjectors(AbstractDependencyInjector[] injectors) { this.injectors = injectors; - Log.e("INJECTION SETTING", "Set Injector List to: " + Arrays.asList(injectors)); // (debug) } } diff --git a/astrid/common-src/com/todoroo/andlib/service/NotificationManager.java b/astrid/common-src/com/todoroo/andlib/service/NotificationManager.java index cb9ff8371..56e229eb7 100644 --- a/astrid/common-src/com/todoroo/andlib/service/NotificationManager.java +++ b/astrid/common-src/com/todoroo/andlib/service/NotificationManager.java @@ -28,7 +28,7 @@ public interface NotificationManager { * */ public static class AndroidNotificationManager implements NotificationManager { - final android.app.NotificationManager nm; + private final android.app.NotificationManager nm; public AndroidNotificationManager(Context context) { nm = (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Constants.java b/astrid/common-src/com/todoroo/andlib/sql/Constants.java similarity index 85% rename from astrid/common-src/com/todoroo/andlib/data/sql/Constants.java rename to astrid/common-src/com/todoroo/andlib/sql/Constants.java index 4596aa34b..36e425eae 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Constants.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Constants.java @@ -1,7 +1,7 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; @SuppressWarnings("nls") -public class Constants { +public final class Constants { static final String SELECT = "SELECT"; static final String SPACE = " "; static final String AS = "AS"; @@ -21,4 +21,5 @@ public class Constants { static final String WHERE = "WHERE"; public static final String EXISTS = "EXISTS"; public static final String NOT = "NOT"; + public static final String LIMIT = "LIMIT"; } diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Criterion.java b/astrid/common-src/com/todoroo/andlib/sql/Criterion.java similarity index 73% rename from astrid/common-src/com/todoroo/andlib/data/sql/Criterion.java rename to astrid/common-src/com/todoroo/andlib/sql/Criterion.java index bae70f44d..9de7d828b 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Criterion.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Criterion.java @@ -1,12 +1,12 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -import static com.todoroo.andlib.data.sql.Constants.AND; -import static com.todoroo.andlib.data.sql.Constants.EXISTS; -import static com.todoroo.andlib.data.sql.Constants.LEFT_PARENTHESIS; -import static com.todoroo.andlib.data.sql.Constants.NOT; -import static com.todoroo.andlib.data.sql.Constants.OR; -import static com.todoroo.andlib.data.sql.Constants.RIGHT_PARENTHESIS; -import static com.todoroo.andlib.data.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.AND; +import static com.todoroo.andlib.sql.Constants.EXISTS; +import static com.todoroo.andlib.sql.Constants.LEFT_PARENTHESIS; +import static com.todoroo.andlib.sql.Constants.NOT; +import static com.todoroo.andlib.sql.Constants.OR; +import static com.todoroo.andlib.sql.Constants.RIGHT_PARENTHESIS; +import static com.todoroo.andlib.sql.Constants.SPACE; public abstract class Criterion { protected final Operator operator; @@ -15,6 +15,13 @@ public abstract class Criterion { this.operator = operator; } + public static Criterion all = new Criterion(Operator.exists) { + @Override + protected void populate(StringBuilder sb) { + sb.append(true); + } + }; + public static Criterion and(final Criterion criterion, final Criterion... criterions) { return new Criterion(Operator.and) { diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/DBObject.java b/astrid/common-src/com/todoroo/andlib/sql/DBObject.java similarity index 88% rename from astrid/common-src/com/todoroo/andlib/data/sql/DBObject.java rename to astrid/common-src/com/todoroo/andlib/sql/DBObject.java index 7837eb77f..7d27d70a6 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/DBObject.java +++ b/astrid/common-src/com/todoroo/andlib/sql/DBObject.java @@ -1,7 +1,7 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -import static com.todoroo.andlib.data.sql.Constants.AS; -import static com.todoroo.andlib.data.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.AS; +import static com.todoroo.andlib.sql.Constants.SPACE; public abstract class DBObject> implements Cloneable { protected String alias; diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/EqCriterion.java b/astrid/common-src/com/todoroo/andlib/sql/EqCriterion.java similarity index 76% rename from astrid/common-src/com/todoroo/andlib/data/sql/EqCriterion.java rename to astrid/common-src/com/todoroo/andlib/sql/EqCriterion.java index b2ec34cb8..99fc74ad2 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/EqCriterion.java +++ b/astrid/common-src/com/todoroo/andlib/sql/EqCriterion.java @@ -1,4 +1,4 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; public class EqCriterion extends UnaryCriterion { EqCriterion(Field field, Object value) { diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Field.java b/astrid/common-src/com/todoroo/andlib/sql/Field.java similarity index 78% rename from astrid/common-src/com/todoroo/andlib/data/sql/Field.java rename to astrid/common-src/com/todoroo/andlib/sql/Field.java index 9111d9c25..5131ba000 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Field.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Field.java @@ -1,11 +1,11 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -import static com.todoroo.andlib.data.sql.Constants.AND; -import static com.todoroo.andlib.data.sql.Constants.BETWEEN; -import static com.todoroo.andlib.data.sql.Constants.COMMA; -import static com.todoroo.andlib.data.sql.Constants.LEFT_PARENTHESIS; -import static com.todoroo.andlib.data.sql.Constants.RIGHT_PARENTHESIS; -import static com.todoroo.andlib.data.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.AND; +import static com.todoroo.andlib.sql.Constants.BETWEEN; +import static com.todoroo.andlib.sql.Constants.COMMA; +import static com.todoroo.andlib.sql.Constants.LEFT_PARENTHESIS; +import static com.todoroo.andlib.sql.Constants.RIGHT_PARENTHESIS; +import static com.todoroo.andlib.sql.Constants.SPACE; public class Field extends DBObject { @@ -18,10 +18,14 @@ public class Field extends DBObject { } public Criterion eq(Object value) { + if(value == null) + return UnaryCriterion.isNull(this); return UnaryCriterion.eq(this, value); } public Criterion neq(Object value) { + if(value == null) + return UnaryCriterion.isNotNull(this); return UnaryCriterion.neq(this, value); } diff --git a/astrid/common-src/com/todoroo/andlib/sql/Functions.java b/astrid/common-src/com/todoroo/andlib/sql/Functions.java new file mode 100644 index 000000000..ac994dad8 --- /dev/null +++ b/astrid/common-src/com/todoroo/andlib/sql/Functions.java @@ -0,0 +1,15 @@ +package com.todoroo.andlib.sql; + +@SuppressWarnings("nls") +public final class Functions { + + public static String caseStatement(Criterion when, Object ifTrue, Object ifFalse) { + return new StringBuilder("CASE WHEN "). + append(when.toString()).append(" THEN ").append(value(ifTrue)). + append(" ELSE ").append(value(ifFalse)).append(" END").toString(); + } + + private static String value(Object value) { + return value.toString(); + } +} diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/GroupBy.java b/astrid/common-src/com/todoroo/andlib/sql/GroupBy.java similarity index 85% rename from astrid/common-src/com/todoroo/andlib/data/sql/GroupBy.java rename to astrid/common-src/com/todoroo/andlib/sql/GroupBy.java index 103128411..c0d1c4290 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/GroupBy.java +++ b/astrid/common-src/com/todoroo/andlib/sql/GroupBy.java @@ -1,4 +1,4 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; import java.util.List; import java.util.ArrayList; diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Join.java b/astrid/common-src/com/todoroo/andlib/sql/Join.java similarity index 56% rename from astrid/common-src/com/todoroo/andlib/data/sql/Join.java rename to astrid/common-src/com/todoroo/andlib/sql/Join.java index f3809dba9..382c1253f 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Join.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Join.java @@ -1,33 +1,33 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -import static com.todoroo.andlib.data.sql.Constants.JOIN; -import static com.todoroo.andlib.data.sql.Constants.ON; -import static com.todoroo.andlib.data.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.JOIN; +import static com.todoroo.andlib.sql.Constants.ON; +import static com.todoroo.andlib.sql.Constants.SPACE; public class Join { - private final Table joinTable; + private final SqlTable joinTable; private final JoinType joinType; private final Criterion[] criterions; - private Join(Table table, JoinType joinType, Criterion... criterions) { + private Join(SqlTable table, JoinType joinType, Criterion... criterions) { joinTable = table; this.joinType = joinType; this.criterions = criterions; } - public static Join inner(Table expression, Criterion... criterions) { + public static Join inner(SqlTable expression, Criterion... criterions) { return new Join(expression, JoinType.INNER, criterions); } - public static Join left(Table table, Criterion... criterions) { + public static Join left(SqlTable table, Criterion... criterions) { return new Join(table, JoinType.LEFT, criterions); } - public static Join right(Table table, Criterion... criterions) { + public static Join right(SqlTable table, Criterion... criterions) { return new Join(table, JoinType.RIGHT, criterions); } - public static Join out(Table table, Criterion... criterions) { + public static Join out(SqlTable table, Criterion... criterions) { return new Join(table, JoinType.OUT, criterions); } diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/JoinType.java b/astrid/common-src/com/todoroo/andlib/sql/JoinType.java similarity index 56% rename from astrid/common-src/com/todoroo/andlib/data/sql/JoinType.java rename to astrid/common-src/com/todoroo/andlib/sql/JoinType.java index 86cd8f2d3..c1cb7389b 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/JoinType.java +++ b/astrid/common-src/com/todoroo/andlib/sql/JoinType.java @@ -1,4 +1,4 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; public enum JoinType { INNER, LEFT, RIGHT, OUT diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Operator.java b/astrid/common-src/com/todoroo/andlib/sql/Operator.java similarity index 92% rename from astrid/common-src/com/todoroo/andlib/data/sql/Operator.java rename to astrid/common-src/com/todoroo/andlib/sql/Operator.java index 55981e71a..5f97301c0 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Operator.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Operator.java @@ -1,6 +1,6 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -import static com.todoroo.andlib.data.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.SPACE; import java.util.HashMap; import java.util.Map; diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Order.java b/astrid/common-src/com/todoroo/andlib/sql/Order.java similarity index 52% rename from astrid/common-src/com/todoroo/andlib/data/sql/Order.java rename to astrid/common-src/com/todoroo/andlib/sql/Order.java index 5bff6bf49..fc1049045 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Order.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Order.java @@ -1,25 +1,25 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -import static com.todoroo.andlib.data.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.SPACE; public class Order { - private final Field expression; + private final Object expression; private final OrderType orderType; - private Order(Field expression) { + private Order(Object expression) { this(expression, OrderType.ASC); } - private Order(Field expression, OrderType orderType) { + private Order(Object expression, OrderType orderType) { this.expression = expression; this.orderType = orderType; } - public static Order asc(Field expression) { + public static Order asc(Object expression) { return new Order(expression); } - public static Order desc(Field expression) { + public static Order desc(Object expression) { return new Order(expression, OrderType.DESC); } diff --git a/astrid/common-src/com/todoroo/andlib/sql/OrderType.java b/astrid/common-src/com/todoroo/andlib/sql/OrderType.java new file mode 100644 index 000000000..299c670c0 --- /dev/null +++ b/astrid/common-src/com/todoroo/andlib/sql/OrderType.java @@ -0,0 +1,5 @@ +package com.todoroo.andlib.sql; + +public enum OrderType { + DESC, ASC +} diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Query.java b/astrid/common-src/com/todoroo/andlib/sql/Query.java similarity index 63% rename from astrid/common-src/com/todoroo/andlib/data/sql/Query.java rename to astrid/common-src/com/todoroo/andlib/sql/Query.java index 7a3d4f7e6..9d4398911 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Query.java +++ b/astrid/common-src/com/todoroo/andlib/sql/Query.java @@ -1,32 +1,32 @@ -package com.todoroo.andlib.data.sql; - -import static com.todoroo.andlib.data.sql.Constants.ALL; -import static com.todoroo.andlib.data.sql.Constants.COMMA; -import static com.todoroo.andlib.data.sql.Constants.FROM; -import static com.todoroo.andlib.data.sql.Constants.GROUP_BY; -import static com.todoroo.andlib.data.sql.Constants.LEFT_PARENTHESIS; -import static com.todoroo.andlib.data.sql.Constants.ORDER_BY; -import static com.todoroo.andlib.data.sql.Constants.RIGHT_PARENTHESIS; -import static com.todoroo.andlib.data.sql.Constants.SELECT; -import static com.todoroo.andlib.data.sql.Constants.SPACE; -import static com.todoroo.andlib.data.sql.Constants.WHERE; -import static com.todoroo.andlib.data.sql.Table.table; +package com.todoroo.andlib.sql; + +import static com.todoroo.andlib.sql.Constants.ALL; +import static com.todoroo.andlib.sql.Constants.COMMA; +import static com.todoroo.andlib.sql.Constants.FROM; +import static com.todoroo.andlib.sql.Constants.GROUP_BY; +import static com.todoroo.andlib.sql.Constants.LEFT_PARENTHESIS; +import static com.todoroo.andlib.sql.Constants.ORDER_BY; +import static com.todoroo.andlib.sql.Constants.RIGHT_PARENTHESIS; +import static com.todoroo.andlib.sql.Constants.SELECT; +import static com.todoroo.andlib.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.WHERE; +import static com.todoroo.andlib.sql.SqlTable.table; import static java.util.Arrays.asList; import java.util.ArrayList; -import java.util.List; import com.todoroo.andlib.data.Property; -public class Query { +public final class Query { - private Table table; - private List criterions = new ArrayList(); - private List fields = new ArrayList(); - private List joins = new ArrayList(); - private List groupBies = new ArrayList(); - private List orders = new ArrayList(); - private List havings = new ArrayList(); + private SqlTable table; + private String queryTemplate = null; + private final ArrayList criterions = new ArrayList(); + private final ArrayList fields = new ArrayList(); + private final ArrayList joins = new ArrayList(); + private final ArrayList groupBies = new ArrayList(); + private final ArrayList orders = new ArrayList(); + private final ArrayList havings = new ArrayList(); private Query(Field... fields) { this.fields.addAll(asList(fields)); @@ -36,7 +36,7 @@ public class Query { return new Query(fields); } - public Query from(Table fromTable) { + public Query from(SqlTable fromTable) { this.table = fromTable; return this; } @@ -81,10 +81,19 @@ public class Query { StringBuilder sql = new StringBuilder(); visitSelectClause(sql); visitFromClause(sql); - visitJoinClause(sql); - visitWhereClause(sql); - visitGroupByClause(sql); - visitOrderByClause(sql); + + if(queryTemplate == null) { + visitJoinClause(sql); + visitWhereClause(sql); + visitGroupByClause(sql); + visitOrderByClause(sql); + } else { + if(joins.size() > 0 || 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); + } + return sql.toString(); } @@ -154,7 +163,7 @@ public class Query { sql.deleteCharAt(sql.length() - 1).append(SPACE); } - public Table as(String alias) { + public SqlTable as(String alias) { return table(LEFT_PARENTHESIS + this.toString() + RIGHT_PARENTHESIS).as(alias); } @@ -170,4 +179,14 @@ public class Query { public Property[] getFields() { return fields.toArray(new Property[fields.size()]); } + + /** + * Add the SQL query template (comes after the "from") + * @param sqlQuery + * @return + */ + public Query withQueryTemplate(String template) { + queryTemplate = template; + return this; + } } diff --git a/astrid/common-src/com/todoroo/andlib/sql/QueryTemplate.java b/astrid/common-src/com/todoroo/andlib/sql/QueryTemplate.java new file mode 100644 index 000000000..6251101a4 --- /dev/null +++ b/astrid/common-src/com/todoroo/andlib/sql/QueryTemplate.java @@ -0,0 +1,117 @@ +package com.todoroo.andlib.sql; + +import static com.todoroo.andlib.sql.Constants.COMMA; +import static com.todoroo.andlib.sql.Constants.GROUP_BY; +import static com.todoroo.andlib.sql.Constants.LIMIT; +import static com.todoroo.andlib.sql.Constants.ORDER_BY; +import static com.todoroo.andlib.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.WHERE; +import static java.util.Arrays.asList; + +import java.util.ArrayList; + +/** + * Query Template returns a bunch of criteria that allows a query to be + * constructed + * + * @author Tim Su + * + */ +public final class QueryTemplate { + + private final ArrayList criterions = new ArrayList(); + private final ArrayList joins = new ArrayList(); + private final ArrayList groupBies = new ArrayList(); + private final ArrayList orders = new ArrayList(); + private final ArrayList havings = new ArrayList(); + private Integer limit = null; + + public QueryTemplate join(Join... join) { + joins.addAll(asList(join)); + return this; + } + + public QueryTemplate where(Criterion criterion) { + criterions.add(criterion); + return this; + } + + public QueryTemplate groupBy(Field... groupBy) { + groupBies.addAll(asList(groupBy)); + return this; + } + + public QueryTemplate orderBy(Order... order) { + orders.addAll(asList(order)); + return this; + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder(); + visitJoinClause(sql); + visitWhereClause(sql); + visitGroupByClause(sql); + visitOrderByClause(sql); + if(limit != null) + sql.append(LIMIT).append(SPACE).append(limit); + return sql.toString(); + } + + private void visitOrderByClause(StringBuilder sql) { + if (orders.isEmpty()) { + return; + } + sql.append(ORDER_BY); + for (Order order : orders) { + sql.append(SPACE).append(order).append(COMMA); + } + sql.deleteCharAt(sql.length() - 1).append(SPACE); + } + + @SuppressWarnings("nls") + private void visitGroupByClause(StringBuilder sql) { + if (groupBies.isEmpty()) { + return; + } + sql.append(GROUP_BY); + for (Field groupBy : groupBies) { + sql.append(SPACE).append(groupBy).append(COMMA); + } + sql.deleteCharAt(sql.length() - 1).append(SPACE); + if (havings.isEmpty()) { + return; + } + sql.append("HAVING"); + for (Criterion havingCriterion : havings) { + sql.append(SPACE).append(havingCriterion).append(COMMA); + } + sql.deleteCharAt(sql.length() - 1).append(SPACE); + } + + private void visitWhereClause(StringBuilder sql) { + if (criterions.isEmpty()) { + return; + } + sql.append(WHERE); + for (Criterion criterion : criterions) { + sql.append(SPACE).append(criterion).append(SPACE); + } + } + + private void visitJoinClause(StringBuilder sql) { + for (Join join : joins) { + sql.append(join).append(SPACE); + } + } + + public QueryTemplate having(Criterion criterion) { + this.havings.add(criterion); + return this; + } + + public QueryTemplate limit(int limitValue) { + this.limit = limitValue; + return this; + } +} diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/Table.java b/astrid/common-src/com/todoroo/andlib/sql/SqlTable.java similarity index 52% rename from astrid/common-src/com/todoroo/andlib/data/sql/Table.java rename to astrid/common-src/com/todoroo/andlib/sql/SqlTable.java index ee04ae0df..07bc2e998 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/Table.java +++ b/astrid/common-src/com/todoroo/andlib/sql/SqlTable.java @@ -1,13 +1,13 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -public class Table extends DBObject { +public class SqlTable extends DBObject { - protected Table(String expression) { + protected SqlTable(String expression) { super(expression); } - public static Table table(String table) { - return new Table(table); + public static SqlTable table(String table) { + return new SqlTable(table); } @SuppressWarnings("nls") diff --git a/astrid/common-src/com/todoroo/andlib/data/sql/UnaryCriterion.java b/astrid/common-src/com/todoroo/andlib/sql/UnaryCriterion.java similarity index 93% rename from astrid/common-src/com/todoroo/andlib/data/sql/UnaryCriterion.java rename to astrid/common-src/com/todoroo/andlib/sql/UnaryCriterion.java index 387c8f36d..e76f90d03 100644 --- a/astrid/common-src/com/todoroo/andlib/data/sql/UnaryCriterion.java +++ b/astrid/common-src/com/todoroo/andlib/sql/UnaryCriterion.java @@ -1,6 +1,6 @@ -package com.todoroo.andlib.data.sql; +package com.todoroo.andlib.sql; -import static com.todoroo.andlib.data.sql.Constants.SPACE; +import static com.todoroo.andlib.sql.Constants.SPACE; public class UnaryCriterion extends Criterion { protected final Field expression; diff --git a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java index c19380b5c..974977776 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/AndroidUtilities.java @@ -7,14 +7,11 @@ import java.net.URL; import java.net.URLConnection; import android.app.Activity; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; @@ -110,4 +107,37 @@ 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()); + } + + /** + * Return index of value in array + * @param array array to search + * @param value value to look for + * @return + */ + public static int indexOf(TYPE[] array, TYPE value) { + for(int i = 0; i < array.length; i++) + if(array[i].equals(value)) + return i; + return -1; + } + } diff --git a/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java index 39685bf13..bb370bca1 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/DateUtilities.java @@ -5,11 +5,15 @@ */ package com.todoroo.andlib.utility; +import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; +import android.content.Context; import android.content.res.Resources; import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; @@ -21,6 +25,9 @@ public class DateUtilities { @Autowired public Integer monthsResource; + @Autowired + public Integer weeksResource; + @Autowired public Integer daysResource; @@ -50,81 +57,209 @@ public class DateUtilities { } /* ====================================================================== - * ============================================================ unix time + * ============================================================ long time * ====================================================================== */ /** Convert unixtime into date */ - public static final Date unixtimeToDate(int seconds) { - if(seconds == 0) + public static final Date unixtimeToDate(long millis) { + if(millis == 0) return null; - return new Date(seconds * 1000L); + return new Date(millis); } /** Convert date into unixtime */ - public static final int dateToUnixtime(Date date) { + public static final long dateToUnixtime(Date date) { if(date == null) return 0; - return (int)(date.getTime() / 1000); + return date.getTime(); } /** Returns unixtime for current time */ - public static final int now() { - return (int) (System.currentTimeMillis() / 1000L); + public static final long now() { + return System.currentTimeMillis(); } + /** Represents a single hour */ + public static long ONE_HOUR = 3600000L; + + /** Represents a single day */ + public static long ONE_DAY = 24 * ONE_HOUR; + + /** Represents a single week */ + public static long ONE_WEEK = 7 * ONE_DAY; + /* ====================================================================== * =========================================================== formatters * ====================================================================== */ + @SuppressWarnings("nls") + public static boolean is24HourFormat(Context context) { + String value = android.provider.Settings.System.getString(context.getContentResolver(), + android.provider.Settings.System.TIME_12_24); + boolean b24 = !(value == null || value.equals("12")); + return b24; + } + + /** + * @return time format (hours and minutes) + */ + public static SimpleDateFormat getTimeFormat(Context context) { + String value = getTimeFormatString(context); + return new SimpleDateFormat(value); + } + + /** + * @return string used for time formatting + */ + @SuppressWarnings("nls") + private static String getTimeFormatString(Context context) { + String value; + if (is24HourFormat(context)) { + value = "H:mm"; + } else { + value = "h:mm a"; + } + return value; + } + + /** + * @return string used for date formatting + */ + @SuppressWarnings("nls") + private static String getDateFormatString(Context context) { + String value = android.provider.Settings.System.getString(context.getContentResolver(), + android.provider.Settings.System.DATE_FORMAT); + if (value == null) { + // united states, you are special + if (Locale.US.equals(Locale.getDefault()) + || Locale.CANADA.equals(Locale.getDefault())) + value = "MMM d yyyy"; + else + value = "d MMM yyyy"; + } + return value; + } + + /** + * @return date format (month, day, year) + */ + public static SimpleDateFormat getDateFormat(Context context) { + return new SimpleDateFormat(getDateFormatString(context)); + } + + /** + * @return date format as getDateFormat with weekday + */ + @SuppressWarnings("nls") + public static SimpleDateFormat getDateFormatWithWeekday(Context context) { + return new SimpleDateFormat("EEE, " + getDateFormatString(context)); + + } + + /** + * @return date with time at the end + */ + @SuppressWarnings("nls") + public static SimpleDateFormat getDateWithTimeFormat(Context context) { + return new SimpleDateFormat(getDateFormatString(context) + " " + + getTimeFormatString(context)); + + } + + /** + * @return formatted date (will contain month, day, year) + */ + public static String getFormattedDate(Context context, Date date) { + return getDateFormat(context).format(date); + } + + + /* ====================================================================== + * ============================================================= duration + * ====================================================================== */ + /** * Convenience method for dropping the preposition argument. + * @param duration in millis + * @param unitsToShow number of units to show (i.e. if 2, then 5 hours + * 3 minutes 2 seconds is truncated to 5 hours 3 minutes) */ - 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); } /** * Format a time into the format: 5 days, 3 hours, 2 minutes * - * @param r Resources to get strings from - * @param timeInSeconds + * @param duration in millis * @param unitsToShow number of units to show (i.e. if 2, then 5 hours * 3 minutes 2 seconds is truncated to 5 hours 3 minutes) * @param withPreposition whether there is a preceding preposition * @return */ - public String getDurationString(Resources r, int timeInSeconds, - int unitsToShow, boolean withPreposition) { - int years, months, days, hours, minutes, seconds; + public String getDurationString(long duration, int unitsToShow, boolean withPreposition) { + Resources r = ContextManager.getContext().getResources(); 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(80, 1, 1); + Date then = new Date(now.getTime() + duration); + + int[] values = new int[] { + then.getYear() - now.getYear(), + then.getMonth() - now.getMonth(), + (then.getDate() - now.getDate())/7, + (then.getDate() - now.getDate()) - (then.getDate() - now.getDate())/7*7, + then.getHours() - now.getHours(), + then.getMinutes() - now.getMinutes(), + then.getSeconds() - now.getSeconds(), + }; + int[] maxValues = new int[] { + Integer.MAX_VALUE, + 12, + 5, + 7, + 24, + 60, + 60 + }; + + // perform rounding (this is definitely magic... trust the unit tests) + int cursor = 0; + while(values[cursor] == 0 && ++cursor < values.length) + ; + int postCursor = cursor + unitsToShow; + for(int i = values.length - 1; i >= postCursor; i--) { + if(values[i] >= maxValues[i]/2) { + values[i-1]++; + } + } + for(int i = Math.min(values.length, postCursor) - 1; i >= 1; i--) { + if(values[i] == maxValues[i]) { + values[i-1]++; + for(int j = i; j < values.length; j++) + values[j] = 0; + } + } - years = then.getYear() - now.getYear(); - months = then.getMonth() - now.getMonth(); - days = then.getDate() - now.getDate(); - hours = then.getHours() - now.getHours(); - minutes = then.getMinutes() - now.getMinutes(); - seconds = then.getSeconds() - now.getSeconds(); StringBuilder result = new StringBuilder(); - unitsDisplayed = displayUnits(r, yearsResource, unitsToShow, years, months >= 6, + unitsDisplayed = displayUnits(r, yearsResource, unitsToShow, values[0], + unitsDisplayed, result); + unitsDisplayed = displayUnits(r, monthsResource, unitsToShow, values[1], unitsDisplayed, result); - unitsDisplayed = displayUnits(r, monthsResource, unitsToShow, months, days >= 15, + unitsDisplayed = displayUnits(r, weeksResource, unitsToShow, values[2], unitsDisplayed, result); - unitsDisplayed = displayUnits(r, daysResource, unitsToShow, days, hours >= 12, + unitsDisplayed = displayUnits(r, daysResource, unitsToShow, values[3], unitsDisplayed, result); - unitsDisplayed = displayUnits(r, hoursResource, unitsToShow, hours, minutes >= 30, + unitsDisplayed = displayUnits(r, hoursResource, unitsToShow, values[4], unitsDisplayed, result); - unitsDisplayed = displayUnits(r, minutesResource, unitsToShow, minutes, seconds >= 30, + unitsDisplayed = displayUnits(r, minutesResource, unitsToShow, values[5], unitsDisplayed, result); - unitsDisplayed = displayUnits(r, secondsResource, unitsToShow, seconds, false, + unitsDisplayed = displayUnits(r, secondsResource, unitsToShow, values[6], unitsDisplayed, result); return result.toString().trim(); @@ -132,11 +267,8 @@ public class DateUtilities { /** Display units, rounding up if necessary. Returns units to show */ private short displayUnits(Resources r, int resource, int unitsToShow, int value, - boolean shouldRound, short unitsDisplayed, StringBuilder result) { + short unitsDisplayed, StringBuilder result) { if(unitsDisplayed < unitsToShow && value > 0) { - // round up if needed - if(unitsDisplayed + 1 == unitsToShow && shouldRound) - value++; result.append(r.getQuantityString(resource, value, value)). append(' '); unitsDisplayed++; diff --git a/astrid/common-src/com/todoroo/andlib/utility/DialogUtilities.java b/astrid/common-src/com/todoroo/andlib/utility/DialogUtilities.java index 32d5a94a5..8d6f3e4f6 100644 --- a/astrid/common-src/com/todoroo/andlib/utility/DialogUtilities.java +++ b/astrid/common-src/com/todoroo/andlib/utility/DialogUtilities.java @@ -15,6 +15,9 @@ public class DialogUtilities { @Autowired public Integer informationDialogTitleResource; + @Autowired + public Integer confirmDialogTitleResource; + public DialogUtilities() { DependencyInjectionService.getInstance().inject(this); } @@ -32,7 +35,7 @@ public class DialogUtilities { activity.runOnUiThread(new Runnable() { public void run() { new AlertDialog.Builder(activity) - .setTitle(informationDialogTitleResource) + .setTitle(confirmDialogTitleResource) .setMessage(text) .setView(view) .setIcon(android.R.drawable.ic_dialog_alert) diff --git a/astrid/default.properties b/astrid/default.properties index c5d5335ee..a72fa071c 100644 --- a/astrid/default.properties +++ b/astrid/default.properties @@ -10,5 +10,5 @@ # Indicates whether an apk should be generated for each density. split.density=false # Project target. -target=android-8 +target=Google Inc.:Google APIs:8 apk-configurations= diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java b/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java index e9a39ab33..77f732acc 100644 --- a/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/Alarm.java @@ -38,8 +38,8 @@ public class Alarm extends AbstractModel { public static final LongProperty TASK = new LongProperty( TABLE, "task"); - /** Alarm Time (seconds since epoch) */ - public static final IntegerProperty TIME = new IntegerProperty( + /** Alarm Time */ + public static final LongProperty TIME = new LongProperty( TABLE, "time"); /** Alarm Type (see constants) */ diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmsDatabase.java b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDatabase.java similarity index 97% rename from astrid/plugin-src/com/todoroo/astrid/alarms/AlarmsDatabase.java rename to astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDatabase.java index 667dcdb0e..9600358f1 100644 --- a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmsDatabase.java +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmDatabase.java @@ -16,7 +16,7 @@ import com.todoroo.andlib.data.Table; * */ @SuppressWarnings("nls") -public class AlarmsDatabase extends AbstractDatabase { +public class AlarmDatabase extends AbstractDatabase { // --- constants diff --git a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java index 015c7ba48..c90c13cf2 100644 --- a/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java +++ b/astrid/plugin-src/com/todoroo/astrid/alarms/AlarmService.java @@ -1,8 +1,11 @@ package com.todoroo.astrid.alarms; -import android.content.Context; +import java.util.ArrayList; +import com.todoroo.andlib.data.GenericDao; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Query; /** * Provides operations for working with alerts @@ -13,13 +16,44 @@ import com.todoroo.andlib.service.DependencyInjectionService; @SuppressWarnings("nls") public class AlarmService { + AlarmDatabase database = new AlarmDatabase(); + + GenericDao dao = new GenericDao(Alarm.class, database); + /** * Metadata key for # of alarms */ public static final String ALARM_COUNT = "alarms-count"; - public AlarmService(@SuppressWarnings("unused") Context context) { + public AlarmService() { DependencyInjectionService.getInstance().inject(this); } + + /** + * Return alarms for the given task + * + * @param taskId + */ + public TodorooCursor getAlarms(long taskId) { + database.openForReading(); + Query query = Query.select(Alarm.PROPERTIES).where(Alarm.TASK.eq(taskId)); + return dao.query(query); + } + + /** + * Save the given array of tags into the database + * @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); + } + } } diff --git a/astrid/plugin-src/com/todoroo/astrid/filters/CoreFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/filters/CoreFilterExposer.java new file mode 100644 index 000000000..2b6e3e8e0 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/filters/CoreFilterExposer.java @@ -0,0 +1,80 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.filters; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +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.Functions; +import com.todoroo.andlib.sql.Order; +import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.activity.FilterListActivity; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.api.SearchFilter; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.model.Task; + +/** + * Exposes Astrid's built in filters to the {@link FilterListActivity} + * + * @author Tim Su + * + */ +public final class CoreFilterExposer extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + Resources r = context.getResources(); + + // build filters + Filter inbox = buildInboxFilter(r); + + Filter all = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_All), + r.getString(R.string.BFE_All), + new QueryTemplate().where(Criterion.not(TaskCriteria.isDeleted())). + orderBy(Order.desc(Task.MODIFICATION_DATE)), + null); + all.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_all)).getBitmap(); + + SearchFilter searchFilter = new SearchFilter(CorePlugin.IDENTIFIER, + "Search"); + searchFilter.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_search)).getBitmap(); + + // transmit filter list + FilterListItem[] list = new FilterListItem[3]; + list[0] = inbox; + list[1] = all; + list[2] = searchFilter; + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, list); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + + /** + * Build inbox filter + * @return + */ + @SuppressWarnings("nls") + public static Filter buildInboxFilter(Resources r) { + Filter inbox = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_Inbox), + r.getString(R.string.BFE_Inbox_title), + new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), + TaskCriteria.isVisible(DateUtilities.now()))).orderBy( + Order.asc(Functions.caseStatement(Task.DUE_DATE.eq(0), + String.format("(%d + 1000000 * %s)", DateUtilities.now(), Task.IMPORTANCE), + String.format("(%s + 1000000 * %s)", Task.DUE_DATE, Task.IMPORTANCE)))), + null); + inbox.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_inbox)).getBitmap(); + return inbox; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/filters/CorePlugin.java b/astrid/plugin-src/com/todoroo/astrid/filters/CorePlugin.java new file mode 100644 index 000000000..8981f42c6 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/filters/CorePlugin.java @@ -0,0 +1,24 @@ +package com.todoroo.astrid.filters; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Plugin; + +public class CorePlugin extends BroadcastReceiver { + + static final String IDENTIFIER = "core"; + + @Override + public void onReceive(Context context, Intent intent) { + Plugin plugin = new Plugin(IDENTIFIER, "Core Filters", "Todoroo", + "Provides 'Inbox' and 'All Tasks' Filters"); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/filters/ExtendedFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/filters/ExtendedFilterExposer.java new file mode 100644 index 000000000..d4f2ba567 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/filters/ExtendedFilterExposer.java @@ -0,0 +1,89 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.filters; + +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +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.Order; +import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.activity.FilterListActivity; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterListHeader; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.model.Task; + +/** + * Exposes Astrid's built in filters to the {@link FilterListActivity} + * + * @author Tim Su + * + */ +public final class ExtendedFilterExposer extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + Resources r = context.getResources(); + + // build filters + FilterListHeader header = new FilterListHeader(ExtendedPlugin.IDENTIFIER, + "Extended"); + + Filter alphabetical = new Filter(ExtendedPlugin.IDENTIFIER, + "Alphabetical", + "Alphabetical", + new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), + TaskCriteria.isVisible(DateUtilities.now()))). + orderBy(Order.asc(Task.TITLE)), + null); + alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_alpha)).getBitmap(); + + Filter recent = new Filter(ExtendedPlugin.IDENTIFIER, + "Recently Modified", + "Recently Modified", + new QueryTemplate().orderBy(Order.desc(Task.MODIFICATION_DATE)).limit(15), + null); + recent.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_recent)).getBitmap(); + + ContentValues hiddenValues = new ContentValues(); + hiddenValues.put(Task.HIDE_UNTIL.name, DateUtilities.now() + DateUtilities.ONE_DAY); + Filter hidden = new Filter(ExtendedPlugin.IDENTIFIER, + "Hidden Tasks", + "Hidden Tasks", + new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), + Criterion.not(TaskCriteria.isVisible(DateUtilities.now())))). + orderBy(Order.asc(Task.HIDE_UNTIL)), + hiddenValues); + hidden.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_hidden)).getBitmap(); + + Filter deleted = new Filter(ExtendedPlugin.IDENTIFIER, + "Deleted Tasks", + "Deleted Tasks", + new QueryTemplate().where(TaskCriteria.isDeleted()). + orderBy(Order.desc(Task.DELETION_DATE)), + null); + deleted.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_deleted)).getBitmap(); + + // transmit filter list + FilterListItem[] list = new FilterListItem[5]; + list[0] = header; + list[1] = alphabetical; + list[2] = recent; + list[3] = hidden; + list[4] = deleted; + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, list); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/filters/ExtendedPlugin.java b/astrid/plugin-src/com/todoroo/astrid/filters/ExtendedPlugin.java new file mode 100644 index 000000000..b942550ed --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/filters/ExtendedPlugin.java @@ -0,0 +1,24 @@ +package com.todoroo.astrid.filters; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Plugin; + +public class ExtendedPlugin extends BroadcastReceiver { + + static final String IDENTIFIER = "extended"; + + @Override + public void onReceive(Context context, Intent intent) { + Plugin plugin = new Plugin(IDENTIFIER, "Extended Filters", "Todoroo", + "Provides extended filters for viewing subsets of your tasks"); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/src-legacy/com/timsu/astrid/activities/TaskListNotify.java b/astrid/plugin-src/com/todoroo/astrid/reminders/NotificationActivity.java similarity index 54% rename from astrid/src-legacy/com/timsu/astrid/activities/TaskListNotify.java rename to astrid/plugin-src/com/todoroo/astrid/reminders/NotificationActivity.java index 75fbb2b6b..1320eb72d 100644 --- a/astrid/src-legacy/com/timsu/astrid/activities/TaskListNotify.java +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/NotificationActivity.java @@ -17,11 +17,18 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package com.timsu.astrid.activities; +package com.todoroo.astrid.reminders; import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.widget.Toast; + +import com.timsu.astrid.R; +import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.astrid.activity.TaskListActivity; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; /** * This activity is launched when a user opens up a notification from the @@ -30,7 +37,14 @@ import android.os.Bundle; * @author timsu * */ -public class TaskListNotify extends Activity { +public class NotificationActivity extends Activity { + + // --- constants + + /** task id from notification */ + public static final String TOKEN_ID = "id"; //$NON-NLS-1$ + + // --- implementation @Override public void onCreate(Bundle savedInstanceState) { @@ -47,10 +61,23 @@ public class TaskListNotify extends Activity { } private void launchTaskList(Intent intent) { - Intent taskListIntent = new Intent(this, TaskList.class); - taskListIntent.putExtra(TaskList.VARIABLES_TAG, intent.getExtras()); + long id = intent.getLongExtra(TOKEN_ID, -1); + if(id == -1) + return; + + Intent taskListIntent = new Intent(this, TaskListActivity.class); + Filter itemFilter = new Filter(ReminderPlugin.IDENTIFIER, + getString(R.string.rmd_NoA_filter), + getString(R.string.rmd_NoA_filter), + new QueryTemplate().where(TaskCriteria.byId(id)), + null); + + taskListIntent.putExtra(TaskListActivity.TOKEN_FILTER, itemFilter); startActivity(taskListIntent); + String reminder = Notifications.getRandomReminder(getResources().getStringArray(R.array.responses)); + Toast.makeText(this, reminder, Toast.LENGTH_LONG).show(); + finish(); } } diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java new file mode 100644 index 000000000..329e55b75 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/Notifications.java @@ -0,0 +1,249 @@ +package com.todoroo.astrid.reminders; + +import java.util.Date; + +import android.app.Activity; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Color; +import android.media.AudioManager; +import android.net.Uri; +import android.util.Log; + +import com.timsu.astrid.R; +import com.timsu.astrid.data.task.TaskIdentifier; +import com.timsu.astrid.utilities.Constants; +import com.timsu.astrid.utilities.Preferences; +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.andlib.service.NotificationManager; +import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.service.AstridDependencyInjector; + +public class Notifications extends BroadcastReceiver { + + // --- constants + + /** task id extra */ + static final String ID_KEY = "id"; //$NON-NLS-1$ + + /** notification type extra */ + static final String TYPE_KEY = "type"; //$NON-NLS-1$ + + // --- instance variables + + @Autowired + private TaskDao taskDao; + + @Autowired + private ExceptionService exceptionService; + + public static NotificationManager notificationManager = null; + + // --- alarm handling + + static { + AstridDependencyInjector.initialize(); + } + + public Notifications() { + DependencyInjectionService.getInstance().inject(this); + } + + @Override + /** Alarm intent */ + public void onReceive(Context context, Intent intent) { + ContextManager.setContext(context); + + long id = intent.getLongExtra(ID_KEY, 0); + int type = intent.getIntExtra(TYPE_KEY, (byte) 0); + + Resources r = context.getResources(); + String reminder; + if(type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE) + reminder = getRandomReminder(r.getStringArray(R.array.reminders_due)); + else if(type == ReminderService.TYPE_SNOOZE) + reminder = getRandomReminder(r.getStringArray(R.array.reminders_snooze)); + else + reminder = getRandomReminder(r.getStringArray(R.array.reminders)); + + if(!showNotification(id, type, reminder)) { + notificationManager.cancel((int)id); + } + } + + // --- notification creation + + /** Clear notifications associated with this application */ + public static void clearAllNotifications(Context context, TaskIdentifier taskId) { + NotificationManager nm = (NotificationManager) + context.getSystemService(Activity.NOTIFICATION_SERVICE); + nm.cancel((int)taskId.getId()); + } + + /** @return a random reminder string */ + static String getRandomReminder(String[] reminders) { + int next = ReminderService.random.nextInt(reminders.length); + String reminder = reminders[next]; + return reminder; + } + + /** + * Schedule a new notification about the given task. Returns false if there was + * some sort of error or the alarm should be disabled. + */ + public boolean showNotification(long id, int type, String reminder) { + Context context = ContextManager.getContext(); + if(notificationManager == null) + notificationManager = new AndroidNotificationManager(context); + + Task task; + try { + task = taskDao.fetch(id, Task.TITLE, Task.HIDE_UNTIL, Task.COMPLETION_DATE, + Task.DELETION_DATE, Task.REMINDER_FLAGS); + if(task == null) + throw new IllegalArgumentException("cound not find item with id"); //$NON-NLS-1$ + + } catch (Exception e) { + exceptionService.reportError("show-notif", e); //$NON-NLS-1$ + return false; + } + + // you're done - don't sound, do delete + if(task.isCompleted() || task.isDeleted()) + return false; + + // it's hidden - don't sound, don't delete + if(task.isHidden() && type == ReminderService.TYPE_RANDOM) + return true; + + // read properties + String taskTitle = task.getValue(Task.TITLE); + boolean nonstopMode = task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_NONSTOP); + + // update last reminder time + task.setValue(Task.REMINDER_LAST, DateUtilities.now()); + taskDao.save(task, false); + + // quiet hours? unless alarm clock + boolean quietHours = false; + Integer quietHoursStart = Preferences.getQuietHourStart(context); + Integer quietHoursEnd = Preferences.getQuietHourEnd(context); + if(quietHoursStart != null && quietHoursEnd != null && !nonstopMode) { + int hour = new Date().getHours(); + if(quietHoursStart < quietHoursEnd) { + if(hour >= quietHoursStart && hour < quietHoursEnd) + quietHours = true; + } else { // wrap across 24/hour boundary + if(hour >= quietHoursStart || hour < quietHoursEnd) + quietHours = true; + } + } + + Resources r = context.getResources(); + + Intent notifyIntent = new Intent(context, NotificationActivity.class); + notifyIntent.putExtra(NotificationActivity.TOKEN_ID, id); + notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent pendingIntent = PendingIntent.getActivity(context, + (int)id, notifyIntent, PendingIntent.FLAG_ONE_SHOT); + + // set up properties (name and icon) for the notification + String appName = r.getString(R.string.app_name); + int icon; + switch(Preferences.getNotificationIconTheme(context)) { + case Preferences.ICON_SET_PINK: + icon = R.drawable.notif_pink_alarm; + break; + case Preferences.ICON_SET_BORING: + icon = R.drawable.notif_boring_alarm; + break; + default: + icon = R.drawable.notif_astrid; + } + + // create notification object + Notification notification = new Notification( + icon, reminder, System.currentTimeMillis()); + notification.setLatestEventInfo(context, + appName, + reminder + " " + taskTitle, //$NON-NLS-1$ + pendingIntent); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + if(Preferences.isPersistenceMode(context)) { + notification.flags |= Notification.FLAG_NO_CLEAR | + Notification.FLAG_SHOW_LIGHTS; + notification.ledOffMS = 5000; + notification.ledOnMS = 700; + notification.ledARGB = Color.YELLOW; + } + else + notification.defaults = Notification.DEFAULT_LIGHTS; + + AudioManager audioManager = (AudioManager)context.getSystemService( + Context.AUDIO_SERVICE); + + // if non-stop mode is activated, set up the flags for insistent + // notification, and increase the volume to full volume, so the user + // will actually pay attention to the alarm + if(nonstopMode && (type != ReminderService.TYPE_RANDOM)) { + notification.flags |= Notification.FLAG_INSISTENT; + notification.audioStreamType = AudioManager.STREAM_ALARM; + audioManager.setStreamVolume(AudioManager.STREAM_ALARM, + audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0); + } else { + notification.audioStreamType = AudioManager.STREAM_NOTIFICATION; + } + + // quiet hours = no sound + if(quietHours) { + notification.sound = null; + } else { + Uri notificationSound = Preferences.getNotificationRingtone(context); + if(audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) { + notification.sound = null; + } else if(notificationSound != null && + !notificationSound.toString().equals("")) { //$NON-NLS-1$ + notification.sound = notificationSound; + } else { + notification.defaults |= Notification.DEFAULT_SOUND; + } + } + + // quiet hours + periodic = no vibrate + if(quietHours && (type == ReminderService.TYPE_RANDOM)) { + notification.vibrate = null; + } else { + if (Preferences.shouldVibrate(context) + && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) { + notification.vibrate = new long[] {0, 1000, 500, 1000, 500, 1000}; + } else { + notification.vibrate = null; + } + } + + if(Constants.DEBUG) + Log.w("Astrid", "Logging notification: " + reminder); //$NON-NLS-1$ //$NON-NLS-2$ + + notificationManager.notify((int)id, notification); + + return true; + } + + // --- notification manager + + public static void setNotificationManager( + NotificationManager notificationManager) { + Notifications.notificationManager = notificationManager; + } + +} \ No newline at end of file diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPlugin.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPlugin.java new file mode 100644 index 000000000..d4c1edd9e --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPlugin.java @@ -0,0 +1,21 @@ +package com.todoroo.astrid.reminders; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class ReminderPlugin extends BroadcastReceiver { + + static final String IDENTIFIER = "reminders"; //$NON-NLS-1$ + + @Override + public void onReceive(Context context, Intent intent) { + /*Plugin plugin = new Plugin(IDENTIFIER, "Reminders", "Todoroo", + "Provides notification reminders for tasks"); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);*/ + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPreferences.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPreferences.java new file mode 100644 index 000000000..e16abe301 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderPreferences.java @@ -0,0 +1,119 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.reminders; + +import android.content.res.Resources; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.preference.Preference.OnPreferenceChangeListener; + +import com.timsu.astrid.R; + +/** + * Displays the preference screen for users to edit their preferences + * + * @author Tim Su + * + */ +public class ReminderPreferences extends PreferenceActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.preferences_reminders); + + PreferenceScreen screen = getPreferenceScreen(); + initializePreference(screen); + + } + + private void initializePreference(Preference preference) { + if(preference instanceof PreferenceGroup) { + PreferenceGroup group = (PreferenceGroup)preference; + for(int i = 0; i < group.getPreferenceCount(); i++) { + initializePreference(group.getPreference(i)); + } + } else { + Object value = null; + if(preference instanceof ListPreference) + value = ((ListPreference)preference).getValue(); + else if(preference instanceof CheckBoxPreference) + value = ((CheckBoxPreference)preference).isChecked(); + else if(preference instanceof EditTextPreference) + value = ((EditTextPreference)preference).getText(); + + if(value != null) + updatePreferences(preference, value); + + preference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference myPreference, Object newValue) { + updatePreferences(myPreference, newValue); + return true; + } + }); + } + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + protected int valueToIndex(String value, String[] array) { + for(int i = 0; i < array.length; i++) + if(array[i].equals(value)) + return i; + return -1; + } + + /** + * + * @param resource if null, updates all resources + */ + protected void updatePreferences(Preference preference, Object value) { + Resources r = getResources(); + + if(r.getString(R.string.p_rmd_quietStart).equals(preference.getKey())) { + int index = valueToIndex((String)value, r.getStringArray(R.array.EPr_quiet_hours_start_values)); + if(index == -1) + preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_desc_none)); + else { + String duration = r.getStringArray(R.array.EPr_quiet_hours_start)[index]; + preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_start_desc, duration)); + } + } else if(r.getString(R.string.p_rmd_quietEnd).equals(preference.getKey())) { + int index = valueToIndex((String)value, r.getStringArray(R.array.EPr_quiet_hours_end_values)); + if(index == -1) + preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_desc_none)); + else { + String duration = r.getStringArray(R.array.EPr_quiet_hours_end)[index]; + preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_end_desc, duration)); + } + } else if(r.getString(R.string.p_rmd_ringtone).equals(preference.getKey())) { + if(value == null) + preference.setSummary(r.getString(R.string.rmd_EPr_ringtone_desc_default)); + else + preference.setSummary(r.getString(R.string.rmd_EPr_ringtone_desc_custom)); + } else if(r.getString(R.string.p_rmd_persistent).equals(preference.getKey())) { + if((Boolean)value) + preference.setSummary(r.getString(R.string.rmd_EPr_persistent_desc_true)); + else + preference.setSummary(r.getString(R.string.rmd_EPr_persistent_desc_false)); + } + + } + +} \ No newline at end of file diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderService.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderService.java new file mode 100644 index 000000000..07b0566e7 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderService.java @@ -0,0 +1,301 @@ +package com.todoroo.astrid.reminders; + +import java.util.Date; +import java.util.Random; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.Resources; +import android.util.Log; + +import com.timsu.astrid.R; +import com.todoroo.andlib.data.Property; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.utility.Constants; +import com.todoroo.astrid.utility.Preferences; + + +/** + * Data service for reminders + * + * @author Tim Su + * + */ +public final class ReminderService { + + // --- constants + + private static final Property[] PROPERTIES = new Property[] { + Task.DUE_DATE, + Task.REMINDER_FLAGS, + Task.REMINDER_PERIOD, + Task.REMINDER_LAST + }; + + /** flag for due date reminder */ + static final int TYPE_DUE = 0; + /** flag for overdue reminder */ + static final int TYPE_OVERDUE = 1; + /** flag for random reminder */ + static final int TYPE_RANDOM = 2; + /** flag for a snoozed reminder */ + static final int TYPE_SNOOZE = 3; + + static final Random random = new Random(); + + // --- instance variables + + @Autowired + private TaskDao taskDao; + + private AlarmScheduler scheduler = new ReminderAlarmScheduler(); + + public ReminderService() { + DependencyInjectionService.getInstance().inject(this); + setPreferenceDefaults(); + } + + // --- preference handling + + private static boolean preferencesInitialized = false; + + /** Set preference defaults, if unset. called at startup */ + public void setPreferenceDefaults() { + if(preferencesInitialized) + return; + + Context context = ContextManager.getContext(); + SharedPreferences prefs = Preferences.getPrefs(context); + Editor editor = prefs.edit(); + Resources r = context.getResources(); + + Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_quietStart, 22); + Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_quietEnd, 10); + Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_default_random_hours, 0); + Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_time, 12); + Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_nagging, true); + Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_persistent, true); + + editor.commit(); + preferencesInitialized = true; + } + + // --- reminder scheduling logic + + /** + * Schedules all alarms + */ + public void scheduleAllAlarms() { + + TodorooCursor cursor = getTasksWithReminders(PROPERTIES); + try { + Task task = new Task(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + task.readFromCursor(cursor); + scheduleAlarm(task, false); + } + } finally { + cursor.close(); + } + } + + private static final long NO_ALARM = Long.MAX_VALUE; + + /** + * Schedules alarms for a single task + * @param task + */ + public void scheduleAlarm(Task task) { + scheduleAlarm(task, true); + } + + /** + * Schedules alarms for a single task + * + * @param shouldPerformPropertyCheck + * whether to check if task has requisite properties + */ + private void scheduleAlarm(Task task, boolean shouldPerformPropertyCheck) { + if(task == null || !task.isSaved()) + return; + + // read data if necessary + if(shouldPerformPropertyCheck) { + for(Property property : PROPERTIES) { + if(!task.containsValue(property)) { + task = taskDao.fetch(task.getId(), PROPERTIES); + if(task == null) + return; + break; + } + } + } + + // random reminders + long whenRandom = calculateNextRandomReminder(task); + + // notifications at due date + long whenDueDate = calculateNextDueDateReminder(task); + + // notifications after due date + long whenOverdue = calculateNextOverdueReminder(task); + + if(whenRandom < whenDueDate && whenRandom < whenOverdue) + scheduler.createAlarm(task, whenRandom, TYPE_RANDOM); + else if(whenDueDate < whenOverdue) + scheduler.createAlarm(task, whenDueDate, TYPE_DUE); + else if(whenOverdue != NO_ALARM) + scheduler.createAlarm(task, whenOverdue, TYPE_OVERDUE); + } + + /** + * Calculate the next alarm time for overdue reminders. If the due date + * has passed, we schedule a reminder some time in the next 4 - 24 hours. + * + * @param task + * @return + */ + private long calculateNextOverdueReminder(Task task) { + if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE)) { + long dueDate = task.getValue(Task.DUE_DATE); + if(dueDate > DateUtilities.now()) + return NO_ALARM; + return DateUtilities.now() + (long)((4 + 20 * random.nextFloat()) * DateUtilities.ONE_HOUR); + } + return NO_ALARM; + } + + /** + * Calculate the next alarm time for due date reminders. If the due date + * has not already passed, we return the due date, altering the time + * if the date was indicated to not have a due time + * + * @param task + * @return + */ + private long calculateNextDueDateReminder(Task task) { + if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE)) { + long dueDate = task.getValue(Task.DUE_DATE); + if(dueDate < DateUtilities.now()) + return NO_ALARM; + else if(task.hasDueTime()) + // return due date straight up + return dueDate; + else { + // return notification time on this day + Date date = new Date(dueDate); + date.setHours(Preferences.getIntegerFromString(R.string.p_rmd_time)); + date.setMinutes(0); + return date.getTime(); + } + } + return NO_ALARM; + } + + /** + * Calculate the next alarm time for random reminders. We take the last + * random reminder time and add approximately the reminder period, until + * we get a time that's in the future. + * + * @param task + * @return + */ + private long calculateNextRandomReminder(Task task) { + long reminderPeriod = task.getValue(Task.REMINDER_PERIOD); + if((reminderPeriod) > 0) { + long when = task.getValue(Task.REMINDER_LAST); + + // get or make up a last notification time + if(when == 0) { + when = DateUtilities.now(); + task.setValue(Task.REMINDER_LAST, when); + taskDao.save(task, false); + } + + when += (long)(reminderPeriod * (0.85f + 0.3f * random.nextFloat())); + return when; + } + return NO_ALARM; + } + + // --- alarm manager alarm creation + + /** + * Interface for testing + */ + public interface AlarmScheduler { + public void createAlarm(Task task, long time, int type); + } + + public void setScheduler(AlarmScheduler scheduler) { + this.scheduler = scheduler; + } + + public AlarmScheduler getScheduler() { + return scheduler; + } + + private class ReminderAlarmScheduler implements AlarmScheduler { + /** + * Create an alarm for the given task at the given type + * + * @param task + * @param time + * @param type + * @param flags + */ + @SuppressWarnings("nls") + public void createAlarm(Task task, long time, int type) { + if(time == 0 || time == NO_ALARM) + return; + + if(time < DateUtilities.now()) { + time = DateUtilities.now() + (long)((0.5f + + 4 * random.nextFloat()) * DateUtilities.ONE_HOUR); + } + + Context context = ContextManager.getContext(); + Intent intent = new Intent(context, Notifications.class); + intent.setType(Long.toString(task.getId())); + intent.setAction(Integer.toString(type)); + intent.putExtra(Notifications.ID_KEY, task.getId()); + intent.putExtra(Notifications.TYPE_KEY, type); + + AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, + intent, 0); + + if(Constants.DEBUG) + Log.e("Astrid", "Alarm (" + task.getId() + ", " + type + + ") set for " + new Date(time)); + am.set(AlarmManager.RTC_WAKEUP, time, pendingIntent); + } + } + + // --- data fetching classes + + /** + * Gets a listing of all tasks that are active & + * @param properties + * @return todoroo cursor. PLEASE CLOSE THIS CURSOR! + */ + private TodorooCursor getTasksWithReminders(Property... properties) { + return taskDao.query(Query.select(properties).where(Criterion.and(TaskCriteria.isActive(), + Task.REMINDER_FLAGS.gt(0)))); + } + + +} \ No newline at end of file diff --git a/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderStartupService.java b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderStartupService.java new file mode 100644 index 000000000..6cdf78fe8 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/reminders/ReminderStartupService.java @@ -0,0 +1,30 @@ +package com.todoroo.astrid.reminders; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.astrid.service.AstridDependencyInjector; + +/** + * Service which handles jobs that need to be run when phone boots + * + * @author Tim Su + * + */ +public class ReminderStartupService extends BroadcastReceiver { + + static { + AstridDependencyInjector.initialize(); + } + + // --- system startup + + @Override + /** Called when the system is started up */ + public void onReceive(Context context, Intent intent) { + ContextManager.setContext(context); + new ReminderService().scheduleAllAlarms(); + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java new file mode 100644 index 000000000..dcd08bcbd --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagDetailExposer.java @@ -0,0 +1,50 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.tags; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.timsu.astrid.R; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.TaskDetail; + +/** + * Exposes {@link TaskDetail} for tags, i.e. "Tags: frogs, animals" + * + * @author Tim Su + * + */ +public class TagDetailExposer extends BroadcastReceiver { + + private static TagService tagService = null; + + @Override + public void onReceive(Context context, Intent intent) { + // get tags associated with this task + long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1); + if(taskId == -1) + return; + + if(tagService == null) + tagService = new TagService(); + String tagList = tagService.getTagsAsString(taskId); + + if(tagList.length() == 0) + return; + + TaskDetail taskDetail = new TaskDetail(TagsPlugin.IDENTIFIER, + context.getString(R.string.tag_TLA_detail, tagList)); + + // transmit + TaskDetail[] details = new TaskDetail[1]; + details[0] = taskDetail; + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, details); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java new file mode 100644 index 000000000..ca0a96c24 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java @@ -0,0 +1,104 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.tags; + +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; + +import com.timsu.astrid.R; +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.tags.TagService.Tag; + +/** + * Exposes filters based on tags + * + * @author Tim Su + * + */ +public class TagFilterExposer extends BroadcastReceiver { + + private TagService tagService; + + @SuppressWarnings("nls") + private Filter filterFromTag(Context context, Tag tag) { + 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(); + ContentValues contentValues = new ContentValues(); + contentValues.put(TagService.KEY, tag.tag); + + Filter filter = new Filter(TagsPlugin.IDENTIFIER, + listTitle, title, + tagTemplate, + contentValues); + +// filters[0].contextMenuLabels = new String[] { +// "Rename Tag", +// "Delete Tag" +// }; +// filters[0].contextMenuIntents = new Intent[] { +// new Intent(), +// new Intent() +// }; + + return filter; + } + + @Override + public void onReceive(Context context, Intent intent) { + tagService = new TagService(); + Tag[] tagsByAlpha = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_ALPHA); + + // If user does not have any tags, don't show this section at all + if(tagsByAlpha.length == 0) + return; + + Resources r = context.getResources(); + + 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]); + + Filter[] filtersBySize = new Filter[tagsBySize.length]; + for(int i = 0; i < tagsBySize.length; i++) + filtersBySize[i] = filterFromTag(context, tagsBySize[i]); + + FilterListHeader tagsHeader = new FilterListHeader(TagsPlugin.IDENTIFIER, + context.getString(R.string.tag_FEx_header)); + Filter untagged = new Filter(TagsPlugin.IDENTIFIER, + "Untagged", + "Untagged", + tagService.untaggedTemplate(), + null); + untagged.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_untagged)).getBitmap(); + FilterCategory tagsCategoryBySize = new FilterCategory(TagsPlugin.IDENTIFIER, + context.getString(R.string.tag_FEx_by_size), filtersBySize); + tagsCategoryBySize.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap(); + FilterCategory tagsCategoryByAlpha = new FilterCategory(TagsPlugin.IDENTIFIER, + context.getString(R.string.tag_FEx_alpha), filtersByAlpha); + tagsCategoryByAlpha.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags2)).getBitmap(); + + // transmit filter list + FilterListItem[] list = new FilterListItem[4]; + list[0] = tagsHeader; + list[1] = untagged; + list[2] = tagsCategoryBySize; + list[3] = tagsCategoryByAlpha; + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, 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 5ec0969a3..b4d8149bd 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java @@ -2,19 +2,18 @@ package com.todoroo.astrid.tags; import java.util.ArrayList; -import android.content.Context; - -import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.Property.CountProperty; -import com.todoroo.andlib.data.sql.Criterion; -import com.todoroo.andlib.data.sql.Join; -import com.todoroo.andlib.data.sql.Order; -import com.todoroo.andlib.data.sql.Query; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Join; +import com.todoroo.andlib.sql.Order; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.astrid.dao.MetadataDao; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.service.MetadataService; @@ -39,7 +38,7 @@ public class TagService { @Autowired private MetadataService metadataService; - public TagService(@SuppressWarnings("unused") Context context) { + public TagService() { DependencyInjectionService.getInstance().inject(this); } @@ -57,7 +56,7 @@ public class TagService { * @author Tim Su * */ - public class Tag { + public final class Tag { public String tag; int count; @@ -65,6 +64,25 @@ public class TagService { public String toString() { return tag; } + + /** + * Return SQL selector query for getting tasks with a given tag + * + * @param tag + * @return + */ + public QueryTemplate queryTemplate() { + return new QueryTemplate().join(Join.inner(Metadata.TABLE, + Task.ID.eq(Metadata.TASK))).where(Criterion.and( + MetadataCriteria.withKey(KEY), Metadata.VALUE.eq(tag))); + } + } + + public QueryTemplate untaggedTemplate() { + return new QueryTemplate().join(Join.left(Metadata.TABLE, + Task.ID.eq(Metadata.TASK))).where(Criterion.and( + TaskCriteria.isActive(), MetadataCriteria.withKey(KEY), + Metadata.VALUE.isNull())); } /** @@ -94,7 +112,7 @@ public class TagService { * Return tags on the given task * * @param taskId - * @return empty array if no tags, otherwise array + * @return cursor. PLEASE CLOSE THE CURSOR! */ public TodorooCursor getTags(long taskId) { Query query = Query.select(Metadata.VALUE).where(Criterion.and(MetadataCriteria.withKey(KEY), @@ -111,30 +129,22 @@ public class TagService { public String getTagsAsString(long taskId) { StringBuilder tagBuilder = new StringBuilder(); TodorooCursor tags = getTags(taskId); - int length = tags.getCount(); - Metadata metadata = new Metadata(); - for (int i = 0; i < length; i++) { - tags.moveToNext(); - metadata.readFromCursor(tags); - tagBuilder.append(metadata.getValue(Metadata.VALUE)); - if (i < length - 1) - tagBuilder.append(", "); + try { + int length = tags.getCount(); + Metadata metadata = new Metadata(); + for (int i = 0; i < length; i++) { + tags.moveToNext(); + metadata.readFromCursor(tags); + tagBuilder.append(metadata.getValue(Metadata.VALUE)); + if (i < length - 1) + tagBuilder.append(", "); + } + } finally { + tags.close(); } return tagBuilder.toString(); } - /** - * Return SQL selector query for getting tasks with a given tag - * - * @param tag - * @return - */ - public Query tasksWithTag(String tag, Property... properties) { - return Query.select(properties).join(Join.inner(Metadata.TABLE, - Task.ID.eq(Metadata.TASK))).where(Criterion.and( - MetadataCriteria.withKey(KEY), Metadata.VALUE.eq(tag))); - } - /** * Save the given array of tags into the database * @param taskId @@ -149,7 +159,7 @@ public class TagService { metadata.setValue(Metadata.TASK, taskId); for(String tag : tags) { metadata.setValue(Metadata.VALUE, tag.trim()); - metadataDao.createItem(metadata); + metadataDao.createNew(metadata); } } } diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagsPlugin.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagsPlugin.java new file mode 100644 index 000000000..b29a045c1 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagsPlugin.java @@ -0,0 +1,24 @@ +package com.todoroo.astrid.tags; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Plugin; + +public class TagsPlugin extends BroadcastReceiver { + + static final String IDENTIFIER = "tags"; + + @Override + public void onReceive(Context context, Intent intent) { + Plugin plugin = new Plugin(IDENTIFIER, "Tags", "Todoroo", + "Provides tagging support for tasks."); + + Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + +} diff --git a/astrid/res/drawable/background_gradient.xml b/astrid/res/drawable/background_gradient.xml new file mode 100644 index 000000000..21ad1b2eb --- /dev/null +++ b/astrid/res/drawable/background_gradient.xml @@ -0,0 +1,11 @@ + + + + diff --git a/astrid/res/drawable/black_white_gradient.xml b/astrid/res/drawable/black_white_gradient.xml new file mode 100644 index 000000000..c468a2135 --- /dev/null +++ b/astrid/res/drawable/black_white_gradient.xml @@ -0,0 +1,9 @@ + + + + diff --git a/astrid/res/drawable/btn_add.png b/astrid/res/drawable/btn_add.png new file mode 100644 index 000000000..6332fefea Binary files /dev/null and b/astrid/res/drawable/btn_add.png differ diff --git a/astrid/res/drawable/btn_add_extended.png b/astrid/res/drawable/btn_add_extended.png new file mode 100644 index 000000000..937bded9d Binary files /dev/null and b/astrid/res/drawable/btn_add_extended.png differ 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_check50.xml b/astrid/res/drawable/btn_check50.xml deleted file mode 100644 index a3bce13c9..000000000 --- a/astrid/res/drawable/btn_check50.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - diff --git a/astrid/res/drawable/btn_check75.xml b/astrid/res/drawable/btn_check75.xml deleted file mode 100644 index f18054182..000000000 --- a/astrid/res/drawable/btn_check75.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - diff --git a/astrid/res/drawable/btn_check_0.png b/astrid/res/drawable/btn_check_0.png deleted file mode 100644 index 8bd6db1f1..000000000 Binary files a/astrid/res/drawable/btn_check_0.png and /dev/null differ diff --git a/astrid/res/drawable/btn_check_25.png b/astrid/res/drawable/btn_check_25.png deleted file mode 100644 index 4108bfa3e..000000000 Binary files a/astrid/res/drawable/btn_check_25.png and /dev/null differ diff --git a/astrid/res/drawable/btn_check_50.png b/astrid/res/drawable/btn_check_50.png deleted file mode 100644 index 60363ca74..000000000 Binary files a/astrid/res/drawable/btn_check_50.png and /dev/null differ diff --git a/astrid/res/drawable/btn_check_75.png b/astrid/res/drawable/btn_check_75.png deleted file mode 100644 index 55795fc2f..000000000 Binary files a/astrid/res/drawable/btn_check_75.png and /dev/null differ diff --git a/astrid/res/drawable/edit_header.png b/astrid/res/drawable/edit_header.png new file mode 100644 index 000000000..91962ba25 Binary files /dev/null and b/astrid/res/drawable/edit_header.png differ diff --git a/astrid/res/drawable/edit_header_red.png b/astrid/res/drawable/edit_header_red.png new file mode 100644 index 000000000..8a13f3e4a Binary files /dev/null and b/astrid/res/drawable/edit_header_red.png differ diff --git a/astrid/res/drawable/edit_titlebar.png b/astrid/res/drawable/edit_titlebar.png new file mode 100644 index 000000000..c3c971588 Binary files /dev/null and b/astrid/res/drawable/edit_titlebar.png differ diff --git a/astrid/res/layout/edit_importance_item.xml b/astrid/res/drawable/expander_group.xml similarity index 61% rename from astrid/res/layout/edit_importance_item.xml rename to astrid/res/drawable/expander_group.xml index 9d25a7832..ef665d3ff 100644 --- a/astrid/res/layout/edit_importance_item.xml +++ b/astrid/res/drawable/expander_group.xml @@ -4,23 +4,17 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - - - - \ No newline at end of file + + + + diff --git a/astrid/res/drawable/expander_ic_maximized.9.png b/astrid/res/drawable/expander_ic_maximized.9.png new file mode 100644 index 000000000..eb461e9d3 Binary files /dev/null and b/astrid/res/drawable/expander_ic_maximized.9.png differ diff --git a/astrid/res/drawable/expander_ic_minimized.9.png b/astrid/res/drawable/expander_ic_minimized.9.png new file mode 100644 index 000000000..e3cec8d1b Binary files /dev/null and b/astrid/res/drawable/expander_ic_minimized.9.png differ diff --git a/astrid/res/drawable/filter_all.png b/astrid/res/drawable/filter_all.png new file mode 100644 index 000000000..3d09261a2 Binary files /dev/null and b/astrid/res/drawable/filter_all.png differ diff --git a/astrid/res/drawable/filter_alpha.png b/astrid/res/drawable/filter_alpha.png new file mode 100644 index 000000000..b7960db9d Binary files /dev/null and b/astrid/res/drawable/filter_alpha.png differ diff --git a/astrid/res/drawable/filter_deleted.png b/astrid/res/drawable/filter_deleted.png new file mode 100644 index 000000000..1514d51a3 Binary files /dev/null and b/astrid/res/drawable/filter_deleted.png differ diff --git a/astrid/res/drawable/filter_hidden.png b/astrid/res/drawable/filter_hidden.png new file mode 100644 index 000000000..5bf8313c6 Binary files /dev/null and b/astrid/res/drawable/filter_hidden.png differ diff --git a/astrid/res/drawable/filter_inbox.png b/astrid/res/drawable/filter_inbox.png new file mode 100644 index 000000000..fed62219f Binary files /dev/null and b/astrid/res/drawable/filter_inbox.png differ diff --git a/astrid/res/drawable/filter_recent.png b/astrid/res/drawable/filter_recent.png new file mode 100644 index 000000000..6a9bf0370 Binary files /dev/null and b/astrid/res/drawable/filter_recent.png differ diff --git a/astrid/res/drawable/filter_search.png b/astrid/res/drawable/filter_search.png new file mode 100644 index 000000000..cf3d97f75 Binary files /dev/null and b/astrid/res/drawable/filter_search.png differ diff --git a/astrid/res/drawable/filter_tags1.png b/astrid/res/drawable/filter_tags1.png new file mode 100644 index 000000000..454a59f30 Binary files /dev/null and b/astrid/res/drawable/filter_tags1.png differ diff --git a/astrid/res/drawable/filter_tags2.png b/astrid/res/drawable/filter_tags2.png new file mode 100644 index 000000000..ebaf0e874 Binary files /dev/null and b/astrid/res/drawable/filter_tags2.png differ diff --git a/astrid/res/drawable/filter_untagged.png b/astrid/res/drawable/filter_untagged.png new file mode 100644 index 000000000..c726d6794 Binary files /dev/null and b/astrid/res/drawable/filter_untagged.png differ diff --git a/astrid/res/drawable/ic_dialog_alert_c.png b/astrid/res/drawable/ic_dialog_alert_c.png deleted file mode 100644 index d0c6f2f39..000000000 Binary files a/astrid/res/drawable/ic_dialog_alert_c.png and /dev/null differ diff --git a/astrid/res/drawable/ic_dialog_info_c.png b/astrid/res/drawable/ic_dialog_info_c.png deleted file mode 100644 index a11458967..000000000 Binary files a/astrid/res/drawable/ic_dialog_info_c.png and /dev/null differ diff --git a/astrid/res/drawable/ic_dialog_time_c.png b/astrid/res/drawable/ic_dialog_time_c.png deleted file mode 100644 index 20b805ad1..000000000 Binary files a/astrid/res/drawable/ic_dialog_time_c.png and /dev/null differ diff --git a/astrid/res/drawable/ic_tasklist_back.png b/astrid/res/drawable/ic_tasklist_back.png new file mode 100644 index 000000000..0581857c2 Binary files /dev/null and b/astrid/res/drawable/ic_tasklist_back.png differ diff --git a/astrid/res/drawable/ic_tasklist_back_pressed.png b/astrid/res/drawable/ic_tasklist_back_pressed.png new file mode 100644 index 000000000..3f052d99f Binary files /dev/null and b/astrid/res/drawable/ic_tasklist_back_pressed.png differ diff --git a/astrid/res/drawable/ic_tasklist_back_selected.png b/astrid/res/drawable/ic_tasklist_back_selected.png new file mode 100644 index 000000000..c9f2bebab Binary files /dev/null and b/astrid/res/drawable/ic_tasklist_back_selected.png differ diff --git a/astrid/res/drawable/icon.png b/astrid/res/drawable/icon.png index fee7992e4..25402e7f6 100644 Binary files a/astrid/res/drawable/icon.png and b/astrid/res/drawable/icon.png differ diff --git a/astrid/res/drawable/icon_add.png b/astrid/res/drawable/icon_add.png deleted file mode 100644 index 98e065838..000000000 Binary files a/astrid/res/drawable/icon_add.png and /dev/null differ diff --git a/astrid/res/drawable/icon_blank.png b/astrid/res/drawable/icon_blank.png new file mode 100644 index 000000000..d490f6130 Binary files /dev/null and b/astrid/res/drawable/icon_blank.png differ diff --git a/astrid/res/drawable/icon_tag.png b/astrid/res/drawable/icon_tag.png deleted file mode 100644 index bf3e1969b..000000000 Binary files a/astrid/res/drawable/icon_tag.png and /dev/null differ diff --git a/astrid/res/drawable/none.png b/astrid/res/drawable/none.png new file mode 100644 index 000000000..3871ebb96 Binary files /dev/null and b/astrid/res/drawable/none.png differ diff --git a/astrid/res/drawable/btn_check25.xml b/astrid/res/drawable/tasklist_back.xml similarity index 54% rename from astrid/res/drawable/btn_check25.xml rename to astrid/res/drawable/tasklist_back.xml index 3891015c0..3883b385f 100644 --- a/astrid/res/drawable/btn_check25.xml +++ b/astrid/res/drawable/tasklist_back.xml @@ -16,20 +16,11 @@ - - - - - - - + + + diff --git a/astrid/res/drawable/tea_tab_basic.png b/astrid/res/drawable/tea_tab_basic.png new file mode 100644 index 000000000..8ef006005 Binary files /dev/null and b/astrid/res/drawable/tea_tab_basic.png differ diff --git a/astrid/res/drawable/tea_tab_extensions.png b/astrid/res/drawable/tea_tab_extensions.png new file mode 100644 index 000000000..8bad05dfc Binary files /dev/null and b/astrid/res/drawable/tea_tab_extensions.png differ diff --git a/astrid/res/drawable/tea_tab_extra.png b/astrid/res/drawable/tea_tab_extra.png new file mode 100644 index 000000000..de1e16e4c Binary files /dev/null and b/astrid/res/drawable/tea_tab_extra.png differ diff --git a/astrid/res/layout/edit_alert_item.xml b/astrid/res/layout/edit_alert_item.xml deleted file mode 100644 index 920e79fee..000000000 --- a/astrid/res/layout/edit_alert_item.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -