Implement caldav service discovery

pull/699/head
Alex Baker 8 years ago
parent cc1bf1f083
commit f8fab59be9

@ -0,0 +1,877 @@
{
"formatVersion": 1,
"database": {
"version": 58,
"identityHash": "119477b5c2bd3389e53fef40e4844e01",
"entities": [
{
"tableName": "notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `type` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "taskId",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_notification_task",
"unique": true,
"columnNames": [
"task"
],
"createSql": "CREATE UNIQUE INDEX `index_notification_task` ON `${TABLE_NAME}` (`task`)"
}
],
"foreignKeys": []
},
{
"tableName": "tagdata",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `name` TEXT, `color` INTEGER, `tagOrdering` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "tagOrdering",
"columnName": "tagOrdering",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "userActivity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `message` TEXT, `picture` TEXT, `target_id` TEXT, `created_at` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "picture",
"columnName": "picture",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "targetId",
"columnName": "target_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created_at",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "task_attachments",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `task_id` TEXT, `name` TEXT, `path` TEXT, `content_type` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "taskId",
"columnName": "task_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "task_list_metadata",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `tag_uuid` TEXT, `filter` TEXT, `task_ids` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tagUuid",
"columnName": "tag_uuid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "filter",
"columnName": "filter",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "taskIds",
"columnName": "task_ids",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `title` TEXT, `importance` INTEGER, `dueDate` INTEGER, `hideUntil` INTEGER, `created` INTEGER, `modified` INTEGER, `completed` INTEGER, `deleted` INTEGER, `notes` TEXT, `estimatedSeconds` INTEGER, `elapsedSeconds` INTEGER, `timerStart` INTEGER, `notificationFlags` INTEGER, `notifications` INTEGER, `lastNotified` INTEGER, `snoozeTime` INTEGER, `recurrence` TEXT, `repeatUntil` INTEGER, `calendarUri` TEXT, `remoteId` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "importance",
"columnName": "importance",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "dueDate",
"columnName": "dueDate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "hideUntil",
"columnName": "hideUntil",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "modified",
"columnName": "modified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "completed",
"columnName": "completed",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "notes",
"columnName": "notes",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "estimatedSeconds",
"columnName": "estimatedSeconds",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "elapsedSeconds",
"columnName": "elapsedSeconds",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timerStart",
"columnName": "timerStart",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "notificationFlags",
"columnName": "notificationFlags",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "notifications",
"columnName": "notifications",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastNotified",
"columnName": "lastNotified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "snoozeTime",
"columnName": "snoozeTime",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "recurrence",
"columnName": "recurrence",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "repeatUntil",
"columnName": "repeatUntil",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "calendarUri",
"columnName": "calendarUri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "t_rid",
"unique": true,
"columnNames": [
"remoteId"
],
"createSql": "CREATE UNIQUE INDEX `t_rid` ON `${TABLE_NAME}` (`remoteId`)"
}
],
"foreignKeys": []
},
{
"tableName": "alarms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `time` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "locations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `radius` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "latitude",
"columnName": "latitude",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "longitude",
"columnName": "longitude",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "radius",
"columnName": "radius",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "tags",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `tag_uid` TEXT, `task_uid` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tagUid",
"columnName": "tag_uid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "taskUid",
"columnName": "task_uid",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "google_tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `remote_id` TEXT, `list_id` TEXT, `parent` INTEGER NOT NULL, `indent` INTEGER NOT NULL, `order` INTEGER NOT NULL, `remote_order` INTEGER NOT NULL, `last_sync` INTEGER NOT NULL, `deleted` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "remoteId",
"columnName": "remote_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "listId",
"columnName": "list_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "parent",
"columnName": "parent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "indent",
"columnName": "indent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "remoteOrder",
"columnName": "remote_order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "last_sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "filters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `sql` TEXT, `values` TEXT, `criterion` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sql",
"columnName": "sql",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "values",
"columnName": "values",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "criterion",
"columnName": "criterion",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "google_task_lists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `remote_id` TEXT, `title` TEXT, `remote_order` INTEGER NOT NULL, `last_sync` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `color` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "remoteId",
"columnName": "remote_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "remoteOrder",
"columnName": "remote_order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "last_sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "caldav_calendar",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` TEXT, `uuid` TEXT, `name` TEXT, `color` INTEGER NOT NULL, `ctag` TEXT, `url` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctag",
"columnName": "ctag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "caldav_tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `calendar` TEXT, `object` TEXT, `remote_id` TEXT, `etag` TEXT, `last_sync` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `vtodo` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "task",
"columnName": "task",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "account",
"columnName": "calendar",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "object",
"columnName": "object",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remote_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "etag",
"columnName": "etag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastSync",
"columnName": "last_sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "vtodo",
"columnName": "vtodo",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "caldav_account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT, `name` TEXT, `url` TEXT, `username` TEXT, `password` TEXT, `iv` BLOB)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "iv",
"columnName": "iv",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"119477b5c2bd3389e53fef40e4844e01\")"
]
}
}

@ -300,7 +300,13 @@
<activity android:name=".activities.FilterSettingsActivity"/> <activity android:name=".activities.FilterSettingsActivity"/>
<activity android:name=".caldav.CaldavSettingsActivity"/> <activity
android:name=".caldav.CaldavAccountSettingsActivity"
android:theme="@style/Tasks"/>
<activity
android:name=".caldav.CaldavCalendarSettingsActivity"
android:theme="@style/Tasks"/>
<activity <activity
android:name=".activities.CalendarSelectionActivity" android:name=".activities.CalendarSelectionActivity"

@ -45,7 +45,7 @@ import org.tasks.activities.TagSettingsActivity;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking; import org.tasks.analytics.Tracking;
import org.tasks.caldav.CaldavListFragment; import org.tasks.caldav.CaldavListFragment;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskList; import org.tasks.data.GoogleTaskList;
import org.tasks.data.TagData; import org.tasks.data.TagData;
@ -341,9 +341,9 @@ public class TaskListActivity extends InjectingAppCompatActivity
} }
} else if (filter instanceof CaldavFilter) { } else if (filter instanceof CaldavFilter) {
CaldavFilter caldavFilter = (CaldavFilter) filter; CaldavFilter caldavFilter = (CaldavFilter) filter;
CaldavAccount account = caldavDao.getByUuid(caldavFilter.getUuid()); CaldavCalendar calendar = caldavDao.getCalendarByUuid(caldavFilter.getUuid());
if (account != null) { if (calendar != null) {
return CaldavListFragment.newCaldavListFragment(caldavFilter, account); return CaldavListFragment.newCaldavListFragment(caldavFilter, calendar);
} }
} else if (filter != null) { } else if (filter != null) {
return subtasksHelper.shouldUseSubtasksFragmentForFilter(filter) return subtasksHelper.shouldUseSubtasksFragmentForFilter(filter)

@ -6,7 +6,6 @@
package com.todoroo.astrid.activity; package com.todoroo.astrid.activity;
import static android.support.v4.content.ContextCompat.getColor; import static android.support.v4.content.ContextCompat.getColor;
import static com.todoroo.astrid.adapter.FilterAdapter.REQUEST_PURCHASE;
import android.app.Activity; import android.app.Activity;
import android.arch.lifecycle.ViewModelProviders; import android.arch.lifecycle.ViewModelProviders;
@ -54,8 +53,6 @@ import org.tasks.R;
import org.tasks.activities.FilterSettingsActivity; import org.tasks.activities.FilterSettingsActivity;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking; import org.tasks.analytics.Tracking;
import org.tasks.billing.Inventory;
import org.tasks.billing.PurchaseActivity;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.SortDialog; import org.tasks.dialogs.SortDialog;
import org.tasks.injection.ForActivity; import org.tasks.injection.ForActivity;
@ -70,7 +67,6 @@ import org.tasks.ui.CheckBoxes;
import org.tasks.ui.MenuColorizer; import org.tasks.ui.MenuColorizer;
import org.tasks.ui.ProgressDialogAsyncTask; import org.tasks.ui.ProgressDialogAsyncTask;
import org.tasks.ui.TaskListViewModel; import org.tasks.ui.TaskListViewModel;
import timber.log.Timber;
/** /**
* Primary activity for the Bente application. Shows a list of upcoming tasks and a user's coaches. * Primary activity for the Bente application. Shows a list of upcoming tasks and a user's coaches.
@ -104,7 +100,6 @@ public class TaskListFragment extends InjectingFragment
@Inject ViewHolderFactory viewHolderFactory; @Inject ViewHolderFactory viewHolderFactory;
@Inject LocalBroadcastManager localBroadcastManager; @Inject LocalBroadcastManager localBroadcastManager;
@Inject Device device; @Inject Device device;
@Inject Inventory inventory;
@BindView(R.id.swipe_layout) @BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout; SwipeRefreshLayout swipeRefreshLayout;
@ -232,7 +227,6 @@ public class TaskListFragment extends InjectingFragment
} }
private void setupMenu(Menu menu) { private void setupMenu(Menu menu) {
menu.findItem(R.id.menu_purchase).setVisible(!inventory.hasPro());
MenuItem hidden = menu.findItem(R.id.menu_show_hidden); MenuItem hidden = menu.findItem(R.id.menu_show_hidden);
if (preferences.getBoolean(R.string.p_show_hidden_tasks, false)) { if (preferences.getBoolean(R.string.p_show_hidden_tasks, false)) {
hidden.setChecked(true); hidden.setChecked(true);
@ -319,9 +313,6 @@ public class TaskListFragment extends InjectingFragment
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show(); .show();
return true; return true;
case R.id.menu_purchase:
startActivityForResult(new Intent(context, PurchaseActivity.class), REQUEST_PURCHASE);
return true;
default: default:
return onOptionsItemSelected(item); return onOptionsItemSelected(item);
} }
@ -531,10 +522,6 @@ public class TaskListFragment extends InjectingFragment
activity.recreate(); activity.recreate();
} }
} }
} else if (requestCode == REQUEST_PURCHASE) {
if (inventory.hasPro()) {
((TaskListActivity) getActivity()).restart();
}
} else { } else {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }

@ -18,6 +18,8 @@ import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.content.res.ResourcesCompat; import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.util.Pair;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -38,7 +40,6 @@ import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity; import org.tasks.activities.TagSettingsActivity;
import org.tasks.billing.Inventory; import org.tasks.billing.Inventory;
import org.tasks.billing.PurchaseActivity; import org.tasks.billing.PurchaseActivity;
import org.tasks.caldav.CaldavSettingsActivity;
import org.tasks.filters.FilterCounter; import org.tasks.filters.FilterCounter;
import org.tasks.filters.FilterProvider; import org.tasks.filters.FilterProvider;
import org.tasks.filters.NavigationDrawerAction; import org.tasks.filters.NavigationDrawerAction;
@ -258,13 +259,19 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
item.icon = R.drawable.ic_cloud_off_black_24dp; item.icon = R.drawable.ic_cloud_off_black_24dp;
add(item); add(item);
String title = preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME); String googleTaskTitle = preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME);
if (Strings.isNullOrEmpty(title)) { if (Strings.isNullOrEmpty(googleTaskTitle)) {
title = activity.getResources().getString(R.string.gtasks_GPr_header); googleTaskTitle = activity.getResources().getString(R.string.gtasks_GPr_header);
} }
addSubMenu(title, filterProvider.getGoogleTaskFilters(), true); addSubMenu(googleTaskTitle, filterProvider.getGoogleTaskFilters(), true);
addSubMenu(R.string.CalDAV, filterProvider.getCaldavFilters(), true); for (Pair<String, List<Filter>> account : filterProvider.getCaldavFilters()) {
String caldavTitle = account.first;
if (TextUtils.isEmpty(caldavTitle)) {
caldavTitle = activity.getString(R.string.CalDAV);
}
addSubMenu(caldavTitle, account.second, true);
}
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -314,17 +321,12 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
} }
} }
if (preferences.getBoolean(R.string.p_sync_caldav, false)) { for (Pair<String, List<Filter>> account : filterProvider.getCaldavFilters()) {
addSubMenu(R.string.CalDAV, filterProvider.getCaldavFilters(), false); String title = account.first;
if (TextUtils.isEmpty(title)) {
if (navigationDrawer) { title = activity.getString(R.string.CalDAV);
add(
new NavigationDrawerAction(
activity.getResources().getString(R.string.add_account),
R.drawable.ic_add_24dp,
new Intent(activity, CaldavSettingsActivity.class),
NavigationDrawerFragment.REQUEST_NEW_CALDAV_ACCOUNT));
} }
addSubMenu(title, account.second, true);
} }
if (navigationDrawer) { if (navigationDrawer) {

@ -10,7 +10,7 @@ import com.todoroo.astrid.data.Task;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavTask; import org.tasks.data.CaldavTask;
public class CaldavFilter extends Filter { public class CaldavFilter extends Filter {
@ -35,49 +35,49 @@ public class CaldavFilter extends Filter {
}; };
private static final int TAG = R.drawable.ic_cloud_black_24dp; private static final int TAG = R.drawable.ic_cloud_black_24dp;
private CaldavAccount account; private CaldavCalendar calendar;
private CaldavFilter() { private CaldavFilter() {
super(); super();
} }
public CaldavFilter(CaldavAccount account) { public CaldavFilter(CaldavCalendar calendar) {
super(account.getName(), queryTemplate(account), getValuesForNewTask(account)); super(calendar.getName(), queryTemplate(calendar), getValuesForNewTask(calendar));
this.account = account; this.calendar = calendar;
tint = account.getColor(); tint = calendar.getColor();
icon = TAG; icon = TAG;
} }
private static QueryTemplate queryTemplate(CaldavAccount caldavAccount) { private static QueryTemplate queryTemplate(CaldavCalendar caldavCalendar) {
return new QueryTemplate() return new QueryTemplate()
.join(Join.left(CaldavTask.TABLE, Task.ID.eq(Field.field("caldav_tasks.task")))) .join(Join.left(CaldavTask.TABLE, Task.ID.eq(Field.field("caldav_tasks.task"))))
.where( .where(
Criterion.and( Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(), TaskDao.TaskCriteria.activeAndVisible(),
Field.field("account").eq(caldavAccount.getUuid()))); Field.field("calendar").eq(caldavCalendar.getUuid())));
} }
private static Map<String, Object> getValuesForNewTask(CaldavAccount caldavAccount) { private static Map<String, Object> getValuesForNewTask(CaldavCalendar caldavCalendar) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put(CaldavTask.KEY, caldavAccount.getUuid()); result.put(CaldavTask.KEY, caldavCalendar.getUuid());
return result; return result;
} }
public String getUuid() { public String getUuid() {
return account.getUuid(); return calendar.getUuid();
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
dest.writeParcelable(account, 0); dest.writeParcelable(calendar, 0);
} }
@Override @Override
protected void readFromParcel(Parcel source) { protected void readFromParcel(Parcel source) {
super.readFromParcel(source); super.readFromParcel(source);
account = source.readParcelable(getClass().getClassLoader()); calendar = source.readParcelable(getClass().getClassLoader());
} }
@Override @Override

@ -15,6 +15,7 @@ import org.tasks.analytics.Tracking;
import org.tasks.data.Alarm; import org.tasks.data.Alarm;
import org.tasks.data.AlarmDao; import org.tasks.data.AlarmDao;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask; import org.tasks.data.CaldavTask;
import org.tasks.data.Filter; import org.tasks.data.Filter;
@ -39,11 +40,6 @@ import org.tasks.notifications.Notification;
import org.tasks.notifications.NotificationDao; import org.tasks.notifications.NotificationDao;
import timber.log.Timber; import timber.log.Timber;
/**
* Database wrapper
*
* @author Tim Su <tim@todoroo.com>
*/
@android.arch.persistence.room.Database( @android.arch.persistence.room.Database(
entities = { entities = {
Notification.class, Notification.class,
@ -58,10 +54,11 @@ import timber.log.Timber;
GoogleTask.class, GoogleTask.class,
Filter.class, Filter.class,
GoogleTaskList.class, GoogleTaskList.class,
CaldavAccount.class, CaldavCalendar.class,
CaldavTask.class CaldavTask.class,
CaldavAccount.class
}, },
version = 57 version = 58
) )
public abstract class Database extends RoomDatabase { public abstract class Database extends RoomDatabase {

@ -27,11 +27,6 @@ import org.tasks.data.LimitOffsetDataSource;
import org.tasks.jobs.AfterSaveIntentService; import org.tasks.jobs.AfterSaveIntentService;
import timber.log.Timber; import timber.log.Timber;
/**
* Data Access layer for {@link Task}-related operations.
*
* @author Tim Su <tim@todoroo.com>
*/
@Dao @Dao
public abstract class TaskDao { public abstract class TaskDao {
@ -103,9 +98,9 @@ public abstract class TaskDao {
@android.arch.persistence.room.Query( @android.arch.persistence.room.Query(
"SELECT tasks.* FROM tasks " "SELECT tasks.* FROM tasks "
+ "LEFT JOIN caldav_tasks ON tasks._id = caldav_tasks.task " + "LEFT JOIN caldav_tasks ON tasks._id = caldav_tasks.task "
+ "WHERE caldav_tasks.account = :uid " + "WHERE caldav_tasks.calendar = :calendar "
+ "AND tasks.modified > caldav_tasks.last_sync") + "AND tasks.modified > caldav_tasks.last_sync")
public abstract List<Task> getCaldavTasksToPush(String uid); public abstract List<Task> getCaldavTasksToPush(String calendar);
@android.arch.persistence.room.Query( @android.arch.persistence.room.Query(
"SELECT * FROM TASKS " "SELECT * FROM TASKS "
@ -146,8 +141,8 @@ public abstract class TaskDao {
public abstract List<Task> getGoogleTasks(String googleTaskList); public abstract List<Task> getGoogleTasks(String googleTaskList);
@android.arch.persistence.room.Query( @android.arch.persistence.room.Query(
"SELECT tasks.* FROM tasks INNER JOIN caldav_tasks ON caldav_tasks.task = tasks._id WHERE caldav_tasks.account = :caldavAccount") "SELECT tasks.* FROM tasks INNER JOIN caldav_tasks ON caldav_tasks.task = tasks._id WHERE caldav_tasks.calendar = :calendar")
public abstract List<Task> getCaldavTasks(String caldavAccount); public abstract List<Task> getCaldavTasks(String calendar);
/** /**
* Saves the given task to the database.getDatabase(). Task must already exist. Returns true on * Saves the given task to the database.getDatabase(). Task must already exist. Returns true on

@ -19,7 +19,6 @@ public class GtasksPreferenceService {
private static final String IDENTIFIER = "gtasks"; // $NON-NLS-1$ private static final String IDENTIFIER = "gtasks"; // $NON-NLS-1$
public static final String PREF_USER_NAME = IDENTIFIER + "_user"; // $NON-NLS-1$ public static final String PREF_USER_NAME = IDENTIFIER + "_user"; // $NON-NLS-1$
private static final String PREF_LAST_SYNC = "_last_sync"; // $NON-NLS-1$ private static final String PREF_LAST_SYNC = "_last_sync"; // $NON-NLS-1$
private static final String PREF_ONGOING = "_ongoing"; // $NON-NLS-1$
private final Preferences preferences; private final Preferences preferences;
@Inject @Inject
@ -40,28 +39,17 @@ public class GtasksPreferenceService {
return preferences.getLong(IDENTIFIER + PREF_LAST_SYNC, 0); return preferences.getLong(IDENTIFIER + PREF_LAST_SYNC, 0);
} }
/** @return Last Error, or null if no last error */
public boolean isOngoing() {
return preferences.getBoolean(IDENTIFIER + PREF_ONGOING, false);
}
/** Deletes Last Successful Sync Date */ /** Deletes Last Successful Sync Date */
public void clearLastSyncDate() { public void clearLastSyncDate() {
preferences.clear(IDENTIFIER + PREF_LAST_SYNC); preferences.clear(IDENTIFIER + PREF_LAST_SYNC);
} }
/** Set Ongoing */
public void stopOngoing() {
preferences.setBoolean(IDENTIFIER + PREF_ONGOING, false);
}
/** Set Last Successful Sync Date */ /** Set Last Successful Sync Date */
public void recordSuccessfulSync() { public void recordSuccessfulSync() {
preferences.setLong(IDENTIFIER + PREF_LAST_SYNC, DateUtilities.now() + 1000); preferences.setLong(IDENTIFIER + PREF_LAST_SYNC, DateUtilities.now() + 1000);
} }
/** Set Last Attempted Sync Date */ public boolean isOngoing() {
public void recordSyncStart() { return preferences.isSyncOngoing();
preferences.setBoolean(IDENTIFIER + PREF_ONGOING, true);
} }
} }

@ -38,6 +38,8 @@ public class Tasks extends InjectingApplication {
AndroidThreeTen.init(this); AndroidThreeTen.init(this);
preferences.setSyncOngoing(false);
jobManager.addJobCreator(jobCreator); jobManager.addJobCreator(jobCreator);
flavorSetup.setup(); flavorSetup.setup();
@ -48,8 +50,6 @@ public class Tasks extends InjectingApplication {
startupService.onStartupApplication(); startupService.onStartupApplication();
gtasksPreferenceService.stopOngoing(); // if sync ongoing flag was set, clear it
jobManager.updateBackgroundSync(); jobManager.updateBackgroundSync();
jobManager.scheduleMidnightRefresh(); jobManager.scheduleMidnightRefresh();
jobManager.scheduleBackup(); jobManager.scheduleBackup();

@ -74,7 +74,7 @@ public class ColorPickerActivity extends ThemedInjectingAppCompatActivity
public void themePicked(ColorPickerDialog.Pickable picked) { public void themePicked(ColorPickerDialog.Pickable picked) {
Intent data = new Intent(); Intent data = new Intent();
data.putExtra(EXTRA_PALETTE, palette); data.putExtra(EXTRA_PALETTE, palette);
data.putExtra(EXTRA_THEME_INDEX, picked.getIndex()); data.putExtra(EXTRA_THEME_INDEX, picked == null ? -1 : picked.getIndex());
setResult(RESULT_OK, data); setResult(RESULT_OK, data);
finish(); finish();
} }

@ -5,7 +5,7 @@ import static java.util.Collections.emptyList;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.List; import java.util.List;
import org.tasks.data.Alarm; import org.tasks.data.Alarm;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavTask; import org.tasks.data.CaldavTask;
import org.tasks.data.Filter; import org.tasks.data.Filter;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
@ -22,19 +22,23 @@ class BackupContainer {
final List<TagData> tags; final List<TagData> tags;
final List<Filter> filters; final List<Filter> filters;
final List<GoogleTaskList> googleTaskLists; final List<GoogleTaskList> googleTaskLists;
final List<CaldavAccount> caldavAccounts; private final List<CaldavCalendar> caldavCalendars;
BackupContainer( BackupContainer(
List<TaskBackup> tasks, List<TaskBackup> tasks,
List<TagData> tags, List<TagData> tags,
List<Filter> filters, List<Filter> filters,
List<GoogleTaskList> googleTaskLists, List<GoogleTaskList> googleTaskLists,
List<CaldavAccount> caldavAccounts) { List<CaldavCalendar> caldavCalendars) {
this.tasks = tasks; this.tasks = tasks;
this.tags = tags; this.tags = tags;
this.filters = filters; this.filters = filters;
this.googleTaskLists = googleTaskLists; this.googleTaskLists = googleTaskLists;
this.caldavAccounts = caldavAccounts; this.caldavCalendars = caldavCalendars;
}
public List<CaldavCalendar> getCaldavCalendars() {
return caldavCalendars == null ? emptyList() : caldavCalendars;
} }
static class TaskBackup { static class TaskBackup {

@ -179,7 +179,7 @@ public class TasksJsonExporter {
tagDataDao.getAll(), tagDataDao.getAll(),
filterDao.getAll(), filterDao.getAll(),
googleTaskListDao.getAll(), googleTaskListDao.getAll(),
caldavDao.getAccounts())); caldavDao.getCalendars()));
File file = new File(output); File file = new File(output);
file.createNewFile(); file.createNewFile();

@ -18,7 +18,7 @@ import org.tasks.LocalBroadcastManager;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.Alarm; import org.tasks.data.Alarm;
import org.tasks.data.AlarmDao; import org.tasks.data.AlarmDao;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask; import org.tasks.data.CaldavTask;
import org.tasks.data.Filter; import org.tasks.data.Filter;
@ -141,9 +141,10 @@ public class TasksJsonImporter {
filterDao.insert(filter); filterDao.insert(filter);
} }
} }
for (CaldavAccount account : backupContainer.caldavAccounts) { // TODO: Add caldav accounts to backup
if (caldavDao.getByUuid(account.getUuid()) == null) { for (CaldavCalendar calendar : backupContainer.getCaldavCalendars()) {
caldavDao.insert(account); if (caldavDao.getCalendarByUuid(calendar.getUuid()) == null) {
caldavDao.insert(calendar);
} }
} }
for (BackupContainer.TaskBackup backup : backupContainer.tasks) { for (BackupContainer.TaskBackup backup : backupContainer.tasks) {

@ -4,7 +4,6 @@ import static android.text.TextUtils.isEmpty;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
@ -12,57 +11,49 @@ import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.PropertyCollection;
import at.bitfire.dav4android.exception.HttpException; import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.dav4android.property.DisplayName;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnFocusChange; import butterknife.OnFocusChange;
import butterknife.OnTextChanged; import butterknife.OnTextChanged;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.helper.UUIDHelper; import com.todoroo.astrid.helper.UUIDHelper;
import com.todoroo.astrid.service.TaskDeleter;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.IDN; import java.net.IDN;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.activities.ColorPickerActivity;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.ThemedInjectingAppCompatActivity; import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters; import org.tasks.sync.SyncAdapters;
import org.tasks.themes.ThemeCache;
import org.tasks.themes.ThemeColor;
import org.tasks.ui.DisplayableException; import org.tasks.ui.DisplayableException;
import timber.log.Timber; import timber.log.Timber;
public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity public class CaldavAccountSettingsActivity extends ThemedInjectingAppCompatActivity
implements Toolbar.OnMenuItemClickListener { implements Toolbar.OnMenuItemClickListener {
public static final String EXTRA_CALDAV_DATA = "caldavData"; // $NON-NLS-1$ public static final String EXTRA_CALDAV_DATA = "caldavData"; // $NON-NLS-1$
public static final String ACTION_RELOAD = "accountRenamed";
public static final String ACTION_DELETED = "accountDeleted";
private static final String EXTRA_CALDAV_UUID = "uuid"; // $NON-NLS-1$
private static final String EXTRA_SELECTED_THEME = "extra_selected_theme";
private static final String PASSWORD_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; private static final String PASSWORD_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
private static final int REQUEST_COLOR_PICKER = 10109;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject ThemeCache themeCache;
@Inject ThemeColor themeColor;
@Inject Tracker tracker; @Inject Tracker tracker;
@Inject CaldavDao caldavDao; @Inject CaldavDao caldavDao;
@Inject SyncAdapters syncAdapters; @Inject SyncAdapters syncAdapters;
@Inject TaskDeleter taskDeleter;
@BindView(R.id.root_layout) @BindView(R.id.root_layout)
LinearLayout root; LinearLayout root;
@ -85,38 +76,29 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
@BindView(R.id.password_layout) @BindView(R.id.password_layout)
TextInputLayout passwordLayout; TextInputLayout passwordLayout;
@BindView(R.id.color)
TextInputEditText color;
@BindView(R.id.toolbar) @BindView(R.id.toolbar)
Toolbar toolbar; Toolbar toolbar;
private CaldavAccount caldavAccount; private CaldavAccount caldavAccount;
private int selectedTheme;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_caldav_settings); setContentView(R.layout.activity_caldav_account_settings);
ButterKnife.bind(this); ButterKnife.bind(this);
caldavAccount = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA); caldavAccount = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA);
if (savedInstanceState == null) { if (savedInstanceState == null) {
if (caldavAccount == null) { if (caldavAccount != null) {
selectedTheme = -1;
} else {
selectedTheme = caldavAccount.getColor();
url.setText(caldavAccount.getUrl()); url.setText(caldavAccount.getUrl());
user.setText(caldavAccount.getUsername()); user.setText(caldavAccount.getUsername());
if (!isEmpty(caldavAccount.getPassword())) { if (!isEmpty(caldavAccount.getPassword())) {
password.setText(PASSWORD_MASK); password.setText(PASSWORD_MASK);
} }
} }
} else {
selectedTheme = savedInstanceState.getInt(EXTRA_SELECTED_THEME);
} }
final boolean backButtonSavesTask = preferences.backButtonSavesTask(); final boolean backButtonSavesTask = preferences.backButtonSavesTask();
@ -133,20 +115,16 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
save(); save();
} }
}); });
toolbar.inflateMenu(R.menu.menu_tag_settings); toolbar.inflateMenu(R.menu.menu_caldav_account_settings);
toolbar.setOnMenuItemClickListener(this); toolbar.setOnMenuItemClickListener(this);
toolbar.showOverflowMenu(); toolbar.showOverflowMenu();
color.setInputType(InputType.TYPE_NULL);
if (caldavAccount == null) { if (caldavAccount == null) {
toolbar.getMenu().findItem(R.id.delete).setVisible(false); toolbar.getMenu().findItem(R.id.remove).setVisible(false);
url.requestFocus(); url.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(url, InputMethodManager.SHOW_IMPLICIT); imm.showSoftInput(url, InputMethodManager.SHOW_IMPLICIT);
} }
updateTheme();
} }
@OnTextChanged(R.id.url) @OnTextChanged(R.id.url)
@ -177,29 +155,6 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
} }
} }
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(EXTRA_SELECTED_THEME, selectedTheme);
}
@OnFocusChange(R.id.color)
void onFocusChange(boolean focused) {
if (focused) {
color.clearFocus();
showThemePicker();
}
}
@OnClick(R.id.color)
protected void showThemePicker() {
Intent intent = new Intent(CaldavSettingsActivity.this, ColorPickerActivity.class);
intent.putExtra(ColorPickerActivity.EXTRA_PALETTE, ColorPickerActivity.ColorPalette.COLORS);
intent.putExtra(ColorPickerActivity.EXTRA_SHOW_NONE, true);
startActivityForResult(intent, REQUEST_COLOR_PICKER);
}
@Override @Override
public void inject(ActivityComponent component) { public void inject(ActivityComponent component) {
component.inject(this); component.inject(this);
@ -276,51 +231,49 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
ProgressDialog dialog = dialogBuilder.newProgressDialog(R.string.contacting_server); ProgressDialog dialog = dialogBuilder.newProgressDialog(R.string.contacting_server);
dialog.show(); dialog.show();
client client
.getDisplayName() .getHomeSet()
.doAfterTerminate(dialog::dismiss) .doAfterTerminate(dialog::dismiss)
.subscribe(this::addAccount, this::getDisplayNameFailed); .subscribe(this::addAccount, this::requestFailed);
} else if (needsValidation()) { } else if (needsValidation()) {
CaldavClient client = new CaldavClient(url, username, password); CaldavClient client = new CaldavClient(url, username, password);
ProgressDialog dialog = dialogBuilder.newProgressDialog(R.string.contacting_server); ProgressDialog dialog = dialogBuilder.newProgressDialog(R.string.contacting_server);
dialog.show(); dialog.show();
client client
.getDisplayName() .getHomeSet()
.doAfterTerminate(dialog::dismiss) .doAfterTerminate(dialog::dismiss)
.subscribe(this::updateAccount, this::getDisplayNameFailed); .subscribe(this::updateAccount, this::requestFailed);
} else if (hasChanges()) { } else if (hasChanges()) {
updateAccount(caldavAccount.getName()); updateAccount(caldavAccount.getUrl());
} else { } else {
finish(); finish();
} }
} }
private void addAccount(String name) { private void addAccount(String principal) {
CaldavAccount newAccount = new CaldavAccount(name, UUIDHelper.newUUID()); Timber.d("Found principal: %s", principal);
newAccount.setColor(selectedTheme);
newAccount.setUrl(getNewURL()); CaldavAccount newAccount = new CaldavAccount();
newAccount.setUrl(principal);
newAccount.setUsername(getNewUsername()); newAccount.setUsername(getNewUsername());
newAccount.setPassword(getNewPassword()); newAccount.setPassword(getNewPassword());
newAccount.setUuid(UUIDHelper.newUUID());
newAccount.setId(caldavDao.insert(newAccount)); newAccount.setId(caldavDao.insert(newAccount));
setResult(
RESULT_OK, setResult(RESULT_OK);
new Intent().putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(newAccount)));
finish(); finish();
} }
private void updateAccount(String name) { private void updateAccount(String principal) {
caldavAccount.setName(name); caldavAccount.setUrl(principal);
caldavAccount.setUrl(getNewURL());
caldavAccount.setUsername(getNewUsername()); caldavAccount.setUsername(getNewUsername());
caldavAccount.setColor(selectedTheme);
caldavAccount.setPassword(getNewPassword()); caldavAccount.setPassword(getNewPassword());
caldavDao.update(caldavAccount); caldavDao.update(caldavAccount);
setResult(
RESULT_OK, setResult(RESULT_OK);
new Intent().putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(caldavAccount)));
finish(); finish();
} }
private void getDisplayNameFailed(Throwable t) { private void requestFailed(Throwable t) {
if (t instanceof HttpException) { if (t instanceof HttpException) {
showSnackbar(t.getMessage()); showSnackbar(t.getMessage());
} else if (t instanceof DisplayableException) { } else if (t instanceof DisplayableException) {
@ -352,12 +305,9 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
private boolean hasChanges() { private boolean hasChanges() {
if (caldavAccount == null) { if (caldavAccount == null) {
return selectedTheme >= 0 return !isEmpty(getNewPassword()) || !isEmpty(getNewURL()) || !isEmpty(getNewUsername());
|| !isEmpty(getNewPassword())
|| !isEmpty(getNewURL())
|| !isEmpty(getNewUsername());
} }
return selectedTheme != caldavAccount.getColor() || needsValidation(); return needsValidation();
} }
private boolean needsValidation() { private boolean needsValidation() {
@ -382,33 +332,20 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
} }
} }
@Override private void removeAccount() {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_COLOR_PICKER) {
if (resultCode == RESULT_OK) {
int index = data.getIntExtra(ColorPickerActivity.EXTRA_THEME_INDEX, 0);
tracker.reportEvent(Tracking.Events.SET_TAG_COLOR, Integer.toString(index));
selectedTheme = index;
updateTheme();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void deleteAccount() {
dialogBuilder dialogBuilder
.newMessageDialog(R.string.delete_tag_confirmation, caldavAccount.getName()) .newMessageDialog(R.string.remove_caldav_account_confirmation, caldavAccount.getName())
.setPositiveButton( .setPositiveButton(
R.string.delete, R.string.remove,
(dialog, which) -> { (dialog, which) -> {
if (caldavAccount != null) { for (CaldavCalendar calendar :
caldavDao.delete(caldavAccount); caldavDao.getCalendarsByAccount(caldavAccount.getUuid())) {
setResult( taskDeleter.markDeleted(caldavDao.getTasksByCalendar(calendar.getUuid()));
RESULT_OK, caldavDao.deleteTasksForCalendar(calendar.getUuid());
new Intent(ACTION_DELETED)
.putExtra(EXTRA_CALDAV_UUID, caldavAccount.getUuid()));
} }
caldavDao.deleteCalendarsForAccount(caldavAccount.getUuid());
caldavDao.delete(caldavAccount);
setResult(RESULT_OK);
finish(); finish();
}) })
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
@ -427,26 +364,13 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
} }
} }
private void updateTheme() {
ThemeColor themeColor;
if (selectedTheme < 0) {
themeColor = this.themeColor;
color.setText(R.string.none);
} else {
themeColor = themeCache.getThemeColor(selectedTheme);
color.setText(themeColor.getName());
}
themeColor.apply(toolbar);
themeColor.applyToStatusBar(this);
}
@Override @Override
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.delete: case R.id.remove:
deleteAccount(); removeAccount();
break; break;
} }
return super.onOptionsItemSelected(item); return onOptionsItemSelected(item);
} }
} }

@ -0,0 +1,191 @@
package org.tasks.caldav;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputEditText;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.widget.LinearLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnFocusChange;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.api.CaldavFilter;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.activities.ColorPickerActivity;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import org.tasks.themes.ThemeCache;
import org.tasks.themes.ThemeColor;
public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActivity {
public static final String EXTRA_CALDAV_DATA = "caldavData"; // $NON-NLS-1$
public static final String ACTION_RELOAD = "accountRenamed";
public static final String ACTION_DELETED = "accountDeleted";
private static final String EXTRA_SELECTED_THEME = "extra_selected_theme";
private static final int REQUEST_COLOR_PICKER = 10109;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject ThemeCache themeCache;
@Inject ThemeColor themeColor;
@Inject Tracker tracker;
@Inject CaldavDao caldavDao;
@Inject SyncAdapters syncAdapters;
@BindView(R.id.root_layout)
LinearLayout root;
@BindView(R.id.color)
TextInputEditText color;
@BindView(R.id.toolbar)
Toolbar toolbar;
private CaldavCalendar caldavCalendar;
private int selectedTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_caldav_calendar_settings);
ButterKnife.bind(this);
caldavCalendar = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA);
if (savedInstanceState == null) {
selectedTheme = caldavCalendar.getColor();
} else {
selectedTheme = savedInstanceState.getInt(EXTRA_SELECTED_THEME);
}
final boolean backButtonSavesTask = preferences.backButtonSavesTask();
toolbar.setTitle(caldavCalendar.getName());
toolbar.setNavigationIcon(
ContextCompat.getDrawable(
this, backButtonSavesTask ? R.drawable.ic_close_24dp : R.drawable.ic_save_24dp));
toolbar.setNavigationOnClickListener(
v -> {
if (backButtonSavesTask) {
discard();
} else {
save();
}
});
color.setInputType(InputType.TYPE_NULL);
updateTheme();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(EXTRA_SELECTED_THEME, selectedTheme);
}
@OnFocusChange(R.id.color)
void onFocusChange(boolean focused) {
if (focused) {
color.clearFocus();
showThemePicker();
}
}
@OnClick(R.id.color)
protected void showThemePicker() {
Intent intent = new Intent(CaldavCalendarSettingsActivity.this, ColorPickerActivity.class);
intent.putExtra(ColorPickerActivity.EXTRA_PALETTE, ColorPickerActivity.ColorPalette.COLORS);
intent.putExtra(ColorPickerActivity.EXTRA_SHOW_NONE, true);
intent.putExtra(ColorPickerActivity.EXTRA_THEME_INDEX, selectedTheme);
startActivityForResult(intent, REQUEST_COLOR_PICKER);
}
@Override
public void inject(ActivityComponent component) {
component.inject(this);
}
private void save() {
if (hasChanges()) {
updateAccount();
} else {
finish();
}
}
private void updateAccount() {
caldavCalendar.setColor(selectedTheme);
caldavDao.update(caldavCalendar);
setResult(
RESULT_OK,
new Intent(ACTION_RELOAD)
.putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(caldavCalendar)));
finish();
}
private boolean hasChanges() {
return selectedTheme != caldavCalendar.getColor();
}
@Override
public void onBackPressed() {
if (preferences.backButtonSavesTask()) {
save();
} else {
discard();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_COLOR_PICKER) {
if (resultCode == RESULT_OK) {
int index = data.getIntExtra(ColorPickerActivity.EXTRA_THEME_INDEX, 0);
tracker.reportEvent(Tracking.Events.SET_TAG_COLOR, Integer.toString(index));
selectedTheme = index;
updateTheme();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void discard() {
if (!hasChanges()) {
finish();
} else {
dialogBuilder
.newMessageDialog(R.string.discard_changes)
.setPositiveButton(R.string.discard, (dialog, which) -> finish())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
private void updateTheme() {
ThemeColor themeColor;
if (selectedTheme < 0) {
themeColor = this.themeColor;
color.setText(R.string.none);
} else {
themeColor = themeCache.getThemeColor(selectedTheme);
color.setText(themeColor.getName());
}
themeColor.apply(toolbar);
themeColor.applyToStatusBar(this);
}
}

@ -1,21 +1,39 @@
package org.tasks.caldav; package org.tasks.caldav;
import static android.text.TextUtils.isEmpty;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import at.bitfire.dav4android.BasicDigestAuthHandler; import at.bitfire.dav4android.BasicDigestAuthHandler;
import at.bitfire.dav4android.DavCalendar; import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.PropertyCollection;
import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.dav4android.property.CalendarHomeSet;
import at.bitfire.dav4android.property.CurrentUserPrincipal;
import at.bitfire.dav4android.property.DisplayName; import at.bitfire.dav4android.property.DisplayName;
import at.bitfire.dav4android.property.GetCTag;
import at.bitfire.dav4android.property.ResourceType;
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.tasks.R; import org.tasks.R;
import org.tasks.ui.DisplayableException; import org.tasks.ui.DisplayableException;
import timber.log.Timber;
class CaldavClient { class CaldavClient {
private final DavCalendar davCalendar; private final DavResource davResource;
private HttpUrl httpUrl;
public CaldavClient(String url, String username, String password) { public CaldavClient(String url, String username, String password) {
BasicDigestAuthHandler basicDigestAuthHandler = BasicDigestAuthHandler basicDigestAuthHandler =
@ -27,18 +45,110 @@ class CaldavClient {
.authenticator(basicDigestAuthHandler) .authenticator(basicDigestAuthHandler)
.cookieJar(new MemoryCookieStore()) .cookieJar(new MemoryCookieStore())
.followRedirects(false) .followRedirects(false)
.followSslRedirects(false) .followSslRedirects(true)
.readTimeout(30, TimeUnit.SECONDS)
.build(); .build();
URI uri = URI.create(url); URI uri = URI.create(url);
HttpUrl httpUrl = HttpUrl.get(uri); httpUrl = HttpUrl.get(uri);
davCalendar = new DavCalendar(httpClient, httpUrl); davResource = new DavResource(httpClient, httpUrl);
}
private String tryFindPrincipal() throws DavException, IOException, HttpException {
for (String link : asList("", "/.well-known/caldav")) {
HttpUrl url = httpUrl.resolve(link);
Timber.d("Checking for principal: %s", url);
davResource.setLocation(url);
try {
davResource.propfind(0, CurrentUserPrincipal.NAME);
} catch (HttpException e) {
switch (e.getStatus()) {
case 405:
Timber.w(e);
break;
default:
throw e;
}
}
PropertyCollection properties = davResource.getProperties();
CurrentUserPrincipal currentUserPrincipal = properties.get(CurrentUserPrincipal.class);
if (currentUserPrincipal != null) {
String href = currentUserPrincipal.getHref();
if (!isEmpty(href)) {
return href;
}
}
}
return null;
}
private String findHomeset() throws DavException, IOException, HttpException {
davResource.propfind(0, CalendarHomeSet.NAME);
PropertyCollection properties = davResource.getProperties();
CalendarHomeSet calendarHomeSet = properties.get(CalendarHomeSet.class);
if (calendarHomeSet == null) {
throw new DisplayableException(R.string.caldav_home_set_not_found);
}
List<String> hrefs = calendarHomeSet.getHrefs();
if (hrefs.size() != 1) {
throw new DisplayableException(R.string.caldav_home_set_not_found);
}
String homeSet = hrefs.get(0);
if (isEmpty(homeSet)) {
throw new DisplayableException(R.string.caldav_home_set_not_found);
}
return davResource.getLocation().resolve(homeSet).toString();
}
public Single<String> getHomeSet() {
return Single.fromCallable(
() -> {
String principal = tryFindPrincipal();
if (!isEmpty(principal)) {
davResource.setLocation(httpUrl.resolve(principal));
}
return findHomeset();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public List<DavResource> getCalendars() {
try {
davResource.propfind(
1, ResourceType.NAME, DisplayName.NAME, SupportedCalendarComponentSet.NAME, GetCTag.NAME);
} catch (IOException | HttpException | DavException e) {
Timber.e(e);
return emptyList();
}
List<DavResource> urls = new ArrayList<>();
for (DavResource member : davResource.getMembers()) {
PropertyCollection properties = member.getProperties();
ResourceType resourceType = properties.get(ResourceType.class);
if (resourceType == null || !resourceType.getTypes().contains(ResourceType.CALENDAR)) {
Timber.d("%s is not a calendar", member);
continue;
}
SupportedCalendarComponentSet supportedCalendarComponentSet =
properties.get(SupportedCalendarComponentSet.class);
if (supportedCalendarComponentSet == null
|| !supportedCalendarComponentSet.getSupportsTasks()) {
Timber.d("%s does not support tasks", member);
continue;
}
Timber.d("Found %s", member);
urls.add(member);
}
if (urls.isEmpty()) {
throw new DisplayableException(R.string.caldav_no_supported_calendars);
}
return urls;
} }
public Single<String> getDisplayName() { public Single<String> getDisplayName() {
Callable<String> callable = Callable<String> callable =
() -> { () -> {
davCalendar.propfind(0, DisplayName.NAME); davResource.propfind(0, DisplayName.NAME);
DisplayName displayName = davCalendar.getProperties().get(DisplayName.class); DisplayName displayName = davResource.getProperties().get(DisplayName.class);
if (displayName == null) { if (displayName == null) {
throw new DisplayableException(R.string.calendar_not_found); throw new DisplayableException(R.string.calendar_not_found);
} }

@ -1,12 +1,15 @@
package org.tasks.caldav; package org.tasks.caldav;
import static com.google.common.collect.Lists.transform;
import android.support.v4.util.Pair;
import com.todoroo.astrid.api.CaldavFilter; import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.sync.SyncAdapters; import org.tasks.sync.SyncAdapters;
@ -21,23 +24,20 @@ public class CaldavFilterExposer {
this.syncAdapters = syncAdapters; this.syncAdapters = syncAdapters;
} }
public List<Filter> getFilters() { public List<Pair<String, List<Filter>>> getFilters() {
if (!syncAdapters.isCaldavSyncEnabled()) { List<Pair<String, List<Filter>>> filters = new ArrayList<>();
return Collections.emptyList(); for (CaldavAccount account : caldavDao.getAccounts()) {
} List<CaldavCalendar> calendars = caldavDao.getCalendarsByAccount(account.getUuid());
List<CaldavAccount> allOrderedByName = caldavDao.getAllOrderedByName(); filters.add(new Pair<>(account.getName(), transform(calendars, CaldavFilter::new)));
List<Filter> result = new ArrayList<>();
for (CaldavAccount account : allOrderedByName) {
result.add(new CaldavFilter(account));
} }
return result; return filters;
} }
public Filter getFilterByUuid(String uuid) { public Filter getFilterByUuid(String uuid) {
if (syncAdapters.isCaldavSyncEnabled()) { if (syncAdapters.isCaldavSyncEnabled()) {
CaldavAccount caldavAccount = caldavDao.getByUuid(uuid); CaldavCalendar caldavCalendar = caldavDao.getCalendarByUuid(uuid);
if (caldavAccount != null) { if (caldavCalendar != null) {
return new CaldavFilter(caldavAccount); return new CaldavFilter(caldavCalendar);
} }
} }
return null; return null;

@ -1,7 +1,7 @@
package org.tasks.caldav; package org.tasks.caldav;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static org.tasks.caldav.CaldavSettingsActivity.EXTRA_CALDAV_DATA; import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_DATA;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -12,19 +12,19 @@ import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.CaldavFilter; import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavCalendar;
import org.tasks.injection.FragmentComponent; import org.tasks.injection.FragmentComponent;
public class CaldavListFragment extends TaskListFragment { public class CaldavListFragment extends TaskListFragment {
private static final String EXTRA_CALDAV_ACCOUNT = "extra_caldav_account"; private static final String EXTRA_CALDAV_CALENDAR = "extra_caldav_calendar";
private static final int REQUEST_ACCOUNT_SETTINGS = 10101; private static final int REQUEST_ACCOUNT_SETTINGS = 10101;
private CaldavAccount account; private CaldavCalendar calendar;
public static TaskListFragment newCaldavListFragment(CaldavFilter filter, CaldavAccount account) { public static TaskListFragment newCaldavListFragment(CaldavFilter filter, CaldavCalendar calendar) {
CaldavListFragment fragment = new CaldavListFragment(); CaldavListFragment fragment = new CaldavListFragment();
fragment.filter = filter; fragment.filter = filter;
fragment.account = account; fragment.calendar = calendar;
return fragment; return fragment;
} }
@ -33,7 +33,7 @@ public class CaldavListFragment extends TaskListFragment {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState != null) { if (savedInstanceState != null) {
this.account = savedInstanceState.getParcelable(EXTRA_CALDAV_ACCOUNT); this.calendar = savedInstanceState.getParcelable(EXTRA_CALDAV_CALENDAR);
} }
} }
@ -47,8 +47,8 @@ public class CaldavListFragment extends TaskListFragment {
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_caldav_list_fragment: case R.id.menu_caldav_list_fragment:
Intent intent = new Intent(getActivity(), CaldavSettingsActivity.class); Intent intent = new Intent(getActivity(), CaldavCalendarSettingsActivity.class);
intent.putExtra(EXTRA_CALDAV_DATA, account); intent.putExtra(EXTRA_CALDAV_DATA, calendar);
startActivityForResult(intent, REQUEST_ACCOUNT_SETTINGS); startActivityForResult(intent, REQUEST_ACCOUNT_SETTINGS);
return true; return true;
default: default:
@ -62,9 +62,9 @@ public class CaldavListFragment extends TaskListFragment {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
TaskListActivity activity = (TaskListActivity) getActivity(); TaskListActivity activity = (TaskListActivity) getActivity();
String action = data.getAction(); String action = data.getAction();
if (CaldavSettingsActivity.ACTION_DELETED.equals(action)) { if (CaldavCalendarSettingsActivity.ACTION_DELETED.equals(action)) {
activity.onFilterItemClicked(null); activity.onFilterItemClicked(null);
} else if (CaldavSettingsActivity.ACTION_RELOAD.equals(action)) { } else if (CaldavCalendarSettingsActivity.ACTION_RELOAD.equals(action)) {
activity activity
.getIntent() .getIntent()
.putExtra( .putExtra(
@ -81,7 +81,7 @@ public class CaldavListFragment extends TaskListFragment {
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_CALDAV_ACCOUNT, account); outState.putParcelable(EXTRA_CALDAV_CALENDAR, calendar);
} }
@Override @Override

@ -5,6 +5,7 @@ import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.partition; import static com.google.common.collect.Iterables.partition;
import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.Sets.newHashSet;
import static org.tasks.time.DateTimeUtils.currentTimeMillis; import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@ -23,6 +24,7 @@ import at.bitfire.dav4android.property.GetETag;
import at.bitfire.ical4android.InvalidCalendarException; import at.bitfire.ical4android.InvalidCalendarException;
import at.bitfire.ical4android.iCalendar; import at.bitfire.ical4android.iCalendar;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
@ -49,6 +51,7 @@ import okhttp3.ResponseBody;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.LocalBroadcastManager; import org.tasks.LocalBroadcastManager;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask; import org.tasks.data.CaldavTask;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
@ -88,18 +91,46 @@ public class CaldavSynchronizer {
// required for dav4android (ServiceLoader) // required for dav4android (ServiceLoader)
Thread.currentThread().setContextClassLoader(context.getClassLoader()); Thread.currentThread().setContextClassLoader(context.getClassLoader());
for (CaldavAccount account : caldavDao.getAccounts()) { for (CaldavAccount account : caldavDao.getAccounts()) {
sync(account); CaldavClient caldavClient =
new CaldavClient(account.getUrl(), account.getUsername(), account.getPassword());
List<DavResource> resources = caldavClient.getCalendars();
Set<String> urls = newHashSet(transform(resources, c -> c.getLocation().toString()));
Timber.d("Found calendars: %s", urls);
for (CaldavCalendar deleted : caldavDao.findDeletedCalendars(account.getUuid(), newArrayList(urls))) {
taskDeleter.markDeleted(caldavDao.getTasksByCalendar(deleted.getUuid()));
caldavDao.deleteTasksForCalendar(deleted.getUuid());
caldavDao.delete(deleted);
localBroadcastManager.broadcastRefreshList();
}
for (DavResource resource : resources) {
String url = resource.getLocation().toString();
PropertyCollection properties = resource.getProperties();
CaldavCalendar calendar = caldavDao.getCalendarByUrl(account.getUuid(), url);
if (calendar == null) {
calendar = new CaldavCalendar();
calendar.setName(properties.get(DisplayName.class).getDisplayName());
calendar.setAccount(account.getUuid());
calendar.setUrl(url);
calendar.setUuid(UUIDHelper.newUUID());
calendar.setId(caldavDao.insert(calendar));
localBroadcastManager.broadcastRefreshList();
}
String ctag = properties.get(GetCTag.class).getCTag();
if (calendar.getCtag() == null || !calendar.getCtag().equals(ctag)) {
sync(account, calendar);
}
}
} }
} }
private void sync(CaldavAccount caldavAccount) { private void sync(CaldavAccount account, CaldavCalendar caldavCalendar) {
if (isNullOrEmpty(caldavAccount.getPassword())) { if (isNullOrEmpty(account.getPassword())) {
Timber.e("Missing password for %s", caldavAccount); Timber.e("Missing password for %s", caldavCalendar);
return; return;
} }
Timber.d("sync(%s)", caldavAccount); Timber.d("sync(%s)", caldavCalendar);
BasicDigestAuthHandler basicDigestAuthHandler = BasicDigestAuthHandler basicDigestAuthHandler =
new BasicDigestAuthHandler(null, caldavAccount.getUsername(), caldavAccount.getPassword()); new BasicDigestAuthHandler(null, account.getUsername(), account.getPassword());
OkHttpClient httpClient = OkHttpClient httpClient =
new OkHttpClient() new OkHttpClient()
.newBuilder() .newBuilder()
@ -110,28 +141,28 @@ public class CaldavSynchronizer {
.followSslRedirects(false) .followSslRedirects(false)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.build(); .build();
URI uri = URI.create(caldavAccount.getUrl()); URI uri = URI.create(caldavCalendar.getUrl());
HttpUrl httpUrl = HttpUrl.get(uri); HttpUrl httpUrl = HttpUrl.get(uri);
DavCalendar davCalendar = new DavCalendar(httpClient, httpUrl); DavCalendar davCalendar = new DavCalendar(httpClient, httpUrl);
try { try {
pushLocalChanges(caldavAccount, httpClient, httpUrl); pushLocalChanges(caldavCalendar, httpClient, httpUrl);
davCalendar.propfind(0, GetCTag.NAME, DisplayName.NAME); davCalendar.propfind(0, GetCTag.NAME, DisplayName.NAME);
PropertyCollection properties = davCalendar.getProperties(); PropertyCollection properties = davCalendar.getProperties();
String remoteName = properties.get(DisplayName.class).getDisplayName(); String remoteName = properties.get(DisplayName.class).getDisplayName();
if (!caldavAccount.getName().equals(remoteName)) { if (!caldavCalendar.getName().equals(remoteName)) {
Timber.d("%s -> %s", caldavAccount.getName(), remoteName); Timber.d("%s -> %s", caldavCalendar.getName(), remoteName);
caldavAccount.setName(remoteName); caldavCalendar.setName(remoteName);
caldavDao.update(caldavAccount); caldavDao.update(caldavCalendar);
localBroadcastManager.broadcastRefreshList(); localBroadcastManager.broadcastRefreshList();
} }
String remoteCtag = properties.get(GetCTag.class).getCTag(); String remoteCtag = properties.get(GetCTag.class).getCTag();
String localCtag = caldavAccount.getCtag(); String localCtag = caldavCalendar.getCtag();
if (localCtag != null && localCtag.equals(remoteCtag)) { if (localCtag != null && localCtag.equals(remoteCtag)) {
Timber.d("%s up to date", caldavAccount.getName()); Timber.d("%s up to date", caldavCalendar.getName());
return; return;
} }
@ -149,7 +180,7 @@ public class CaldavSynchronizer {
return false; return false;
} }
CaldavTask caldavTask = CaldavTask caldavTask =
caldavDao.getTask(caldavAccount.getUuid(), vCard.fileName()); caldavDao.getTask(caldavCalendar.getUuid(), vCard.fileName());
return caldavTask == null || !eTag.getETag().equals(caldavTask.getEtag()); return caldavTask == null || !eTag.getETag().equals(caldavTask.getEtag());
}); });
@ -168,14 +199,15 @@ public class CaldavSynchronizer {
try { try {
reader = responseBody.charStream(); reader = responseBody.charStream();
processVTodo( processVTodo(
vCard.fileName(), caldavAccount, eTag.getETag(), CharStreams.toString(reader)); vCard.fileName(), caldavCalendar, eTag.getETag(), CharStreams.toString(reader));
} finally { } finally {
if (reader != null) { if (reader != null) {
reader.close(); reader.close();
} }
} }
} else { } else {
ArrayList<HttpUrl> urls = newArrayList(transform(items, DavResource::getLocation)); ArrayList<HttpUrl> urls =
newArrayList(Iterables.transform(items, DavResource::getLocation));
davCalendar.multiget(urls); davCalendar.multiget(urls);
Timber.d("MULTI %s", urls); Timber.d("MULTI %s", urls);
@ -195,7 +227,7 @@ public class CaldavSynchronizer {
} }
processVTodo( processVTodo(
vCard.fileName(), caldavAccount, eTag.getETag(), calendarData.getICalendar()); vCard.fileName(), caldavCalendar, eTag.getETag(), calendarData.getICalendar());
} }
} }
} }
@ -203,17 +235,17 @@ public class CaldavSynchronizer {
List<String> deleted = List<String> deleted =
newArrayList( newArrayList(
difference( difference(
newHashSet(caldavDao.getObjects(caldavAccount.getUuid())), newHashSet(caldavDao.getObjects(caldavCalendar.getUuid())),
newHashSet(remoteObjects))); newHashSet(remoteObjects)));
if (deleted.size() > 0) { if (deleted.size() > 0) {
Timber.d("DELETED %s", deleted); Timber.d("DELETED %s", deleted);
taskDeleter.markDeleted(caldavDao.getTasks(caldavAccount.getUuid(), deleted)); taskDeleter.markDeleted(caldavDao.getTasks(caldavCalendar.getUuid(), deleted));
caldavDao.deleteObjects(caldavAccount.getUuid(), deleted); caldavDao.deleteObjects(caldavCalendar.getUuid(), deleted);
} }
caldavAccount.setCtag(remoteCtag); caldavCalendar.setCtag(remoteCtag);
Timber.d("UPDATE %s", caldavAccount); Timber.d("UPDATE %s", caldavCalendar);
caldavDao.update(caldavAccount); caldavDao.update(caldavCalendar);
} catch (IOException | HttpException | DavException e) { } catch (IOException | HttpException | DavException e) {
Timber.e(e); Timber.e(e);
} catch (Exception e) { } catch (Exception e) {
@ -224,11 +256,11 @@ public class CaldavSynchronizer {
} }
private void pushLocalChanges( private void pushLocalChanges(
CaldavAccount caldavAccount, OkHttpClient httpClient, HttpUrl httpUrl) { CaldavCalendar caldavCalendar, OkHttpClient httpClient, HttpUrl httpUrl) {
List<Task> tasks = taskDao.getCaldavTasksToPush(caldavAccount.getUuid()); List<Task> tasks = taskDao.getCaldavTasksToPush(caldavCalendar.getUuid());
for (com.todoroo.astrid.data.Task task : tasks) { for (com.todoroo.astrid.data.Task task : tasks) {
try { try {
pushTask(task, caldavAccount, httpClient, httpUrl); pushTask(task, caldavCalendar, httpClient, httpUrl);
} catch (IOException e) { } catch (IOException e) {
Timber.e(e); Timber.e(e);
} }
@ -258,10 +290,10 @@ public class CaldavSynchronizer {
} }
private void pushTask( private void pushTask(
Task task, CaldavAccount caldavAccount, OkHttpClient httpClient, HttpUrl httpUrl) Task task, CaldavCalendar caldavCalendar, OkHttpClient httpClient, HttpUrl httpUrl)
throws IOException { throws IOException {
Timber.d("pushing %s", task); Timber.d("pushing %s", task);
List<CaldavTask> deleted = getDeleted(task.getId(), caldavAccount); List<CaldavTask> deleted = getDeleted(task.getId(), caldavCalendar);
if (!deleted.isEmpty()) { if (!deleted.isEmpty()) {
for (CaldavTask entry : deleted) { for (CaldavTask entry : deleted) {
deleteRemoteResource(httpClient, httpUrl, entry); deleteRemoteResource(httpClient, httpUrl, entry);
@ -317,11 +349,12 @@ public class CaldavSynchronizer {
Timber.d("SENT %s", caldavTask); Timber.d("SENT %s", caldavTask);
} }
private List<CaldavTask> getDeleted(long taskId, CaldavAccount caldavAccount) { private List<CaldavTask> getDeleted(long taskId, CaldavCalendar caldavCalendar) {
return caldavDao.getDeleted(taskId, caldavAccount.getUuid()); return caldavDao.getDeleted(taskId, caldavCalendar.getUuid());
} }
private void processVTodo(String fileName, CaldavAccount caldavAccount, String eTag, String vtodo) private void processVTodo(
String fileName, CaldavCalendar caldavCalendar, String eTag, String vtodo)
throws IOException { throws IOException {
List<at.bitfire.ical4android.Task> tasks; List<at.bitfire.ical4android.Task> tasks;
try { try {
@ -334,12 +367,12 @@ public class CaldavSynchronizer {
if (tasks.size() == 1) { if (tasks.size() == 1) {
at.bitfire.ical4android.Task remote = tasks.get(0); at.bitfire.ical4android.Task remote = tasks.get(0);
Task task; Task task;
CaldavTask caldavTask = caldavDao.getTask(caldavAccount.getUuid(), fileName); CaldavTask caldavTask = caldavDao.getTask(caldavCalendar.getUuid(), fileName);
if (caldavTask == null) { if (caldavTask == null) {
task = taskCreator.createWithValues(null, ""); task = taskCreator.createWithValues(null, "");
taskDao.createNew(task); taskDao.createNew(task);
caldavTask = caldavTask =
new CaldavTask(task.getId(), caldavAccount.getUuid(), remote.getUid(), fileName); new CaldavTask(task.getId(), caldavCalendar.getUuid(), remote.getUid(), fileName);
} else { } else {
task = taskDao.fetch(caldavTask.getTask()); task = taskDao.fetch(caldavTask.getTask());
} }

@ -8,13 +8,14 @@ import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey; import android.arch.persistence.room.PrimaryKey;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.TextUtils; import java.util.Arrays;
@Entity(tableName = "caldav_account") @Entity(tableName = "caldav_account")
public final class CaldavAccount implements Parcelable { public class CaldavAccount implements Parcelable {
public static Parcelable.Creator<CaldavAccount> CREATOR = public static Parcelable.Creator<CaldavAccount> CREATOR =
new Parcelable.Creator<CaldavAccount>() { new Parcelable.Creator<CaldavAccount>() {
@Override @Override
public CaldavAccount createFromParcel(Parcel source) { public CaldavAccount createFromParcel(Parcel source) {
return new CaldavAccount(source); return new CaldavAccount(source);
@ -36,12 +37,6 @@ public final class CaldavAccount implements Parcelable {
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
private String name = ""; private String name = "";
@ColumnInfo(name = "color")
private int color = -1;
@ColumnInfo(name = "ctag")
private String ctag;
@ColumnInfo(name = "url") @ColumnInfo(name = "url")
private String url = ""; private String url = "";
@ -51,24 +46,20 @@ public final class CaldavAccount implements Parcelable {
@ColumnInfo(name = "password") @ColumnInfo(name = "password")
private transient String password = ""; private transient String password = "";
public CaldavAccount() {} @ColumnInfo(name = "iv")
private transient byte[] iv = null;
@Ignore public CaldavAccount() {}
public CaldavAccount(String name, String uuid) {
this.name = name;
this.uuid = uuid;
}
@Ignore @Ignore
public CaldavAccount(Parcel source) { public CaldavAccount(Parcel source) {
id = source.readLong(); id = source.readLong();
uuid = source.readString(); uuid = source.readString();
name = source.readString(); name = source.readString();
color = source.readInt();
ctag = source.readString();
url = source.readString(); url = source.readString();
username = source.readString(); username = source.readString();
password = source.readString(); password = source.readString();
iv = source.createByteArray();
} }
public long getId() { public long getId() {
@ -95,22 +86,6 @@ public final class CaldavAccount implements Parcelable {
this.name = name; this.name = name;
} }
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public String getCtag() {
return ctag;
}
public void setCtag(String ctag) {
this.ctag = ctag;
}
public String getUrl() { public String getUrl() {
return url; return url;
} }
@ -135,49 +110,25 @@ public final class CaldavAccount implements Parcelable {
this.password = password; this.password = password;
} }
@Override public byte[] getIv() {
public int describeContents() { return iv;
return 0;
} }
@Override public void setIv(byte[] iv) {
public void writeToParcel(Parcel dest, int flags) { this.iv = iv;
dest.writeLong(id);
dest.writeString(uuid);
dest.writeString(name);
dest.writeInt(color);
dest.writeString(ctag);
dest.writeString(url);
dest.writeString(username);
dest.writeString(password);
} }
@Override @Override
public String toString() { public String toString() {
return "CaldavAccount{" return "CaldavAccount{" +
+ "id=" "id=" + id +
+ id ", uuid='" + uuid + '\'' +
+ ", uuid='" ", name='" + name + '\'' +
+ uuid ", url='" + url + '\'' +
+ '\'' ", username='" + username + '\'' +
+ ", name='" ", password='" + password + '\'' +
+ name ", iv=" + Arrays.toString(iv) +
+ '\'' '}';
+ ", color="
+ color
+ ", ctag='"
+ ctag
+ '\''
+ ", url='"
+ url
+ '\''
+ ", username='"
+ username
+ '\''
+ ", password='"
+ (TextUtils.isEmpty(password) ? "null" : "******")
+ '\''
+ '}';
} }
@Override @Override
@ -194,25 +145,22 @@ public final class CaldavAccount implements Parcelable {
if (id != that.id) { if (id != that.id) {
return false; return false;
} }
if (color != that.color) {
return false;
}
if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) { if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) {
return false; return false;
} }
if (name != null ? !name.equals(that.name) : that.name != null) { if (name != null ? !name.equals(that.name) : that.name != null) {
return false; return false;
} }
if (ctag != null ? !ctag.equals(that.ctag) : that.ctag != null) {
return false;
}
if (url != null ? !url.equals(that.url) : that.url != null) { if (url != null ? !url.equals(that.url) : that.url != null) {
return false; return false;
} }
if (username != null ? !username.equals(that.username) : that.username != null) { if (username != null ? !username.equals(that.username) : that.username != null) {
return false; return false;
} }
return password != null ? password.equals(that.password) : that.password == null; if (password != null ? !password.equals(that.password) : that.password != null) {
return false;
}
return Arrays.equals(iv, that.iv);
} }
@Override @Override
@ -220,11 +168,26 @@ public final class CaldavAccount implements Parcelable {
int result = (int) (id ^ (id >>> 32)); int result = (int) (id ^ (id >>> 32));
result = 31 * result + (uuid != null ? uuid.hashCode() : 0); result = 31 * result + (uuid != null ? uuid.hashCode() : 0);
result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + color;
result = 31 * result + (ctag != null ? ctag.hashCode() : 0);
result = 31 * result + (url != null ? url.hashCode() : 0); result = 31 * result + (url != null ? url.hashCode() : 0);
result = 31 * result + (username != null ? username.hashCode() : 0); result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0); result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + Arrays.hashCode(iv);
return result; return result;
} }
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(uuid);
dest.writeString(name);
dest.writeString(url);
dest.writeString(username);
dest.writeString(password);
dest.writeByteArray(iv);
}
} }

@ -0,0 +1,197 @@
package org.tasks.data;
import static com.todoroo.astrid.data.Task.NO_UUID;
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey;
import android.os.Parcel;
import android.os.Parcelable;
@Entity(tableName = "caldav_calendar")
public final class CaldavCalendar implements Parcelable {
public static Parcelable.Creator<CaldavCalendar> CREATOR =
new Parcelable.Creator<CaldavCalendar>() {
@Override
public CaldavCalendar createFromParcel(Parcel source) {
return new CaldavCalendar(source);
}
@Override
public CaldavCalendar[] newArray(int size) {
return new CaldavCalendar[size];
}
};
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
private long id;
@ColumnInfo(name = "account")
private String account = NO_UUID;
@ColumnInfo(name = "uuid")
private String uuid = NO_UUID;
@ColumnInfo(name = "name")
private String name = "";
@ColumnInfo(name = "color")
private int color = -1;
@ColumnInfo(name = "ctag")
private String ctag;
@ColumnInfo(name = "url")
private String url = "";
public CaldavCalendar() {}
@Ignore
public CaldavCalendar(String name, String uuid) {
this.name = name;
this.uuid = uuid;
}
@Ignore
public CaldavCalendar(Parcel source) {
id = source.readLong();
account = source.readString();
uuid = source.readString();
name = source.readString();
color = source.readInt();
ctag = source.readString();
url = source.readString();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public String getCtag() {
return ctag;
}
public void setCtag(String ctag) {
this.ctag = ctag;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(account);
dest.writeString(uuid);
dest.writeString(name);
dest.writeInt(color);
dest.writeString(ctag);
dest.writeString(url);
}
@Override
public String toString() {
return "CaldavCalendar{" +
"id=" + id +
", account='" + account + '\'' +
", uuid='" + uuid + '\'' +
", name='" + name + '\'' +
", color=" + color +
", ctag='" + ctag + '\'' +
", url='" + url + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CaldavCalendar)) {
return false;
}
CaldavCalendar that = (CaldavCalendar) o;
if (id != that.id) {
return false;
}
if (color != that.color) {
return false;
}
if (account != null ? !account.equals(that.account) : that.account != null) {
return false;
}
if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
if (ctag != null ? !ctag.equals(that.ctag) : that.ctag != null) {
return false;
}
return url != null ? url.equals(that.url) : that.url == null;
}
@Override
public int hashCode() {
int result = (int) (id ^ (id >>> 32));
result = 31 * result + (account != null ? account.hashCode() : 0);
result = 31 * result + (uuid != null ? uuid.hashCode() : 0);
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + color;
result = 31 * result + (ctag != null ? ctag.hashCode() : 0);
result = 31 * result + (url != null ? url.hashCode() : 0);
return result;
}
}

@ -10,11 +10,11 @@ import java.util.List;
@Dao @Dao
public interface CaldavDao { public interface CaldavDao {
@Query("SELECT * FROM caldav_account WHERE uuid = :uuid LIMIT 1") @Query("SELECT * FROM caldav_calendar WHERE uuid = :uuid LIMIT 1")
CaldavAccount getByUuid(String uuid); CaldavCalendar getCalendarByUuid(String uuid);
@Query("SELECT * FROM caldav_account ORDER BY UPPER(name) ASC") @Query("SELECT * FROM caldav_account ORDER BY UPPER(name) ASC")
List<CaldavAccount> getAllOrderedByName(); List<CaldavAccount> getAccounts();
@Insert @Insert
long insert(CaldavAccount caldavAccount); long insert(CaldavAccount caldavAccount);
@ -22,6 +22,18 @@ public interface CaldavDao {
@Update @Update
void update(CaldavAccount caldavAccount); void update(CaldavAccount caldavAccount);
@Delete
void delete(CaldavAccount caldavAccount);
@Insert
long insert(CaldavCalendar caldavCalendar);
@Update
void update(CaldavCalendar caldavCalendar);
@Delete
void delete(CaldavCalendar caldavCalendar);
@Insert @Insert
long insert(CaldavTask caldavTask); long insert(CaldavTask caldavTask);
@ -31,14 +43,14 @@ public interface CaldavDao {
@Delete @Delete
void delete(CaldavTask caldavTask); void delete(CaldavTask caldavTask);
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId AND deleted > 0 AND account = :account") @Query("SELECT * FROM caldav_tasks WHERE task = :taskId AND deleted > 0 AND calendar = :calendar")
List<CaldavTask> getDeleted(long taskId, String account); List<CaldavTask> getDeleted(long taskId, String calendar);
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId AND deleted = 0 LIMIT 1") @Query("SELECT * FROM caldav_tasks WHERE task = :taskId AND deleted = 0 LIMIT 1")
CaldavTask getTask(long taskId); CaldavTask getTask(long taskId);
@Query("SELECT * FROM caldav_tasks WHERE account = :account AND object = :object LIMIT 1") @Query("SELECT * FROM caldav_tasks WHERE calendar = :calendar AND object = :object LIMIT 1")
CaldavTask getTask(String account, String object); CaldavTask getTask(String calendar, String object);
@Query("DELETE FROM caldav_tasks WHERE task = :taskId") @Query("DELETE FROM caldav_tasks WHERE task = :taskId")
void deleteById(long taskId); void deleteById(long taskId);
@ -46,24 +58,36 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId") @Query("SELECT * FROM caldav_tasks WHERE task = :taskId")
List<CaldavTask> getTasks(long taskId); List<CaldavTask> getTasks(long taskId);
@Query("SELECT * FROM caldav_account") @Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar")
List<CaldavAccount> getAccounts(); List<Long> getTasksByCalendar(String calendar);
@Delete @Query("SELECT * FROM caldav_calendar")
void delete(CaldavAccount caldavAccount); List<CaldavCalendar> getCalendars();
@Query("SELECT * FROM caldav_calendar WHERE account = :account")
List<CaldavCalendar> getCalendarsByAccount(String account);
@Query("SELECT * FROM caldav_calendar WHERE uuid = :uuid LIMIT 1")
CaldavCalendar getCalendar(String uuid);
@Query("DELETE FROM caldav_calendar WHERE account = :account")
void deleteCalendarsForAccount(String account);
@Query("DELETE FROM caldav_tasks WHERE calendar = :calendar")
void deleteTasksForCalendar(String calendar);
@Query("SELECT * FROM caldav_account WHERE uuid = :uuid LIMIT 1") @Query("SELECT object FROM caldav_tasks WHERE calendar = :calendar")
CaldavAccount getAccount(String uuid); List<String> getObjects(String calendar);
@Query("DELETE FROM caldav_tasks WHERE account = :uuid") @Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)")
void deleteTasksForAccount(String uuid); List<Long> getTasks(String calendar, List<String> objects);
@Query("SELECT object FROM caldav_tasks WHERE account = :account") @Query("DELETE FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)")
List<String> getObjects(String account); void deleteObjects(String calendar, List<String> objects);
@Query("SELECT task FROM caldav_tasks WHERE account = :account AND object IN (:objects)") @Query("SELECT * FROM caldav_calendar WHERE account = :account AND url NOT IN (:urls)")
List<Long> getTasks(String account, List<String> objects); List<CaldavCalendar> findDeletedCalendars(String account, List<String> urls);
@Query("DELETE FROM caldav_tasks WHERE account = :account AND object IN (:objects)") @Query("SELECT * FROM caldav_calendar WHERE account = :account AND url = :url LIMIT 1")
void deleteObjects(String account, List<String> objects); CaldavCalendar getCalendarByUrl(String account, String url);
} }

@ -20,8 +20,8 @@ public class CaldavTask {
@ColumnInfo(name = "task") @ColumnInfo(name = "task")
private long task; private long task;
@ColumnInfo(name = "account") @ColumnInfo(name = "calendar")
private String account; private String calendar;
@ColumnInfo(name = "object") @ColumnInfo(name = "object")
private String object; private String object;
@ -49,9 +49,9 @@ public class CaldavTask {
} }
@Ignore @Ignore
public CaldavTask(long task, String account, String remoteId, String object) { public CaldavTask(long task, String calendar, String remoteId, String object) {
this.task = task; this.task = task;
this.account = account; this.calendar = calendar;
this.remoteId = remoteId; this.remoteId = remoteId;
this.object = object; this.object = object;
} }
@ -72,12 +72,12 @@ public class CaldavTask {
this.task = task; this.task = task;
} }
public String getAccount() { public String getCalendar() {
return account; return calendar;
} }
public void setAccount(String account) { public void setCalendar(String calendar) {
this.account = account; this.calendar = calendar;
} }
public String getObject() { public String getObject() {
@ -135,8 +135,8 @@ public class CaldavTask {
+ id + id
+ ", task=" + ", task="
+ task + task
+ ", account='" + ", calendar='"
+ account + calendar
+ '\'' + '\''
+ ", object='" + ", object='"
+ object + object

@ -1,5 +1,6 @@
package org.tasks.filters; package org.tasks.filters;
import android.support.v4.util.Pair;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.core.BuiltInFilterExposer; import com.todoroo.astrid.core.BuiltInFilterExposer;
import com.todoroo.astrid.core.CustomFilterExposer; import com.todoroo.astrid.core.CustomFilterExposer;
@ -8,6 +9,7 @@ import com.todoroo.astrid.tags.TagFilterExposer;
import com.todoroo.astrid.timers.TimerFilterExposer; import com.todoroo.astrid.timers.TimerFilterExposer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.caldav.CaldavFilterExposer; import org.tasks.caldav.CaldavFilterExposer;
@ -56,7 +58,7 @@ public class FilterProvider {
return gtasksFilterExposer.getFilters(); return gtasksFilterExposer.getFilters();
} }
public List<Filter> getCaldavFilters() { public List<Pair<String, List<Filter>>> getCaldavFilters() {
return caldavFilterExposer.getFilters(); return caldavFilterExposer.getFilters();
} }
} }

@ -32,7 +32,6 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
@ -43,7 +42,6 @@ import org.tasks.injection.ForApplication;
import org.tasks.notifications.NotificationManager; import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.DefaultFilterProvider; import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.sync.RecordSyncStatusCallback;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import timber.log.Timber; import timber.log.Timber;
@ -53,7 +51,6 @@ public class GoogleTaskSynchronizer {
private final Context context; private final Context context;
private final GtasksPreferenceService gtasksPreferenceService; private final GtasksPreferenceService gtasksPreferenceService;
private final LocalBroadcastManager localBroadcastManager;
private final GoogleTaskListDao googleTaskListDao; private final GoogleTaskListDao googleTaskListDao;
private final GtasksSyncService gtasksSyncService; private final GtasksSyncService gtasksSyncService;
private final GtasksListService gtasksListService; private final GtasksListService gtasksListService;
@ -71,7 +68,6 @@ public class GoogleTaskSynchronizer {
public GoogleTaskSynchronizer( public GoogleTaskSynchronizer(
@ForApplication Context context, @ForApplication Context context,
GtasksPreferenceService gtasksPreferenceService, GtasksPreferenceService gtasksPreferenceService,
LocalBroadcastManager localBroadcastManager,
GoogleTaskListDao googleTaskListDao, GoogleTaskListDao googleTaskListDao,
GtasksSyncService gtasksSyncService, GtasksSyncService gtasksSyncService,
GtasksListService gtasksListService, GtasksListService gtasksListService,
@ -86,7 +82,6 @@ public class GoogleTaskSynchronizer {
DefaultFilterProvider defaultFilterProvider) { DefaultFilterProvider defaultFilterProvider) {
this.context = context; this.context = context;
this.gtasksPreferenceService = gtasksPreferenceService; this.gtasksPreferenceService = gtasksPreferenceService;
this.localBroadcastManager = localBroadcastManager;
this.googleTaskListDao = googleTaskListDao; this.googleTaskListDao = googleTaskListDao;
this.gtasksSyncService = gtasksSyncService; this.gtasksSyncService = gtasksSyncService;
this.gtasksListService = gtasksListService; this.gtasksListService = gtasksListService;
@ -122,10 +117,7 @@ public class GoogleTaskSynchronizer {
return; return;
} }
Timber.d("%s: start sync", account); Timber.d("%s: start sync", account);
RecordSyncStatusCallback callback =
new RecordSyncStatusCallback(gtasksPreferenceService, localBroadcastManager);
try { try {
callback.started();
synchronize(); synchronize();
gtasksPreferenceService.recordSuccessfulSync(); gtasksPreferenceService.recordSuccessfulSync();
} catch (UserRecoverableAuthIOException e) { } catch (UserRecoverableAuthIOException e) {
@ -136,7 +128,6 @@ public class GoogleTaskSynchronizer {
} catch (Exception e) { } catch (Exception e) {
tracker.reportException(e); tracker.reportException(e);
} finally { } finally {
callback.finished();
Timber.d("%s: end sync", account); Timber.d("%s: end sync", account);
} }
} }

@ -23,7 +23,8 @@ import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity; import org.tasks.activities.TagSettingsActivity;
import org.tasks.activities.TimePickerActivity; import org.tasks.activities.TimePickerActivity;
import org.tasks.billing.PurchaseActivity; import org.tasks.billing.PurchaseActivity;
import org.tasks.caldav.CaldavSettingsActivity; import org.tasks.caldav.CaldavAccountSettingsActivity;
import org.tasks.caldav.CaldavCalendarSettingsActivity;
import org.tasks.dashclock.DashClockSettings; import org.tasks.dashclock.DashClockSettings;
import org.tasks.files.FileExplore; import org.tasks.files.FileExplore;
import org.tasks.files.MyFilePickerActivity; import org.tasks.files.MyFilePickerActivity;
@ -127,11 +128,13 @@ public interface ActivityComponent {
void inject(GoogleTaskListSettingsActivity googleTaskListSettingsActivity); void inject(GoogleTaskListSettingsActivity googleTaskListSettingsActivity);
void inject(CaldavSettingsActivity caldavSettingsActivity); void inject(CaldavCalendarSettingsActivity caldavCalendarSettingsActivity);
void inject(TaskerCreateTaskActivity taskerCreateTaskActivity); void inject(TaskerCreateTaskActivity taskerCreateTaskActivity);
void inject(TaskListViewModel taskListViewModel); void inject(TaskListViewModel taskListViewModel);
void inject(PurchaseActivity purchaseActivity); void inject(PurchaseActivity purchaseActivity);
void inject(CaldavAccountSettingsActivity caldavAccountSettingsActivity);
} }

@ -65,7 +65,8 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
return new NotificationJob(preferences, notifier, notificationQueue); return new NotificationJob(preferences, notifier, notificationQueue);
case TAG_SYNC: case TAG_SYNC:
case TAG_BACKGROUND_SYNC: case TAG_BACKGROUND_SYNC:
return new SyncJob(caldavSynchronizer, googleTaskSynchronizer); return new SyncJob(
caldavSynchronizer, googleTaskSynchronizer, localBroadcastManager, preferences);
case TAG_BACKUP: case TAG_BACKUP:
return new BackupJob(context, tasksJsonExporter, preferences); return new BackupJob(context, tasksJsonExporter, preferences);
case TAG_MIDNIGHT_REFRESH: case TAG_MIDNIGHT_REFRESH:

@ -11,6 +11,7 @@ import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.CaldavDao;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import timber.log.Timber; import timber.log.Timber;
@ -28,11 +29,16 @@ public class JobManager {
private final com.evernote.android.job.JobManager jobManager; private final com.evernote.android.job.JobManager jobManager;
private final Preferences preferences; private final Preferences preferences;
private final CaldavDao caldavDao;
@Inject @Inject
public JobManager(com.evernote.android.job.JobManager jobManager, Preferences preferences) { public JobManager(
com.evernote.android.job.JobManager jobManager,
Preferences preferences,
CaldavDao caldavDao) {
this.jobManager = jobManager; this.jobManager = jobManager;
this.preferences = preferences; this.preferences = preferences;
this.caldavDao = caldavDao;
} }
public void scheduleNotification(long time) { public void scheduleNotification(long time) {
@ -76,7 +82,7 @@ public class JobManager {
boolean accountsPresent = boolean accountsPresent =
forceAccountPresent == null forceAccountPresent == null
? (preferences.getBoolean(R.string.sync_gtasks, false) ? (preferences.getBoolean(R.string.sync_gtasks, false)
|| preferences.getBoolean(R.string.p_sync_caldav, false)) || caldavDao.getAccounts().size() > 0)
: forceAccountPresent; : forceAccountPresent;
boolean onlyOnWifi = boolean onlyOnWifi =
forceOnlyOnUnmetered == null forceOnlyOnUnmetered == null

@ -2,24 +2,52 @@ package org.tasks.jobs;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.evernote.android.job.Job; import com.evernote.android.job.Job;
import org.tasks.LocalBroadcastManager;
import org.tasks.caldav.CaldavSynchronizer; import org.tasks.caldav.CaldavSynchronizer;
import org.tasks.gtasks.GoogleTaskSynchronizer; import org.tasks.gtasks.GoogleTaskSynchronizer;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
public class SyncJob extends Job { public class SyncJob extends Job {
private static final Object LOCK = new Object();
private final CaldavSynchronizer caldavSynchronizer; private final CaldavSynchronizer caldavSynchronizer;
private final GoogleTaskSynchronizer googleTaskSynchronizer; private final GoogleTaskSynchronizer googleTaskSynchronizer;
private final LocalBroadcastManager localBroadcastManager;
private final Preferences preferences;
SyncJob(CaldavSynchronizer caldavSynchronizer, GoogleTaskSynchronizer googleTaskSynchronizer) { SyncJob(
CaldavSynchronizer caldavSynchronizer,
GoogleTaskSynchronizer googleTaskSynchronizer,
LocalBroadcastManager localBroadcastManager,
Preferences preferences) {
this.caldavSynchronizer = caldavSynchronizer; this.caldavSynchronizer = caldavSynchronizer;
this.googleTaskSynchronizer = googleTaskSynchronizer; this.googleTaskSynchronizer = googleTaskSynchronizer;
this.localBroadcastManager = localBroadcastManager;
this.preferences = preferences;
} }
@NonNull @NonNull
@Override @Override
protected Result onRunJob(@NonNull Params params) { protected Result onRunJob(@NonNull Params params) {
synchronized (LOCK) {
if (preferences.isSyncOngoing()) {
return Result.SUCCESS;
}
}
preferences.setSyncOngoing(true);
localBroadcastManager.broadcastRefresh();
try {
caldavSynchronizer.sync(); caldavSynchronizer.sync();
googleTaskSynchronizer.sync(); googleTaskSynchronizer.sync();
} catch (Exception e) {
Timber.e(e);
} finally {
preferences.setSyncOngoing(false);
localBroadcastManager.broadcastRefresh();
}
return Result.SUCCESS; return Result.SUCCESS;
} }
} }

@ -445,4 +445,12 @@ public class Preferences {
public boolean usePersistentReminders() { public boolean usePersistentReminders() {
return getBoolean(R.string.p_rmd_persistent, true); return getBoolean(R.string.p_rmd_persistent, true);
} }
public void setSyncOngoing(boolean value) {
setBoolean(R.string.p_sync_ongoing, value);
}
public boolean isSyncOngoing() {
return getBoolean(R.string.p_sync_ongoing, false);
}
} }

@ -1,30 +0,0 @@
package org.tasks.sync;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.sync.SyncResultCallback;
import org.tasks.LocalBroadcastManager;
public class RecordSyncStatusCallback implements SyncResultCallback {
private final GtasksPreferenceService gtasksPreferenceService;
private final LocalBroadcastManager localBroadcastManager;
public RecordSyncStatusCallback(
GtasksPreferenceService gtasksPreferenceService,
LocalBroadcastManager localBroadcastManager) {
this.gtasksPreferenceService = gtasksPreferenceService;
this.localBroadcastManager = localBroadcastManager;
}
@Override
public void started() {
gtasksPreferenceService.recordSyncStart();
localBroadcastManager.broadcastRefresh();
}
@Override
public void finished() {
gtasksPreferenceService.stopOngoing();
localBroadcastManager.broadcastRefresh();
}
}

@ -2,25 +2,22 @@ package org.tasks.sync;
import android.app.Activity; import android.app.Activity;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.data.CaldavDao;
import org.tasks.gtasks.GtaskSyncAdapterHelper; import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.jobs.JobManager; import org.tasks.jobs.JobManager;
import org.tasks.preferences.Preferences;
public class SyncAdapters { public class SyncAdapters {
private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper; private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
private final Preferences preferences;
private final JobManager jobManager; private final JobManager jobManager;
private final CaldavDao caldavDao;
@Inject @Inject
public SyncAdapters( public SyncAdapters(
GtaskSyncAdapterHelper gtaskSyncAdapterHelper, GtaskSyncAdapterHelper gtaskSyncAdapterHelper, JobManager jobManager, CaldavDao caldavDao) {
Preferences preferences,
JobManager jobManager) {
this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper; this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper;
this.preferences = preferences;
this.jobManager = jobManager; this.jobManager = jobManager;
this.caldavDao = caldavDao;
} }
public boolean syncNow() { public boolean syncNow() {
@ -40,7 +37,7 @@ public class SyncAdapters {
} }
public boolean isCaldavSyncEnabled() { public boolean isCaldavSyncEnabled() {
return preferences.getBoolean(R.string.p_sync_caldav, false); return caldavDao.getAccounts().size() > 0;
} }
public void checkPlayServices(Activity activity) { public void checkPlayServices(Activity activity) {

@ -10,6 +10,8 @@ import static org.tasks.PermissionUtil.verifyPermissions;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.GtasksPreferenceService;
@ -18,6 +20,9 @@ import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking; import org.tasks.analytics.Tracking;
import org.tasks.caldav.CaldavAccountSettingsActivity;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskDao; import org.tasks.data.GoogleTaskDao;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.gtasks.GoogleAccountManager; import org.tasks.gtasks.GoogleAccountManager;
@ -34,6 +39,7 @@ import org.tasks.preferences.Preferences;
public class SynchronizationPreferences extends InjectingPreferenceActivity { public class SynchronizationPreferences extends InjectingPreferenceActivity {
private static final int REQUEST_LOGIN = 0; private static final int REQUEST_LOGIN = 0;
private static final int REQUEST_CALDAV_SETTINGS = 101;
@Inject GtasksPreferenceService gtasksPreferenceService; @Inject GtasksPreferenceService gtasksPreferenceService;
@Inject ActivityPermissionRequestor permissionRequestor; @Inject ActivityPermissionRequestor permissionRequestor;
@ -47,6 +53,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
@Inject GoogleAccountManager googleAccountManager; @Inject GoogleAccountManager googleAccountManager;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject JobManager jobManager; @Inject JobManager jobManager;
@Inject CaldavDao caldavDao;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -54,14 +61,25 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
addPreferencesFromResource(R.xml.preferences_synchronization); addPreferencesFromResource(R.xml.preferences_synchronization);
CheckBoxPreference caldavEnabled = PreferenceCategory caldavPreferences = (PreferenceCategory) findPreference(getString(R.string.CalDAV));
(CheckBoxPreference) findPreference(getString(R.string.p_sync_caldav)); for (CaldavAccount caldavAccount : caldavDao.getAccounts()) {
caldavEnabled.setChecked(syncAdapters.isCaldavSyncEnabled()); Preference accountPreferences = new Preference(this);
caldavEnabled.setOnPreferenceChangeListener( accountPreferences.setTitle(caldavAccount.getName());
(preference, newValue) -> { accountPreferences.setOnPreferenceClickListener(preference -> {
jobManager.updateBackgroundSync(((boolean) newValue), null, null); Intent intent = new Intent(this, CaldavAccountSettingsActivity.class);
return true; intent.putExtra(CaldavAccountSettingsActivity.EXTRA_CALDAV_DATA, caldavAccount);
startActivityForResult(intent, REQUEST_CALDAV_SETTINGS);
return false;
});
caldavPreferences.addPreference(accountPreferences);
}
findPreference(getString(R.string.add_account)).setOnPreferenceClickListener(
preference -> {
startActivityForResult(new Intent(this, CaldavAccountSettingsActivity.class),
REQUEST_CALDAV_SETTINGS);
return false;
}); });
final CheckBoxPreference gtaskPreference = final CheckBoxPreference gtaskPreference =
(CheckBoxPreference) findPreference(getString(R.string.sync_gtasks)); (CheckBoxPreference) findPreference(getString(R.string.sync_gtasks));
gtaskPreference.setChecked(syncAdapters.isGoogleTaskSyncEnabled()); gtaskPreference.setChecked(syncAdapters.isGoogleTaskSyncEnabled());
@ -77,7 +95,6 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
} else { } else {
jobManager.updateBackgroundSync(); jobManager.updateBackgroundSync();
tracker.reportEvent(Tracking.Events.GTASK_DISABLED); tracker.reportEvent(Tracking.Events.GTASK_DISABLED);
gtasksPreferenceService.stopOngoing();
return true; return true;
} }
}); });
@ -143,6 +160,13 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
jobManager.updateBackgroundSync(); jobManager.updateBackgroundSync();
} }
((CheckBoxPreference) findPreference(getString(R.string.sync_gtasks))).setChecked(enabled); ((CheckBoxPreference) findPreference(getString(R.string.sync_gtasks))).setChecked(enabled);
} else if (requestCode == REQUEST_CALDAV_SETTINGS) {
if (resultCode == RESULT_OK) {
jobManager.updateBackgroundSync();
Intent intent = getIntent();
finish();
startActivity(intent);
}
} else { } else {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }

@ -33,7 +33,6 @@ public class NavigationDrawerFragment extends InjectingFragment {
public static final int REQUEST_NEW_LIST = 4; public static final int REQUEST_NEW_LIST = 4;
public static final int ACTIVITY_REQUEST_NEW_FILTER = 5; public static final int ACTIVITY_REQUEST_NEW_FILTER = 5;
public static final int REQUEST_NEW_GTASK_LIST = 6; public static final int REQUEST_NEW_GTASK_LIST = 6;
public static final int REQUEST_NEW_CALDAV_ACCOUNT = 7;
private static final String TOKEN_LAST_SELECTED = "lastSelected"; // $NON-NLS-1$ private static final String TOKEN_LAST_SELECTED = "lastSelected"; // $NON-NLS-1$
private final RefreshReceiver refreshReceiver = new RefreshReceiver(); private final RefreshReceiver refreshReceiver = new RefreshReceiver();
@Inject LocalBroadcastManager localBroadcastManager; @Inject LocalBroadcastManager localBroadcastManager;
@ -77,8 +76,7 @@ public class NavigationDrawerFragment extends InjectingFragment {
} }
} else if (requestCode == REQUEST_NEW_LIST } else if (requestCode == REQUEST_NEW_LIST
|| requestCode == ACTIVITY_REQUEST_NEW_FILTER || requestCode == ACTIVITY_REQUEST_NEW_FILTER
|| requestCode == REQUEST_NEW_GTASK_LIST || requestCode == REQUEST_NEW_GTASK_LIST) {
|| requestCode == REQUEST_NEW_CALDAV_ACCOUNT) {
if (resultCode == RESULT_OK && data != null) { if (resultCode == RESULT_OK && data != null) {
Filter newList = data.getParcelableExtra(TaskListActivity.OPEN_FILTER); Filter newList = data.getParcelableExtra(TaskListActivity.OPEN_FILTER);
if (newList != null) { if (newList != null) {

@ -20,6 +20,7 @@ import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.helper.UUIDHelper; import com.todoroo.astrid.helper.UUIDHelper;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask; import org.tasks.data.CaldavTask;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
@ -60,7 +61,7 @@ public class RemoteListFragment extends TaskEditControlFragment {
originalList = originalList =
new GtasksFilter(gtasksListService.getList(task.getTransitory(GoogleTask.KEY))); new GtasksFilter(gtasksListService.getList(task.getTransitory(GoogleTask.KEY)));
} else if (task.hasTransitory(CaldavTask.KEY)) { } else if (task.hasTransitory(CaldavTask.KEY)) {
originalList = new CaldavFilter(caldavDao.getByUuid(task.getTransitory(CaldavTask.KEY))); originalList = new CaldavFilter(caldavDao.getCalendarByUuid(task.getTransitory(CaldavTask.KEY)));
} else { } else {
originalList = defaultFilterProvider.getDefaultRemoteList(); originalList = defaultFilterProvider.getDefaultRemoteList();
} }
@ -70,7 +71,10 @@ public class RemoteListFragment extends TaskEditControlFragment {
if (googleTask != null) { if (googleTask != null) {
originalList = new GtasksFilter(gtasksListService.getList(googleTask.getListId())); originalList = new GtasksFilter(gtasksListService.getList(googleTask.getListId()));
} else if (caldavTask != null) { } else if (caldavTask != null) {
originalList = new CaldavFilter(caldavDao.getByUuid(caldavTask.getAccount())); CaldavCalendar calendarByUuid = caldavDao.getCalendarByUuid(caldavTask.getCalendar());
if (calendarByUuid != null) {
originalList = new CaldavFilter(calendarByUuid);
}
} }
} }
@ -125,7 +129,7 @@ public class RemoteListFragment extends TaskEditControlFragment {
CaldavTask caldavTask = caldavDao.getTask(task.getId()); CaldavTask caldavTask = caldavDao.getTask(task.getId());
if (caldavTask != null if (caldavTask != null
&& selectedList instanceof CaldavFilter && selectedList instanceof CaldavFilter
&& caldavTask.getAccount().equals(((CaldavFilter) selectedList).getUuid())) { && caldavTask.getCalendar().equals(((CaldavFilter) selectedList).getUuid())) {
return; return;
} }
task.putTransitory(SyncFlags.FORCE_SYNC, true); task.putTransitory(SyncFlags.FORCE_SYNC, true);

@ -64,16 +64,6 @@
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout style="@style/TagSettingsRow">
<android.support.design.widget.TextInputEditText
android:id="@+id/color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/color"
android:textColor="?attr/asTextColor"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/asContentBackground"
android:orientation="vertical"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true">
<include layout="@layout/toolbar"/>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout style="@style/TagSettingsRow">
<android.support.design.widget.TextInputEditText
android:id="@+id/color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/color"
android:textColor="?attr/asTextColor"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/remove"
android:icon="@drawable/ic_delete_24dp"
android:title="@string/remove"
app:showAsAction="ifRoom"/>
</menu>

@ -3,6 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/menu_caldav_list_fragment" android:id="@+id/menu_caldav_list_fragment"
android:title="@string/account_settings" android:title="@string/calendar_settings"
app:showAsAction="never"/> app:showAsAction="never"/>
</menu> </menu>

@ -446,7 +446,6 @@
<string name="user">Потребител</string> <string name="user">Потребител</string>
<string name="password">Парола</string> <string name="password">Парола</string>
<string name="error_adding_account">Грешка при добавяне на акаунт</string> <string name="error_adding_account">Грешка при добавяне на акаунт</string>
<string name="account_settings">Настройки на акаунта</string>
<string name="notification_channel_settings">Управление на уведомления</string> <string name="notification_channel_settings">Управление на уведомления</string>
<string name="battery_optimization_settings">Управление на оптимизациите на батерията</string> <string name="battery_optimization_settings">Управление на оптимизациите на батерията</string>
<string name="notification_disable_battery_optimizations_description">Оптимизациите на батерията могат да забавят уведомленията</string> <string name="notification_disable_battery_optimizations_description">Оптимизациите на батерията могат да забавят уведомленията</string>

@ -432,7 +432,6 @@
<string name="user">Benutzer</string> <string name="user">Benutzer</string>
<string name="password">Passwort</string> <string name="password">Passwort</string>
<string name="error_adding_account">Fehler beim Hinzufügen des Accounts</string> <string name="error_adding_account">Fehler beim Hinzufügen des Accounts</string>
<string name="account_settings">Account Einstellungen</string>
<string name="notification_channel_settings">Verwalte Benachrichtigungen</string> <string name="notification_channel_settings">Verwalte Benachrichtigungen</string>
<string name="battery_optimization_settings">Energiespar-Optionen verwalten</string> <string name="battery_optimization_settings">Energiespar-Optionen verwalten</string>
<string name="notification_disable_battery_optimizations_description">Energiesparfunktionen verwalten</string> <string name="notification_disable_battery_optimizations_description">Energiesparfunktionen verwalten</string>

@ -440,7 +440,6 @@
<string name="user">Usuario</string> <string name="user">Usuario</string>
<string name="password">Contraseña</string> <string name="password">Contraseña</string>
<string name="error_adding_account">Error añadiendo cuenta</string> <string name="error_adding_account">Error añadiendo cuenta</string>
<string name="account_settings">Configuración de cuenta</string>
<string name="notification_channel_settings">Gestionar notificaciones</string> <string name="notification_channel_settings">Gestionar notificaciones</string>
<string name="battery_optimization_settings">Optimizaciones en gestión de batería</string> <string name="battery_optimization_settings">Optimizaciones en gestión de batería</string>
<string name="notification_disable_battery_optimizations_description">Optimizaciones de batería pueden retrasar notificaciones</string> <string name="notification_disable_battery_optimizations_description">Optimizaciones de batería pueden retrasar notificaciones</string>

@ -427,7 +427,6 @@
<string name="password">Mot de passe</string> <string name="password">Mot de passe</string>
<string name="url">Adresse URL</string> <string name="url">Adresse URL</string>
<string name="error_adding_account">Erreur dans l\'ajout du compte</string> <string name="error_adding_account">Erreur dans l\'ajout du compte</string>
<string name="account_settings">Paramètres du compte</string>
<string name="notification_channel_settings">Gérer les notifications</string> <string name="notification_channel_settings">Gérer les notifications</string>
<string name="battery_optimization_settings">Gérer les optimisations de la batterie</string> <string name="battery_optimization_settings">Gérer les optimisations de la batterie</string>
<string name="notification_disable_battery_optimizations_description">Optimisations de la batterie avec des notifications par délais</string> <string name="notification_disable_battery_optimizations_description">Optimisations de la batterie avec des notifications par délais</string>

@ -447,7 +447,6 @@
<string name="password">Jelszó</string> <string name="password">Jelszó</string>
<string name="url">Link</string> <string name="url">Link</string>
<string name="error_adding_account">Felhasználói fiók hozzáadása sikertelen</string> <string name="error_adding_account">Felhasználói fiók hozzáadása sikertelen</string>
<string name="account_settings">Felhasználói fiók beállítások</string>
<string name="notification_channel_settings">Értesítések kezelése</string> <string name="notification_channel_settings">Értesítések kezelése</string>
<string name="battery_optimization_settings">Akkumulátor optimalizálások kezelése</string> <string name="battery_optimization_settings">Akkumulátor optimalizálások kezelése</string>
<string name="notification_disable_battery_optimizations_description">Az akkumulátor optimalizálási funkciók késleltethetik az értesítéseket</string> <string name="notification_disable_battery_optimizations_description">Az akkumulátor optimalizálási funkciók késleltethetik az értesítéseket</string>

@ -442,7 +442,6 @@
<string name="add_account">Aggiungi account</string> <string name="add_account">Aggiungi account</string>
<string name="user">Utente</string> <string name="user">Utente</string>
<string name="error_adding_account">Aggiunta account fallita</string> <string name="error_adding_account">Aggiunta account fallita</string>
<string name="account_settings">Impostazioni account</string>
<string name="notification_channel_settings">Gestione notifiche</string> <string name="notification_channel_settings">Gestione notifiche</string>
<string name="battery_optimization_settings">Ottimizza consumo batteria</string> <string name="battery_optimization_settings">Ottimizza consumo batteria</string>
<string name="notification_disable_battery_optimizations_description">L\'ottimizzazione consumi può ritardare le notifiche</string> <string name="notification_disable_battery_optimizations_description">L\'ottimizzazione consumi può ritardare le notifiche</string>

@ -395,7 +395,6 @@
<string name="password">סיסמא</string> <string name="password">סיסמא</string>
<string name="url">כתובת URL</string> <string name="url">כתובת URL</string>
<string name="error_adding_account">שגיאה בהוספת חשבון</string> <string name="error_adding_account">שגיאה בהוספת חשבון</string>
<string name="account_settings">הגדרות חשבון</string>
<string name="notification_channel_settings">נהל התראות</string> <string name="notification_channel_settings">נהל התראות</string>
<string name="battery_optimization_settings">נהל חיסכון בסוללה</string> <string name="battery_optimization_settings">נהל חיסכון בסוללה</string>
<string name="notification_disable_battery_optimizations_description">חיסכון בסוללה עלול לעכב התראות</string> <string name="notification_disable_battery_optimizations_description">חיסכון בסוללה עלול לעכב התראות</string>

@ -444,7 +444,6 @@
<string name="user">ユーザー</string> <string name="user">ユーザー</string>
<string name="password">パスワード</string> <string name="password">パスワード</string>
<string name="error_adding_account">アカウントの追加時にエラーが発生しました</string> <string name="error_adding_account">アカウントの追加時にエラーが発生しました</string>
<string name="account_settings">アカウント設定</string>
<string name="notification_channel_settings">通知を管理</string> <string name="notification_channel_settings">通知を管理</string>
<string name="battery_optimization_settings">バッテリー最適化を管理</string> <string name="battery_optimization_settings">バッテリー最適化を管理</string>
<string name="notification_disable_battery_optimizations_description">バッテリー最適化をすると通知が遅れることがあります</string> <string name="notification_disable_battery_optimizations_description">バッテリー最適化をすると通知が遅れることがあります</string>

@ -447,7 +447,6 @@
<string name="user">사용자</string> <string name="user">사용자</string>
<string name="password">비밀번호</string> <string name="password">비밀번호</string>
<string name="error_adding_account">계정 추가 오류</string> <string name="error_adding_account">계정 추가 오류</string>
<string name="account_settings">계정 설정</string>
<string name="notification_channel_settings">알림 관리</string> <string name="notification_channel_settings">알림 관리</string>
<string name="battery_optimization_settings">배터리 최적화 관리</string> <string name="battery_optimization_settings">배터리 최적화 관리</string>
<string name="notification_disable_battery_optimizations_description">배터리 최적화 시 알림이 지연될 수 있습니다. </string> <string name="notification_disable_battery_optimizations_description">배터리 최적화 시 알림이 지연될 수 있습니다. </string>

@ -443,7 +443,6 @@
<string name="user">Vartotojas</string> <string name="user">Vartotojas</string>
<string name="password">Slaptažodis</string> <string name="password">Slaptažodis</string>
<string name="error_adding_account">Klaida kuriant paskyrą</string> <string name="error_adding_account">Klaida kuriant paskyrą</string>
<string name="account_settings">Paskyros nustatymai</string>
<string name="notification_channel_settings">Valdyti pranešimus</string> <string name="notification_channel_settings">Valdyti pranešimus</string>
<string name="battery_optimization_settings">Valdyti baterijos optimizacijas</string> <string name="battery_optimization_settings">Valdyti baterijos optimizacijas</string>
<string name="notification_disable_battery_optimizations_description">Baterijos optimizacijos gali uždelsti pranešimus</string> <string name="notification_disable_battery_optimizations_description">Baterijos optimizacijos gali uždelsti pranešimus</string>

@ -433,7 +433,6 @@
<string name="user">Gebruiker</string> <string name="user">Gebruiker</string>
<string name="password">Wachtwoord</string> <string name="password">Wachtwoord</string>
<string name="error_adding_account">Fout bij account toevoegen</string> <string name="error_adding_account">Fout bij account toevoegen</string>
<string name="account_settings">Account instellingen</string>
<string name="notification_channel_settings">Beheer notificaties</string> <string name="notification_channel_settings">Beheer notificaties</string>
<string name="battery_optimization_settings">Beheer batterij optimalisatie</string> <string name="battery_optimization_settings">Beheer batterij optimalisatie</string>
<string name="notification_disable_battery_optimizations_description">Batterij optimalisatie kan meldingen vertragen</string> <string name="notification_disable_battery_optimizations_description">Batterij optimalisatie kan meldingen vertragen</string>

@ -420,7 +420,6 @@
<string name="user">Użytkownik</string> <string name="user">Użytkownik</string>
<string name="password">Hasło</string> <string name="password">Hasło</string>
<string name="error_adding_account">Błąd przy dodawaniu konta</string> <string name="error_adding_account">Błąd przy dodawaniu konta</string>
<string name="account_settings">Ustawienia konta</string>
<string name="notification_channel_settings">Zarządzaj powiadamieniami</string> <string name="notification_channel_settings">Zarządzaj powiadamieniami</string>
<string name="battery_optimization_settings">Zarządzaj zużyciem energii</string> <string name="battery_optimization_settings">Zarządzaj zużyciem energii</string>
<string name="notification_disable_battery_optimizations_description">Optymalizacja zużycia energii może opóźniać powiadomienia</string> <string name="notification_disable_battery_optimizations_description">Optymalizacja zużycia energii może opóźniać powiadomienia</string>

@ -444,7 +444,6 @@
<string name="user">Пользователь</string> <string name="user">Пользователь</string>
<string name="password">Пароль</string> <string name="password">Пароль</string>
<string name="error_adding_account">Ошибка при добавлении учётной записи</string> <string name="error_adding_account">Ошибка при добавлении учётной записи</string>
<string name="account_settings">Настройки учётной записи</string>
<string name="notification_channel_settings">Управление уведомлениями</string> <string name="notification_channel_settings">Управление уведомлениями</string>
<string name="battery_optimization_settings">Управление расходом батареи</string> <string name="battery_optimization_settings">Управление расходом батареи</string>
<string name="notification_disable_battery_optimizations_description">Оптимизация расхода может задерживать уведомления</string> <string name="notification_disable_battery_optimizations_description">Оптимизация расхода может задерживать уведомления</string>

@ -443,7 +443,6 @@
<string name="user">Užívateľ</string> <string name="user">Užívateľ</string>
<string name="password">Heslo</string> <string name="password">Heslo</string>
<string name="error_adding_account">Chyba pridania účtu</string> <string name="error_adding_account">Chyba pridania účtu</string>
<string name="account_settings">Nastavenie účtu</string>
<string name="notification_channel_settings">Spravovať upozornenia</string> <string name="notification_channel_settings">Spravovať upozornenia</string>
<string name="battery_optimization_settings">Zníženie spotreby energie</string> <string name="battery_optimization_settings">Zníženie spotreby energie</string>
<string name="notification_disable_battery_optimizations_description">Zníženie spotreby môže spôsobiť oneskorenie upozornení</string> <string name="notification_disable_battery_optimizations_description">Zníženie spotreby môže spôsobiť oneskorenie upozornení</string>

@ -449,7 +449,6 @@
<string name="user">Kullanıcı</string> <string name="user">Kullanıcı</string>
<string name="password">Parola</string> <string name="password">Parola</string>
<string name="error_adding_account">Hesap eklenirken hata</string> <string name="error_adding_account">Hesap eklenirken hata</string>
<string name="account_settings">Hesap ayarları</string>
<string name="notification_channel_settings">Bildirimleri yönet</string> <string name="notification_channel_settings">Bildirimleri yönet</string>
<string name="battery_optimization_settings">Pil optimizasyonlarını yönet</string> <string name="battery_optimization_settings">Pil optimizasyonlarını yönet</string>
<string name="notification_disable_battery_optimizations_description">Pil optimizasyonları bildirimleri geciktirebilir</string> <string name="notification_disable_battery_optimizations_description">Pil optimizasyonları bildirimleri geciktirebilir</string>

@ -444,7 +444,6 @@
<string name="password">密码</string> <string name="password">密码</string>
<string name="url">网址</string> <string name="url">网址</string>
<string name="error_adding_account">添加帐号错误</string> <string name="error_adding_account">添加帐号错误</string>
<string name="account_settings">帐号设置</string>
<string name="notification_channel_settings">管理通知</string> <string name="notification_channel_settings">管理通知</string>
<string name="battery_optimization_settings">电池优化管理</string> <string name="battery_optimization_settings">电池优化管理</string>
<string name="notification_disable_battery_optimizations_description">电池优化可能会延迟通知</string> <string name="notification_disable_battery_optimizations_description">电池优化可能会延迟通知</string>

@ -289,8 +289,7 @@
<string name="p_bundle_notifications">bundle_notifications</string> <string name="p_bundle_notifications">bundle_notifications</string>
<string name="p_strict_mode">strict_mode</string> <string name="p_strict_mode">strict_mode</string>
<string name="warned_play_services">warned_play_services</string> <string name="warned_play_services">warned_play_services</string>
<string name="p_sync_caldav">sync_caldav</string>
<string name="p_background_sync_unmetered_only">background_sync_unmetered_only</string> <string name="p_background_sync_unmetered_only">background_sync_unmetered_only</string>
<string name="p_purchases">purchases</string> <string name="p_purchases">purchases</string>
<string name="p_sync_ongoing">sync_ongoing</string>
</resources> </resources>

@ -603,7 +603,7 @@ File %1$s contained %2$s.\n\n
<string name="tag_FEx_untagged">Uncategorized</string> <string name="tag_FEx_untagged">Uncategorized</string>
<string name="delete_tag_confirmation">Delete %s?</string> <string name="delete_tag_confirmation">Delete %s?</string>
<string name="remove_caldav_account_confirmation">Remove %s?</string>
<!-- Android Notification Title (%s => # tasks) --> <!-- Android Notification Title (%s => # tasks) -->
<string name="TPl_notification">Timers Active for %s!</string> <string name="TPl_notification">Timers Active for %s!</string>
@ -830,7 +830,7 @@ File %1$s contained %2$s.\n\n
<string name="password">Password</string> <string name="password">Password</string>
<string name="url">URL</string> <string name="url">URL</string>
<string name="error_adding_account">Error adding account</string> <string name="error_adding_account">Error adding account</string>
<string name="account_settings">Account settings</string> <string name="calendar_settings">Calendar settings</string>
<string name="notification_channel_settings">Manage notifications</string> <string name="notification_channel_settings">Manage notifications</string>
<string name="battery_optimization_settings">Manage battery optimizations</string> <string name="battery_optimization_settings">Manage battery optimizations</string>
<string name="notification_disable_battery_optimizations_description">Battery optimizations may delay notifications</string> <string name="notification_disable_battery_optimizations_description">Battery optimizations may delay notifications</string>
@ -875,6 +875,9 @@ File %1$s contained %2$s.\n\n
<string name="tasker_create_task">Create task</string> <string name="tasker_create_task">Create task</string>
<string name="tasker_list_notification">List notification</string> <string name="tasker_list_notification">List notification</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="caldav_principal_not_found">Principal not found</string>
<string name="caldav_home_set_not_found">Home set not found</string>
<string name="caldav_no_supported_calendars">Could not find any calendars with VTODO support</string>
<string name="calendar_not_found">Calendar not found</string> <string name="calendar_not_found">Calendar not found</string>
<string name="network_error">Connection failed</string> <string name="network_error">Connection failed</string>
<string name="background_sync_unmetered_only">Only on unmetered connections</string> <string name="background_sync_unmetered_only">Only on unmetered connections</string>

@ -22,11 +22,12 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/CalDAV"> <PreferenceCategory
<CheckBoxPreference android:key="@string/CalDAV"
android:defaultValue="false" android:title="@string/CalDAV">
android:key="@string/p_sync_caldav" <Preference
android:title="@string/enabled"/> android:key="@string/add_account"
android:title="@string/add_account"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/sync_SPr_interval_title"> <PreferenceCategory android:title="@string/sync_SPr_interval_title">

@ -52,5 +52,5 @@
<screenName name="org.tasks.preferences.MiscellaneousPreferences">MiscellaneousPreferences <screenName name="org.tasks.preferences.MiscellaneousPreferences">MiscellaneousPreferences
</screenName> </screenName>
<screenName name="org.tasks.widget.WidgetConfigActivity">WidgetConfigActivity</screenName> <screenName name="org.tasks.widget.WidgetConfigActivity">WidgetConfigActivity</screenName>
<screenName name="org.tasks.caldav.CaldavSettingsActivity">CaldavSettingsActivity</screenName> <screenName name="org.tasks.caldav.CaldavCalendarSettingsActivity">CaldavSettingsActivity</screenName>
</resources> </resources>
Loading…
Cancel
Save