Skip to content

Commit

Permalink
Merge pull request #186 from sbosley/120611_sb_dual_sync_improvements
Browse files Browse the repository at this point in the history
Gtasks+Astrid.com sync improvements
  • Loading branch information
sbosley committed Jun 16, 2012
2 parents 283603c + 66df75c commit 692aa20
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 56 deletions.
11 changes: 8 additions & 3 deletions api/src/com/todoroo/andlib/sql/Join.java
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;


import static com.todoroo.andlib.sql.SqlConstants.AND;
import static com.todoroo.andlib.sql.SqlConstants.JOIN; import static com.todoroo.andlib.sql.SqlConstants.JOIN;
import static com.todoroo.andlib.sql.SqlConstants.ON; import static com.todoroo.andlib.sql.SqlConstants.ON;
import static com.todoroo.andlib.sql.SqlConstants.SPACE; import static com.todoroo.andlib.sql.SqlConstants.SPACE;
Expand Down Expand Up @@ -32,12 +33,16 @@ public static Join out(SqlTable table, Criterion... criterions) {
} }


@Override @Override
@SuppressWarnings("nls")
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(joinType).append(SPACE).append(JOIN).append(SPACE).append(joinTable).append(SPACE).append(ON); sb.append(joinType).append(SPACE).append(JOIN).append(SPACE).append(joinTable).append(SPACE).append(ON).append(SPACE).append("(");
for (Criterion criterion : criterions) { for (int i = 0; i < criterions.length; i++) {
sb.append(SPACE).append(criterion); sb.append(criterions[i]);
if (i < criterions.length - 1)
sb.append(SPACE).append(AND).append(SPACE);
} }
sb.append(")");
return sb.toString(); return sb.toString();
} }
} }
2 changes: 2 additions & 0 deletions astrid/plugin-src/com/timsu/astrid/C2DMReceiver.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ protected void handleWebUpdate(Intent intent) {
task.readFromCursor(cursor); task.readFromCursor(cursor);
} }


