Create new caldav collections

pull/699/head
Alex Baker 6 years ago
parent 863d86ed18
commit d304b55353

@ -7,6 +7,7 @@ package com.todoroo.astrid.adapter;
import static android.support.v4.content.ContextCompat.getColor;
import static com.todoroo.andlib.utility.AndroidUtilities.preLollipop;
import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT;
import android.app.Activity;
import android.content.BroadcastReceiver;
@ -40,6 +41,8 @@ import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
import org.tasks.billing.Inventory;
import org.tasks.billing.PurchaseActivity;
import org.tasks.caldav.CaldavCalendarSettingsActivity;
import org.tasks.data.CaldavAccount;
import org.tasks.filters.FilterCounter;
import org.tasks.filters.FilterProvider;
import org.tasks.filters.NavigationDrawerAction;
@ -226,7 +229,8 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
return getView(position, convertView, parent);
}
private void addSubMenu(final int titleResource, List<Filter> filters, boolean hideIfEmpty) {
private void addSubMenu(
final int titleResource, List<Filter> filters, boolean hideIfEmpty) {
addSubMenu(activity.getResources().getString(titleResource), filters, hideIfEmpty);
}
@ -265,12 +269,13 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
}
addSubMenu(googleTaskTitle, filterProvider.getGoogleTaskFilters(), true);
for (Pair<String, List<Filter>> account : filterProvider.getCaldavFilters()) {
String caldavTitle = account.first;
for (Pair<CaldavAccount, List<Filter>> filters : filterProvider.getCaldavFilters()) {
CaldavAccount account = filters.first;
String caldavTitle = account.getName();
if (TextUtils.isEmpty(caldavTitle)) {
caldavTitle = activity.getString(R.string.CalDAV);
}
addSubMenu(caldavTitle, account.second, true);
addSubMenu(caldavTitle, filters.second, true);
}
notifyDataSetChanged();
@ -321,12 +326,23 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
}
}
for (Pair<String, List<Filter>> account : filterProvider.getCaldavFilters()) {
String title = account.first;
for (Pair<CaldavAccount, List<Filter>> filters : filterProvider.getCaldavFilters()) {
CaldavAccount account = filters.first;
String title = account.getName();
if (TextUtils.isEmpty(title)) {
title = activity.getString(R.string.CalDAV);
}
addSubMenu(title, account.second, true);
addSubMenu(title, filters.second, !navigationDrawer);
if (navigationDrawer) {
add(
new NavigationDrawerAction(
activity.getString(R.string.caldav_create_new_collection),
R.drawable.ic_add_24dp,
new Intent(activity, CaldavCalendarSettingsActivity.class)
.putExtra(EXTRA_CALDAV_ACCOUNT, account),
NavigationDrawerFragment.REQUEST_NEW_CALDAV_COLLECTION));
}
}
if (navigationDrawer) {

@ -1,36 +1,49 @@
package org.tasks.caldav;
import static android.text.TextUtils.isEmpty;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
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.widget.LinearLayout;
import at.bitfire.dav4android.exception.HttpException;
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 java.net.ConnectException;
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.security.Encryption;
import org.tasks.sync.SyncAdapters;
import org.tasks.themes.ThemeCache;
import org.tasks.themes.ThemeColor;
import org.tasks.ui.DisplayableException;
import org.tasks.ui.MenuColorizer;
public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActivity {
public static final String EXTRA_CALDAV_DATA = "caldavData"; // $NON-NLS-1$
public static final String EXTRA_CALDAV_CALENDAR = "extra_caldav_calendar";
public static final String EXTRA_CALDAV_ACCOUNT = "extra_caldav_account";
public static final String ACTION_RELOAD = "accountRenamed";
public static final String ACTION_DELETED = "accountDeleted";
private static final String EXTRA_SELECTED_THEME = "extra_selected_theme";
@ -42,18 +55,26 @@ public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActi
@Inject Tracker tracker;
@Inject CaldavDao caldavDao;
@Inject SyncAdapters syncAdapters;
@Inject Encryption encryption;
@BindView(R.id.root_layout)
LinearLayout root;
@BindView(R.id.name)
TextInputEditText name;
@BindView(R.id.color)
TextInputEditText color;
@BindView(R.id.name_layout)
TextInputLayout nameLayout;
@BindView(R.id.toolbar)
Toolbar toolbar;
private CaldavCalendar caldavCalendar;
private int selectedTheme;
private CaldavAccount caldavAccount;
private int selectedTheme = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -63,16 +84,27 @@ public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActi
ButterKnife.bind(this);
caldavCalendar = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA);
Intent intent = getIntent();
caldavCalendar = intent.getParcelableExtra(EXTRA_CALDAV_CALENDAR);
caldavAccount =
caldavCalendar == null
? intent.getParcelableExtra(EXTRA_CALDAV_ACCOUNT)
: caldavDao.getAccountByUuid(caldavCalendar.getAccount());
if (savedInstanceState == null) {
selectedTheme = caldavCalendar.getColor();
if (caldavCalendar != null) {
name.setText(caldavCalendar.getName());
selectedTheme = caldavCalendar.getColor();
}
} else {
selectedTheme = savedInstanceState.getInt(EXTRA_SELECTED_THEME);
}
final boolean backButtonSavesTask = preferences.backButtonSavesTask();
toolbar.setTitle(caldavCalendar.getName());
toolbar.setTitle(
caldavCalendar == null
? getString(R.string.caldav_create_new_collection)
: caldavCalendar.getName());
toolbar.setNavigationIcon(
ContextCompat.getDrawable(
this, backButtonSavesTask ? R.drawable.ic_close_24dp : R.drawable.ic_save_24dp));
@ -84,6 +116,7 @@ public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActi
save();
}
});
MenuColorizer.colorToolbar(this, toolbar);
color.setInputType(InputType.TYPE_NULL);
@ -97,6 +130,11 @@ public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActi
outState.putInt(EXTRA_SELECTED_THEME, selectedTheme);
}
@OnTextChanged(R.id.name)
void onNameChanged(CharSequence text) {
nameLayout.setError(null);
}
@OnFocusChange(R.id.color)
void onFocusChange(boolean focused) {
if (focused) {
@ -120,14 +158,85 @@ public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActi
}
private void save() {
if (hasChanges()) {
String name = getNewName();
boolean failed = false;
if (isEmpty(name)) {
nameLayout.setError(getString(R.string.name_cannot_be_empty));
failed = true;
} else {
CaldavCalendar calendarByName = caldavDao.getCalendar(caldavAccount.getUuid(), name);
if (calendarByName != null && !calendarByName.equals(caldavCalendar)) {
nameLayout.setError(getString(R.string.duplicate_name));
failed = true;
}
}
if (failed) {
return;
}
if (caldavCalendar == null) {
CaldavClient client = new CaldavClient(caldavAccount, encryption);
ProgressDialog dialog = dialogBuilder.newProgressDialog(R.string.creating_new_list);
dialog.show();
client
.makeCollection(name)
.doAfterTerminate(dialog::dismiss)
.subscribe(this::createSuccessful, this::requestFailed);
} else if (hasChanges()) {
updateAccount();
} else {
finish();
}
}
private void requestFailed(Throwable t) {
if (t instanceof HttpException) {
showSnackbar(t.getMessage());
} else if (t instanceof DisplayableException) {
showSnackbar(((DisplayableException) t).getResId());
} else if (t instanceof ConnectException) {
showSnackbar(R.string.network_error);
} else {
showGenericError();
}
}
private void showGenericError() {
showSnackbar(R.string.error_adding_account);
}
private void showSnackbar(int resId) {
showSnackbar(getString(resId));
}
private void showSnackbar(String message) {
Snackbar snackbar =
Snackbar.make(root, message, 8000)
.setActionTextColor(ContextCompat.getColor(this, R.color.snackbar_text_color));
snackbar
.getView()
.setBackgroundColor(ContextCompat.getColor(this, R.color.snackbar_background));
snackbar.show();
}
private void createSuccessful(String url) {
CaldavCalendar caldavCalendar = new CaldavCalendar();
caldavCalendar.setAccount(caldavAccount.getUuid());
caldavCalendar.setUrl(url);
caldavCalendar.setName(getNewName());
caldavCalendar.setColor(selectedTheme);
caldavCalendar.setId(caldavDao.insert(caldavCalendar));
setResult(
RESULT_OK,
new Intent().putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(caldavCalendar)));
finish();
}
private void updateAccount() {
caldavCalendar.setName(getNewName());
caldavCalendar.setColor(selectedTheme);
caldavDao.update(caldavCalendar);
setResult(
@ -138,7 +247,15 @@ public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActi
}
private boolean hasChanges() {
return selectedTheme != caldavCalendar.getColor();
if (caldavCalendar == null) {
return !isEmpty(getNewName()) || selectedTheme != -1;
}
return !caldavCalendar.getName().equals(getNewName())
|| selectedTheme != caldavCalendar.getColor();
}
private String getNewName() {
return name.getText().toString().trim();
}
@Override

@ -1,12 +1,15 @@
package org.tasks.caldav;
import static android.text.TextUtils.isEmpty;
import static at.bitfire.dav4android.XmlUtils.NS_CALDAV;
import static at.bitfire.dav4android.XmlUtils.NS_CARDDAV;
import static at.bitfire.dav4android.XmlUtils.NS_WEBDAV;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import at.bitfire.dav4android.BasicDigestAuthHandler;
import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.PropertyCollection;
import at.bitfire.dav4android.XmlUtils;
import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.dav4android.property.CalendarHomeSet;
@ -15,14 +18,15 @@ 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 com.todoroo.astrid.helper.UUIDHelper;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.io.IOException;
import java.io.StringWriter;
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;
@ -30,6 +34,7 @@ import org.tasks.R;
import org.tasks.data.CaldavAccount;
import org.tasks.security.Encryption;
import org.tasks.ui.DisplayableException;
import org.xmlpull.v1.XmlSerializer;
import timber.log.Timber;
class CaldavClient {
@ -152,4 +157,50 @@ class CaldavClient {
}
return urls;
}
public Single<String> makeCollection(String displayName) {
return Single.fromCallable(
() -> {
davResource.setLocation(
davResource.getLocation().resolve(UUIDHelper.newUUID() + "/"));
String mkcolString = getMkcolString(displayName);
davResource.mkCol(mkcolString);
return davResource.getLocation().toString();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private String getMkcolString(String displayName) throws IOException {
StringWriter stringWriter = new StringWriter();
XmlSerializer xml = XmlUtils.newSerializer();
xml.setOutput(stringWriter);
xml.startDocument("UTF-8", null);
xml.setPrefix("", NS_WEBDAV);
xml.setPrefix("CAL", NS_CALDAV);
xml.setPrefix("CARD", NS_CARDDAV);
xml.startTag(NS_WEBDAV, "mkcol");
xml.startTag(XmlUtils.NS_WEBDAV, "set");
xml.startTag(XmlUtils.NS_WEBDAV, "prop");
xml.startTag(XmlUtils.NS_WEBDAV, "resourcetype");
xml.startTag(XmlUtils.NS_WEBDAV, "collection");
xml.endTag(XmlUtils.NS_WEBDAV, "collection");
xml.startTag(XmlUtils.NS_CALDAV, "calendar");
xml.endTag(XmlUtils.NS_CALDAV, "calendar");
xml.endTag(XmlUtils.NS_WEBDAV, "resourcetype");
xml.startTag(XmlUtils.NS_WEBDAV, "displayname");
xml.text(displayName);
xml.endTag(XmlUtils.NS_WEBDAV, "displayname");
xml.startTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set");
xml.startTag(XmlUtils.NS_CALDAV, "comp");
xml.attribute(null, "name", "VTODO");
xml.endTag(XmlUtils.NS_CALDAV, "comp");
xml.endTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set");
xml.endTag(XmlUtils.NS_WEBDAV, "prop");
xml.endTag(XmlUtils.NS_WEBDAV, "set");
xml.endTag(XmlUtils.NS_WEBDAV, "mkcol");
xml.endDocument();
xml.flush();
return stringWriter.toString();
}
}

@ -24,11 +24,11 @@ public class CaldavFilterExposer {
this.syncAdapters = syncAdapters;
}
public List<Pair<String, List<Filter>>> getFilters() {
List<Pair<String, List<Filter>>> filters = new ArrayList<>();
public List<Pair<CaldavAccount, List<Filter>>> getFilters() {
List<Pair<CaldavAccount, 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)));
filters.add(new Pair<>(account, transform(calendars, CaldavFilter::new)));
}
return filters;
}

@ -1,7 +1,6 @@
package org.tasks.caldav;
import static android.app.Activity.RESULT_OK;
import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_DATA;
import android.content.Intent;
import android.os.Bundle;
@ -48,7 +47,7 @@ public class CaldavListFragment extends TaskListFragment {
switch (item.getItemId()) {
case R.id.menu_caldav_list_fragment:
Intent intent = new Intent(getActivity(), CaldavCalendarSettingsActivity.class);
intent.putExtra(EXTRA_CALDAV_DATA, calendar);
intent.putExtra(EXTRA_CALDAV_CALENDAR, calendar);
startActivityForResult(intent, REQUEST_ACCOUNT_SETTINGS);
return true;
default:

@ -73,6 +73,10 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_calendar WHERE uuid = :uuid LIMIT 1")
CaldavCalendar getCalendar(String uuid);
@Query(
"SELECT * FROM caldav_calendar WHERE account = :account AND name = :name COLLATE NOCASE LIMIT 1")
CaldavCalendar getCalendar(String account, String name);
@Query("DELETE FROM caldav_calendar WHERE account = :account")
void deleteCalendarsForAccount(String account);

@ -1,6 +1,7 @@
package org.tasks.filters;
import android.support.v4.util.Pair;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.core.BuiltInFilterExposer;
import com.todoroo.astrid.core.CustomFilterExposer;
@ -12,6 +13,7 @@ import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.tasks.caldav.CaldavFilterExposer;
import org.tasks.data.CaldavAccount;
public class FilterProvider {
@ -58,7 +60,7 @@ public class FilterProvider {
return gtasksFilterExposer.getFilters();
}
public List<Pair<String, List<Filter>>> getCaldavFilters() {
public List<Pair<CaldavAccount, List<Filter>>> getCaldavFilters() {
return caldavFilterExposer.getFilters();
}
}

@ -33,6 +33,7 @@ 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_COLLECTION = 7;
private static final String TOKEN_LAST_SELECTED = "lastSelected"; // $NON-NLS-1$
private final RefreshReceiver refreshReceiver = new RefreshReceiver();
@Inject LocalBroadcastManager localBroadcastManager;
@ -76,7 +77,8 @@ public class NavigationDrawerFragment extends InjectingFragment {
}
} else if (requestCode == REQUEST_NEW_LIST
|| requestCode == ACTIVITY_REQUEST_NEW_FILTER
|| requestCode == REQUEST_NEW_GTASK_LIST) {
|| requestCode == REQUEST_NEW_GTASK_LIST
|| requestCode == REQUEST_NEW_CALDAV_COLLECTION) {
if (resultCode == RESULT_OK && data != null) {
Filter newList = data.getParcelableExtra(TaskListActivity.OPEN_FILTER);
if (newList != null) {
@ -220,15 +222,13 @@ public class NavigationDrawerFragment extends InjectingFragment {
@Override
public void onResume() {
super.onResume();
if (adapter != null) {
localBroadcastManager.registerRefreshReceiver(adapter.getFilterListUpdateReceiver());
adapter.populateList();
}
localBroadcastManager.registerRefreshReceiver(refreshReceiver);
localBroadcastManager.registerRefreshListReceiver(refreshReceiver);
repopulateList();
if (adapter != null) {
localBroadcastManager.registerRefreshReceiver(adapter.getFilterListUpdateReceiver());
repopulateList();
}
}
public interface OnFilterItemClickedListener {

@ -4,23 +4,34 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/asContentBackground"
android:orientation="vertical"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true">
android:orientation="vertical">
<include layout="@layout/toolbar"/>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:id="@+id/name_layout"
style="@style/TagSettingsRow">
android:orientation="vertical">
<android.support.design.widget.TextInputEditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/name"
android:imeOptions="flagNoExtractUi"
android:textColor="?attr/asTextColor"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout style="@style/TagSettingsRow">

@ -899,5 +899,6 @@ File %1$s contained %2$s.\n\n
<string name="pro_multiple_google_task_accounts">Multiple Google Task accounts</string>
<string name="pro_tasker_plugins">Tasker plugins</string>
<string name="pro_dashclock_extension">Dashclock extension</string>
<string name="caldav_create_new_collection">Create new collection</string>
</resources>

Loading…
Cancel
Save