RTM can sync! Now to make it better...

pull/14/head
Tim Su 14 years ago
parent 9031eb079b
commit 115bda98f6

@ -4,7 +4,7 @@
<classpathentry kind="src" path="src-legacy"/> <classpathentry kind="src" path="src-legacy"/>
<classpathentry kind="src" path="api-src"/> <classpathentry kind="src" path="api-src"/>
<classpathentry kind="src" path="common-src"/> <classpathentry kind="src" path="common-src"/>
<classpathentry excluding="com/todoroo/astrid/rmilk/DetailExposer.java|com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/rmilk/StartupReceiver.java" kind="src" path="plugin-src"/> <classpathentry excluding="com/todoroo/astrid/rmilk/EditOperationExposer.java|com/todoroo/astrid/rmilk/MilkEditActivity.java|com/todoroo/astrid/rmilk/StartupReceiver.java" kind="src" path="plugin-src"/>
<classpathentry kind="src" path="gen"/> <classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/> <classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/>

@ -22,11 +22,6 @@ import com.todoroo.andlib.sql.QueryTemplate;
*/ */
public final 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 * Expanded title of this filter. This is displayed at the top
* of the screen when user is viewing this filter. * of the screen when user is viewing this filter.
@ -62,9 +57,6 @@ public final class Filter extends FilterListItem {
/** /**
* Utility constructor for creating a TaskList object * Utility constructor for creating a TaskList object
*
* @param plugin
* {@link Addon} identifier that encompasses object
* @param listingTitle * @param listingTitle
* Title of this item as displayed on the lists page, e.g. Inbox * Title of this item as displayed on the lists page, e.g. Inbox
* @param title * @param title
@ -75,9 +67,8 @@ public final class Filter extends FilterListItem {
* @param valuesForNewTasks * @param valuesForNewTasks
* see {@link sqlForNewTasks} * see {@link sqlForNewTasks}
*/ */
public Filter(String plugin, String listingTitle, public Filter(String listingTitle, String title,
String title, QueryTemplate sqlQuery, ContentValues valuesForNewTasks) { QueryTemplate sqlQuery, ContentValues valuesForNewTasks) {
this.plugin = plugin;
this.listingTitle = listingTitle; this.listingTitle = listingTitle;
this.title = title; this.title = title;
this.sqlQuery = sqlQuery.toString(); this.sqlQuery = sqlQuery.toString();
@ -90,8 +81,8 @@ public final class Filter extends FilterListItem {
* @param plugin * @param plugin
* {@link Addon} identifier that encompasses object * {@link Addon} identifier that encompasses object
*/ */
protected Filter(String plugin) { protected Filter() {
this.plugin = plugin; // do nothing
} }
// --- parcelable // --- parcelable
@ -108,7 +99,6 @@ public final class Filter extends FilterListItem {
*/ */
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(plugin);
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
dest.writeString(title); dest.writeString(title);
dest.writeString(sqlQuery); dest.writeString(sqlQuery);
@ -124,7 +114,7 @@ public final class Filter extends FilterListItem {
* {@inheritDoc} * {@inheritDoc}
*/ */
public Filter createFromParcel(Parcel source) { public Filter createFromParcel(Parcel source) {
Filter item = new Filter(source.readString()); Filter item = new Filter();
item.readFromParcel(source); item.readFromParcel(source);
item.title = source.readString(); item.title = source.readString();
item.sqlQuery = source.readString(); item.sqlQuery = source.readString();

@ -25,14 +25,13 @@ import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.service.NotificationManager; import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Task;
import com.todoroo.astrid.rmilk.MilkPreferences; import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.service.TaskService;
/** /**
* A helper class for writing synchronization services for Astrid. This class * A helper class for writing synchronization services for Astrid. This class
* contains logic for merging incoming changes and writing outgoing changes. * contains logic for merging incoming changes and writing outgoing changes.
* <p> * <p>
* Use {@link synchronize} as the entry point for your synchronization service, * Use {@link initiate} as the entry point for your synchronization service,
* which should handle authentication and then call {@link synchronizeTasks} to * which should handle authentication and then call {@link synchronizeTasks} to
* initiate synchronization. * initiate synchronization.
* *
@ -41,15 +40,13 @@ import com.todoroo.astrid.service.TaskService;
*/ */
public abstract class SynchronizationProvider { public abstract class SynchronizationProvider {
/** Notification Manager id for RMilk notifications */
private static final int RMILK_NOTIFICATION_ID = -1;
// --- abstract methods - your services should implement these // --- abstract methods - your services should implement these
/** /**
* Synchronize with the service * Perform authenticate and other pre-synchronization steps, then
* synchronize.
*/ */
abstract public void synchronize(); abstract protected void initiate();
/** /**
* Push variables from given task to the remote server. * Push variables from given task to the remote server.
@ -77,6 +74,14 @@ public abstract class SynchronizationProvider {
*/ */
abstract protected Task read(Task task) throws IOException; abstract protected Task read(Task task) throws IOException;
/**
* Save task. Used to save local tasks that have been updated and remote
* tasks that need to be created locally
*
* @param task
*/
abstract protected void save(Task task) throws IOException;
/** /**
* Finds a task in the list with the same remote identifier(s) as * Finds a task in the list with the same remote identifier(s) as
* the task passed in * the task passed in
@ -92,9 +97,6 @@ public abstract class SynchronizationProvider {
// --- implementation // --- implementation
@Autowired
private TaskService taskService;
@Autowired @Autowired
private ExceptionService exceptionService; private ExceptionService exceptionService;
@ -110,6 +112,27 @@ public abstract class SynchronizationProvider {
notification = new Notification(icon, null, when); notification = new Notification(icon, null, when);
} }
public void synchronize() {
Context context = ContextManager.getContext();
// display notification
notificationIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
postUpdate(context, context.getString(R.string.SyP_progress_starting));
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
nm.notify(Constants.NOTIFICATION_SYNC, notification);
// start next step in background thread
new Thread(new Runnable() {
public void run() {
try {
initiate();
} finally {
nm.cancel(Constants.NOTIFICATION_SYNC);
}
}
}).start();
}
// --- utilities // --- utilities
/** /**
@ -148,12 +171,6 @@ public abstract class SynchronizationProvider {
Context context = ContextManager.getContext(); Context context = ContextManager.getContext();
Resources r = context.getResources(); Resources r = context.getResources();
// create notification
notificationIntent = PendingIntent.getActivity(context, 0, new Intent(context, MilkPreferences.class), 0);
postUpdate(context, r.getString(R.string.rmilk_progress_starting));
NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
nm.notify(RMILK_NOTIFICATION_ID, notification);
// create internal data structures // create internal data structures
HashMap<String, Task> remoteNewTaskNameMap = new HashMap<String, Task>(); HashMap<String, Task> remoteNewTaskNameMap = new HashMap<String, Task>();
length = data.remoteUpdated.size(); length = data.remoteUpdated.size();
@ -171,7 +188,7 @@ public abstract class SynchronizationProvider {
task.readFromCursor(data.localCreated); task.readFromCursor(data.localCreated);
String taskTitle = task.getValue(Task.TITLE); String taskTitle = task.getValue(Task.TITLE);
postUpdate(context, r.getString(R.string.rmilk_progress_localtx, postUpdate(context, r.getString(R.string.SyP_progress_localtx,
taskTitle)); taskTitle));
/* If there exists an incoming remote task with the same name and no /* If there exists an incoming remote task with the same name and no
@ -185,10 +202,14 @@ public abstract class SynchronizationProvider {
transferIdentifiers(remote, task); transferIdentifiers(remote, task);
push(task, remote); push(task, remote);
read(remote);
// re-read remote task after merge
Task newRemote = read(remote);
remote.mergeWith(newRemote.getMergedValues());
} else { } else {
create(task); create(task);
} }
save(task);
} }
// 2. UPDATE: for each updated local task // 2. UPDATE: for each updated local task
@ -196,14 +217,17 @@ public abstract class SynchronizationProvider {
for(int i = 0; i < length; i++) { for(int i = 0; i < length; i++) {
data.localUpdated.moveToNext(); data.localUpdated.moveToNext();
task.readFromCursor(data.localUpdated); task.readFromCursor(data.localUpdated);
postUpdate(context, r.getString(R.string.rmilk_progress_localtx, postUpdate(context, r.getString(R.string.SyP_progress_localtx,
task.getValue(Task.TITLE))); task.getValue(Task.TITLE)));
// if there is a conflict, merge // if there is a conflict, merge
Task remote = matchTask(data.remoteUpdated, task); Task remote = matchTask(data.remoteUpdated, task);
if(remote != null) { if(remote != null) {
push(task, remote); push(task, remote);
read(remote);
// re-read remote task after merge
Task newRemote = read(remote);
remote.mergeWith(newRemote.getMergedValues());
} else { } else {
push(task, null); push(task, null);
} }
@ -243,11 +267,10 @@ public abstract class SynchronizationProvider {
length = data.remoteUpdated.size(); length = data.remoteUpdated.size();
for(int i = 0; i < length; i++) { for(int i = 0; i < length; i++) {
task = data.remoteUpdated.get(i); task = data.remoteUpdated.get(i);
postUpdate(context, r.getString(R.string.rmilk_progress_remotetx, postUpdate(context, r.getString(R.string.SyP_progress_remotetx,
task.getValue(Task.TITLE))); task.getValue(Task.TITLE)));
// save the data (TODO: save metadata) save(task);
taskService.save(task, true);
} }
} }

@ -14,11 +14,6 @@ import android.os.Parcelable;
*/ */
public final class TaskDetail implements Parcelable { public final class TaskDetail implements Parcelable {
/**
* Plug-in Identifier
*/
public final String plugin;
/** /**
* Text of detail * Text of detail
*/ */
@ -31,28 +26,23 @@ public final class TaskDetail implements Parcelable {
/** /**
* Creates a TaskDetail object * Creates a TaskDetail object
*
* @param plugin
* {@link Addon} identifier that encompasses object
* @param text * @param text
* text to display * text to display
* @param color * @param color
* color to use for text. Use <code>0</code> for default color * color to use for text. Use <code>0</code> for default color
*/ */
public TaskDetail(String plugin, String text, int color) { public TaskDetail(String text, int color) {
this.plugin = plugin;
this.text = text; this.text = text;
this.color = color; this.color = color;
} }
/** /**
* Convenience constructor to make a TaskDetail with default color * Convenience constructor to make a TaskDetail with default color
*
* @param text * @param text
* text to display * text to display
*/ */
public TaskDetail(String plugin, String text) { public TaskDetail(String text) {
this(plugin, text, 0); this(text, 0);
} }
// --- parcelable helpers // --- parcelable helpers
@ -68,7 +58,6 @@ public final class TaskDetail implements Parcelable {
* {@inheritDoc} * {@inheritDoc}
*/ */
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(plugin);
dest.writeString(text); dest.writeString(text);
dest.writeInt(color); dest.writeInt(color);
} }
@ -81,7 +70,7 @@ public final class TaskDetail implements Parcelable {
* {@inheritDoc} * {@inheritDoc}
*/ */
public TaskDetail createFromParcel(Parcel source) { public TaskDetail createFromParcel(Parcel source) {
return new TaskDetail(source.readString(), source.readString(), source.readInt()); return new TaskDetail(source.readString(), source.readInt());
} }
/** /**

@ -10,9 +10,11 @@ import java.lang.reflect.InvocationTargetException;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.util.Log;
import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.utility.Constants;
@ -62,6 +64,8 @@ public class GenericDao<TYPE extends AbstractModel> {
*/ */
public TodorooCursor<TYPE> query(Query query) { public TodorooCursor<TYPE> query(Query query) {
query.from(table); query.from(table);
if(Constants.DEBUG)
Log.d("SQL-" + modelClass.getSimpleName(), query.toString()); //$NON-NLS-1$
Cursor cursor = database.rawQuery(query.toString(), null); Cursor cursor = database.rawQuery(query.toString(), null);
return new TodorooCursor<TYPE>(cursor, query.getFields()); return new TodorooCursor<TYPE>(cursor, query.getFields());
} }

@ -39,10 +39,6 @@ public class DependencyInjectionService {
@SuppressWarnings("nls") @SuppressWarnings("nls")
public void inject(Object caller) { public void inject(Object caller) {
if(Constants.DEBUG) {
Log.d("INJECTOR", "Invoked Injector on " + caller, new Throwable());
}
// Traverse through class and all parent classes, looking for // Traverse through class and all parent classes, looking for
// fields declared with the @Autowired annotation and using // fields declared with the @Autowired annotation and using
// dependency injection to set them as appropriate // dependency injection to set them as appropriate
@ -103,7 +99,7 @@ public class DependencyInjectionService {
Object injection = injector.getInjection(caller, field); Object injection = injector.getInjection(caller, field);
if (injection != null) { if (injection != null) {
if(Constants.DEBUG) if(Constants.DEBUG)
Log.e("INJECTOR", injector + ":" + caller + "." + field.getName() + " => " + injection); Log.d("INJECTOR", injector + ":" + caller + "." + field.getName() + " => " + injection);
field.set(caller, injection); field.set(caller, injection);
return; return;
} }

@ -22,6 +22,13 @@ public abstract class Criterion {
} }
}; };
public static Criterion none = new Criterion(Operator.exists) {
@Override
protected void populate(StringBuilder sb) {
sb.append(0);
}
};
public static Criterion and(final Criterion criterion, final Criterion... criterions) { public static Criterion and(final Criterion criterion, final Criterion... criterions) {
return new Criterion(Operator.and) { return new Criterion(Operator.and) {

@ -1,5 +1,6 @@
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
@SuppressWarnings("nls") @SuppressWarnings("nls")
public final class Functions { public final class Functions {
@ -13,4 +14,8 @@ public final class Functions {
return value.toString(); return value.toString();
} }
public static Field upper(Field title) {
return new Field("UPPER(" + title.toString() + ")");
}
} }

@ -47,8 +47,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
FilterCategory extended = new FilterCategory(CorePlugin.IDENTIFIER, FilterCategory extended = new FilterCategory(CorePlugin.IDENTIFIER,
r.getString(R.string.BFE_Extended), new Filter[5]); r.getString(R.string.BFE_Extended), new Filter[5]);
Filter alphabetical = new Filter(CorePlugin.IDENTIFIER, Filter alphabetical = new Filter(r.getString(R.string.BFE_Alphabetical),
r.getString(R.string.BFE_Alphabetical),
r.getString(R.string.BFE_Alphabetical), r.getString(R.string.BFE_Alphabetical),
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
TaskCriteria.isVisible(DateUtilities.now()))). TaskCriteria.isVisible(DateUtilities.now()))).
@ -56,8 +55,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
null); null);
alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_alpha)).getBitmap(); alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_alpha)).getBitmap();
Filter recent = new Filter(CorePlugin.IDENTIFIER, Filter recent = new Filter(r.getString(R.string.BFE_Recent),
r.getString(R.string.BFE_Recent),
r.getString(R.string.BFE_Recent), r.getString(R.string.BFE_Recent),
new QueryTemplate().orderBy(Order.desc(Task.MODIFICATION_DATE)).limit(15), new QueryTemplate().orderBy(Order.desc(Task.MODIFICATION_DATE)).limit(15),
null); null);
@ -65,26 +63,23 @@ public final class CoreFilterExposer extends BroadcastReceiver {
ContentValues hiddenValues = new ContentValues(); ContentValues hiddenValues = new ContentValues();
hiddenValues.put(Task.HIDE_UNTIL.name, DateUtilities.now() + DateUtilities.ONE_DAY); hiddenValues.put(Task.HIDE_UNTIL.name, DateUtilities.now() + DateUtilities.ONE_DAY);
Filter hidden = new Filter(CorePlugin.IDENTIFIER, Filter hidden = new Filter(r.getString(R.string.BFE_Hidden),
r.getString(R.string.BFE_Hidden),
r.getString(R.string.BFE_Hidden), r.getString(R.string.BFE_Hidden),
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
Criterion.not(TaskCriteria.isVisible(DateUtilities.now())))). Criterion.not(TaskCriteria.isVisible(DateUtilities.now())))).
orderBy(Order.asc(Task.HIDE_UNTIL)), orderBy(Order.asc(Task.HIDE_UNTIL)),
hiddenValues); hiddenValues);
hidden.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_clouds)).getBitmap(); hidden.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_clouds)).getBitmap();
ContentValues completedValues = new ContentValues(); ContentValues completedValues = new ContentValues();
hiddenValues.put(Task.COMPLETION_DATE.name, DateUtilities.now()); hiddenValues.put(Task.COMPLETION_DATE.name, DateUtilities.now());
Filter completed = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_Completed), Filter completed = new Filter(r.getString(R.string.BFE_Completed), r.getString(R.string.BFE_Completed),
r.getString(R.string.BFE_Completed),
new QueryTemplate().where(Criterion.and(TaskCriteria.completedBefore(DateUtilities.now()), new QueryTemplate().where(Criterion.and(TaskCriteria.completedBefore(DateUtilities.now()),
Criterion.not(TaskCriteria.isDeleted()))). orderBy(Order.desc(Task.COMPLETION_DATE)), Criterion.not(TaskCriteria.isDeleted()))). orderBy(Order.desc(Task.COMPLETION_DATE)),
completedValues); completedValues);
completed.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_check)).getBitmap(); completed.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.tango_check)).getBitmap();
Filter deleted = new Filter(CorePlugin.IDENTIFIER, Filter deleted = new Filter(r.getString(R.string.BFE_Deleted),
r.getString(R.string.BFE_Deleted),
r.getString(R.string.BFE_Deleted), r.getString(R.string.BFE_Deleted),
new QueryTemplate().where(TaskCriteria.isDeleted()). new QueryTemplate().where(TaskCriteria.isDeleted()).
orderBy(Order.desc(Task.DELETION_DATE)), orderBy(Order.desc(Task.DELETION_DATE)),
@ -112,8 +107,7 @@ public final class CoreFilterExposer extends BroadcastReceiver {
* @return * @return
*/ */
public static Filter buildInboxFilter(Resources r) { public static Filter buildInboxFilter(Resources r) {
Filter inbox = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_Active), Filter inbox = new Filter(r.getString(R.string.BFE_Active), r.getString(R.string.BFE_Active_title),
r.getString(R.string.BFE_Active_title),
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(), new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
TaskCriteria.isVisible(DateUtilities.now()))), TaskCriteria.isVisible(DateUtilities.now()))),
null); null);

