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=".caldav.CaldavSettingsActivity"/>
<activity
android:name=".caldav.CaldavAccountSettingsActivity"
android:theme="@style/Tasks"/>
<activity
android:name=".caldav.CaldavCalendarSettingsActivity"
android:theme="@style/Tasks"/>
<activity
android:name=".activities.CalendarSelectionActivity"

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

@ -6,7 +6,6 @@
package com.todoroo.astrid.activity;
import static android.support.v4.content.ContextCompat.getColor;
import static com.todoroo.astrid.adapter.FilterAdapter.REQUEST_PURCHASE;
import android.app.Activity;
import android.arch.lifecycle.ViewModelProviders;
@ -54,8 +53,6 @@ import org.tasks.R;
import org.tasks.activities.FilterSettingsActivity;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.billing.Inventory;
import org.tasks.billing.PurchaseActivity;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.SortDialog;
import org.tasks.injection.ForActivity;
@ -70,7 +67,6 @@ import org.tasks.ui.CheckBoxes;
import org.tasks.ui.MenuColorizer;
import org.tasks.ui.ProgressDialogAsyncTask;
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.
@ -104,7 +100,6 @@ public class TaskListFragment extends InjectingFragment
@Inject ViewHolderFactory viewHolderFactory;
@Inject LocalBroadcastManager localBroadcastManager;
@Inject Device device;
@Inject Inventory inventory;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@ -232,7 +227,6 @@ public class TaskListFragment extends InjectingFragment
}
private void setupMenu(Menu menu) {
menu.findItem(R.id.menu_purchase).setVisible(!inventory.hasPro());
MenuItem hidden = menu.findItem(R.id.menu_show_hidden);
if (preferences.getBoolean(R.string.p_show_hidden_tasks, false)) {
hidden.setChecked(true);
@ -319,9 +313,6 @@ public class TaskListFragment extends InjectingFragment
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
case R.id.menu_purchase:
startActivityForResult(new Intent(context, PurchaseActivity.class), REQUEST_PURCHASE);
return true;
default:
return onOptionsItemSelected(item);
}
@ -531,10 +522,6 @@ public class TaskListFragment extends InjectingFragment
activity.recreate();
}
}
} else if (requestCode == REQUEST_PURCHASE) {
if (inventory.hasPro()) {
((TaskListActivity) getActivity()).restart();
}
} else {
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.res.ResourcesCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.util.Pair;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -38,7 +40,6 @@ import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
import org.tasks.billing.Inventory;
import org.tasks.billing.PurchaseActivity;
import org.tasks.caldav.CaldavSettingsActivity;
import org.tasks.filters.FilterCounter;
import org.tasks.filters.FilterProvider;
import org.tasks.filters.NavigationDrawerAction;
@ -258,13 +259,19 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
item.icon = R.drawable.ic_cloud_off_black_24dp;
add(item);
String title = preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME);
if (Strings.isNullOrEmpty(title)) {
title = activity.getResources().getString(R.string.gtasks_GPr_header);
String googleTaskTitle = preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME);
if (Strings.isNullOrEmpty(googleTaskTitle)) {
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();
}
@ -314,17 +321,12 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
}
}
if (preferences.getBoolean(R.string.p_sync_caldav, false)) {
addSubMenu(R.string.CalDAV, filterProvider.getCaldavFilters(), false);
if (navigationDrawer) {
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));
for (Pair<String, List<Filter>> account : filterProvider.getCaldavFilters()) {
String title = account.first;
if (TextUtils.isEmpty(title)) {
title = activity.getString(R.string.CalDAV);
}
addSubMenu(title, account.second, true);
}
if (navigationDrawer) {

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

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

@ -27,11 +27,6 @@ import org.tasks.data.LimitOffsetDataSource;
import org.tasks.jobs.AfterSaveIntentService;
import timber.log.Timber;
/**
* Data Access layer for {@link Task}-related operations.
*
* @author Tim Su <tim@todoroo.com>
*/
@Dao
public abstract class TaskDao {
@ -103,9 +98,9 @@ public abstract class TaskDao {
@android.arch.persistence.room.Query(
"SELECT tasks.* FROM tasks "
+ "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")
public abstract List<Task> getCaldavTasksToPush(String uid);
public abstract List<Task> getCaldavTasksToPush(String calendar);
@android.arch.persistence.room.Query(
"SELECT * FROM TASKS "
@ -146,8 +141,8 @@ public abstract class TaskDao {
public abstract List<Task> getGoogleTasks(String googleTaskList);
@android.arch.persistence.room.Query(
"SELECT tasks.* FROM tasks INNER JOIN caldav_tasks ON caldav_tasks.task = tasks._id WHERE caldav_tasks.account = :caldavAccount")
public abstract List<Task> getCaldavTasks(String 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 calendar);
/**
* 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$
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_ONGOING = "_ongoing"; // $NON-NLS-1$
private final Preferences preferences;
@Inject
@ -40,28 +39,17 @@ public class GtasksPreferenceService {
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 */
public void clearLastSyncDate() {
preferences.clear(IDENTIFIER + PREF_LAST_SYNC);
}
/** Set Ongoing */
public void stopOngoing() {
preferences.setBoolean(IDENTIFIER + PREF_ONGOING, false);
}
/** Set Last Successful Sync Date */
public void recordSuccessfulSync() {
preferences.setLong(IDENTIFIER + PREF_LAST_SYNC, DateUtilities.now() + 1000);
}
/** Set Last Attempted Sync Date */
public void recordSyncStart() {
preferences.setBoolean(IDENTIFIER + PREF_ONGOING, true);
public boolean isOngoing() {
return preferences.isSyncOngoing();
}
}

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

@ -74,7 +74,7 @@ public class ColorPickerActivity extends ThemedInjectingAppCompatActivity
public void themePicked(ColorPickerDialog.Pickable picked) {
Intent data = new Intent();
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);
finish();
}

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

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

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

@ -4,7 +4,6 @@ import static android.text.TextUtils.isEmpty;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
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.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.view.MenuItem;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.PropertyCollection;
import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.dav4android.property.DisplayName;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnFocusChange;
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.service.TaskDeleter;
import java.net.ConnectException;
import java.net.IDN;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
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.CaldavAccount;
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;
import org.tasks.ui.DisplayableException;
import timber.log.Timber;
public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
public class CaldavAccountSettingsActivity extends ThemedInjectingAppCompatActivity
implements Toolbar.OnMenuItemClickListener {
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 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;
@Inject TaskDeleter taskDeleter;
@BindView(R.id.root_layout)
LinearLayout root;
@ -85,38 +76,29 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
@BindView(R.id.password_layout)
TextInputLayout passwordLayout;
@BindView(R.id.color)
TextInputEditText color;
@BindView(R.id.toolbar)
Toolbar toolbar;
private CaldavAccount caldavAccount;
private int selectedTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_caldav_settings);
setContentView(R.layout.activity_caldav_account_settings);
ButterKnife.bind(this);
caldavAccount = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA);
if (savedInstanceState == null) {
if (caldavAccount == null) {
selectedTheme = -1;
} else {
selectedTheme = caldavAccount.getColor();
if (caldavAccount != null) {
url.setText(caldavAccount.getUrl());
user.setText(caldavAccount.getUsername());
if (!isEmpty(caldavAccount.getPassword())) {
password.setText(PASSWORD_MASK);
}
}
} else {
selectedTheme = savedInstanceState.getInt(EXTRA_SELECTED_THEME);
}
final boolean backButtonSavesTask = preferences.backButtonSavesTask();
@ -133,20 +115,16 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
save();
}
});
toolbar.inflateMenu(R.menu.menu_tag_settings);
toolbar.inflateMenu(R.menu.menu_caldav_account_settings);
toolbar.setOnMenuItemClickListener(this);
toolbar.showOverflowMenu();
color.setInputType(InputType.TYPE_NULL);
if (caldavAccount == null) {
toolbar.getMenu().findItem(R.id.delete).setVisible(false);
toolbar.getMenu().findItem(R.id.remove).setVisible(false);
url.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(url, InputMethodManager.SHOW_IMPLICIT);
}
updateTheme();
}
@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
public void inject(ActivityComponent component) {
component.inject(this);
@ -276,51 +231,49 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
ProgressDialog dialog = dialogBuilder.newProgressDialog(R.string.contacting_server);
dialog.show();
client
.getDisplayName()
.getHomeSet()
.doAfterTerminate(dialog::dismiss)
.subscribe(this::addAccount, this::getDisplayNameFailed);
.subscribe(this::addAccount, this::requestFailed);
} else if (needsValidation()) {
CaldavClient client = new CaldavClient(url, username, password);
ProgressDialog dialog = dialogBuilder.newProgressDialog(R.string.contacting_server);
dialog.show();
client
.getDisplayName()
.getHomeSet()
.doAfterTerminate(dialog::dismiss)
.subscribe(this::updateAccount, this::getDisplayNameFailed);
.subscribe(this::updateAccount, this::requestFailed);
} else if (hasChanges()) {
updateAccount(caldavAccount.getName());
updateAccount(caldavAccount.getUrl());
} else {
finish();
}
}
private void addAccount(String name) {
CaldavAccount newAccount = new CaldavAccount(name, UUIDHelper.newUUID());
newAccount.setColor(selectedTheme);
newAccount.setUrl(getNewURL());
private void addAccount(String principal) {
Timber.d("Found principal: %s", principal);
CaldavAccount newAccount = new CaldavAccount();
newAccount.setUrl(principal);
newAccount.setUsername(getNewUsername());
newAccount.setPassword(getNewPassword());
newAccount.setUuid(UUIDHelper.newUUID());
newAccount.setId(caldavDao.insert(newAccount));
setResult(
RESULT_OK,
new Intent().putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(newAccount)));
setResult(RESULT_OK);
finish();
}
private void updateAccount(String name) {
caldavAccount.setName(name);
caldavAccount.setUrl(getNewURL());
private void updateAccount(String principal) {
caldavAccount.setUrl(principal);
caldavAccount.setUsername(getNewUsername());
caldavAccount.setColor(selectedTheme);
caldavAccount.setPassword(getNewPassword());
caldavDao.update(caldavAccount);
setResult(
RESULT_OK,
new Intent().putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(caldavAccount)));
setResult(RESULT_OK);
finish();
}
private void getDisplayNameFailed(Throwable t) {
private void requestFailed(Throwable t) {
if (t instanceof HttpException) {
showSnackbar(t.getMessage());
} else if (t instanceof DisplayableException) {
@ -352,12 +305,9 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
private boolean hasChanges() {
if (caldavAccount == null) {
return selectedTheme >= 0
|| !isEmpty(getNewPassword())
|| !isEmpty(getNewURL())
|| !isEmpty(getNewUsername());
return !isEmpty(getNewPassword()) || !isEmpty(getNewURL()) || !isEmpty(getNewUsername());
}
return selectedTheme != caldavAccount.getColor() || needsValidation();
return needsValidation();
}
private boolean needsValidation() {
@ -382,33 +332,20 @@ public class CaldavSettingsActivity extends ThemedInjectingAppCompatActivity
}
}
@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 deleteAccount() {
private void removeAccount() {
dialogBuilder
.newMessageDialog(R.string.delete_tag_confirmation, caldavAccount.getName())
.newMessageDialog(R.string.remove_caldav_account_confirmation, caldavAccount.getName())
.setPositiveButton(
R.string.delete,
R.string.remove,
(dialog, which) -> {
if (caldavAccount != null) {
caldavDao.delete(caldavAccount);
setResult(
RESULT_OK,
new Intent(ACTION_DELETED)
.putExtra(EXTRA_CALDAV_UUID, caldavAccount.getUuid()));
for (CaldavCalendar calendar :
caldavDao.getCalendarsByAccount(caldavAccount.getUuid())) {
taskDeleter.markDeleted(caldavDao.getTasksByCalendar(calendar.getUuid()));
caldavDao.deleteTasksForCalendar(calendar.getUuid());
}
caldavDao.deleteCalendarsForAccount(caldavAccount.getUuid());
caldavDao.delete(caldavAccount);
setResult(RESULT_OK);
finish();
})
.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
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
deleteAccount();
case R.id.remove:
removeAccount();
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;
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.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.GetCTag;
import at.bitfire.dav4android.property.ResourceType;
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import org.tasks.R;
import org.tasks.ui.DisplayableException;
import timber.log.Timber;
class CaldavClient {
private final DavCalendar davCalendar;
private final DavResource davResource;
private HttpUrl httpUrl;
public CaldavClient(String url, String username, String password) {
BasicDigestAuthHandler basicDigestAuthHandler =
@ -27,18 +45,110 @@ class CaldavClient {
.authenticator(basicDigestAuthHandler)
.cookieJar(new MemoryCookieStore())
.followRedirects(false)
.followSslRedirects(false)
.followSslRedirects(true)
.readTimeout(30, TimeUnit.SECONDS)
.build();
URI uri = URI.create(url);
HttpUrl httpUrl = HttpUrl.get(uri);
davCalendar = new DavCalendar(httpClient, httpUrl);
httpUrl = HttpUrl.get(uri);
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() {
Callable<String> callable =
() -> {
davCalendar.propfind(0, DisplayName.NAME);
DisplayName displayName = davCalendar.getProperties().get(DisplayName.class);
davResource.propfind(0, DisplayName.NAME);
DisplayName displayName = davResource.getProperties().get(DisplayName.class);
if (displayName == null) {
throw new DisplayableException(R.string.calendar_not_found);
}

@ -1,12 +1,15 @@
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.Filter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
import org.tasks.sync.SyncAdapters;
@ -21,23 +24,20 @@ public class CaldavFilterExposer {
this.syncAdapters = syncAdapters;
}
public List<Filter> getFilters() {
if (!syncAdapters.isCaldavSyncEnabled()) {
return Collections.emptyList();
}
List<CaldavAccount> allOrderedByName = caldavDao.getAllOrderedByName();
List<Filter> result = new ArrayList<>();
for (CaldavAccount account : allOrderedByName) {
result.add(new CaldavFilter(account));
public List<Pair<String, List<Filter>>> getFilters() {
List<Pair<String, List<Filter>>> filters = new ArrayList<>();
for (CaldavAccount account : caldavDao.getAccounts()) {
List<CaldavCalendar> calendars = caldavDao.getCalendarsByAccount(account.getUuid());
filters.add(new Pair<>(account.getName(), transform(calendars, CaldavFilter::new)));
}
return result;
return filters;
}
public Filter getFilterByUuid(String uuid) {
if (syncAdapters.isCaldavSyncEnabled()) {
CaldavAccount caldavAccount = caldavDao.getByUuid(uuid);
if (caldavAccount != null) {
return new CaldavFilter(caldavAccount);
CaldavCalendar caldavCalendar = caldavDao.getCalendarByUuid(uuid);
if (caldavCalendar != null) {
return new CaldavFilter(caldavCalendar);
}
}
return null;

@ -1,7 +1,7 @@
package org.tasks.caldav;
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.os.Bundle;
@ -12,19 +12,19 @@ import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
import org.tasks.R;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.injection.FragmentComponent;
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 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();
fragment.filter = filter;
fragment.account = account;
fragment.calendar = calendar;
return fragment;
}
@ -33,7 +33,7 @@ public class CaldavListFragment extends TaskListFragment {
super.onCreate(savedInstanceState);
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) {
switch (item.getItemId()) {
case R.id.menu_caldav_list_fragment:
Intent intent = new Intent(getActivity(), CaldavSettingsActivity.class);
intent.putExtra(EXTRA_CALDAV_DATA, account);
Intent intent = new Intent(getActivity(), CaldavCalendarSettingsActivity.class);
intent.putExtra(EXTRA_CALDAV_DATA, calendar);
startActivityForResult(intent, REQUEST_ACCOUNT_SETTINGS);
return true;
default:
@ -62,9 +62,9 @@ public class CaldavListFragment extends TaskListFragment {
if (resultCode == RESULT_OK) {
TaskListActivity activity = (TaskListActivity) getActivity();
String action = data.getAction();
if (CaldavSettingsActivity.ACTION_DELETED.equals(action)) {
if (CaldavCalendarSettingsActivity.ACTION_DELETED.equals(action)) {
activity.onFilterItemClicked(null);
} else if (CaldavSettingsActivity.ACTION_RELOAD.equals(action)) {
} else if (CaldavCalendarSettingsActivity.ACTION_RELOAD.equals(action)) {
activity
.getIntent()
.putExtra(
@ -81,7 +81,7 @@ public class CaldavListFragment extends TaskListFragment {
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_CALDAV_ACCOUNT, account);
outState.putParcelable(EXTRA_CALDAV_CALENDAR, calendar);
}
@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.transform;
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.newHashSet;
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.iCalendar;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.io.CharStreams;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao;
@ -49,6 +51,7 @@ import okhttp3.ResponseBody;
import org.tasks.BuildConfig;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask;
import org.tasks.injection.ForApplication;
@ -88,18 +91,46 @@ public class CaldavSynchronizer {
// required for dav4android (ServiceLoader)
Thread.currentThread().setContextClassLoader(context.getClassLoader());
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) {
if (isNullOrEmpty(caldavAccount.getPassword())) {
Timber.e("Missing password for %s", caldavAccount);
private void sync(CaldavAccount account, CaldavCalendar caldavCalendar) {
if (isNullOrEmpty(account.getPassword())) {
Timber.e("Missing password for %s", caldavCalendar);
return;
}
Timber.d("sync(%s)", caldavAccount);
Timber.d("sync(%s)", caldavCalendar);
BasicDigestAuthHandler basicDigestAuthHandler =
new BasicDigestAuthHandler(null, caldavAccount.getUsername(), caldavAccount.getPassword());
new BasicDigestAuthHandler(null, account.getUsername(), account.getPassword());
OkHttpClient httpClient =
new OkHttpClient()
.newBuilder()
@ -110,28 +141,28 @@ public class CaldavSynchronizer {
.followSslRedirects(false)
.readTimeout(30, TimeUnit.SECONDS)
.build();
URI uri = URI.create(caldavAccount.getUrl());
URI uri = URI.create(caldavCalendar.getUrl());
HttpUrl httpUrl = HttpUrl.get(uri);
DavCalendar davCalendar = new DavCalendar(httpClient, httpUrl);
try {
pushLocalChanges(caldavAccount, httpClient, httpUrl);
pushLocalChanges(caldavCalendar, httpClient, httpUrl);
davCalendar.propfind(0, GetCTag.NAME, DisplayName.NAME);
PropertyCollection properties = davCalendar.getProperties();
String remoteName = properties.get(DisplayName.class).getDisplayName();
if (!caldavAccount.getName().equals(remoteName)) {
Timber.d("%s -> %s", caldavAccount.getName(), remoteName);
caldavAccount.setName(remoteName);
caldavDao.update(caldavAccount);
if (!caldavCalendar.getName().equals(remoteName)) {
Timber.d("%s -> %s", caldavCalendar.getName(), remoteName);
caldavCalendar.setName(remoteName);
caldavDao.update(caldavCalendar);
localBroadcastManager.broadcastRefreshList();
}
String remoteCtag = properties.get(GetCTag.class).getCTag();
String localCtag = caldavAccount.getCtag();
String localCtag = caldavCalendar.getCtag();
if (localCtag != null && localCtag.equals(remoteCtag)) {
Timber.d("%s up to date", caldavAccount.getName());
Timber.d("%s up to date", caldavCalendar.getName());
return;
}
@ -149,7 +180,7 @@ public class CaldavSynchronizer {
return false;
}
CaldavTask caldavTask =
caldavDao.getTask(caldavAccount.getUuid(), vCard.fileName());
caldavDao.getTask(caldavCalendar.getUuid(), vCard.fileName());
return caldavTask == null || !eTag.getETag().equals(caldavTask.getEtag());
});
@ -168,14 +199,15 @@ public class CaldavSynchronizer {
try {
reader = responseBody.charStream();
processVTodo(
vCard.fileName(), caldavAccount, eTag.getETag(), CharStreams.toString(reader));
vCard.fileName(), caldavCalendar, eTag.getETag(), CharStreams.toString(reader));
} finally {
if (reader != null) {
reader.close();
}
}
} else {
ArrayList<HttpUrl> urls = newArrayList(transform(items, DavResource::getLocation));
ArrayList<HttpUrl> urls =
newArrayList(Iterables.transform(items, DavResource::getLocation));
davCalendar.multiget(urls);
Timber.d("MULTI %s", urls);
@ -195,7 +227,7 @@ public class CaldavSynchronizer {
}
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 =
newArrayList(
difference(
newHashSet(caldavDao.getObjects(caldavAccount.getUuid())),
newHashSet(caldavDao.getObjects(caldavCalendar.getUuid())),
newHashSet(remoteObjects)));
if (deleted.size() > 0) {
Timber.d("DELETED %s", deleted);
taskDeleter.markDeleted(caldavDao.getTasks(caldavAccount.getUuid(), deleted));
caldavDao.deleteObjects(caldavAccount.getUuid(), deleted);
taskDeleter.markDeleted(caldavDao.getTasks(caldavCalendar.getUuid(), deleted));
caldavDao.deleteObjects(caldavCalendar.getUuid(), deleted);
}
caldavAccount.setCtag(remoteCtag);
Timber.d("UPDATE %s", caldavAccount);
caldavDao.update(caldavAccount);
caldavCalendar.setCtag(remoteCtag);
Timber.d("UPDATE %s", caldavCalendar);
caldavDao.update(caldavCalendar);
} catch (IOException | HttpException | DavException e) {
Timber.e(e);
} catch (Exception e) {
@ -224,11 +256,11 @@ public class CaldavSynchronizer {
}
private void pushLocalChanges(
CaldavAccount caldavAccount, OkHttpClient httpClient, HttpUrl httpUrl) {
List<Task> tasks = taskDao.getCaldavTasksToPush(caldavAccount.getUuid());
CaldavCalendar caldavCalendar, OkHttpClient httpClient, HttpUrl httpUrl) {
List<Task> tasks = taskDao.getCaldavTasksToPush(caldavCalendar.getUuid());
for (com.todoroo.astrid.data.Task task : tasks) {
try {
pushTask(task, caldavAccount, httpClient, httpUrl);
pushTask(task, caldavCalendar, httpClient, httpUrl);
} catch (IOException e) {
Timber.e(e);
}
@ -258,10 +290,10 @@ public class CaldavSynchronizer {
}
private void pushTask(
Task task, CaldavAccount caldavAccount, OkHttpClient httpClient, HttpUrl httpUrl)
Task task, CaldavCalendar caldavCalendar, OkHttpClient httpClient, HttpUrl httpUrl)
throws IOException {
Timber.d("pushing %s", task);
List<CaldavTask> deleted = getDeleted(task.getId(), caldavAccount);
List<CaldavTask> deleted = getDeleted(task.getId(), caldavCalendar);
if (!deleted.isEmpty()) {
for (CaldavTask entry : deleted) {
deleteRemoteResource(httpClient, httpUrl, entry);
@ -317,11 +349,12 @@ public class CaldavSynchronizer {
Timber.d("SENT %s", caldavTask);
}
private List<CaldavTask> getDeleted(long taskId, CaldavAccount caldavAccount) {
return caldavDao.getDeleted(taskId, caldavAccount.getUuid());
private List<CaldavTask> getDeleted(long taskId, CaldavCalendar caldavCalendar) {
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 {
List<at.bitfire.ical4android.Task> tasks;
try {
@ -334,12 +367,12 @@ public class CaldavSynchronizer {
if (tasks.size() == 1) {
at.bitfire.ical4android.Task remote = tasks.get(0);
Task task;
CaldavTask caldavTask = caldavDao.getTask(caldavAccount.getUuid(), fileName);
CaldavTask caldavTask = caldavDao.getTask(caldavCalendar.getUuid(), fileName);
if (caldavTask == null) {
task = taskCreator.createWithValues(null, "");
taskDao.createNew(task);
caldavTask =
new CaldavTask(task.getId(), caldavAccount.getUuid(), remote.getUid(), fileName);
new CaldavTask(task.getId(), caldavCalendar.getUuid(), remote.getUid(), fileName);
} else {
task = taskDao.fetch(caldavTask.getTask());
}

@ -8,13 +8,14 @@ import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import java.util.Arrays;
@Entity(tableName = "caldav_account")
public final class CaldavAccount implements Parcelable {
public class CaldavAccount implements Parcelable {
public static Parcelable.Creator<CaldavAccount> CREATOR =
new Parcelable.Creator<CaldavAccount>() {
@Override
public CaldavAccount createFromParcel(Parcel source) {
return new CaldavAccount(source);
@ -36,12 +37,6 @@ public final class CaldavAccount implements Parcelable {
@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 = "";
@ -51,24 +46,20 @@ public final class CaldavAccount implements Parcelable {
@ColumnInfo(name = "password")
private transient String password = "";
public CaldavAccount() {}
@ColumnInfo(name = "iv")
private transient byte[] iv = null;
@Ignore
public CaldavAccount(String name, String uuid) {
this.name = name;
this.uuid = uuid;
}
public CaldavAccount() {}
@Ignore
public CaldavAccount(Parcel source) {
id = source.readLong();
uuid = source.readString();
name = source.readString();
color = source.readInt();
ctag = source.readString();
url = source.readString();
username = source.readString();
password = source.readString();
iv = source.createByteArray();
}
public long getId() {
@ -95,22 +86,6 @@ public final class CaldavAccount implements Parcelable {
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;
}
@ -135,49 +110,25 @@ public final class CaldavAccount implements Parcelable {
this.password = password;
}
@Override
public int describeContents() {
return 0;
public byte[] getIv() {
return iv;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(uuid);
dest.writeString(name);
dest.writeInt(color);
dest.writeString(ctag);
dest.writeString(url);
dest.writeString(username);
dest.writeString(password);
public void setIv(byte[] iv) {
this.iv = iv;
}
@Override
public String toString() {
return "CaldavAccount{"
+ "id="
+ id
+ ", uuid='"
+ uuid
+ '\''
+ ", name='"
+ name
+ '\''
+ ", color="
+ color
+ ", ctag='"
+ ctag
+ '\''
+ ", url='"
+ url
+ '\''
+ ", username='"
+ username
+ '\''
+ ", password='"
+ (TextUtils.isEmpty(password) ? "null" : "******")
+ '\''
+ '}';
return "CaldavAccount{" +
"id=" + id +
", uuid='" + uuid + '\'' +
", name='" + name + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", iv=" + Arrays.toString(iv) +
'}';
}
@Override
@ -194,25 +145,22 @@ public final class CaldavAccount implements Parcelable {
if (id != that.id) {
return false;
}
if (color != that.color) {
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;
}
if (url != null ? !url.equals(that.url) : that.url != null) {
return false;
}
if (username != null ? !username.equals(that.username) : that.username != null) {
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
@ -220,11 +168,26 @@ public final class CaldavAccount implements Parcelable {
int result = (int) (id ^ (id >>> 32));
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);
result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + Arrays.hashCode(iv);
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
public interface CaldavDao {
@Query("SELECT * FROM caldav_account WHERE uuid = :uuid LIMIT 1")
CaldavAccount getByUuid(String uuid);
@Query("SELECT * FROM caldav_calendar WHERE uuid = :uuid LIMIT 1")
CaldavCalendar getCalendarByUuid(String uuid);
@Query("SELECT * FROM caldav_account ORDER BY UPPER(name) ASC")
List<CaldavAccount> getAllOrderedByName();
List<CaldavAccount> getAccounts();
@Insert
long insert(CaldavAccount caldavAccount);
@ -22,6 +22,18 @@ public interface CaldavDao {
@Update
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
long insert(CaldavTask caldavTask);
@ -31,14 +43,14 @@ public interface CaldavDao {
@Delete
void delete(CaldavTask caldavTask);
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId AND deleted > 0 AND account = :account")
List<CaldavTask> getDeleted(long taskId, String account);
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId AND deleted > 0 AND calendar = :calendar")
List<CaldavTask> getDeleted(long taskId, String calendar);
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId AND deleted = 0 LIMIT 1")
CaldavTask getTask(long taskId);
@Query("SELECT * FROM caldav_tasks WHERE account = :account AND object = :object LIMIT 1")
CaldavTask getTask(String account, String object);
@Query("SELECT * FROM caldav_tasks WHERE calendar = :calendar AND object = :object LIMIT 1")
CaldavTask getTask(String calendar, String object);
@Query("DELETE FROM caldav_tasks WHERE task = :taskId")
void deleteById(long taskId);
@ -46,24 +58,36 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId")
List<CaldavTask> getTasks(long taskId);
@Query("SELECT * FROM caldav_account")
List<CaldavAccount> getAccounts();
@Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar")
List<Long> getTasksByCalendar(String calendar);
@Delete
void delete(CaldavAccount caldavAccount);
@Query("SELECT * FROM caldav_calendar")
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")
CaldavAccount getAccount(String uuid);
@Query("SELECT object FROM caldav_tasks WHERE calendar = :calendar")
List<String> getObjects(String calendar);
@Query("DELETE FROM caldav_tasks WHERE account = :uuid")
void deleteTasksForAccount(String uuid);
@Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)")
List<Long> getTasks(String calendar, List<String> objects);
@Query("SELECT object FROM caldav_tasks WHERE account = :account")
List<String> getObjects(String account);
@Query("DELETE FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)")
void deleteObjects(String calendar, List<String> objects);
@Query("SELECT task FROM caldav_tasks WHERE account = :account AND object IN (:objects)")
List<Long> getTasks(String account, List<String> objects);
@Query("SELECT * FROM caldav_calendar WHERE account = :account AND url NOT IN (:urls)")
List<CaldavCalendar> findDeletedCalendars(String account, List<String> urls);
@Query("DELETE FROM caldav_tasks WHERE account = :account AND object IN (:objects)")
void deleteObjects(String account, List<String> objects);
@Query("SELECT * FROM caldav_calendar WHERE account = :account AND url = :url LIMIT 1")
CaldavCalendar getCalendarByUrl(String account, String url);
}

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

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

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

@ -23,7 +23,8 @@ import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
import org.tasks.activities.TimePickerActivity;
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.files.FileExplore;
import org.tasks.files.MyFilePickerActivity;
@ -127,11 +128,13 @@ public interface ActivityComponent {
void inject(GoogleTaskListSettingsActivity googleTaskListSettingsActivity);
void inject(CaldavSettingsActivity caldavSettingsActivity);
void inject(CaldavCalendarSettingsActivity caldavCalendarSettingsActivity);
void inject(TaskerCreateTaskActivity taskerCreateTaskActivity);
void inject(TaskListViewModel taskListViewModel);
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);
case TAG_SYNC:
case TAG_BACKGROUND_SYNC:
return new SyncJob(caldavSynchronizer, googleTaskSynchronizer);
return new SyncJob(
caldavSynchronizer, googleTaskSynchronizer, localBroadcastManager, preferences);
case TAG_BACKUP:
return new BackupJob(context, tasksJsonExporter, preferences);
case TAG_MIDNIGHT_REFRESH:

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

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

@ -445,4 +445,12 @@ public class Preferences {
public boolean usePersistentReminders() {
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 javax.inject.Inject;
import org.tasks.R;
import org.tasks.data.CaldavDao;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.jobs.JobManager;
import org.tasks.preferences.Preferences;
public class SyncAdapters {
private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
private final Preferences preferences;
private final JobManager jobManager;
private final CaldavDao caldavDao;
@Inject
public SyncAdapters(
GtaskSyncAdapterHelper gtaskSyncAdapterHelper,
Preferences preferences,
JobManager jobManager) {
GtaskSyncAdapterHelper gtaskSyncAdapterHelper, JobManager jobManager, CaldavDao caldavDao) {
this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper;
this.preferences = preferences;
this.jobManager = jobManager;
this.caldavDao = caldavDao;
}
public boolean syncNow() {
@ -40,7 +37,7 @@ public class SyncAdapters {
}
public boolean isCaldavSyncEnabled() {
return preferences.getBoolean(R.string.p_sync_caldav, false);
return caldavDao.getAccounts().size() > 0;
}
public void checkPlayServices(Activity activity) {

@ -10,6 +10,8 @@ import static org.tasks.PermissionUtil.verifyPermissions;
import android.content.Intent;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.support.annotation.NonNull;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
@ -18,6 +20,9 @@ import javax.inject.Inject;
import org.tasks.R;
import org.tasks.analytics.Tracker;
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.dialogs.DialogBuilder;
import org.tasks.gtasks.GoogleAccountManager;
@ -34,6 +39,7 @@ import org.tasks.preferences.Preferences;
public class SynchronizationPreferences extends InjectingPreferenceActivity {
private static final int REQUEST_LOGIN = 0;
private static final int REQUEST_CALDAV_SETTINGS = 101;
@Inject GtasksPreferenceService gtasksPreferenceService;
@Inject ActivityPermissionRequestor permissionRequestor;
@ -47,6 +53,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
@Inject GoogleAccountManager googleAccountManager;
@Inject Preferences preferences;
@Inject JobManager jobManager;
@Inject CaldavDao caldavDao;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -54,14 +61,25 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
addPreferencesFromResource(R.xml.preferences_synchronization);
CheckBoxPreference caldavEnabled =
(CheckBoxPreference) findPreference(getString(R.string.p_sync_caldav));
caldavEnabled.setChecked(syncAdapters.isCaldavSyncEnabled());
caldavEnabled.setOnPreferenceChangeListener(
(preference, newValue) -> {
jobManager.updateBackgroundSync(((boolean) newValue), null, null);
return true;
PreferenceCategory caldavPreferences = (PreferenceCategory) findPreference(getString(R.string.CalDAV));
for (CaldavAccount caldavAccount : caldavDao.getAccounts()) {
Preference accountPreferences = new Preference(this);
accountPreferences.setTitle(caldavAccount.getName());
accountPreferences.setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(this, CaldavAccountSettingsActivity.class);
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 =
(CheckBoxPreference) findPreference(getString(R.string.sync_gtasks));
gtaskPreference.setChecked(syncAdapters.isGoogleTaskSyncEnabled());
@ -77,7 +95,6 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
} else {
jobManager.updateBackgroundSync();
tracker.reportEvent(Tracking.Events.GTASK_DISABLED);
gtasksPreferenceService.stopOngoing();
return true;
}
});
@ -143,6 +160,13 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
jobManager.updateBackgroundSync();
}
((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 {
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 ACTIVITY_REQUEST_NEW_FILTER = 5;
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 final RefreshReceiver refreshReceiver = new RefreshReceiver();
@Inject LocalBroadcastManager localBroadcastManager;
@ -77,8 +76,7 @@ public class NavigationDrawerFragment extends InjectingFragment {
}
} else if (requestCode == REQUEST_NEW_LIST
|| requestCode == ACTIVITY_REQUEST_NEW_FILTER
|| requestCode == REQUEST_NEW_GTASK_LIST
|| requestCode == REQUEST_NEW_CALDAV_ACCOUNT) {
|| requestCode == REQUEST_NEW_GTASK_LIST) {
if (resultCode == RESULT_OK && data != null) {
Filter newList = data.getParcelableExtra(TaskListActivity.OPEN_FILTER);
if (newList != null) {

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

@ -64,16 +64,6 @@
</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>
</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">
<item
android:id="@+id/menu_caldav_list_fragment"
android:title="@string/account_settings"
android:title="@string/calendar_settings"
app:showAsAction="never"/>
</menu>

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

@ -432,7 +432,6 @@
<string name="user">Benutzer</string>
<string name="password">Passwort</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="battery_optimization_settings">Energiespar-Optionen verwalten</string>
<string name="notification_disable_battery_optimizations_description">Energiesparfunktionen verwalten</string>

@ -440,7 +440,6 @@
<string name="user">Usuario</string>
<string name="password">Contraseña</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="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>

@ -427,7 +427,6 @@
<string name="password">Mot de passe</string>
<string name="url">Adresse URL</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="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>

@ -447,7 +447,6 @@
<string name="password">Jelszó</string>
<string name="url">Link</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="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>

@ -442,7 +442,6 @@
<string name="add_account">Aggiungi account</string>
<string name="user">Utente</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="battery_optimization_settings">Ottimizza consumo batteria</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="url">כתובת URL</string>
<string name="error_adding_account">שגיאה בהוספת חשבון</string>
<string name="account_settings">הגדרות חשבון</string>
<string name="notification_channel_settings">נהל התראות</string>
<string name="battery_optimization_settings">נהל חיסכון בסוללה</string>
<string name="notification_disable_battery_optimizations_description">חיסכון בסוללה עלול לעכב התראות</string>

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

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

@ -443,7 +443,6 @@
<string name="user">Vartotojas</string>
<string name="password">Slaptažodis</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="battery_optimization_settings">Valdyti baterijos optimizacijas</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="password">Wachtwoord</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="battery_optimization_settings">Beheer batterij optimalisatie</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="password">Hasło</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="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>

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

@ -443,7 +443,6 @@
<string name="user">Užívateľ</string>
<string name="password">Heslo</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="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>

@ -449,7 +449,6 @@
<string name="user">Kullanıcı</string>
<string name="password">Parola</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="battery_optimization_settings">Pil optimizasyonlarını yönet</string>
<string name="notification_disable_battery_optimizations_description">Pil optimizasyonları bildirimleri geciktirebilir</string>

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

@ -289,8 +289,7 @@
<string name="p_bundle_notifications">bundle_notifications</string>
<string name="p_strict_mode">strict_mode</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_purchases">purchases</string>
<string name="p_sync_ongoing">sync_ongoing</string>
</resources>

@ -603,7 +603,7 @@ File %1$s contained %2$s.\n\n
<string name="tag_FEx_untagged">Uncategorized</string>
<string name="delete_tag_confirmation">Delete %s?</string>
<string name="remove_caldav_account_confirmation">Remove %s?</string>
<!-- Android Notification Title (%s => # tasks) -->
<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="url">URL</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="battery_optimization_settings">Manage battery optimizations</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_list_notification">List notification</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="network_error">Connection failed</string>
<string name="background_sync_unmetered_only">Only on unmetered connections</string>

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

@ -52,5 +52,5 @@
<screenName name="org.tasks.preferences.MiscellaneousPreferences">MiscellaneousPreferences
</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>
Loading…
Cancel
Save