From d304b5535347d8cf99cdd1ad942ad77c1bf85c88 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 9 Apr 2018 15:37:49 -0500 Subject: [PATCH] Create new caldav collections --- .../todoroo/astrid/adapter/FilterAdapter.java | 30 +++- .../CaldavCalendarSettingsActivity.java | 131 +++++++++++++++++- .../java/org/tasks/caldav/CaldavClient.java | 55 +++++++- .../org/tasks/caldav/CaldavFilterExposer.java | 6 +- .../org/tasks/caldav/CaldavListFragment.java | 3 +- .../main/java/org/tasks/data/CaldavDao.java | 4 + .../org/tasks/filters/FilterProvider.java | 4 +- .../tasks/ui/NavigationDrawerFragment.java | 14 +- .../activity_caldav_calendar_settings.xml | 23 ++- app/src/main/res/values/strings.xml | 1 + 10 files changed, 236 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java b/app/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java index 04bc5b4f7..b89036e1c 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java +++ b/app/src/main/java/com/todoroo/astrid/adapter/FilterAdapter.java @@ -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 { return getView(position, convertView, parent); } - private void addSubMenu(final int titleResource, List filters, boolean hideIfEmpty) { + private void addSubMenu( + final int titleResource, List filters, boolean hideIfEmpty) { addSubMenu(activity.getResources().getString(titleResource), filters, hideIfEmpty); } @@ -265,12 +269,13 @@ public class FilterAdapter extends ArrayAdapter { } addSubMenu(googleTaskTitle, filterProvider.getGoogleTaskFilters(), true); - for (Pair> account : filterProvider.getCaldavFilters()) { - String caldavTitle = account.first; + for (Pair> 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 { } } - for (Pair> account : filterProvider.getCaldavFilters()) { - String title = account.first; + for (Pair> 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) { diff --git a/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.java b/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.java index 5767d20b5..6c1f73d9c 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.java +++ b/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.java @@ -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 diff --git a/app/src/main/java/org/tasks/caldav/CaldavClient.java b/app/src/main/java/org/tasks/caldav/CaldavClient.java index 12ae5e940..1c9e8cb74 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavClient.java +++ b/app/src/main/java/org/tasks/caldav/CaldavClient.java @@ -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 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(); + } } diff --git a/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java b/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java index 05ed8a276..f8625cafd 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java +++ b/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java @@ -24,11 +24,11 @@ public class CaldavFilterExposer { this.syncAdapters = syncAdapters; } - public List>> getFilters() { - List>> filters = new ArrayList<>(); + public List>> getFilters() { + List>> filters = new ArrayList<>(); for (CaldavAccount account : caldavDao.getAccounts()) { List 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; } diff --git a/app/src/main/java/org/tasks/caldav/CaldavListFragment.java b/app/src/main/java/org/tasks/caldav/CaldavListFragment.java index 290a4ff1b..8a00546e2 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavListFragment.java +++ b/app/src/main/java/org/tasks/caldav/CaldavListFragment.java @@ -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: diff --git a/app/src/main/java/org/tasks/data/CaldavDao.java b/app/src/main/java/org/tasks/data/CaldavDao.java index ae2768a24..db5435b15 100644 --- a/app/src/main/java/org/tasks/data/CaldavDao.java +++ b/app/src/main/java/org/tasks/data/CaldavDao.java @@ -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); diff --git a/app/src/main/java/org/tasks/filters/FilterProvider.java b/app/src/main/java/org/tasks/filters/FilterProvider.java index c29f57999..303907402 100644 --- a/app/src/main/java/org/tasks/filters/FilterProvider.java +++ b/app/src/main/java/org/tasks/filters/FilterProvider.java @@ -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>> getCaldavFilters() { + public List>> getCaldavFilters() { return caldavFilterExposer.getFilters(); } } diff --git a/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.java b/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.java index 0fe6e52b8..86c32bf6f 100644 --- a/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.java +++ b/app/src/main/java/org/tasks/ui/NavigationDrawerFragment.java @@ -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 { diff --git a/app/src/main/res/layout/activity_caldav_calendar_settings.xml b/app/src/main/res/layout/activity_caldav_calendar_settings.xml index 591a56925..5b15b53b8 100644 --- a/app/src/main/res/layout/activity_caldav_calendar_settings.xml +++ b/app/src/main/res/layout/activity_caldav_calendar_settings.xml @@ -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"> + android:layout_height="wrap_content"> + - android:orientation="vertical"> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bab96f213..e9f211fc1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -899,5 +899,6 @@ File %1$s contained %2$s.\n\n Multiple Google Task accounts Tasker plugins Dashclock extension + Create new collection