@ -48,7 +48,7 @@ public class NoteDetailExposer extends BroadcastReceiver {
if(notes.length() == 0) if(notes.length() == 0)
return; return;
TaskDetail taskDetail = new TaskDetail(NotesPlugin.IDENTIFIER, notes); TaskDetail taskDetail = new TaskDetail(notes);
// transmit // transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);

@ -65,8 +65,7 @@ public class NotificationActivity extends Activity {
return; return;
Intent taskListIntent = new Intent(this, TaskListActivity.class); Intent taskListIntent = new Intent(this, TaskListActivity.class);
Filter itemFilter = new Filter(ReminderPlugin.IDENTIFIER, Filter itemFilter = new Filter(getString(R.string.rmd_NoA_filter),
getString(R.string.rmd_NoA_filter),
getString(R.string.rmd_NoA_filter), getString(R.string.rmd_NoA_filter),
new QueryTemplate().where(TaskCriteria.byId(id)), new QueryTemplate().where(TaskCriteria.byId(id)),
null); null);

@ -104,7 +104,7 @@ public class RepeatDetailExposer extends BroadcastReceiver {
detail = context.getString(R.string.repeat_detail_duedate, interval); detail = context.getString(R.string.repeat_detail_duedate, interval);
TaskDetail taskDetail = new TaskDetail(RepeatsPlugin.IDENTIFIER, detail); TaskDetail taskDetail = new TaskDetail(detail);
// transmit // transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);

@ -7,10 +7,10 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.todoroo.astrid.R; import com.timsu.astrid.R;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.TaskDetail; import com.todoroo.astrid.api.TaskDetail;
import com.todoroo.astrid.model.Task; import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.rmilk.data.MilkDataService;
/** /**
@ -34,22 +34,20 @@ public class DetailExposer extends BroadcastReceiver {
if(taskId == -1) if(taskId == -1)
return; return;
MilkDataService service = new MilkDataService(context); Metadata metadata = MilkDataService.getInstance().getTaskMetadata(taskId);
Task task = service.readTask(taskId); if(metadata == null)
if(task == null)
return; return;
TaskDetail[] details = new TaskDetail[2]; TaskDetail[] details = new TaskDetail[2];
String listId = task.getValue(MilkDataService.LIST_ID); long listId = metadata.getValue(MilkDataService.LIST_ID);
if(listId != null && listId.length() > 0) if(listId > 0)
details[0] = new TaskDetail(context.getString(R.string.rmilk_TLA_list, details[0] = new TaskDetail(context.getString(R.string.rmilk_TLA_list,
service.getList(listId))); MilkDataService.getInstance().getListName(listId)));
else else
details[0] = null; details[0] = null;
int repeat = task.getValue(MilkDataService.REPEAT); int repeat = metadata.getValue(MilkDataService.REPEATING);
if(repeat != 0) if(repeat != 0)
details[1] = new TaskDetail(context.getString(R.string.rmilk_TLA_repeat)); details[1] = new TaskDetail(context.getString(R.string.rmilk_TLA_repeat));
else else
@ -57,7 +55,8 @@ public class DetailExposer extends BroadcastReceiver {
// transmit // transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, details); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, Utilities.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, details);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }

@ -15,9 +15,9 @@ import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterCategory; import com.todoroo.astrid.api.FilterCategory;
import com.todoroo.astrid.api.FilterListHeader; import com.todoroo.astrid.api.FilterListHeader;
import com.todoroo.astrid.api.FilterListItem; import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.model.Metadata;
import com.todoroo.astrid.rmilk.Utilities.ListContainer; import com.todoroo.astrid.rmilk.Utilities.ListContainer;
import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.rmilk.data.MilkDataService;
import com.todoroo.astrid.rmilk.data.MilkTask;
/** /**
* Exposes filters based on RTM lists * Exposes filters based on RTM lists
@ -27,16 +27,16 @@ import com.todoroo.astrid.rmilk.data.MilkTask;
*/ */
public class FilterExposer extends BroadcastReceiver { public class FilterExposer extends BroadcastReceiver {
@SuppressWarnings("nls") @SuppressWarnings("nls")
private Filter filterFromList(Context context, ListContainer list) { private Filter filterFromList(Context context, ListContainer list) {
String listTitle = context.getString(R.string.rmilk_FEx_list_item). String listTitle = context.getString(R.string.rmilk_FEx_list_item).
replace("$N", list.name).replace("$C", Integer.toString(list.count)); replace("$N", list.name).replace("$C", Integer.toString(list.count));
String title = context.getString(R.string.rmilk_FEx_list_title, list.name); String title = context.getString(R.string.rmilk_FEx_list_title, list.name);
ContentValues values = new ContentValues(); // TODO ContentValues values = new ContentValues();
Filter filter = new Filter(Utilities.IDENTIFIER, listTitle, title, values.put(Metadata.KEY.name, MilkDataService.METADATA_KEY);
new QueryTemplate().join(MilkDataService.MILK_JOIN).where(MilkTask.LIST_ID.eq(list.id)), values.put(MilkDataService.LIST_ID.name, list.id);
values); Filter filter = new Filter(listTitle, title, new QueryTemplate().join(MilkDataService.METADATA_JOIN).where(MilkDataService.LIST_ID.eq(list.id)),
values);
return filter; return filter;
} }
@ -47,8 +47,7 @@ public class FilterExposer extends BroadcastReceiver {
if(!Utilities.isLoggedIn()) if(!Utilities.isLoggedIn())
return; return;
MilkDataService service = new MilkDataService(context); ListContainer[] lists = MilkDataService.getInstance().getListsWithCounts();
ListContainer[] lists = service.getListsWithCounts();
// If user does not have any tags, don't show this section at all // If user does not have any tags, don't show this section at all
if(lists.length == 0) if(lists.length == 0)
@ -68,6 +67,7 @@ public class FilterExposer extends BroadcastReceiver {
list[0] = rtmHeader; list[0] = rtmHeader;
list[1] = rtmLists; list[1] = rtmLists;
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, Utilities.IDENTIFIER);
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list); broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, list);
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
} }

@ -3,8 +3,6 @@
*/ */
package com.todoroo.astrid.rmilk; package com.todoroo.astrid.rmilk;
import java.util.Date;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@ -42,7 +40,7 @@ public class Utilities {
public ListContainer(long id, String name) { public ListContainer(long id, String name) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.count = -1; this.count = 0;
} }
@Override @Override
@ -87,22 +85,15 @@ public class Utilities {
editor.commit(); editor.commit();
} }
/** RTM Last Successful Sync Date, or null */ /** RTM Last Successful Sync Date, or 0 */
public static Date getLastSyncDate() { public static long getLastSyncDate() {
Long value = getPrefs().getLong(PREF_LAST_SYNC, 0); return getPrefs().getLong(PREF_LAST_SYNC, 0);
if (value == 0)
return null;
return new Date(value);
} }
/** Set RTM Last Successful Sync Date */ /** Set RTM Last Successful Sync Date */
public static void setLastSyncDate(Date date) { public static void setLastSyncDate(long time) {
Editor editor = getPrefs().edit(); Editor editor = getPrefs().edit();
if (date == null) { editor.putLong(PREF_LAST_SYNC, time);
editor.remove(PREF_LAST_SYNC);
} else {
editor.putLong(PREF_LAST_SYNC, date.getTime());
}
editor.commit(); editor.commit();
} }

@ -50,7 +50,7 @@ import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority;
* @author timsu January 2009 * @author timsu January 2009
*/ */
@SuppressWarnings("nls") @SuppressWarnings("nls")
public class ServiceImpl public class ServiceImpl
{ {
public final static String SERVER_HOST_NAME = "api.rememberthemilk.com"; //$NON-NLS-1$ public final static String SERVER_HOST_NAME = "api.rememberthemilk.com"; //$NON-NLS-1$
@ -288,7 +288,7 @@ public class ServiceImpl
} }
/** /**
* Adds a task, name, to the list specified by list_id. * Adds a task, name, to the list specified by list_id.
* @param timelineId * @param timelineId
* @param listId can be null to omit this parameter (assumes Inbox) * @param listId can be null to omit this parameter (assumes Inbox)
* @param name * @param name
@ -303,9 +303,9 @@ public class ServiceImpl
response = invoker.invoke(new Param("method", "rtm.tasks.add"), new Param("timeline", timelineId), new Param("list_id", listId), response = invoker.invoke(new Param("method", "rtm.tasks.add"), new Param("timeline", timelineId), new Param("list_id", listId),
new Param("name", name), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey())); new Param("name", name), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
else else
response = invoker.invoke(new Param("method", "rtm.tasks.add"), new Param("timeline", timelineId), response = invoker.invoke(new Param("method", "rtm.tasks.add"), new Param("timeline", timelineId),
new Param("name", name), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey())); new Param("name", name), new Param("auth_token", currentAuthToken), new Param("api_key", applicationInfo.getApiKey()));
RtmTaskList rtmTaskList = new RtmTaskList(response); RtmTaskList rtmTaskList = new RtmTaskList(response);
if (rtmTaskList.getSeries().size() == 1) if (rtmTaskList.getSeries().size() == 1)
{ {
@ -409,6 +409,8 @@ public class ServiceImpl
public RtmTaskSeries tasks_moveTo(String timelineId, String fromListId, String toListId, String taskSeriesId, String taskId) public RtmTaskSeries tasks_moveTo(String timelineId, String fromListId, String toListId, String taskSeriesId, String taskId)
throws ServiceException throws ServiceException
{ {
if(fromListId.equals(toListId))
return null;
Element elt = invoker.invoke(new Param("method", "rtm.tasks.moveTo"), new Param("timeline", timelineId), new Param("from_list_id", fromListId), Element elt = invoker.invoke(new Param("method", "rtm.tasks.moveTo"), new Param("timeline", timelineId), new Param("from_list_id", fromListId),
new Param("to_list_id", toListId), new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken), new Param("to_list_id", toListId), new Param("taskseries_id", taskSeriesId), new Param("task_id", taskId), new Param("auth_token", currentAuthToken),
new Param("api_key", applicationInfo.getApiKey())); new Param("api_key", applicationInfo.getApiKey()));

@ -27,13 +27,15 @@ public class RtmList extends RtmData {
private final String id; private final String id;
private final boolean smart; private final boolean smart;
private final boolean archived; private final boolean archived;
private final int position;
private final String name; private final String name;
public RtmList(String id, String name, boolean smart) { public RtmList(String id, String name, boolean smart, boolean archived, int position) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.smart = smart; this.smart = smart;
this.archived = false; this.archived = archived;
this.position = position;
} }
public RtmList(Element elt) { public RtmList(Element elt) {
@ -41,6 +43,7 @@ public class RtmList extends RtmData {
name = elt.getAttribute("name"); name = elt.getAttribute("name");
smart = elt.getAttribute("smart") == "1"; smart = elt.getAttribute("smart") == "1";
archived = elt.getAttribute("archived") == "1"; archived = elt.getAttribute("archived") == "1";
position = Integer.parseInt(elt.getAttribute("position"));
} }
public String getId() { public String getId() {
@ -57,5 +60,13 @@ public class RtmList extends RtmData {
public boolean isArchived() { public boolean isArchived() {
return archived; return archived;
} }
public int getPosition() {
return position;
}
public boolean isInbox() {
return position == -1;
}
} }

@ -3,6 +3,7 @@
*/ */
package com.todoroo.astrid.rmilk.data; package com.todoroo.astrid.rmilk.data;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
@ -12,25 +13,62 @@ import com.todoroo.andlib.data.GenericDao;
import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.data.Property.CountProperty; import com.todoroo.andlib.data.Property.CountProperty;
import com.todoroo.andlib.data.Property.IntegerProperty;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
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.model.Task;
import com.todoroo.astrid.rmilk.Utilities;
import com.todoroo.astrid.rmilk.Utilities.ListContainer; import com.todoroo.astrid.rmilk.Utilities.ListContainer;
import com.todoroo.astrid.rmilk.api.data.RtmList; import com.todoroo.astrid.rmilk.api.data.RtmList;
import com.todoroo.astrid.rmilk.api.data.RtmLists; import com.todoroo.astrid.rmilk.api.data.RtmLists;
public class MilkDataService { public final class MilkDataService {
// --- constants // --- public constants
/** for joining milk task table with task table */ /** metadata key */
public static final Join MILK_JOIN = Join.left(MilkTask.TABLE, public static final String METADATA_KEY = "rmilk"; //$NON-NLS-1$
Task.ID.eq(MilkTask.TASK));
/** {@link MilkList} id */
public static final LongProperty LIST_ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE1.name);
/** RTM Task Series Id */
public static final LongProperty TASK_SERIES_ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE2.name);
/** RTM Task Id */
public static final LongProperty TASK_ID = new LongProperty(Metadata.TABLE,
Metadata.VALUE3.name);
/** Whether task repeats in RTM (1 or 0) */
public static final IntegerProperty REPEATING = new IntegerProperty(Metadata.TABLE,
Metadata.VALUE4.name);
/** Utility for joining tasks with metadata */
public static final Join METADATA_JOIN = Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK));
// --- singleton
private static MilkDataService instance = null;
public static synchronized MilkDataService getInstance() {
if(instance == null)
instance = new MilkDataService(ContextManager.getContext());
return instance;
}
// --- instance variables // --- instance variables
@ -39,18 +77,19 @@ public class MilkDataService {
private final MilkDatabase milkDatabase = new MilkDatabase(); private final MilkDatabase milkDatabase = new MilkDatabase();
private final GenericDao<MilkList> milkListDao; private final GenericDao<MilkList> milkListDao;
private final GenericDao<MilkTask> milkTaskDao;
@Autowired @Autowired
private TaskDao taskDao; private TaskDao taskDao;
@Autowired
private MetadataDao metadataDao;
static final Random random = new Random(); static final Random random = new Random();
public MilkDataService(Context context) { private MilkDataService(Context context) {
this.context = context; this.context = context;
DependencyInjectionService.getInstance().inject(this); DependencyInjectionService.getInstance().inject(this);
milkListDao = new GenericDao<MilkList>(MilkList.class, milkDatabase); milkListDao = new GenericDao<MilkList>(MilkList.class, milkDatabase);
milkTaskDao = new GenericDao<MilkTask>(MilkTask.class, milkDatabase);
milkDatabase.openForReading(); milkDatabase.openForReading();
} }
@ -60,7 +99,7 @@ public class MilkDataService {
* Clears RTM metadata information. Used when user logs out of RTM * Clears RTM metadata information. Used when user logs out of RTM
*/ */
public void clearMetadata() { public void clearMetadata() {
milkTaskDao.deleteWhere(Criterion.all); metadataDao.deleteWhere(Metadata.KEY.eq(METADATA_KEY));
} }
/** /**
@ -70,20 +109,85 @@ public class MilkDataService {
*/ */
public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) { public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) {
return return
taskDao.query(Query.select(properties).join(MILK_JOIN).where( taskDao.query(Query.select(properties).join(METADATA_JOIN).where(
Criterion.or(MilkTask.UPDATED.eq(0), MilkTask.TASK.isNull()))); Criterion.or(TASK_ID.eq(0), Metadata.TASK.isNull())));
} }
/** /**
* Gets tasks that were modified since last sync * Gets tasks that were modified since last sync
* @param properties * @param properties
* @return * @return null if never sync'd
*/ */
public TodorooCursor<Task> getLocallyUpdated(Property<?>[] properties) { public TodorooCursor<Task> getLocallyUpdated(Property<?>[] properties) {
long lastSyncDate = Utilities.getLastSyncDate();
if(lastSyncDate == 0)
return taskDao.query(Query.select(Task.ID).where(Criterion.none));
return return
taskDao.query(Query.select(properties).join(MILK_JOIN). taskDao.query(Query.select(properties).join(METADATA_JOIN).
where(Criterion.and(MilkTask.UPDATED.neq(0), where(Task.MODIFICATION_DATE.gt(lastSyncDate)));
MilkTask.UPDATED.lt(Task.MODIFICATION_DATE)))); }
/**
* Searches for a local task with same remote id, updates this task's id
* @param task
*/
public void updateFromLocalCopy(Task task) {
if(task.getId() != Task.NO_ID)
return;
TodorooCursor<Task> cursor = taskDao.query(Query.select(Task.ID).
join(METADATA_JOIN).where(Criterion.and(LIST_ID.eq(task.getValue(LIST_ID)),
TASK_SERIES_ID.eq(task.getValue(TASK_SERIES_ID)),
TASK_ID.eq(task.getValue(TASK_ID)))));
try {
if(cursor.getCount() == 0)
return;
cursor.moveToFirst();
task.setId(cursor.get(Task.ID));
} finally {
cursor.close();
}
}
/**
* Saves a task and its metadata
* @param task
*/
public void saveTaskAndMetadata(Task task) {
Metadata metadata = new Metadata();
metadata.setValue(Metadata.KEY, METADATA_KEY);
metadata.setValue(LIST_ID, task.getValue(LIST_ID));
metadata.setValue(TASK_SERIES_ID, task.getValue(TASK_SERIES_ID));
metadata.setValue(TASK_ID, task.getValue(TASK_ID));
metadata.setValue(REPEATING, task.getValue(REPEATING));
task.clearValue(LIST_ID);
task.clearValue(TASK_SERIES_ID);
task.clearValue(TASK_ID);
task.clearValue(REPEATING);
taskDao.save(task, true);
metadata.setValue(Metadata.TASK, task.getId());
metadataDao.deleteWhere(MetadataCriteria.byTaskAndwithKey(task.getId(),
METADATA_KEY));
metadataDao.persist(metadata);
}
/**
* Reads metadata out of a task
* @return null if no metadata found
*/
public Metadata getTaskMetadata(long taskId) {
TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(Metadata.PROPERTIES).where(
MetadataCriteria.byTaskAndwithKey(taskId, METADATA_KEY)));
try {
if(cursor.getCount() == 0)
return null;
cursor.moveToFirst();
return new Metadata(cursor);
} finally {
cursor.close();
}
} }
// --- list methods // --- list methods
@ -93,7 +197,7 @@ public class MilkDataService {
* @param listId * @param listId
* @return null if no list by this id exists, otherwise list name * @return null if no list by this id exists, otherwise list name
*/ */
public String getList(String listId) { public String getListName(long listId) {
TodorooCursor<MilkList> cursor = milkListDao.query(Query.select( TodorooCursor<MilkList> cursor = milkListDao.query(Query.select(
MilkList.NAME).where(MilkList.ID.eq(listId))); MilkList.NAME).where(MilkList.ID.eq(listId)));
try { try {
@ -113,22 +217,40 @@ public class MilkDataService {
public ListContainer[] getListsWithCounts() { public ListContainer[] getListsWithCounts() {
CountProperty COUNT = new CountProperty(); CountProperty COUNT = new CountProperty();
// read list names
TodorooCursor<MilkList> listCursor = milkListDao.query(Query.select(MilkList.ID,
MilkList.NAME).where(MilkList.ARCHIVED.eq(0)).orderBy(Order.asc(MilkList.POSITION)));
ListContainer[] lists = new ListContainer[listCursor.getCount()];
HashMap<Long, ListContainer> listIdToContainerMap;
try {
int length = listCursor.getCount();
if(length == 0)
return lists;
listIdToContainerMap = new HashMap<Long, ListContainer>(length);
MilkList list = new MilkList();
for(int i = 0; i < length; i++) {
listCursor.moveToNext();
list.readFromCursor(listCursor);
lists[i] = new ListContainer(list);
listIdToContainerMap.put(list.getId(), lists[i]);
}
} finally {
listCursor.close();
}
// read all list counts // read all list counts
TodorooCursor<MilkTask> cursor = milkTaskDao.query(Query.select(MilkList.ID, MilkList.NAME, COUNT). TodorooCursor<Metadata> cursor = metadataDao.query(Query.select(LIST_ID, COUNT).
join(Join.inner(MilkList.TABLE, MilkTask.LIST_ID.eq(MilkList.ID))). join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
orderBy(Order.asc(MilkList.POSITION), Order.asc(MilkList.ID)). where(Criterion.and(TaskCriteria.isVisible(DateUtilities.now()), TaskCriteria.isActive())).
groupBy(MilkTask.LIST_ID)); groupBy(LIST_ID));
ListContainer[] containers = new ListContainer[cursor.getCount()];
try { try {
for(int i = 0; i < containers.length; i++) { for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
cursor.moveToNext(); ListContainer container = listIdToContainerMap.get(cursor.get(LIST_ID));
long id = cursor.get(MilkList.ID); if(container != null) {
String name = cursor.get(MilkList.NAME); container.count = cursor.get(COUNT);
int count = cursor.get(COUNT); }
containers[i] = new ListContainer(id, name);
containers[i].count = count;
} }
return containers; return lists;
} finally { } finally {
cursor.close(); cursor.close();
} }
@ -160,18 +282,31 @@ public class MilkDataService {
}*/ }*/
/** /**
* Clears current cache of RTM lists and re-populates * Clears current cache of RTM lists and re-populates. Returns the inbox
* list.
*
* @param lists * @param lists
* @return list with the name "inbox"
*/ */
public void setLists(RtmLists lists) { public MilkList setLists(RtmLists lists) {
milkListDao.deleteWhere(Criterion.all); milkListDao.deleteWhere(Criterion.all);
MilkList model = new MilkList(); MilkList model = new MilkList();
MilkList inbox = null;
for(Map.Entry<String, RtmList> list : lists.getLists().entrySet()) { for(Map.Entry<String, RtmList> list : lists.getLists().entrySet()) {
if(list.getValue().isSmart())
continue;
model.setValue(MilkList.ID, Long.parseLong(list.getValue().getId())); model.setValue(MilkList.ID, Long.parseLong(list.getValue().getId()));
model.setValue(MilkList.NAME, list.getValue().getName()); model.setValue(MilkList.NAME, list.getValue().getName());
model.setValue(MilkList.POSITION, list.getValue().getPosition());
model.setValue(MilkList.ARCHIVED, list.getValue().isArchived()? 1 : 0); model.setValue(MilkList.ARCHIVED, list.getValue().isArchived()? 1 : 0);
milkListDao.createNew(model); milkListDao.createNew(model);
if(list.getValue().isInbox()) {
inbox = model;
model = new MilkList();
}
} }
return inbox;
} }
} }

@ -37,7 +37,6 @@ public class MilkDatabase extends AbstractDatabase {
*/ */
public static final Table[] TABLES = new Table[] { public static final Table[] TABLES = new Table[] {
MilkList.TABLE, MilkList.TABLE,
MilkTask.TABLE
}; };
// --- implementation // --- implementation

@ -1,104 +0,0 @@
/**
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.rmilk.data;
import android.content.ContentValues;
import com.todoroo.andlib.data.AbstractModel;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Table;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.data.Property.IntegerProperty;
import com.todoroo.andlib.data.Property.LongProperty;
import com.todoroo.astrid.model.Task;
/**
* Data Model which represents a list in RTM
*
* @author Tim Su <tim@todoroo.com>
*
*/
@SuppressWarnings("nls")
public class MilkTask extends AbstractModel {
// --- table
public static final Table TABLE = new Table("tasks", MilkTask.class);
// --- properties
/** Task Id */
public static final LongProperty TASK = new LongProperty(
TABLE, "task");
/** {@link MilkList} id */
public static final LongProperty LIST_ID = new LongProperty(
TABLE, "listId");
/** RTM Task Id */
public static final LongProperty TASK_SERIES_ID = new LongProperty(
TABLE, "taskSeriesId");
/** RTM Task Series Id */
public static final LongProperty TASK_ID = new LongProperty(
TABLE, "taskId");
/** Whether task repeats in RTM (1 or 0) */
public static final IntegerProperty REPEATING = new IntegerProperty(
TABLE, "repeating");
/** Unixtime task was last updated in RTM */
public static final LongProperty UPDATED = new LongProperty(
TABLE, "updated");
/** List of all properties for this model */
public static final Property<?>[] PROPERTIES = generateProperties(MilkTask.class);
// --- defaults
/** Default values container */
private static final ContentValues defaultValues = new ContentValues();
static {
defaultValues.put(REPEATING.name, 0);
defaultValues.put(UPDATED.name, 0);
}
@Override
public ContentValues getDefaultValues() {
return defaultValues;
}
// --- data access boilerplate
public MilkTask() {
super();
}
public MilkTask(TodorooCursor<MilkTask> cursor) {
this();
readPropertiesFromCursor(cursor);
}
public void readFromCursor(TodorooCursor<MilkTask> cursor) {
super.readPropertiesFromCursor(cursor);
}
@Override
public long getId() {
return getIdHelper(TASK);
};
// --- parcelable helpers
private static final Creator<Task> CREATOR = new ModelCreator<Task>(Task.class);
@Override
protected Creator<? extends AbstractModel> getCreator() {
return CREATOR;
}
}

@ -7,14 +7,11 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Handler; import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import com.flurry.android.FlurryAgent; import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R; import com.timsu.astrid.R;
@ -44,7 +41,6 @@ import com.todoroo.astrid.rmilk.api.data.RtmTasks;
import com.todoroo.astrid.rmilk.api.data.RtmAuth.Perms; import com.todoroo.astrid.rmilk.api.data.RtmAuth.Perms;
import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority; import com.todoroo.astrid.rmilk.api.data.RtmTask.Priority;
import com.todoroo.astrid.rmilk.data.MilkDataService; import com.todoroo.astrid.rmilk.data.MilkDataService;
import com.todoroo.astrid.rmilk.data.MilkTask;
import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.AstridDependencyInjector;
public class RTMSyncProvider extends SynchronizationProvider { public class RTMSyncProvider extends SynchronizationProvider {
@ -52,7 +48,6 @@ public class RTMSyncProvider extends SynchronizationProvider {
private ServiceImpl rtmService = null; private ServiceImpl rtmService = null;
private String timeline = null; private String timeline = null;
private MilkDataService dataService = null; private MilkDataService dataService = null;
private ProgressDialog progressDialog = null;
static { static {
AstridDependencyInjector.initialize(); AstridDependencyInjector.initialize();
@ -73,27 +68,12 @@ public class RTMSyncProvider extends SynchronizationProvider {
// ------------------------------------------------------- public methods // ------------------------------------------------------- public methods
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@Override
public void synchronize() {
Context context = ContextManager.getContext();
dataService = new MilkDataService(context);
if(context instanceof Activity) {
progressDialog = dialogUtilities.progressDialog(context,
context.getString(R.string.DLG_communicating_text));
progressDialog.show();
}
// authenticate the user. this will automatically call the next step
authenticate(context);
}
/** /**
* Sign out of RTM, deleting all synchronization metadata * Sign out of RTM, deleting all synchronization metadata
*/ */
public void signOut() { public void signOut() {
Utilities.setToken(null); Utilities.setToken(null);
Utilities.setLastSyncDate(null); Utilities.setLastSyncDate(0);
dataService.clearMetadata(); dataService.clearMetadata();
} }
@ -138,19 +118,22 @@ public class RTMSyncProvider extends SynchronizationProvider {
} }
} }
private void authenticate(final Context context) { @Override
new Thread(new Runnable() { protected void initiate() {
public void run() { Context context = ContextManager.getContext();
authenticateInNewThread(context); dataService = MilkDataService.getInstance();
}
}).start(); // authenticate the user. this will automatically call the next step
authenticate(context);
} }
/** /**
* Perform authentication with RTM. Will open the SyncBrowser if necessary * Perform authentication with RTM. Will open the SyncBrowser if necessary
*/ */
@SuppressWarnings("nls") @SuppressWarnings("nls")
private void authenticateInNewThread(final Context context) { private void authenticate(final Context context) {
final Resources r = context.getResources(); final Resources r = context.getResources();
FlurryAgent.onEvent("rtm-started"); FlurryAgent.onEvent("rtm-started");
@ -237,10 +220,10 @@ public class RTMSyncProvider extends SynchronizationProvider {
// read all tasks // read all tasks
ArrayList<Task> remoteChanges = new ArrayList<Task>(); ArrayList<Task> remoteChanges = new ArrayList<Task>();
Date lastSyncDate = Utilities.getLastSyncDate(); Date lastSyncDate = new Date(Utilities.getLastSyncDate());
boolean shouldSyncIndividualLists = false; boolean shouldSyncIndividualLists = false;
String filter = null; String filter = null;
if(lastSyncDate == null) if(lastSyncDate.getTime() == 0)
filter = "status:incomplete"; //$NON-NLS-1$ // 1st time sync: get unfinished tasks filter = "status:incomplete"; //$NON-NLS-1$ // 1st time sync: get unfinished tasks
// try the quick synchronization // try the quick synchronization
@ -273,8 +256,7 @@ public class RTMSyncProvider extends SynchronizationProvider {
SyncData syncData = populateSyncData(remoteChanges); SyncData syncData = populateSyncData(remoteChanges);
synchronizeTasks(syncData); synchronizeTasks(syncData);
Date syncTime = new Date(System.currentTimeMillis()); Utilities.setLastSyncDate(DateUtilities.now());
Utilities.setLastSyncDate(syncTime);
FlurryAgent.onEvent("rtm-sync-finished"); //$NON-NLS-1$ FlurryAgent.onEvent("rtm-sync-finished"); //$NON-NLS-1$
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
@ -301,10 +283,10 @@ public class RTMSyncProvider extends SynchronizationProvider {
Task.CREATION_DATE, Task.CREATION_DATE,
Task.COMPLETION_DATE, Task.COMPLETION_DATE,
Task.DELETION_DATE, Task.DELETION_DATE,
MilkTask.LIST_ID, MilkDataService.LIST_ID,
MilkTask.TASK_SERIES_ID, MilkDataService.TASK_SERIES_ID,
MilkTask.TASK_ID, MilkDataService.TASK_ID,
MilkTask.REPEATING, MilkDataService.REPEATING,
// TODO tags // TODO tags
}; };
@ -350,23 +332,29 @@ public class RTMSyncProvider extends SynchronizationProvider {
@Override @Override
protected void create(Task task) throws IOException { protected void create(Task task) throws IOException {
String listId = null; String listId = null;
if(task.containsValue(MilkTask.LIST_ID)) if(task.containsValue(MilkDataService.LIST_ID) && task.getValue(MilkDataService.LIST_ID) != null)
listId = Long.toString(task.getValue(MilkTask.LIST_ID)); listId = Long.toString(task.getValue(MilkDataService.LIST_ID));
if("0".equals(listId)) //$NON-NLS-1$
listId = null;
RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId, RtmTaskSeries rtmTask = rtmService.tasks_add(timeline, listId,
task.getValue(Task.TITLE)); task.getValue(Task.TITLE));
push(task, parseRemoteTask(rtmTask)); Task newRemoteTask = parseRemoteTask(rtmTask);
task.mergeWith(newRemoteTask.getMergedValues());
push(task, newRemoteTask);
} }
/** Send changes for the given TaskProxy across the wire */ /**
* Send changes for the given Task across the wire. If a remoteTask is
* supplied, we attempt to intelligently only transmit the values that
* have changed.
*/
@Override @Override
protected void push(Task task, Task remoteTask) throws IOException { protected void push(Task task, Task remoteTask) throws IOException {
RtmId id = new RtmId(task);
// fetch remote task for comparison // fetch remote task for comparison
if(remoteTask == null) { if(remoteTask == null)
remoteTask = read(task); remoteTask = read(task);
} RtmId id = new RtmId(task);
if(shouldTransmit(task, Task.TITLE, remoteTask)) if(shouldTransmit(task, Task.TITLE, remoteTask))
rtmService.tasks_setName(timeline, id.listId, id.taskSeriesId, rtmService.tasks_setName(timeline, id.listId, id.taskSeriesId,
@ -386,42 +374,52 @@ public class RTMSyncProvider extends SynchronizationProvider {
rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId, rtmService.tasks_complete(timeline, id.listId, id.taskSeriesId,
id.taskId); id.taskId);
} }
if(shouldTransmit(task, Task.DELETION_DATE, remoteTask)) if(shouldTransmit(task, Task.DELETION_DATE, remoteTask) &&
task.getValue(Task.DELETION_DATE) > 0)
rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId, rtmService.tasks_delete(timeline, id.listId, id.taskSeriesId,
id.taskId); id.taskId);
if(shouldTransmit(task, MilkTask.LIST_ID, remoteTask) && remoteTask != null) // TODO tags, notes, url, ...
rtmService.tasks_moveTo(timeline, Long.toString(remoteTask.getValue(MilkTask.LIST_ID)),
if(remoteTask != null && shouldTransmit(task, MilkDataService.LIST_ID, remoteTask) &&
task.getValue(MilkDataService.LIST_ID) != 0)
rtmService.tasks_moveTo(timeline, Long.toString(remoteTask.getValue(MilkDataService.LIST_ID)),
id.listId, id.taskSeriesId, id.taskId); id.listId, id.taskSeriesId, id.taskId);
} }
@Override
protected void save(Task task) throws IOException {
// if task has no id, try to find a corresponding task
if(task.getId() == Task.NO_ID) {
dataService.updateFromLocalCopy(task);
}
dataService.saveTaskAndMetadata(task);
}
/** Create a task proxy for the given RtmTaskSeries */ /** Create a task proxy for the given RtmTaskSeries */
private Task parseRemoteTask(RtmTaskSeries rtmTaskSeries) { private Task parseRemoteTask(RtmTaskSeries rtmTaskSeries) {
Task task = new Task(); Task task = new Task();
task.setValue(MilkTask.LIST_ID, Long.parseLong(rtmTaskSeries.getList().getId())); task.setValue(MilkDataService.LIST_ID, Long.parseLong(rtmTaskSeries.getList().getId()));
task.setValue(MilkTask.TASK_SERIES_ID, Long.parseLong(rtmTaskSeries.getId())); task.setValue(MilkDataService.TASK_SERIES_ID, Long.parseLong(rtmTaskSeries.getId()));
task.setValue(Task.TITLE, rtmTaskSeries.getName()); task.setValue(Task.TITLE, rtmTaskSeries.getName());
task.setValue(MilkTask.REPEATING, rtmTaskSeries.hasRecurrence() ? 1 : 0); task.setValue(MilkDataService.REPEATING, rtmTaskSeries.hasRecurrence() ? 1 : 0);
RtmTask rtmTask = rtmTaskSeries.getTask(); RtmTask rtmTask = rtmTaskSeries.getTask();
if(rtmTask != null) { task.setValue(MilkDataService.TASK_ID, Long.parseLong(rtmTask.getId()));
task.setValue(Task.CREATION_DATE, DateUtilities.dateToUnixtime(rtmTask.getAdded())); task.setValue(Task.CREATION_DATE, DateUtilities.dateToUnixtime(rtmTask.getAdded()));
task.setValue(Task.COMPLETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getCompleted())); task.setValue(Task.COMPLETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getCompleted()));
task.setValue(Task.DELETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getDeleted())); task.setValue(Task.DELETION_DATE, DateUtilities.dateToUnixtime(rtmTask.getDeleted()));
if(rtmTask.getDue() != null) { if(rtmTask.getDue() != null) {
task.setValue(Task.DUE_DATE, task.setValue(Task.DUE_DATE,
task.createDueDate(rtmTask.getHasDueTime() ? Task.URGENCY_SPECIFIC_DAY_TIME : task.createDueDate(rtmTask.getHasDueTime() ? Task.URGENCY_SPECIFIC_DAY_TIME :
Task.URGENCY_SPECIFIC_DAY, DateUtilities.dateToUnixtime(rtmTask.getDue()))); Task.URGENCY_SPECIFIC_DAY, DateUtilities.dateToUnixtime(rtmTask.getDue())));
} else {
task.setValue(Task.DUE_DATE, 0L);
}
task.setValue(Task.IMPORTANCE, rtmTask.getPriority().ordinal());
} else { } else {
// error in upstream code, try to handle gracefully task.setValue(Task.DUE_DATE, 0L);
Log.e("rtmsync", "Got null task parsing remote task series", //$NON-NLS-1$//$NON-NLS-2$
new Throwable());
} }
task.setValue(Task.IMPORTANCE, rtmTask.getPriority().ordinal());
return task; return task;
} }
@ -431,9 +429,9 @@ public class RTMSyncProvider extends SynchronizationProvider {
int length = tasks.size(); int length = tasks.size();
for(int i = 0; i < length; i++) { for(int i = 0; i < length; i++) {
Task task = tasks.get(i); Task task = tasks.get(i);
if(task.getValue(MilkTask.LIST_ID).equals(target.getValue(MilkTask.LIST_ID)) && if(task.getValue(MilkDataService.LIST_ID).equals(target.getValue(MilkDataService.LIST_ID)) &&
task.getValue(MilkTask.TASK_SERIES_ID).equals(target.getValue(MilkTask.TASK_SERIES_ID)) && task.getValue(MilkDataService.TASK_SERIES_ID).equals(target.getValue(MilkDataService.TASK_SERIES_ID)) &&
task.getValue(MilkTask.TASK_ID).equals(target.getValue(MilkTask.TASK_ID))) task.getValue(MilkDataService.TASK_ID).equals(target.getValue(MilkDataService.TASK_ID)))
return task; return task;
} }
return null; return null;
@ -441,7 +439,7 @@ public class RTMSyncProvider extends SynchronizationProvider {
@Override @Override
protected Task read(Task task) throws IOException { protected Task read(Task task) throws IOException {
RtmTaskSeries rtmTask = rtmService.tasks_getTask(Long.toString(task.getValue(MilkTask.TASK_SERIES_ID)), RtmTaskSeries rtmTask = rtmService.tasks_getTask(Long.toString(task.getValue(MilkDataService.TASK_SERIES_ID)),
task.getValue(Task.TITLE)); task.getValue(Task.TITLE));
if(rtmTask != null) if(rtmTask != null)
return parseRemoteTask(rtmTask); return parseRemoteTask(rtmTask);
@ -450,9 +448,9 @@ public class RTMSyncProvider extends SynchronizationProvider {
@Override @Override
protected void transferIdentifiers(Task source, Task destination) { protected void transferIdentifiers(Task source, Task destination) {
destination.setValue(MilkTask.LIST_ID, source.getValue(MilkTask.LIST_ID)); destination.setValue(MilkDataService.LIST_ID, source.getValue(MilkDataService.LIST_ID));
destination.setValue(MilkTask.TASK_SERIES_ID, source.getValue(MilkTask.TASK_SERIES_ID)); destination.setValue(MilkDataService.TASK_SERIES_ID, source.getValue(MilkDataService.TASK_SERIES_ID));
destination.setValue(MilkTask.TASK_ID, source.getValue(MilkTask.TASK_ID)); destination.setValue(MilkDataService.TASK_ID, source.getValue(MilkDataService.TASK_ID));
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -466,9 +464,9 @@ public class RTMSyncProvider extends SynchronizationProvider {
public String listId; public String listId;
public RtmId(Task task) { public RtmId(Task task) {
taskId = Long.toString(task.getValue(MilkTask.TASK_ID)); taskId = Long.toString(task.getValue(MilkDataService.TASK_ID));
taskSeriesId = Long.toString(task.getValue(MilkTask.TASK_SERIES_ID)); taskSeriesId = Long.toString(task.getValue(MilkDataService.TASK_SERIES_ID));
listId = Long.toString(task.getValue(MilkTask.LIST_ID)); listId = Long.toString(task.getValue(MilkDataService.LIST_ID));
} }
} }

@ -35,8 +35,7 @@ public class TagDetailExposer extends BroadcastReceiver {
if(tagList.length() == 0) if(tagList.length() == 0)
return; return;
TaskDetail taskDetail = new TaskDetail(TagsPlugin.IDENTIFIER, TaskDetail taskDetail = new TaskDetail(context.getString(R.string.tag_TLA_detail, tagList));
context.getString(R.string.tag_TLA_detail, tagList));
// transmit // transmit
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS); Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);

@ -40,9 +40,8 @@ public class TagFilterExposer extends BroadcastReceiver {
contentValues.put(Metadata.KEY.name, TagService.KEY); contentValues.put(Metadata.KEY.name, TagService.KEY);
contentValues.put(TagService.TAG.name, tag.tag); contentValues.put(TagService.TAG.name, tag.tag);
Filter filter = new Filter(TagsPlugin.IDENTIFIER, Filter filter = new Filter(listTitle,
listTitle, title, title, tagTemplate,
tagTemplate,
contentValues); contentValues);
// filters[0].contextMenuLabels = new String[] { // filters[0].contextMenuLabels = new String[] {
@ -79,8 +78,7 @@ public class TagFilterExposer extends BroadcastReceiver {
FilterListHeader tagsHeader = new FilterListHeader(TagsPlugin.IDENTIFIER, FilterListHeader tagsHeader = new FilterListHeader(TagsPlugin.IDENTIFIER,
context.getString(R.string.tag_FEx_header)); context.getString(R.string.tag_FEx_header));
Filter untagged = new Filter(TagsPlugin.IDENTIFIER, Filter untagged = new Filter(r.getString(R.string.tag_FEx_untagged),
r.getString(R.string.tag_FEx_untagged),
r.getString(R.string.tag_FEx_untagged), r.getString(R.string.tag_FEx_untagged),
tagService.untaggedTemplate(), tagService.untaggedTemplate(),
null); null);

@ -164,6 +164,7 @@ public class TagService {
metadata.setValue(Metadata.KEY, KEY); metadata.setValue(Metadata.KEY, KEY);
metadata.setValue(Metadata.TASK, taskId); metadata.setValue(Metadata.TASK, taskId);
for(String tag : tags) { for(String tag : tags) {
metadata.clearValue(Metadata.ID);
metadata.setValue(TAG, tag.trim()); metadata.setValue(TAG, tag.trim());
metadataDao.createNew(metadata); metadataDao.createNew(metadata);
} }

@ -402,5 +402,15 @@ If you don\'t want to see the new task right after you complete the old one, you
<!-- Add Ons: author for internal authors --> <!-- Add Ons: author for internal authors -->
<string name="AOA_internal_author">Astrid Team</string> <string name="AOA_internal_author">Astrid Team</string>
<!-- ========================================== SynchronizationProvider == -->
<!-- Sync Notification: message when starting up -->
<string name="SyP_progress_starting">Logging In...</string>
<!-- Sync Notification: transmitting local task (%s -> task name) -->
<string name="SyP_progress_localtx">Transmitting: %s</string>
<!-- Sync Notification: receiving remote task (%s -> task name) -->
<string name="SyP_progress_remotetx">Receiving: %s</string>
</resources> </resources>

@ -322,6 +322,7 @@ occur (it is a minor drain on battery).
<string name="sync_result_merged">Merged: %d</string> <string name="sync_result_merged">Merged: %d</string>
<string name="sync_progress_remote">Reading Remote Data</string> <string name="sync_progress_remote">Reading Remote Data</string>
<string name="sync_progress_starting">Reading Remote Data</string>
<string name="sync_progress_rxlist">Reading List: %s</string> <string name="sync_progress_rxlist">Reading List: %s</string>
<string name="sync_progress_repeating">Synchronizing Repeating Task</string> <string name="sync_progress_repeating">Synchronizing Repeating Task</string>
<string name="sync_progress_localtx">Transmitting: %s</string> <string name="sync_progress_localtx">Transmitting: %s</string>

@ -115,17 +115,10 @@ Error Message: %s
</string> </string>
<!-- ======================== Synchronization ========================== --> <!-- ======================== Synchronization ========================== -->
<string name="rmilk_notification_title">Astrid: Remember the Milk</string> <!-- generic sync constants -->
<string name="rmilk_error">Sync Error! Sorry for the inconvenience! Error:</string> <string name="rmilk_error">Sync Error! Sorry for the inconvenience! Error:</string>
<string name="rmilk_ioerror">Connection Error! Check your Internet connection,
or maybe RTM servers (status.rememberthemilk.com), for possible solutions.</string>
<string name="rmilk_uptodate">Sync: Up to date!</string> <string name="rmilk_uptodate">Sync: Up to date!</string>
<string name="rmilk_forget_confirm">Clear data for selected services?</string>
<string name="rmilk_no_synchronizers">No Synchronizers Enabled!</string>
<string name="rmilk_last_sync">Last Sync Date: %s</string>
<string name="rmilk_last_auto_sync">Last AutoSync Attempt: %s</string>
<string name="rmilk_date_never">never</string>
<string name="rmilk_result_title">%s Results</string> <string name="rmilk_result_title">%s Results</string>
<string name="rmilk_result_local">Summary - Astrid Tasks:</string> <string name="rmilk_result_local">Summary - Astrid Tasks:</string>
<string name="rmilk_result_remote">Summary - Remote Server:</string> <string name="rmilk_result_remote">Summary - Remote Server:</string>
@ -133,12 +126,16 @@ Error Message: %s
<string name="rmilk_result_updated">Updated: %d</string> <string name="rmilk_result_updated">Updated: %d</string>
<string name="rmilk_result_deleted">Deleted: %d</string> <string name="rmilk_result_deleted">Deleted: %d</string>
<string name="rmilk_result_merged">Merged: %d</string> <string name="rmilk_result_merged">Merged: %d</string>
<!-- rmilk-specific constants -->
<string name="rmilk_notification_title">Astrid: Remember the Milk</string>
<string name="rmilk_forget_confirm">Log out / clear synchronization data?</string>
<string name="rmilk_last_sync">Last Sync Date: %s</string>
<string name="rmilk_last_auto_sync">Last AutoSync Attempt: %s</string>
<string name="rmilk_date_never">never</string>
<string name="rmilk_progress_starting">Starting Sync...</string> <string name="rmilk_ioerror">Connection Error! Check your Internet connection,
<string name="rmilk_progress_rxlist">Reading List: %s</string> or maybe RTM servers (status.rememberthemilk.com), for possible solutions.</string>
<string name="rmilk_progress_localtx">Transmitting: %s</string>
<string name="rmilk_progress_localdel">Deleting: %s</string>
<string name="rmilk_progress_remotetx">Receiving: %s</string>
<string-array name="rmilk_MPr_interval_entries"> <string-array name="rmilk_MPr_interval_entries">
<!-- rmilk_MPr_interval_entries: Synchronization Intervals --> <!-- rmilk_MPr_interval_entries: Synchronization Intervals -->

@ -24,8 +24,8 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.Map.Entry;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -38,15 +38,15 @@ import com.mdt.rtm.ApplicationInfo;
import com.mdt.rtm.ServiceException; import com.mdt.rtm.ServiceException;
import com.mdt.rtm.ServiceImpl; import com.mdt.rtm.ServiceImpl;
import com.mdt.rtm.ServiceInternalException; import com.mdt.rtm.ServiceInternalException;
import com.mdt.rtm.data.RtmAuth.Perms;
import com.mdt.rtm.data.RtmList; import com.mdt.rtm.data.RtmList;
import com.mdt.rtm.data.RtmLists; import com.mdt.rtm.data.RtmLists;
import com.mdt.rtm.data.RtmTask; import com.mdt.rtm.data.RtmTask;
import com.mdt.rtm.data.RtmTask.Priority;
import com.mdt.rtm.data.RtmTaskList; import com.mdt.rtm.data.RtmTaskList;
import com.mdt.rtm.data.RtmTaskNote; import com.mdt.rtm.data.RtmTaskNote;
import com.mdt.rtm.data.RtmTaskSeries; import com.mdt.rtm.data.RtmTaskSeries;
import com.mdt.rtm.data.RtmTasks; import com.mdt.rtm.data.RtmTasks;
import com.mdt.rtm.data.RtmAuth.Perms;
import com.mdt.rtm.data.RtmTask.Priority;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.timsu.astrid.activities.SyncLoginActivity; import com.timsu.astrid.activities.SyncLoginActivity;
import com.timsu.astrid.activities.SyncLoginActivity.SyncLoginCallback; import com.timsu.astrid.activities.SyncLoginActivity.SyncLoginCallback;
@ -537,7 +537,7 @@ public class RTMSyncProvider extends SynchronizationProvider {
/** SynchronizeHelper for remember the milk */ /** SynchronizeHelper for remember the milk */
class RtmSyncHelper implements SynchronizeHelper { class RtmSyncHelper implements SynchronizeHelper {
private String timeline; private final String timeline;
private String lastCreatedTask = null; private String lastCreatedTask = null;
private Context context; private Context context;

@ -22,7 +22,6 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.ViewGroup.OnHierarchyChangeListener;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ExpandableListView; import android.widget.ExpandableListView;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -34,6 +33,7 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.adapter.FilterAdapter; import com.todoroo.astrid.adapter.FilterAdapter;
@ -111,9 +111,9 @@ public class FilterListActivity extends ExpandableListActivity {
final String intentAction = intent.getAction(); final String intentAction = intent.getAction();
if (Intent.ACTION_SEARCH.equals(intentAction)) { if (Intent.ACTION_SEARCH.equals(intentAction)) {
String query = intent.getStringExtra(SearchManager.QUERY).trim(); String query = intent.getStringExtra(SearchManager.QUERY).trim();
Filter filter = new Filter(null, null, Filter filter = new Filter(null, getString(R.string.FLA_search_filter, query),
getString(R.string.FLA_search_filter, query), new QueryTemplate().where(Functions.upper(Task.TITLE).like("%" + //$NON-NLS-1$
new QueryTemplate().where(Task.TITLE.like("%" + query + "%")), //$NON-NLS-1$ //$NON-NLS-2$ query.toUpperCase() + "%")),
null); null);
intent = new Intent(FilterListActivity.this, TaskListActivity.class); intent = new Intent(FilterListActivity.this, TaskListActivity.class);
intent.putExtra(TaskListActivity.TOKEN_FILTER, filter); intent.putExtra(TaskListActivity.TOKEN_FILTER, filter);
@ -208,7 +208,7 @@ public class FilterListActivity extends ExpandableListActivity {
registerForContextMenu(getExpandableListView()); registerForContextMenu(getExpandableListView());
getExpandableListView().setGroupIndicator( getExpandableListView().setGroupIndicator(
getResources().getDrawable(R.drawable.expander_group)); getResources().getDrawable(R.drawable.expander_group));
getExpandableListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() { /*getExpandableListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
@Override @Override
public void onChildViewAdded(View parent, View child) { public void onChildViewAdded(View parent, View child) {
// auto-expand adapter // auto-expand adapter
@ -233,7 +233,7 @@ public class FilterListActivity extends ExpandableListActivity {
public void onChildViewRemoved(View parent, View child) { public void onChildViewRemoved(View parent, View child) {
// do nothing // do nothing
} }
}); });*/
} }
/** /**

@ -174,7 +174,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener {
if(extras.containsKey(TOKEN_FILTER_VALUES)) if(extras.containsKey(TOKEN_FILTER_VALUES))
values = AndroidUtilities.contentValuesFromString(extras.getString(TOKEN_FILTER_VALUES)); values = AndroidUtilities.contentValuesFromString(extras.getString(TOKEN_FILTER_VALUES));
filter = new Filter("", "", title, new QueryTemplate(), values); //$NON-NLS-1$ //$NON-NLS-2$ filter = new Filter("", title, new QueryTemplate(), values); //$NON-NLS-1$ //$NON-NLS-2$
filter.sqlQuery = sql; filter.sqlQuery = sql;
} else { } else {
filter = CoreFilterExposer.buildInboxFilter(getResources()); filter = CoreFilterExposer.buildInboxFilter(getResources());

@ -52,6 +52,11 @@ public class MetadataDao extends GenericDao<Metadata> {
return Metadata.KEY.eq(key); return Metadata.KEY.eq(key);
} }
/** Returns all metadata associated with a given key */
public static Criterion byTaskAndwithKey(long taskId, String key) {
return Criterion.and(withKey(key), byTask(taskId));
}
} }

@ -8,13 +8,12 @@ import android.app.AlertDialog;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.timsu.astrid.sync.SynchronizationService;
import com.timsu.astrid.utilities.BackupService; import com.timsu.astrid.utilities.BackupService;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.ContextManager;
@ -105,10 +104,6 @@ public class StartupService {
am.setInexactRepeating(AlarmManager.RTC, 0, am.setInexactRepeating(AlarmManager.RTC, 0,
Constants.WIDGET_UPDATE_INTERVAL, pendingIntent); Constants.WIDGET_UPDATE_INTERVAL, pendingIntent);
// start synchronization service
if(Constants.SYNCHRONIZE)
SynchronizationService.scheduleService(context);
// start backup service // start backup service
BackupService.scheduleService(context); BackupService.scheduleService(context);

@ -2,6 +2,8 @@ package com.todoroo.astrid.utility;
public final class Constants { public final class Constants {
// --- general application constants
/** /**
* Flurry API Key * Flurry API Key
*/ */
@ -17,11 +19,6 @@ public final class Constants {
*/ */
public static final boolean OEM = false; public static final boolean OEM = false;
/**
* Whether to display synchronization options
*/
public static final boolean SYNCHRONIZE = !OEM;
/** /**
* Interval to update the widget (in order to detect hidden tasks * Interval to update the widget (in order to detect hidden tasks
* becoming visible) * becoming visible)
@ -31,5 +28,11 @@ public final class Constants {
/** /**
* Whether to turn on debugging logging and UI * Whether to turn on debugging logging and UI
*/ */
public static final boolean DEBUG = false; public static final boolean DEBUG = true;
// --- notification id's
/** Notification Manager id for RMilk notifications */
public static final int NOTIFICATION_SYNC = -1;
} }

Loading…
Cancel
Save