diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fc9aca623..465ecd4b8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,6 +12,7 @@ repositories { jcenter() google() maven(url = "https://jitpack.io") + maven(url = "https://oss.sonatype.org/content/repositories/snapshots") } android { @@ -200,6 +201,7 @@ dependencies { implementation("androidx.work:work-runtime:${Versions.work}") implementation("com.mapbox.mapboxsdk:mapbox-android-sdk:7.3.0") implementation("com.mapbox.mapboxsdk:mapbox-sdk-services:4.6.0") + implementation("com.etesync:journalmanager:1.0.2-SNAPSHOT") googleplayImplementation("com.crashlytics.sdk.android:crashlytics:${Versions.crashlytics}") googleplayImplementation("com.google.firebase:firebase-analytics:${Versions.firebase}") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 078b736da..110ffb73b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -326,6 +326,10 @@ android:name=".caldav.CaldavAccountSettingsActivity" android:theme="@style/Tasks"/> + + diff --git a/app/src/main/java/org/tasks/activities/AddEteSyncAccountViewModel.java b/app/src/main/java/org/tasks/activities/AddEteSyncAccountViewModel.java new file mode 100644 index 000000000..46e2b925e --- /dev/null +++ b/app/src/main/java/org/tasks/activities/AddEteSyncAccountViewModel.java @@ -0,0 +1,29 @@ +package org.tasks.activities; + +import android.content.Context; +import androidx.core.util.Pair; +import com.etesync.journalmanager.Crypto.AsymmetricKeyPair; +import org.tasks.etesync.EteSyncClient; +import org.tasks.gtasks.PlayServices; +import org.tasks.ui.CompletableViewModel; + +public class AddEteSyncAccountViewModel + extends CompletableViewModel> { + public void addAccount( + PlayServices playServices, + Context context, + EteSyncClient client, + String url, + String username, + String password, + String encryptionPassword) { + run( + () -> { + playServices.updateSecurityProvider(context); + return client + .setForeground() + .forUrl(url, username, encryptionPassword, null) + .getKeyAndToken(password); + }); + } +} diff --git a/app/src/main/java/org/tasks/activities/UpdateEteSyncAccountViewModel.java b/app/src/main/java/org/tasks/activities/UpdateEteSyncAccountViewModel.java new file mode 100644 index 000000000..0351c3a23 --- /dev/null +++ b/app/src/main/java/org/tasks/activities/UpdateEteSyncAccountViewModel.java @@ -0,0 +1,23 @@ +package org.tasks.activities; + +import android.content.Context; +import androidx.core.util.Pair; +import org.tasks.etesync.EteSyncClient; +import org.tasks.ui.CompletableViewModel; + +public class UpdateEteSyncAccountViewModel extends CompletableViewModel> { + public void addAccount( + Context context, + EteSyncClient client, + String url, + String username, + String password, + String encryptionPassword) { + run( + () -> + client + .setForeground() + .forUrl(url, username, encryptionPassword, null) + .getKeyAndToken(password)); + } +} diff --git a/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.java b/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.java index 279807d10..18d6ced3c 100644 --- a/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.java +++ b/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.java @@ -30,6 +30,7 @@ import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavDao; import org.tasks.databinding.ActivityCaldavAccountSettingsBinding; import org.tasks.dialogs.DialogBuilder; +import org.tasks.etesync.EteSyncAccountSettingsActivity; import org.tasks.injection.ThemedInjectingAppCompatActivity; import org.tasks.preferences.Preferences; import org.tasks.security.Encryption; @@ -41,7 +42,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA implements Toolbar.OnMenuItemClickListener { public static final String EXTRA_CALDAV_DATA = "caldavData"; // $NON-NLS-1$ - private static final String PASSWORD_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + protected static final String PASSWORD_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; @Inject protected Tracker tracker; @Inject protected CaldavDao caldavDao; @Inject protected Encryption encryption; @@ -71,6 +72,9 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA if (!isEmpty(caldavAccount.getPassword())) { binding.password.setText(PASSWORD_MASK); } + if (!isEmpty(caldavAccount.getEncryptionKey())) { + binding.encryptionPassword.setText(PASSWORD_MASK); + } binding.repeat.setChecked(caldavAccount.isSuppressRepeatingTasks()); } } @@ -168,7 +172,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA return binding.user.getText().toString().trim(); } - private boolean passwordChanged() { + protected boolean passwordChanged() { return caldavAccount == null || !PASSWORD_MASK.equals(binding.password.getText().toString().trim()); } @@ -177,6 +181,13 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA return PASSWORD_MASK.equals(input) ? encryption.decrypt(caldavAccount.getPassword()) : input; } + protected String getNewEncryptionPassword() { + String input = binding.encryptionPassword.getText().toString().trim(); + return PASSWORD_MASK.equals(input) + ? null + : input; + } + private void save() { if (requestInProgress()) { return; @@ -186,6 +197,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA String username = getNewUsername(); String url = getNewURL(); String password = getNewPassword(); + String encryptionPassword = getNewEncryptionPassword(); boolean failed = false; @@ -242,6 +254,13 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA failed = true; } + if (this instanceof EteSyncAccountSettingsActivity) { + if (caldavAccount == null && isEmpty(encryptionPassword)) { + binding.encryptionPassword.setError(getString(R.string.password_required)); + failed = true; + } + } + if (failed) { return; } @@ -253,7 +272,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA showProgressIndicator(); updateAccount(url, username, password); } else if (hasChanges()) { - updateAccount(caldavAccount.getUrl()); + updateAccount(); } else { finish(); } @@ -263,20 +282,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA protected abstract void updateAccount(String url, String username, String password); - protected void updateAccount(String principal) { - caldavAccount.setName(getNewName()); - caldavAccount.setUrl(principal); - caldavAccount.setUsername(getNewUsername()); - caldavAccount.setError(""); - if (passwordChanged()) { - caldavAccount.setPassword(encryption.encrypt(getNewPassword())); - } - caldavAccount.setSuppressRepeatingTasks(binding.repeat.isChecked()); - caldavDao.update(caldavAccount); - - setResult(RESULT_OK); - finish(); - } + protected abstract void updateAccount(); protected void requestFailed(Throwable t) { hideProgressIndicator(); @@ -321,7 +327,7 @@ public abstract class BaseCaldavAccountSettingsActivity extends ThemedInjectingA || binding.repeat.isChecked() != caldavAccount.isSuppressRepeatingTasks(); } - private boolean needsValidation() { + protected boolean needsValidation() { return !getNewURL().equals(caldavAccount.getUrl()) || !getNewUsername().equals(caldavAccount.getUsername()) || passwordChanged(); diff --git a/app/src/main/java/org/tasks/caldav/CaldavAccountSettingsActivity.java b/app/src/main/java/org/tasks/caldav/CaldavAccountSettingsActivity.java index 91fdd7663..0ff60643a 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavAccountSettingsActivity.java +++ b/app/src/main/java/org/tasks/caldav/CaldavAccountSettingsActivity.java @@ -54,6 +54,21 @@ public class CaldavAccountSettingsActivity extends BaseCaldavAccountSettingsActi finish(); } + protected void updateAccount(String principal) { + caldavAccount.setName(getNewName()); + caldavAccount.setUrl(principal); + caldavAccount.setUsername(getNewUsername()); + caldavAccount.setError(""); + if (passwordChanged()) { + caldavAccount.setPassword(encryption.encrypt(getNewPassword())); + } + caldavAccount.setSuppressRepeatingTasks(binding.repeat.isChecked()); + caldavDao.update(caldavAccount); + + setResult(RESULT_OK); + finish(); + } + @Override protected void addAccount(String url, String username, String password) { addCaldavAccountViewModel.addAccount(playServices, context, client, url, username, password); @@ -64,6 +79,11 @@ public class CaldavAccountSettingsActivity extends BaseCaldavAccountSettingsActi updateCaldavAccountViewModel.updateCaldavAccount(client, url, username, password); } + @Override + protected void updateAccount() { + updateAccount(caldavAccount.getUrl()); + } + @Override public void inject(ActivityComponent component) { component.inject(this); diff --git a/app/src/main/java/org/tasks/caldav/MemoryCookieStore.java b/app/src/main/java/org/tasks/caldav/MemoryCookieStore.java index e79c6588b..fd600acac 100644 --- a/app/src/main/java/org/tasks/caldav/MemoryCookieStore.java +++ b/app/src/main/java/org/tasks/caldav/MemoryCookieStore.java @@ -23,7 +23,7 @@ import org.apache.commons.collections4.map.MultiKeyMap; * session cookies. */ @SuppressWarnings("all") -class MemoryCookieStore implements CookieJar { +public class MemoryCookieStore implements CookieJar { /** * Stored cookies. The multi-key consists of three parts: name, domain, and path. This ensures diff --git a/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.java b/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.java new file mode 100644 index 000000000..cbcd99f4d --- /dev/null +++ b/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.java @@ -0,0 +1,126 @@ +package org.tasks.etesync; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.core.util.Pair; +import androidx.lifecycle.ViewModelProviders; +import butterknife.OnFocusChange; +import butterknife.OnTextChanged; +import com.todoroo.astrid.helper.UUIDHelper; +import javax.inject.Inject; +import org.tasks.R; +import org.tasks.activities.AddEteSyncAccountViewModel; +import org.tasks.activities.UpdateEteSyncAccountViewModel; +import org.tasks.analytics.Tracking.Events; +import org.tasks.caldav.BaseCaldavAccountSettingsActivity; +import org.tasks.data.CaldavAccount; +import org.tasks.gtasks.PlayServices; +import org.tasks.injection.ActivityComponent; +import org.tasks.injection.ForApplication; + +public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsActivity + implements Toolbar.OnMenuItemClickListener { + + @Inject @ForApplication Context context; + @Inject PlayServices playServices; + @Inject EteSyncClient eteSyncClient; + + private AddEteSyncAccountViewModel addAccountViewModel; + private UpdateEteSyncAccountViewModel updateAccountViewModel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding.repeat.setVisibility(View.GONE); + binding.encryptionPasswordLayout.setVisibility(View.VISIBLE); + + addAccountViewModel = ViewModelProviders.of(this).get(AddEteSyncAccountViewModel.class); + updateAccountViewModel = ViewModelProviders.of(this).get(UpdateEteSyncAccountViewModel.class); + + if (savedInstanceState == null) { + if (caldavAccount == null) { + binding.url.setText(R.string.etesync_url); + } + } + + addAccountViewModel.observe(this, this::addAccount, this::requestFailed); + updateAccountViewModel.observe(this, this::updateAccount, this::requestFailed); + } + + private void addAccount(Pair authentication) { + CaldavAccount newAccount = new CaldavAccount(); + newAccount.setAccountType(CaldavAccount.TYPE_ETESYNC); + newAccount.setUuid(UUIDHelper.newUUID()); + applyTo(newAccount, authentication); + newAccount.setId(caldavDao.insert(newAccount)); + + tracker.reportEvent(Events.CALDAV_ACCOUNT_ADDED); + + setResult(RESULT_OK); + finish(); + } + + private void updateAccount(Pair authentication) { + applyTo(caldavAccount, authentication); + caldavAccount.setError(""); + caldavDao.update(caldavAccount); + setResult(RESULT_OK); + finish(); + } + + private void applyTo(CaldavAccount account, @Nullable Pair authentication) { + account.setName(getNewName()); + account.setUrl(getNewURL()); + account.setUsername(getNewUsername()); + if (authentication != null) { + account.setPassword(encryption.encrypt(authentication.first)); + account.setEncryptionKey(encryption.encrypt(authentication.second)); + } + } + + @Override + protected boolean needsValidation() { + return super.needsValidation() || encryptionPasswordChanged(); + } + + protected boolean encryptionPasswordChanged() { + return caldavAccount == null + || !PASSWORD_MASK.equals(binding.encryptionPassword.getText().toString().trim()); + } + + @Override + protected void addAccount(String url, String username, String password) { + addAccountViewModel.addAccount( + playServices, context, eteSyncClient, url, username, password, getNewEncryptionPassword()); + } + + @Override + protected void updateAccount(String url, String username, String password) { + updateAccountViewModel.addAccount( + context, eteSyncClient, url, username, password, getNewEncryptionPassword()); + } + + @Override + protected void updateAccount() { + updateAccount(null); + } + + @Override + public void inject(ActivityComponent component) { + component.inject(this); + } + + @OnTextChanged(R.id.encryption_password) + void onEncryptionPasswordChanged(CharSequence text) { + binding.encryptionPasswordLayout.setError(null); + } + + @OnFocusChange(R.id.encryption_password) + void onEncryptionPasswordFocused(boolean hasFocus) { + changePasswordFocus(binding.encryptionPassword, hasFocus); + } +} diff --git a/app/src/main/java/org/tasks/etesync/EteSyncClient.java b/app/src/main/java/org/tasks/etesync/EteSyncClient.java new file mode 100644 index 000000000..5c47957a3 --- /dev/null +++ b/app/src/main/java/org/tasks/etesync/EteSyncClient.java @@ -0,0 +1,173 @@ +package org.tasks.etesync; + +import android.content.Context; +import androidx.annotation.Nullable; +import androidx.core.util.Pair; +import at.bitfire.cert4android.CustomCertManager; +import at.bitfire.cert4android.CustomCertManager.CustomHostnameVerifier; +import com.etesync.journalmanager.Crypto; +import com.etesync.journalmanager.Crypto.CryptoManager; +import com.etesync.journalmanager.Exceptions; +import com.etesync.journalmanager.Exceptions.IntegrityException; +import com.etesync.journalmanager.Exceptions.VersionTooNewException; +import com.etesync.journalmanager.JournalAuthenticator; +import com.etesync.journalmanager.JournalManager; +import com.etesync.journalmanager.JournalManager.Journal; +import com.etesync.journalmanager.UserInfoManager; +import com.etesync.journalmanager.UserInfoManager.UserInfo; +import com.etesync.journalmanager.model.CollectionInfo; +import com.etesync.journalmanager.util.TokenAuthenticator; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; +import okhttp3.internal.tls.OkHostnameVerifier; +import org.tasks.DebugNetworkInterceptor; +import org.tasks.caldav.MemoryCookieStore; +import org.tasks.injection.ForApplication; +import org.tasks.preferences.Preferences; +import org.tasks.security.Encryption; +import timber.log.Timber; + +public class EteSyncClient { + + private final Encryption encryption; + private final Preferences preferences; + private final DebugNetworkInterceptor interceptor; + private final String username; + private final String encryptionPassword; + private final OkHttpClient httpClient; + private final HttpUrl httpUrl; + private final Context context; + private final JournalManager journalManager; + private boolean foreground; + + @Inject + public EteSyncClient( + @ForApplication Context context, + Encryption encryption, + Preferences preferences, + DebugNetworkInterceptor interceptor) { + this.context = context; + this.encryption = encryption; + this.preferences = preferences; + this.interceptor = interceptor; + username = null; + encryptionPassword = null; + httpClient = null; + httpUrl = null; + journalManager = null; + } + + private EteSyncClient( + Context context, + Encryption encryption, + Preferences preferences, + DebugNetworkInterceptor interceptor, + String url, + String username, + String encryptionPassword, + String token, + boolean foreground) + throws NoSuchAlgorithmException, KeyManagementException { + this.context = context; + this.encryption = encryption; + this.preferences = preferences; + this.interceptor = interceptor; + this.username = username; + this.encryptionPassword = encryptionPassword; + + CustomCertManager customCertManager = new CustomCertManager(context); + customCertManager.setAppInForeground(foreground); + CustomHostnameVerifier hostnameVerifier = + customCertManager.hostnameVerifier(OkHostnameVerifier.INSTANCE); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] {customCertManager}, null); + + Builder builder = + new OkHttpClient() + .newBuilder() + .addNetworkInterceptor(new TokenAuthenticator(null, token)) + .cookieJar(new MemoryCookieStore()) + .followRedirects(false) + .followSslRedirects(true) + .sslSocketFactory(sslContext.getSocketFactory(), customCertManager) + .hostnameVerifier(hostnameVerifier) + .readTimeout(30, TimeUnit.SECONDS); + if (preferences.isFlipperEnabled()) { + interceptor.add(builder); + } + httpClient = builder.build(); + httpUrl = HttpUrl.parse(url); + journalManager = new JournalManager(httpClient, httpUrl); + } + + public EteSyncClient forUrl(String url, String username, String encryptionPassword, String token) + throws KeyManagementException, NoSuchAlgorithmException { + return new EteSyncClient( + context, + encryption, + preferences, + interceptor, + url, + username, + encryptionPassword, + token, + foreground); + } + + public Pair getKeyAndToken(String password) + throws IOException, Exceptions.HttpException, VersionTooNewException, IntegrityException { + JournalAuthenticator journalAuthenticator = new JournalAuthenticator(httpClient, httpUrl); + String token = journalAuthenticator.getAuthToken(username, password); + UserInfoManager userInfoManager = new UserInfoManager(httpClient, httpUrl); + UserInfo userInfo = userInfoManager.fetch(username); + String key = Crypto.deriveKey(username, encryptionPassword); + CryptoManager cryptoManager = new CryptoManager(userInfo.getVersion(), key, "userInfo"); + userInfo.verify(cryptoManager); + return Pair.create(token, key); + } + + public CryptoManager getCrypto(Journal journal) + throws VersionTooNewException, IntegrityException { + return new CryptoManager(journal.getVersion(), encryptionPassword, journal.getUid()); + } + + private @Nullable CollectionInfo convertJournalToCollection(Journal journal) { + try { + CryptoManager cryptoManager = getCrypto(journal); + journal.verify(cryptoManager); + CollectionInfo collection = + CollectionInfo.Companion.fromJson(journal.getContent(cryptoManager)); + collection.updateFromJournal(journal); + return collection; + } catch (IntegrityException | VersionTooNewException e) { + Timber.e(e); + return null; + } + } + + public Map getCalendars() throws Exceptions.HttpException { + Map result = new HashMap<>(); + for (Journal journal : journalManager.list()) { + CollectionInfo collection = convertJournalToCollection(journal); + if (collection != null && collection.getType().equals("TASKS")) { + result.put(journal, collection); + } + } + return result; + } + + public EteSyncClient setForeground() { + foreground = true; + return this; + } +} diff --git a/app/src/main/java/org/tasks/injection/ActivityComponent.java b/app/src/main/java/org/tasks/injection/ActivityComponent.java index bb7bf52e5..2cf9d73b3 100644 --- a/app/src/main/java/org/tasks/injection/ActivityComponent.java +++ b/app/src/main/java/org/tasks/injection/ActivityComponent.java @@ -19,6 +19,7 @@ import org.tasks.activities.DatePickerActivity; import org.tasks.activities.FilterSelectionActivity; import org.tasks.activities.FilterSettingsActivity; import org.tasks.activities.GoogleTaskListSettingsActivity; +import org.tasks.etesync.EteSyncAccountSettingsActivity; import org.tasks.tags.TagPickerActivity; import org.tasks.activities.TagSettingsActivity; import org.tasks.activities.TimePickerActivity; @@ -134,6 +135,8 @@ public interface ActivityComponent { void inject(CaldavAccountSettingsActivity caldavAccountSettingsActivity); + void inject(EteSyncAccountSettingsActivity eteSyncAccountSettingsActivity); + void inject(DriveLoginActivity driveLoginActivity); void inject(DebugPreferences debugPreferences); diff --git a/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java b/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java index 632fc8fa0..42cad66fd 100644 --- a/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java +++ b/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java @@ -17,6 +17,7 @@ import android.preference.Preference; import android.preference.PreferenceCategory; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.common.base.Strings; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.service.TaskDeleter; import javax.inject.Inject; @@ -26,6 +27,7 @@ import org.tasks.analytics.Tracking; import org.tasks.billing.Inventory; import org.tasks.billing.PurchaseActivity; import org.tasks.caldav.CaldavAccountSettingsActivity; +import org.tasks.etesync.EteSyncAccountSettingsActivity; import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavDao; import org.tasks.data.GoogleTaskAccount; @@ -48,6 +50,8 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity { private static final int REQUEST_CALDAV_SETTINGS = 101; private static final int REQUEST_CALDAV_SUBSCRIBE = 102; private static final int REQUEST_GOOGLE_TASKS_SUBSCRIBE = 103; + private static final int REQUEST_ETESYNC_SETTINGS = 104; + private static final int REQUEST_ETESYNC_SUBSCRIBE = 105; @Inject ActivityPermissionRequestor permissionRequestor; @Inject PermissionChecker permissionChecker; @@ -130,6 +134,23 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity { return false; }); } + + Preference addEteSyncAccount = findPreference(R.string.p_add_etesync_account); + if (inventory.hasPro()) { + addEteSyncAccount.setOnPreferenceClickListener( + preference -> { + addEteSyncAccount(); + return false; + }); + } else { + addEteSyncAccount.setSummary(R.string.requires_pro_subscription); + addEteSyncAccount.setOnPreferenceClickListener( + preference -> { + startActivityForResult( + new Intent(this, PurchaseActivity.class), REQUEST_ETESYNC_SUBSCRIBE); + return false; + }); + } } private void logoutConfirmation(GoogleTaskAccount account) { @@ -201,19 +222,34 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity { private void addCaldavAccounts() { PreferenceCategory caldavPreferences = (PreferenceCategory) findPreference(getString(R.string.CalDAV)); + PreferenceCategory eteSyncPreferences = + (PreferenceCategory) findPreference(R.string.etesync); caldavPreferences.removeAll(); + eteSyncPreferences.removeAll(); for (CaldavAccount caldavAccount : caldavDao.getAccounts()) { Preference accountPreferences = new Preference(this); accountPreferences.setTitle(caldavAccount.getName()); accountPreferences.setSummary(caldavAccount.getError()); - 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); + if (caldavAccount.isCaldavAccount()) { + 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); + } else if (caldavAccount.isEteSyncAccount()) { + accountPreferences.setOnPreferenceClickListener( + preference -> { + Intent intent = new Intent(this, EteSyncAccountSettingsActivity.class); + intent.putExtra(CaldavAccountSettingsActivity.EXTRA_CALDAV_DATA, caldavAccount); + startActivityForResult(intent, REQUEST_ETESYNC_SETTINGS); + return false; + } + ); + eteSyncPreferences.addPreference(accountPreferences); + } } } @@ -222,6 +258,11 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity { new Intent(this, CaldavAccountSettingsActivity.class), REQUEST_CALDAV_SETTINGS); } + private void addEteSyncAccount() { + startActivityForResult( + new Intent(this, EteSyncAccountSettingsActivity.class), REQUEST_ETESYNC_SETTINGS); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_LOGIN) { @@ -233,7 +274,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity { } else if (data != null) { toaster.longToast(data.getStringExtra(GtasksLoginActivity.EXTRA_ERROR)); } - } else if (requestCode == REQUEST_CALDAV_SETTINGS) { + } else if (requestCode == REQUEST_CALDAV_SETTINGS || requestCode == REQUEST_ETESYNC_SETTINGS) { if (resultCode == RESULT_OK) { workManager.updateBackgroundSync(); restart(); @@ -246,6 +287,10 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity { if (inventory.hasPro()) { requestLogin(); } + } else if (requestCode == REQUEST_ETESYNC_SUBSCRIBE) { + if (inventory.hasPro()) { + addEteSyncAccount(); + } } else { super.onActivityResult(requestCode, resultCode, data); } diff --git a/app/src/main/res/layout/activity_caldav_account_settings.xml b/app/src/main/res/layout/activity_caldav_account_settings.xml index a578fd940..7a6459d6e 100644 --- a/app/src/main/res/layout/activity_caldav_account_settings.xml +++ b/app/src/main/res/layout/activity_caldav_account_settings.xml @@ -80,6 +80,22 @@ + + + + + + Tasks Shortcut + EteSync + https://api.etesync.com date_shortcut_morning date_shortcut_afternoon @@ -303,6 +305,7 @@ preference_screen add_google_task_account add_caldav_account + add_etesync_account google_tasks_add_to_top google_tasks_position_hack Custom order synchronization fix diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 122843ddf..b3853ef7f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -378,6 +378,7 @@ File %1$s contained %2$s.\n\n Name cannot be empty Username required Password required + Encryption password required URL required Host name required Must begin with http(s):// @@ -447,6 +448,7 @@ File %1$s contained %2$s.\n\n Add account User Password + Encryption password URL Error: %s Calendar settings diff --git a/app/src/main/res/xml/preferences_synchronization.xml b/app/src/main/res/xml/preferences_synchronization.xml index ffbc0290d..436388f44 100644 --- a/app/src/main/res/xml/preferences_synchronization.xml +++ b/app/src/main/res/xml/preferences_synchronization.xml @@ -31,6 +31,14 @@ android:key="@string/p_add_caldav_account" android:title="@string/add_account"/> + + + +