task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
actFmSyncService.fetchTask(task); actFmSyncService.fetchTask(task);
} catch(NumberFormatException e) { } catch(NumberFormatException e) {
// invalid task id // invalid task id
Expand Down Expand Up @@ -300,6 +301,7 @@ private Intent createTaskIntent(Intent intent) {
task.setValue(Task.TITLE, intent.getStringExtra("title")); task.setValue(Task.TITLE, intent.getStringExtra("title"));
task.setValue(Task.REMOTE_ID, Long.parseLong(intent.getStringExtra("task_id"))); task.setValue(Task.REMOTE_ID, Long.parseLong(intent.getStringExtra("task_id")));
task.setValue(Task.USER_ID, Task.USER_ID_UNASSIGNED); task.setValue(Task.USER_ID, Task.USER_ID_UNASSIGNED);
task.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
task.putTransitory(SyncFlags.ACTFM_SUPPRESS_SYNC, true); task.putTransitory(SyncFlags.ACTFM_SUPPRESS_SYNC, true);
taskService.save(task); taskService.save(task);


Expand Down
22 changes: 20 additions & 2 deletions astrid/plugin-src/com/todoroo/astrid/actfm/ActFmPreferences.java
Original file line number Original file line Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.todoroo.astrid.actfm; package com.todoroo.astrid.actfm;


import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;


import com.timsu.astrid.R; import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.actfm.sync.ActFmSyncV2Provider; import com.todoroo.astrid.actfm.sync.ActFmSyncV2Provider;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.sync.SyncProviderPreferences; import com.todoroo.astrid.sync.SyncProviderPreferences;
import com.todoroo.astrid.sync.SyncProviderUtilities; import com.todoroo.astrid.sync.SyncProviderUtilities;


Expand All @@ -22,6 +25,7 @@
public class ActFmPreferences extends SyncProviderPreferences { public class ActFmPreferences extends SyncProviderPreferences {


@Autowired ActFmPreferenceService actFmPreferenceService; @Autowired ActFmPreferenceService actFmPreferenceService;
@Autowired GtasksPreferenceService gtasksPreferenceService;


@Override @Override
public int getPreferenceResource() { public int getPreferenceResource() {
Expand All @@ -31,14 +35,28 @@ public int getPreferenceResource() {
@Override @Override
public void startSync() { public void startSync() {
if (!actFmPreferenceService.isLoggedIn()) { if (!actFmPreferenceService.isLoggedIn()) {
Intent intent = new Intent(this, ActFmLoginActivity.class); if (gtasksPreferenceService.isLoggedIn()) {
startActivityForResult(intent, REQUEST_LOGIN); DialogUtilities.okCancelDialog(this, getString(R.string.DLG_warning), getString(R.string.actfm_dual_sync_warning),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startLogin();
}
}, null);
} else {
startLogin();
}
} else { } else {
setResult(RESULT_CODE_SYNCHRONIZE); setResult(RESULT_CODE_SYNCHRONIZE);
finish(); finish();
} }
} }


private void startLogin() {
Intent intent = new Intent(this, ActFmLoginActivity.class);
startActivityForResult(intent, REQUEST_LOGIN);
}

@Override @Override
public void logOut() { public void logOut() {
new ActFmSyncV2Provider().signOut(); new ActFmSyncV2Provider().signOut();
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
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.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
Expand All @@ -59,6 +60,8 @@
import com.todoroo.astrid.data.Update; import com.todoroo.astrid.data.Update;
import com.todoroo.astrid.data.User; import com.todoroo.astrid.data.User;
import com.todoroo.astrid.files.FileMetadata; import com.todoroo.astrid.files.FileMetadata;
import com.todoroo.astrid.gtasks.GtasksMetadata;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.helper.ImageDiskCache; import com.todoroo.astrid.helper.ImageDiskCache;
import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsConstants;
Expand All @@ -84,6 +87,7 @@ public final class ActFmSyncService {
@Autowired MetadataService metadataService; @Autowired MetadataService metadataService;
@Autowired TaskService taskService; @Autowired TaskService taskService;
@Autowired ActFmPreferenceService actFmPreferenceService; @Autowired ActFmPreferenceService actFmPreferenceService;
@Autowired GtasksPreferenceService gtasksPreferenceService;
@Autowired ActFmInvoker actFmInvoker; @Autowired ActFmInvoker actFmInvoker;
@Autowired ActFmDataService actFmDataService; @Autowired ActFmDataService actFmDataService;
@Autowired TaskDao taskDao; @Autowired TaskDao taskDao;
Expand Down Expand Up @@ -1139,6 +1143,11 @@ else if(remote.isCompleted())
remote.putTransitory(SyncFlags.ACTFM_SUPPRESS_SYNC, true); remote.putTransitory(SyncFlags.ACTFM_SUPPRESS_SYNC, true);
if (remote.getValue(Task.USER_ID) != Task.USER_ID_SELF) if (remote.getValue(Task.USER_ID) != Task.USER_ID_SELF)
remote.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true); remote.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);

if (!remote.isSaved() && gtasksPreferenceService.isLoggedIn()) {
titleMatchOnGoogleTask(remote);
}

taskService.save(remote); taskService.save(remote);
ids.add(remote.getId()); ids.add(remote.getId());
metadataService.synchronizeMetadata(remote.getId(), metadata, MetadataCriteria.withKey(TagService.KEY)); metadataService.synchronizeMetadata(remote.getId(), metadata, MetadataCriteria.withKey(TagService.KEY));
Expand All @@ -1152,6 +1161,21 @@ else if(remote.isCompleted())
} }
} }


private void titleMatchOnGoogleTask(Task remote) {
String title = remote.getValue(Task.TITLE);
TodorooCursor<Task> match = taskService.query(Query.select(Task.ID)
.join(Join.inner(Metadata.TABLE, Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), Metadata.TASK.eq(Task.ID))))
.where(Criterion.and(Task.TITLE.eq(title), Task.REMOTE_ID.isNull())));
try {
if (match.getCount() > 0) {
match.moveToFirst();
remote.setId(match.get(Task.ID));
}
} finally {
match.close();
}
}

protected void deleteExtras(Long[] localIds) { protected void deleteExtras(Long[] localIds) {
taskService.deleteWhere(Criterion.and(TaskCriteria.activeVisibleMine(), taskService.deleteWhere(Criterion.and(TaskCriteria.activeVisibleMine(),
Task.REMOTE_ID.isNotNull(), Task.REMOTE_ID.isNotNull(),
Expand Down
22 changes: 20 additions & 2 deletions astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferences.java
Original file line number Original file line Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;


import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;


import com.timsu.astrid.R; 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.utility.DialogUtilities;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider; import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider;
import com.todoroo.astrid.sync.SyncProviderPreferences; import com.todoroo.astrid.sync.SyncProviderPreferences;
Expand All @@ -21,6 +24,7 @@
public class GtasksPreferences extends SyncProviderPreferences { public class GtasksPreferences extends SyncProviderPreferences {


@Autowired private GtasksPreferenceService gtasksPreferenceService; @Autowired private GtasksPreferenceService gtasksPreferenceService;
@Autowired private ActFmPreferenceService actFmPreferenceService;


public GtasksPreferences() { public GtasksPreferences() {
super(); super();
Expand All @@ -40,14 +44,28 @@ public int getPreferenceResource() {
@Override @Override
public void startSync() { public void startSync() {
if (!gtasksPreferenceService.isLoggedIn()) { if (!gtasksPreferenceService.isLoggedIn()) {
Intent intent = new Intent(this, GtasksLoginActivity.class); if (actFmPreferenceService.isLoggedIn()) {
startActivityForResult(intent, REQUEST_LOGIN); DialogUtilities.okCancelDialog(this, getString(R.string.DLG_warning), getString(R.string.gtasks_dual_sync_warning),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startLogin();
}
}, null);
} else {
startLogin();
}
} else { } else {
setResult(RESULT_CODE_SYNCHRONIZE); setResult(RESULT_CODE_SYNCHRONIZE);
finish(); finish();
} }
} }


private void startLogin() {
Intent intent = new Intent(this, GtasksLoginActivity.class);
startActivityForResult(intent, REQUEST_LOGIN);
}

@Override @Override
public void logOut() { public void logOut() {
GtasksSyncV2Provider.getInstance().signOut(); GtasksSyncV2Provider.getInstance().signOut();
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ public void onModelUpdated(final Task model) {
return; return;


Task toPush = taskDao.fetch(model.getId(), TASK_PROPERTIES); Task toPush = taskDao.fetch(model.getId(), TASK_PROPERTIES);
if (toPush.getValue(Task.USER_ID) != Task.USER_ID_SELF)
return;

operationQueue.offer(new TaskPushOp(toPush)); operationQueue.offer(new TaskPushOp(toPush));
} }
}); });
Expand Down Expand Up @@ -154,7 +151,7 @@ public void waitUntilEmpty() {
} }


private static final Property<?>[] TASK_PROPERTIES = { Task.ID, Task.TITLE, private static final Property<?>[] TASK_PROPERTIES = { Task.ID, Task.TITLE,
Task.NOTES, Task.DUE_DATE, Task.COMPLETION_DATE, Task.DELETION_DATE }; Task.NOTES, Task.DUE_DATE, Task.COMPLETION_DATE, Task.DELETION_DATE, Task.USER_ID };


/** /**
* Checks to see if any of the values changed are among the properties we sync * Checks to see if any of the values changed are among the properties we sync
Expand Down Expand Up @@ -197,6 +194,18 @@ public void pushTaskOnSave(Task task, ContentValues values, GtasksInvoker invoke
com.google.api.services.tasks.model.Task remoteModel = null; com.google.api.services.tasks.model.Task remoteModel = null;
boolean newlyCreated = false; boolean newlyCreated = false;


if (values.containsKey(Task.USER_ID.name) && values.getAsLong(Task.USER_ID.name) != Task.USER_ID_SELF) {
if (gtasksMetadata != null && !TextUtils.isEmpty(gtasksMetadata.getValue(GtasksMetadata.ID))) {
try {
invoker.deleteGtask(gtasksMetadata.getValue(GtasksMetadata.LIST_ID), gtasksMetadata.getValue(GtasksMetadata.ID));
metadataDao.delete(gtasksMetadata.getId());
} catch (IOException e) {
//
}
}
return;
}

String remoteId = null; String remoteId = null;
String listId = Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST); String listId = Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST);
if (listId == null) { if (listId == null) {
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences; import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.StoreObjectDao; import com.todoroo.astrid.dao.StoreObjectDao;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
@Autowired TaskService taskService; @Autowired TaskService taskService;
@Autowired MetadataService metadataService; @Autowired MetadataService metadataService;
@Autowired StoreObjectDao storeObjectDao; @Autowired StoreObjectDao storeObjectDao;
@Autowired ActFmPreferenceService actFmPreferenceService;
@Autowired GtasksPreferenceService gtasksPreferenceService; @Autowired GtasksPreferenceService gtasksPreferenceService;
@Autowired GtasksSyncService gtasksSyncService; @Autowired GtasksSyncService gtasksSyncService;
@Autowired GtasksListService gtasksListService; @Autowired GtasksListService gtasksListService;
Expand Down Expand Up @@ -126,32 +128,30 @@ public void run() {
callback.incrementMax(25 * lists.length); callback.incrementMax(25 * lists.length);
final AtomicInteger finisher = new AtomicInteger(lists.length); final AtomicInteger finisher = new AtomicInteger(lists.length);


pushUpdated(invoker, callback);

for (final StoreObject list : lists) { for (final StoreObject list : lists) {
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
synchronizeListHelper(list, invoker, manual, handler, callback); synchronizeListHelper(list, invoker, manual, handler, callback);
callback.incrementProgress(25); callback.incrementProgress(25);
if (finisher.decrementAndGet() == 0) { if (finisher.decrementAndGet() == 0) {
pushUpdated(invoker, callback);
finishSync(callback); finishSync(callback);
} }
} }
}).start(); }).start();
} }

} }
}).start(); }).start();
} }


private synchronized void pushUpdated(GtasksInvoker invoker, SyncResultCallback callback) { private synchronized void pushUpdated(GtasksInvoker invoker, SyncResultCallback callback) {
TodorooCursor<Task> queued = taskService.query(Query.select(Task.PROPERTIES). TodorooCursor<Task> queued = taskService.query(Query.select(Task.PROPERTIES).
join(Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK))).where( join(Join.left(Metadata.TABLE, Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))).where(
Criterion.and(Task.USER_ID.eq(Task.USER_ID_SELF), Criterion.or(Task.MODIFICATION_DATE.gt(GtasksMetadata.LAST_SYNC),
Criterion.or( Criterion.and(Task.USER_ID.neq(Task.USER_ID_SELF), GtasksMetadata.ID.isNotNull()),
Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Metadata.KEY.isNull())));
Task.MODIFICATION_DATE.gt(GtasksMetadata.LAST_SYNC)),
Metadata.KEY.isNull()))));
callback.incrementMax(queued.getCount() * 10); callback.incrementMax(queued.getCount() * 10);
try { try {
Task task = new Task(); Task task = new Task();
Expand Down Expand Up @@ -303,6 +303,9 @@ else if (remoteTask.getDeleted().booleanValue())


private void write(GtasksTaskContainer task) throws IOException { private void write(GtasksTaskContainer task) throws IOException {
// merge astrid dates with google dates // merge astrid dates with google dates
if (!task.task.isSaved() && actFmPreferenceService.isLoggedIn())
titleMatchWithActFm(task.task);

if(task.task.isSaved()) { if(task.task.isSaved()) {
Task local = PluginServices.getTaskService().fetchById(task.task.getId(), Task.DUE_DATE, Task.COMPLETION_DATE); Task local = PluginServices.getTaskService().fetchById(task.task.getId(), Task.DUE_DATE, Task.COMPLETION_DATE);
if (local == null) { if (local == null) {
Expand All @@ -312,7 +315,9 @@ private void write(GtasksTaskContainer task) throws IOException {
if(task.task.isCompleted() && !local.isCompleted()) if(task.task.isCompleted() && !local.isCompleted())
StatisticsService.reportEvent(StatisticsConstants.GTASKS_TASK_COMPLETED); StatisticsService.reportEvent(StatisticsConstants.GTASKS_TASK_COMPLETED);
} }
} else { // Set default reminders for remotely created tasks } else { // Set default importance and reminders for remotely created tasks
task.task.setValue(Task.IMPORTANCE, Preferences.getIntegerFromString(
R.string.p_default_importance_key, Task.IMPORTANCE_SHOULD_DO));
TaskDao.setDefaultReminders(task.task); TaskDao.setDefaultReminders(task.task);
} }
if (!TextUtils.isEmpty(task.task.getValue(Task.TITLE))) { if (!TextUtils.isEmpty(task.task.getValue(Task.TITLE))) {
Expand All @@ -321,6 +326,21 @@ private void write(GtasksTaskContainer task) throws IOException {
} }
} }


private void titleMatchWithActFm(Task task) {
String title = task.getValue(Task.TITLE);
TodorooCursor<Task> match = taskService.query(Query.select(Task.ID)
.join(Join.left(Metadata.TABLE, Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), Metadata.TASK.eq(Task.ID))))
.where(Criterion.and(Task.TITLE.eq(title), GtasksMetadata.ID.isNull())));
try {
if (match.getCount() > 0) {
match.moveToFirst();
task.setId(match.get(Task.ID));
}
} finally {
match.close();
}
}

private void mergeDates(Task remote, Task local) { private void mergeDates(Task remote, Task local) {
if(remote.hasDueDate() && local.hasDueTime()) { if(remote.hasDueDate() && local.hasDueTime()) {
Date newDate = new Date(remote.getValue(Task.DUE_DATE)); Date newDate = new Date(remote.getValue(Task.DUE_DATE));
Expand Down
4 changes: 4 additions & 0 deletions astrid/res/values/strings-actfm.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -287,5 +287,9 @@
<!-- text for notification when comments are received --> <!-- text for notification when comments are received -->
<string name="actfm_notification_comments">New comments received / click for more details</string> <string name="actfm_notification_comments">New comments received / click for more details</string>


<string name="actfm_dual_sync_warning">You are currently synchronizing with Google Tasks.

This comment has been minimized.

Copy link
@jonparis

jonparis Dec 5, 2012

Member

I am not sure when / who decided to put this line in but I don't think it was a good thing. We need to revisit.

Be advised that synchronizing with both services can in some cases lead to unexpected results.
Are you sure you want to sync with Astrid.com?</string>

</resources> </resources>


2 changes: 2 additions & 0 deletions astrid/res/values/strings-core.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@


<string name="DLG_undo">Undo</string> <string name="DLG_undo">Undo</string>


<string name="DLG_warning">Warning</string>

<!-- =============================================================== UI == --> <!-- =============================================================== UI == -->


<!-- Label for DateButtons with no value --> <!-- Label for DateButtons with no value -->
Expand Down
3 changes: 3 additions & 0 deletions astrid/res/values/strings-gtasks.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
<!-- Error when authorization error happens in background sync --> <!-- Error when authorization error happens in background sync -->
<string name="gtasks_error_background_sync_auth">Error authenticating in background. Please try initiating a sync while Astrid is running.</string> <string name="gtasks_error_background_sync_auth">Error authenticating in background. Please try initiating a sync while Astrid is running.</string>


<string name="gtasks_dual_sync_warning">You are currently synchronizing with Astrid.com.
Be advised that synchronizing with both services can in some cases lead to unexpected results.
Are you sure you want to sync with Google Tasks?</string>


</resources> </resources>


Loading

0 comments on commit 692aa20

Please sign in to comment.