diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f5b521078..7ab710945 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -381,6 +381,10 @@
android:name=".etesync.EteSyncAccountSettingsActivity"
android:theme="@style/Tasks" />
+
+
@@ -601,6 +605,14 @@
android:name=".etesync.EncryptionSettingsActivity"
android:theme="@style/Tasks" />
+
+
+
+
diff --git a/app/src/main/java/org/tasks/analytics/Constants.kt b/app/src/main/java/org/tasks/analytics/Constants.kt
index f91440b72..c6713e17e 100644
--- a/app/src/main/java/org/tasks/analytics/Constants.kt
+++ b/app/src/main/java/org/tasks/analytics/Constants.kt
@@ -6,4 +6,5 @@ object Constants {
const val SYNC_TYPE_DAVX5 = "davx5"
const val SYNC_TYPE_GOOGLE_TASKS = "google_tasks"
const val SYNC_TYPE_ETESYNC_OT = "etesync_ot"
+ const val SYNC_TYPE_ETEBASE = "etebase"
}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt b/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt
index ad891851e..0d20845e4 100644
--- a/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt
+++ b/app/src/main/java/org/tasks/caldav/BaseCaldavAccountSettingsActivity.kt
@@ -108,7 +108,7 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
get() = !inventory.hasPro
@get:StringRes
- protected abstract val description: Int
+ protected open val description = 0
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
diff --git a/app/src/main/java/org/tasks/data/CaldavAccount.kt b/app/src/main/java/org/tasks/data/CaldavAccount.kt
index 8532f0118..5f36281f4 100644
--- a/app/src/main/java/org/tasks/data/CaldavAccount.kt
+++ b/app/src/main/java/org/tasks/data/CaldavAccount.kt
@@ -12,6 +12,7 @@ import org.tasks.activities.BaseListSettingsActivity
import org.tasks.caldav.CaldavCalendarSettingsActivity
import org.tasks.caldav.LocalListSettingsActivity
import org.tasks.data.OpenTaskDao.Companion.ACCOUNT_TYPE_ETESYNC
+import org.tasks.etebase.EteBaseCalendarSettingsActivity
import org.tasks.etesync.EteSyncCalendarSettingsActivity
import org.tasks.opentasks.OpenTasksListSettingsActivity
import org.tasks.security.KeyStoreEncryption
@@ -84,9 +85,13 @@ class CaldavAccount : Parcelable {
val isCaldavAccount: Boolean
get() = accountType == TYPE_CALDAV
+ @Deprecated("use etebase")
val isEteSyncAccount: Boolean
get() = accountType == TYPE_ETESYNC
+ val isEteBaseAccount: Boolean
+ get() = accountType == TYPE_ETEBASE
+
val isOpenTasks: Boolean
get() = accountType == TYPE_OPENTASKS
@@ -100,6 +105,7 @@ class CaldavAccount : Parcelable {
TYPE_ETESYNC -> EteSyncCalendarSettingsActivity::class.java
TYPE_LOCAL -> LocalListSettingsActivity::class.java
TYPE_OPENTASKS -> OpenTasksListSettingsActivity::class.java
+ TYPE_ETEBASE -> EteBaseCalendarSettingsActivity::class.java
else -> CaldavCalendarSettingsActivity::class.java
}
@@ -159,10 +165,11 @@ class CaldavAccount : Parcelable {
companion object {
const val TYPE_CALDAV = 0
- const val TYPE_ETESYNC = 1
+ @Deprecated("use etebase") const val TYPE_ETESYNC = 1
const val TYPE_LOCAL = 2
const val TYPE_OPENTASKS = 3
const val TYPE_TASKS = 4
+ const val TYPE_ETEBASE = 5
fun String?.openTaskType(): String? = this?.split(":")?.get(0)
diff --git a/app/src/main/java/org/tasks/etebase/AddEteBaseAccountViewModel.kt b/app/src/main/java/org/tasks/etebase/AddEteBaseAccountViewModel.kt
new file mode 100644
index 000000000..30deac0aa
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/AddEteBaseAccountViewModel.kt
@@ -0,0 +1,17 @@
+package org.tasks.etebase
+
+import androidx.core.util.Pair
+import androidx.hilt.lifecycle.ViewModelInject
+import com.etesync.journalmanager.UserInfoManager.UserInfo
+import org.tasks.ui.CompletableViewModel
+
+class AddEteBaseAccountViewModel @ViewModelInject constructor(
+ private val client: EteBaseClient): CompletableViewModel>() {
+ suspend fun addAccount(url: String, username: String, password: String) {
+ run {
+ client.setForeground()
+ val token = client.forUrl(url, username, null, null).getToken(password)
+ Pair.create(client.forUrl(url, username, null, token!!).userInfo(), token)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/CreateCalendarViewModel.kt b/app/src/main/java/org/tasks/etebase/CreateCalendarViewModel.kt
new file mode 100644
index 000000000..dfd4d5b43
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/CreateCalendarViewModel.kt
@@ -0,0 +1,12 @@
+package org.tasks.etebase
+
+import androidx.hilt.lifecycle.ViewModelInject
+import org.tasks.data.CaldavAccount
+import org.tasks.ui.CompletableViewModel
+
+class CreateCalendarViewModel @ViewModelInject constructor(
+ private val client: EteBaseClient) : CompletableViewModel() {
+ suspend fun createCalendar(account: CaldavAccount, name: String, color: Int) {
+ run { client.forAccount(account).makeCollection(name, color) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/CreateUserInfoViewModel.kt b/app/src/main/java/org/tasks/etebase/CreateUserInfoViewModel.kt
new file mode 100644
index 000000000..02a7f5bd1
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/CreateUserInfoViewModel.kt
@@ -0,0 +1,15 @@
+package org.tasks.etebase
+
+import androidx.hilt.lifecycle.ViewModelInject
+import org.tasks.data.CaldavAccount
+import org.tasks.ui.CompletableViewModel
+
+class CreateUserInfoViewModel @ViewModelInject constructor(
+ private val client: EteBaseClient): CompletableViewModel() {
+ suspend fun createUserInfo(caldavAccount: CaldavAccount, derivedKey: String) {
+ run {
+ client.forAccount(caldavAccount).createUserInfo(derivedKey)
+ derivedKey
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/DeleteCalendarViewModel.kt b/app/src/main/java/org/tasks/etebase/DeleteCalendarViewModel.kt
new file mode 100644
index 000000000..0c4906e61
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/DeleteCalendarViewModel.kt
@@ -0,0 +1,13 @@
+package org.tasks.etebase
+
+import androidx.hilt.lifecycle.ViewModelInject
+import org.tasks.data.CaldavAccount
+import org.tasks.data.CaldavCalendar
+import org.tasks.ui.ActionViewModel
+
+class DeleteCalendarViewModel @ViewModelInject constructor(
+ private val client: EteBaseClient) : ActionViewModel() {
+ suspend fun deleteCalendar(account: CaldavAccount, calendar: CaldavCalendar) {
+ run { client.forAccount(account).deleteCollection(calendar) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/EncryptionSettingsActivity.kt b/app/src/main/java/org/tasks/etebase/EncryptionSettingsActivity.kt
new file mode 100644
index 000000000..8a64567f9
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/EncryptionSettingsActivity.kt
@@ -0,0 +1,188 @@
+package org.tasks.etebase
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.view.MenuItem
+import android.view.View
+import androidx.activity.viewModels
+import androidx.appcompat.widget.Toolbar
+import androidx.lifecycle.lifecycleScope
+import at.bitfire.dav4jvm.exception.HttpException
+import butterknife.ButterKnife
+import butterknife.OnTextChanged
+import com.etesync.journalmanager.Constants.Companion.CURRENT_VERSION
+import com.etesync.journalmanager.Crypto.CryptoManager
+import com.etesync.journalmanager.Crypto.deriveKey
+import com.etesync.journalmanager.Exceptions.IntegrityException
+import com.etesync.journalmanager.Exceptions.VersionTooNewException
+import com.etesync.journalmanager.UserInfoManager
+import com.google.android.material.snackbar.Snackbar
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+import org.tasks.R
+import org.tasks.Strings.isNullOrEmpty
+import org.tasks.data.CaldavAccount
+import org.tasks.databinding.ActivityEtesyncEncryptionSettingsBinding
+import org.tasks.injection.ThemedInjectingAppCompatActivity
+import org.tasks.security.KeyStoreEncryption
+import org.tasks.ui.DisplayableException
+import java.net.ConnectException
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class EncryptionSettingsActivity : ThemedInjectingAppCompatActivity(), Toolbar.OnMenuItemClickListener {
+ @Inject lateinit var encryption: KeyStoreEncryption
+
+ private lateinit var binding: ActivityEtesyncEncryptionSettingsBinding
+ private var userInfo: UserInfoManager.UserInfo? = null
+ private var caldavAccount: CaldavAccount? = null
+ private val createUserInfoViewModel: CreateUserInfoViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityEtesyncEncryptionSettingsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ ButterKnife.bind(this)
+ val intent = intent
+ caldavAccount = intent.getParcelableExtra(EXTRA_ACCOUNT)
+ userInfo = intent.getSerializableExtra(EXTRA_USER_INFO) as UserInfoManager.UserInfo
+ if (userInfo == null) {
+ binding.description.visibility = View.VISIBLE
+ binding.repeatEncryptionPasswordLayout.visibility = View.VISIBLE
+ }
+ val toolbar = binding.toolbar.toolbar
+ toolbar.title = if (caldavAccount == null) getString(R.string.add_account) else caldavAccount!!.name
+ toolbar.navigationIcon = getDrawable(R.drawable.ic_outline_save_24px)
+ toolbar.setNavigationOnClickListener { save() }
+ toolbar.inflateMenu(R.menu.menu_help)
+ toolbar.setOnMenuItemClickListener(this)
+ themeColor.apply(toolbar)
+ createUserInfoViewModel.observe(this, { returnDerivedKey(it) }, this::requestFailed)
+ if (createUserInfoViewModel.inProgress) {
+ showProgressIndicator()
+ }
+ }
+
+ private fun showProgressIndicator() {
+ binding.progressBar.progressBar.visibility = View.VISIBLE
+ }
+
+ private fun hideProgressIndicator() {
+ binding.progressBar.progressBar.visibility = View.GONE
+ }
+
+ private fun requestInProgress() = binding.progressBar.progressBar.visibility == View.VISIBLE
+
+ private fun returnDerivedKey(derivedKey: String) {
+ hideProgressIndicator()
+ val result = Intent()
+ result.putExtra(EXTRA_DERIVED_KEY, derivedKey)
+ setResult(Activity.RESULT_OK, result)
+ finish()
+ return
+ }
+
+ private fun save() = lifecycleScope.launch {
+ if (requestInProgress()) {
+ return@launch
+ }
+ val encryptionPassword = newEncryptionPassword
+ val derivedKey = caldavAccount!!.getEncryptionPassword(encryption)
+ if (isNullOrEmpty(encryptionPassword) && isNullOrEmpty(derivedKey)) {
+ binding.encryptionPasswordLayout.error = getString(R.string.encryption_password_required)
+ return@launch
+ }
+ if (userInfo == null) {
+ val repeatEncryptionPassword = binding.repeatEncryptionPassword.text.toString().trim { it <= ' ' }
+ if (encryptionPassword != repeatEncryptionPassword) {
+ binding.repeatEncryptionPasswordLayout.error = getString(R.string.passwords_do_not_match)
+ return@launch
+ }
+ }
+ val key = if (isNullOrEmpty(encryptionPassword)) derivedKey else deriveKey(caldavAccount!!.username!!, encryptionPassword)
+ val cryptoManager: CryptoManager
+ cryptoManager = try {
+ val version = if (userInfo == null) CURRENT_VERSION else userInfo!!.version!!.toInt()
+ CryptoManager(version, key, "userInfo")
+ } catch (e: VersionTooNewException) {
+ requestFailed(e)
+ return@launch
+ } catch (e: IntegrityException) {
+ requestFailed(e)
+ return@launch
+ }
+ if (userInfo == null) {
+ showProgressIndicator()
+ createUserInfoViewModel.createUserInfo(caldavAccount!!, key)
+ } else {
+ try {
+ userInfo!!.verify(cryptoManager)
+ returnDerivedKey(key)
+ } catch (e: IntegrityException) {
+ binding.encryptionPasswordLayout.error = getString(R.string.encryption_password_wrong)
+ }
+ }
+ }
+
+ private fun requestFailed(t: Throwable) {
+ hideProgressIndicator()
+ when (t) {
+ is HttpException -> showSnackbar(t.message)
+ is DisplayableException -> showSnackbar(t.resId)
+ is ConnectException -> showSnackbar(R.string.network_error)
+ else -> showSnackbar(R.string.error_adding_account, t.message!!)
+ }
+ }
+
+ private fun showSnackbar(resId: Int, vararg formatArgs: Any) =
+ showSnackbar(getString(resId, *formatArgs))
+
+ private fun showSnackbar(message: String?) =
+ newSnackbar(message).show()
+
+ private fun newSnackbar(message: String?): Snackbar {
+ val snackbar = Snackbar.make(binding.rootLayout, message!!, 8000)
+ .setTextColor(getColor(R.color.snackbar_text_color))
+ .setActionTextColor(getColor(R.color.snackbar_action_color))
+ snackbar
+ .view
+ .setBackgroundColor(getColor(R.color.snackbar_background))
+ return snackbar
+ }
+
+ @OnTextChanged(R.id.repeat_encryption_password)
+ fun onRpeatEncryptionPasswordChanged() {
+ binding.repeatEncryptionPasswordLayout.error = null
+ }
+
+ @OnTextChanged(R.id.encryption_password)
+ fun onEncryptionPasswordChanged() {
+ binding.encryptionPasswordLayout.error = null
+ }
+
+ private val newEncryptionPassword: String
+ get() = binding.encryptionPassword.text.toString().trim { it <= ' ' }
+
+ override fun finish() {
+ if (!requestInProgress()) {
+ super.finish()
+ }
+ }
+
+ override fun onMenuItemClick(item: MenuItem): Boolean {
+ return if (item.itemId == R.id.menu_help) {
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_etesync))))
+ true
+ } else {
+ onOptionsItemSelected(item)
+ }
+ }
+
+ companion object {
+ const val EXTRA_USER_INFO = "extra_user_info"
+ const val EXTRA_ACCOUNT = "extra_account"
+ const val EXTRA_DERIVED_KEY = "extra_derived_key"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/EteBaseAccountSettingsActivity.kt b/app/src/main/java/org/tasks/etebase/EteBaseAccountSettingsActivity.kt
new file mode 100644
index 000000000..37cd0246c
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/EteBaseAccountSettingsActivity.kt
@@ -0,0 +1,183 @@
+package org.tasks.etebase
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.activity.viewModels
+import androidx.appcompat.widget.Toolbar
+import androidx.core.util.Pair
+import androidx.lifecycle.lifecycleScope
+import butterknife.OnCheckedChanged
+import com.etesync.journalmanager.Crypto.CryptoManager
+import com.etesync.journalmanager.Exceptions.IntegrityException
+import com.etesync.journalmanager.Exceptions.VersionTooNewException
+import com.etesync.journalmanager.UserInfoManager
+import com.todoroo.astrid.data.Task
+import com.todoroo.astrid.helper.UUIDHelper
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+import org.tasks.R
+import org.tasks.Strings.isNullOrEmpty
+import org.tasks.analytics.Constants
+import org.tasks.caldav.BaseCaldavAccountSettingsActivity
+import org.tasks.data.CaldavAccount
+import timber.log.Timber
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class EteBaseAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Toolbar.OnMenuItemClickListener {
+ @Inject lateinit var eteBaseClient: EteBaseClient
+
+ private val addAccountViewModel: AddEteBaseAccountViewModel by viewModels()
+ private val updateAccountViewModel: UpdateEteBaseAccountViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding.repeat.visibility = View.GONE
+ binding.showAdvanced.visibility = View.VISIBLE
+ updateUrlVisibility()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (!isFinishing) {
+ addAccountViewModel.observe(this, this::addAccount, this::requestFailed)
+ updateAccountViewModel.observe(this, this::updateAccount, this::requestFailed)
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ addAccountViewModel.removeObserver(this)
+ updateAccountViewModel.removeObserver(this)
+ }
+
+ override val description: Int
+ get() = R.string.etesync_account_description
+
+ private suspend fun addAccount(userInfoAndToken: Pair) {
+ caldavAccount = CaldavAccount()
+ caldavAccount!!.accountType = CaldavAccount.TYPE_ETEBASE
+ caldavAccount!!.uuid = UUIDHelper.newUUID()
+ applyTo(caldavAccount!!, userInfoAndToken)
+ }
+
+ private suspend fun updateAccount(userInfoAndToken: Pair) {
+ caldavAccount!!.error = ""
+ applyTo(caldavAccount!!, userInfoAndToken)
+ }
+
+ private suspend fun applyTo(account: CaldavAccount, userInfoAndToken: Pair) {
+ hideProgressIndicator()
+ account.name = newName
+ account.url = newURL
+ account.username = newUsername
+ val token = userInfoAndToken.second
+ if (token != account.getPassword(encryption)) {
+ account.password = encryption.encrypt(token!!)
+ }
+ val userInfo = userInfoAndToken.first
+ if (testUserInfo(userInfo)) {
+ saveAccountAndFinish()
+ } else {
+ val intent = Intent(this, EncryptionSettingsActivity::class.java)
+ intent.putExtra(EncryptionSettingsActivity.EXTRA_USER_INFO, userInfo)
+ intent.putExtra(EncryptionSettingsActivity.EXTRA_ACCOUNT, account)
+ startActivityForResult(intent, REQUEST_ENCRYPTION_PASSWORD)
+ }
+ }
+
+ private fun testUserInfo(userInfo: UserInfoManager.UserInfo?): Boolean {
+ val encryptionKey = caldavAccount!!.getEncryptionPassword(encryption)
+ if (userInfo != null && !isNullOrEmpty(encryptionKey)) {
+ try {
+ val cryptoManager = CryptoManager(userInfo.version!!.toInt(), encryptionKey, "userInfo")
+ userInfo.verify(cryptoManager)
+ return true
+ } catch (e: IntegrityException) {
+ Timber.e(e)
+ } catch (e: VersionTooNewException) {
+ Timber.e(e)
+ }
+ }
+ return false
+ }
+
+ @OnCheckedChanged(R.id.show_advanced)
+ fun toggleUrl() {
+ updateUrlVisibility()
+ }
+
+ private fun updateUrlVisibility() {
+ binding.urlLayout.visibility = if (binding.showAdvanced.isChecked) View.VISIBLE else View.GONE
+ }
+
+ override fun needsValidation(): Boolean {
+ return super.needsValidation() || isNullOrEmpty(caldavAccount!!.encryptionKey)
+ }
+
+ override suspend fun addAccount(url: String, username: String, password: String) =
+ addAccountViewModel.addAccount(url, username, password)
+
+ override suspend fun updateAccount(url: String, username: String, password: String) =
+ updateAccountViewModel.updateAccount(
+ url,
+ username,
+ if (PASSWORD_MASK == password) null else password,
+ caldavAccount!!.getPassword(encryption))
+
+ override suspend fun updateAccount() {
+ caldavAccount!!.name = newName
+ saveAccountAndFinish()
+ }
+
+ override val newURL: String
+ get() {
+ val url = super.newURL
+ return if (isNullOrEmpty(url)) getString(R.string.etesync_url) else url // TODO: change to etebase url
+ }
+
+ override val newPassword: String
+ get() = binding.password.text.toString().trim { it <= ' ' }
+
+ override val helpUrl: String
+ get() = getString(R.string.url_etesync)
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == REQUEST_ENCRYPTION_PASSWORD) {
+ if (resultCode == Activity.RESULT_OK) {
+ lifecycleScope.launch {
+ val key = data!!.getStringExtra(EncryptionSettingsActivity.EXTRA_DERIVED_KEY)!!
+ caldavAccount!!.encryptionKey = encryption.encrypt(key)
+ saveAccountAndFinish()
+ }
+ }
+ } else {
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+ }
+
+ private suspend fun saveAccountAndFinish() {
+ if (caldavAccount!!.id == Task.NO_ID) {
+ caldavDao.insert(caldavAccount!!)
+ firebase.logEvent(
+ R.string.event_sync_add_account,
+ R.string.param_type to Constants.SYNC_TYPE_ETEBASE
+ )
+ } else {
+ caldavDao.update(caldavAccount!!)
+ }
+ setResult(Activity.RESULT_OK)
+ finish()
+ }
+
+ override suspend fun removeAccount() {
+ caldavAccount?.let { eteBaseClient.forAccount(it).invalidateToken() }
+ super.removeAccount()
+ }
+
+ companion object {
+ private const val REQUEST_ENCRYPTION_PASSWORD = 10101
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/EteBaseCalendarSettingsActivity.kt b/app/src/main/java/org/tasks/etebase/EteBaseCalendarSettingsActivity.kt
new file mode 100644
index 000000000..6d7be65d3
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/EteBaseCalendarSettingsActivity.kt
@@ -0,0 +1,33 @@
+package org.tasks.etebase
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import dagger.hilt.android.AndroidEntryPoint
+import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
+import org.tasks.data.CaldavAccount
+import org.tasks.data.CaldavCalendar
+
+@AndroidEntryPoint
+class EteBaseCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
+ private val createCalendarViewModel: CreateCalendarViewModel by viewModels()
+ private val deleteCalendarViewModel: DeleteCalendarViewModel by viewModels()
+ private val updateCalendarViewModel: UpdateCalendarViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed)
+ deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed)
+ updateCalendarViewModel.observe(this, { updateCalendar() }, this::requestFailed)
+ }
+
+ override suspend fun createCalendar(caldavAccount: CaldavAccount, name: String, color: Int) =
+ createCalendarViewModel.createCalendar(caldavAccount, name, color)
+
+ override suspend fun updateNameAndColor(
+ account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) =
+ updateCalendarViewModel.updateCalendar(account, calendar, name, color)
+
+ override suspend fun deleteCalendar(caldavAccount: CaldavAccount, caldavCalendar: CaldavCalendar) =
+ deleteCalendarViewModel.deleteCalendar(caldavAccount, caldavCalendar)
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/EteBaseClient.kt b/app/src/main/java/org/tasks/etebase/EteBaseClient.kt
new file mode 100644
index 000000000..8db849a98
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/EteBaseClient.kt
@@ -0,0 +1,277 @@
+package org.tasks.etebase
+
+import android.content.Context
+import androidx.core.util.Pair
+import at.bitfire.cert4android.CustomCertManager
+import com.etesync.journalmanager.*
+import com.etesync.journalmanager.Constants.Companion.CURRENT_VERSION
+import com.etesync.journalmanager.Crypto.AsymmetricKeyPair
+import com.etesync.journalmanager.Crypto.CryptoManager
+import com.etesync.journalmanager.Exceptions.IntegrityException
+import com.etesync.journalmanager.Exceptions.VersionTooNewException
+import com.etesync.journalmanager.JournalManager.Journal
+import com.etesync.journalmanager.UserInfoManager.UserInfo.Companion.generate
+import com.etesync.journalmanager.model.CollectionInfo
+import com.etesync.journalmanager.model.CollectionInfo.Companion.fromJson
+import com.etesync.journalmanager.model.SyncEntry
+import com.etesync.journalmanager.util.TokenAuthenticator
+import com.google.common.collect.Lists
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import okhttp3.HttpUrl
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.OkHttpClient
+import okhttp3.internal.tls.OkHostnameVerifier
+import org.tasks.DebugNetworkInterceptor
+import org.tasks.caldav.MemoryCookieStore
+import org.tasks.data.CaldavAccount
+import org.tasks.data.CaldavCalendar
+import org.tasks.preferences.Preferences
+import org.tasks.security.KeyStoreEncryption
+import timber.log.Timber
+import java.io.IOException
+import java.security.KeyManagementException
+import java.security.NoSuchAlgorithmException
+import java.util.*
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.net.ssl.SSLContext
+
+class EteBaseClient {
+ private val encryption: KeyStoreEncryption
+ private val preferences: Preferences
+ private val interceptor: DebugNetworkInterceptor
+ private val username: String?
+ private val token: String?
+ private val encryptionPassword: String?
+ private val httpClient: OkHttpClient?
+ private val httpUrl: HttpUrl?
+ private val context: Context
+ private val journalManager: JournalManager?
+ private var foreground = false
+
+ @Inject
+ constructor(
+ @ApplicationContext context: Context,
+ encryption: KeyStoreEncryption,
+ preferences: Preferences,
+ interceptor: DebugNetworkInterceptor) {
+ this.context = context
+ this.encryption = encryption
+ this.preferences = preferences
+ this.interceptor = interceptor
+ username = null
+ token = null
+ encryptionPassword = null
+ httpClient = null
+ httpUrl = null
+ journalManager = null
+ }
+
+ private constructor(
+ context: Context,
+ encryption: KeyStoreEncryption,
+ preferences: Preferences,
+ interceptor: DebugNetworkInterceptor,
+ url: String?,
+ username: String?,
+ encryptionPassword: String?,
+ token: String?,
+ foreground: Boolean) {
+ this.context = context
+ this.encryption = encryption
+ this.preferences = preferences
+ this.interceptor = interceptor
+ this.username = username
+ this.encryptionPassword = encryptionPassword
+ this.token = token
+ this.foreground = foreground
+ val customCertManager = CustomCertManager(context)
+ customCertManager.appInForeground = foreground
+ val hostnameVerifier = customCertManager.hostnameVerifier(OkHostnameVerifier)
+ val sslContext = SSLContext.getInstance("TLS")
+ sslContext.init(null, arrayOf(customCertManager), null)
+ val builder = OkHttpClient()
+ .newBuilder()
+ .addNetworkInterceptor(TokenAuthenticator(null, token))
+ .cookieJar(MemoryCookieStore())
+ .followRedirects(false)
+ .followSslRedirects(true)
+ .sslSocketFactory(sslContext.socketFactory, customCertManager)
+ .hostnameVerifier(hostnameVerifier)
+ .connectTimeout(15, TimeUnit.SECONDS)
+ .writeTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(120, TimeUnit.SECONDS)
+ if (preferences.isFlipperEnabled) {
+ interceptor.apply(builder)
+ }
+ httpClient = builder.build()
+ httpUrl = url?.toHttpUrlOrNull()
+ journalManager = JournalManager(httpClient, httpUrl!!)
+ }
+
+ @Throws(NoSuchAlgorithmException::class, KeyManagementException::class)
+ suspend fun forAccount(account: CaldavAccount): EteBaseClient {
+ return forUrl(
+ account.url,
+ account.username,
+ account.getEncryptionPassword(encryption),
+ account.getPassword(encryption))
+ }
+
+ @Throws(KeyManagementException::class, NoSuchAlgorithmException::class)
+ suspend fun forUrl(url: String?, username: String?, encryptionPassword: String?, token: String?): EteBaseClient = withContext(Dispatchers.IO) {
+ EteBaseClient(
+ context,
+ encryption,
+ preferences,
+ interceptor,
+ url,
+ username,
+ encryptionPassword,
+ token,
+ foreground)
+ }
+
+ @Throws(IOException::class, Exceptions.HttpException::class)
+ suspend fun getToken(password: String?): String? = withContext(Dispatchers.IO) {
+ JournalAuthenticator(httpClient!!, httpUrl!!).getAuthToken(username!!, password!!)
+ }
+
+ @Throws(Exceptions.HttpException::class)
+ suspend fun userInfo(): UserInfoManager.UserInfo? = withContext(Dispatchers.IO) {
+ val userInfoManager = UserInfoManager(httpClient!!, httpUrl!!)
+ userInfoManager.fetch(username!!)
+ }
+
+ @Throws(VersionTooNewException::class, IntegrityException::class)
+ fun getCrypto(userInfo: UserInfoManager.UserInfo?, journal: Journal): CryptoManager {
+ if (journal.key == null) {
+ return CryptoManager(journal.version, encryptionPassword!!, journal.uid!!)
+ }
+ if (userInfo == null) {
+ throw RuntimeException("Missing userInfo")
+ }
+ val cryptoManager = CryptoManager(userInfo.version!!.toInt(), encryptionPassword!!, "userInfo")
+ val keyPair = AsymmetricKeyPair(userInfo.getContent(cryptoManager)!!, userInfo.pubkey!!)
+ return CryptoManager(journal.version, keyPair, journal.key!!)
+ }
+
+ private fun convertJournalToCollection(userInfo: UserInfoManager.UserInfo?, journal: Journal): CollectionInfo? {
+ return try {
+ val cryptoManager = getCrypto(userInfo, journal)
+ journal.verify(cryptoManager)
+ val collection = fromJson(journal.getContent(cryptoManager))
+ collection.updateFromJournal(journal)
+ collection
+ } catch (e: IntegrityException) {
+ Timber.e(e)
+ null
+ } catch (e: VersionTooNewException) {
+ Timber.e(e)
+ null
+ }
+ }
+
+ @Throws(Exceptions.HttpException::class)
+ suspend fun getCalendars(userInfo: UserInfoManager.UserInfo?): Map = withContext(Dispatchers.IO) {
+ val result: MutableMap = HashMap()
+ for (journal in journalManager!!.list()) {
+ val collection = convertJournalToCollection(userInfo, journal)
+ if (collection != null) {
+ if (TYPE_TASKS == collection.type) {
+ Timber.v("Found %s", collection)
+ result[journal] = collection
+ } else {
+ Timber.v("Ignoring %s", collection)
+ }
+ }
+ }
+ result
+ }
+
+ @Throws(IntegrityException::class, Exceptions.HttpException::class, VersionTooNewException::class)
+ suspend fun getSyncEntries(
+ userInfo: UserInfoManager.UserInfo?,
+ journal: Journal,
+ calendar: CaldavCalendar,
+ callback: suspend (List>) -> Unit) = withContext(Dispatchers.IO) {
+ val journalEntryManager = JournalEntryManager(httpClient!!, httpUrl!!, journal.uid!!)
+ val crypto = getCrypto(userInfo, journal)
+ var journalEntries: List
+ do {
+ journalEntries = journalEntryManager.list(crypto, calendar.ctag, MAX_FETCH)
+ callback.invoke(journalEntries.map {
+ Pair.create(it, SyncEntry.fromJournalEntry(crypto, it))
+ })
+ } while (journalEntries.size >= MAX_FETCH)
+ }
+
+ @Throws(Exceptions.HttpException::class)
+ suspend fun pushEntries(journal: Journal, entries: List?, remoteCtag: String?) = withContext(Dispatchers.IO) {
+ var remoteCtag = remoteCtag
+ val journalEntryManager = JournalEntryManager(httpClient!!, httpUrl!!, journal.uid!!)
+ for (partition in Lists.partition(entries!!, MAX_PUSH)) {
+ journalEntryManager.create(partition, remoteCtag)
+ remoteCtag = partition[partition.size - 1].uid
+ }
+ }
+
+ fun setForeground() {
+ foreground = true
+ }
+
+ suspend fun invalidateToken() = withContext(Dispatchers.IO) {
+ try {
+ JournalAuthenticator(httpClient!!, httpUrl!!).invalidateAuthToken(token!!)
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ }
+
+ @Throws(VersionTooNewException::class, IntegrityException::class, Exceptions.HttpException::class)
+ suspend fun makeCollection(name: String?, color: Int): String = withContext(Dispatchers.IO) {
+ val uid = Journal.genUid()
+ val collectionInfo = CollectionInfo()
+ collectionInfo.displayName = name
+ collectionInfo.type = TYPE_TASKS
+ collectionInfo.uid = uid
+ collectionInfo.selected = true
+ collectionInfo.color = if (color == 0) null else color
+ val crypto = CryptoManager(collectionInfo.version, encryptionPassword!!, uid)
+ journalManager!!.create(Journal(crypto, collectionInfo.toJson(), uid))
+ uid
+ }
+
+ @Throws(VersionTooNewException::class, IntegrityException::class, Exceptions.HttpException::class)
+ suspend fun updateCollection(calendar: CaldavCalendar, name: String?, color: Int): String? = withContext(Dispatchers.IO) {
+ val uid = calendar.url
+ val journal = journalManager!!.fetch(uid!!)
+ val userInfo = userInfo()
+ val crypto = getCrypto(userInfo, journal)
+ val collectionInfo = convertJournalToCollection(userInfo, journal)
+ collectionInfo!!.displayName = name
+ collectionInfo.color = if (color == 0) null else color
+ journalManager.update(Journal(crypto, collectionInfo.toJson(), uid))
+ uid
+ }
+
+ @Throws(Exceptions.HttpException::class)
+ suspend fun deleteCollection(calendar: CaldavCalendar) = withContext(Dispatchers.IO) {
+ journalManager!!.delete(Journal.fakeWithUid(calendar.url!!))
+ }
+
+ @Throws(Exceptions.HttpException::class, VersionTooNewException::class, IntegrityException::class, IOException::class)
+ suspend fun createUserInfo(derivedKey: String?) = withContext(Dispatchers.IO) {
+ val cryptoManager = CryptoManager(CURRENT_VERSION, derivedKey!!, "userInfo")
+ val userInfo: UserInfoManager.UserInfo = generate(cryptoManager, username!!)
+ UserInfoManager(httpClient!!, httpUrl!!).create(userInfo)
+ }
+
+ companion object {
+ private const val TYPE_TASKS = "TASKS"
+ private const val MAX_FETCH = 50
+ private const val MAX_PUSH = 30
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/EteBaseSynchronizer.kt b/app/src/main/java/org/tasks/etebase/EteBaseSynchronizer.kt
new file mode 100644
index 000000000..7b5b9f336
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/EteBaseSynchronizer.kt
@@ -0,0 +1,224 @@
+package org.tasks.etebase
+
+import android.content.Context
+import androidx.core.util.Pair
+import at.bitfire.ical4android.ICalendar.Companion.prodId
+import com.etesync.journalmanager.Exceptions
+import com.etesync.journalmanager.Exceptions.IntegrityException
+import com.etesync.journalmanager.Exceptions.VersionTooNewException
+import com.etesync.journalmanager.JournalEntryManager
+import com.etesync.journalmanager.JournalEntryManager.Entry.Companion.getFakeWithUid
+import com.etesync.journalmanager.JournalManager.Journal
+import com.etesync.journalmanager.UserInfoManager
+import com.etesync.journalmanager.model.SyncEntry
+import com.todoroo.astrid.helper.UUIDHelper
+import com.todoroo.astrid.service.TaskDeleter
+import dagger.hilt.android.qualifiers.ApplicationContext
+import net.fortuna.ical4j.model.property.ProdId
+import org.tasks.BuildConfig
+import org.tasks.LocalBroadcastManager
+import org.tasks.R
+import org.tasks.Strings.isNullOrEmpty
+import org.tasks.billing.Inventory
+import org.tasks.caldav.iCalendar
+import org.tasks.caldav.iCalendar.Companion.fromVtodo
+import org.tasks.data.CaldavAccount
+import org.tasks.data.CaldavCalendar
+import org.tasks.data.CaldavDao
+import org.tasks.data.CaldavTaskContainer
+import timber.log.Timber
+import java.security.KeyManagementException
+import java.security.NoSuchAlgorithmException
+import java.util.*
+import javax.inject.Inject
+import kotlin.collections.HashSet
+
+class EteBaseSynchronizer @Inject constructor(
+ @param:ApplicationContext private val context: Context,
+ private val caldavDao: CaldavDao,
+ private val localBroadcastManager: LocalBroadcastManager,
+ private val taskDeleter: TaskDeleter,
+ private val inventory: Inventory,
+ private val client: EteBaseClient,
+ private val iCal: iCalendar) {
+ companion object {
+ init {
+ prodId = ProdId("+//IDN tasks.org//android-" + BuildConfig.VERSION_CODE + "//EN")
+ }
+ }
+
+ suspend fun sync(account: CaldavAccount) {
+ Thread.currentThread().contextClassLoader = context.classLoader
+
+ if (!inventory.hasPro) {
+ setError(account, context.getString(R.string.requires_pro_subscription))
+ return
+ }
+ if (isNullOrEmpty(account.password)) {
+ setError(account, context.getString(R.string.password_required))
+ return
+ }
+ if (isNullOrEmpty(account.encryptionKey)) {
+ setError(account, context.getString(R.string.encryption_password_required))
+ return
+ }
+ try {
+ synchronize(account)
+ } catch (e: KeyManagementException) {
+ setError(account, e.message)
+ } catch (e: NoSuchAlgorithmException) {
+ setError(account, e.message)
+ } catch (e: Exceptions.HttpException) {
+ setError(account, e.message)
+ } catch (e: IntegrityException) {
+ setError(account, e.message)
+ } catch (e: VersionTooNewException) {
+ setError(account, e.message)
+ }
+ }
+
+ @Throws(KeyManagementException::class, NoSuchAlgorithmException::class, Exceptions.HttpException::class, IntegrityException::class, VersionTooNewException::class)
+ private suspend fun synchronize(account: CaldavAccount) {
+ val client = client.forAccount(account)
+ val userInfo = client.userInfo()
+ val resources = client.getCalendars(userInfo)
+ val uids: Set = resources.values.mapNotNull { it.uid }.toHashSet()
+ Timber.d("Found uids: %s", uids)
+ for (calendar in caldavDao.findDeletedCalendars(account.uuid!!, uids.toList())) {
+ taskDeleter.delete(calendar)
+ }
+ for ((key, collection) in resources) {
+ val uid = collection.uid
+ var calendar = caldavDao.getCalendarByUrl(account.uuid!!, uid!!)
+ val colorInt = collection.color
+ val color = colorInt ?: 0
+ if (calendar == null) {
+ calendar = CaldavCalendar()
+ calendar.name = collection.displayName
+ calendar.account = account.uuid
+ calendar.url = collection.uid
+ calendar.uuid = UUIDHelper.newUUID()
+ calendar.color = color
+ caldavDao.insert(calendar)
+ } else {
+ if (calendar.name != collection.displayName
+ || calendar.color != color) {
+ calendar.name = collection.displayName
+ calendar.color = color
+ caldavDao.update(calendar)
+ localBroadcastManager.broadcastRefreshList()
+ }
+ }
+ sync(client, userInfo!!, calendar, key)
+ }
+ setError(account, "")
+ }
+
+ private suspend fun setError(account: CaldavAccount, message: String?) {
+ account.error = message
+ caldavDao.update(account)
+ localBroadcastManager.broadcastRefreshList()
+ if (!isNullOrEmpty(message)) {
+ Timber.e(message)
+ }
+ }
+
+ @Throws(IntegrityException::class, Exceptions.HttpException::class, VersionTooNewException::class)
+ private suspend fun sync(
+ client: EteBaseClient,
+ userInfo: UserInfoManager.UserInfo,
+ caldavCalendar: CaldavCalendar,
+ journal: Journal) {
+ Timber.d("sync(%s)", caldavCalendar)
+ val localChanges = HashMap()
+ for (task in caldavDao.getCaldavTasksToPush(caldavCalendar.uuid!!)) {
+ localChanges[task.remoteId] = task
+ }
+ var remoteCtag = journal.lastUid
+ if (isNullOrEmpty(remoteCtag) || remoteCtag != caldavCalendar.ctag) {
+ Timber.v("Applying remote changes")
+ client.getSyncEntries(userInfo, journal, caldavCalendar) {
+ applyEntries(caldavCalendar, it, localChanges.keys)
+ }
+ } else {
+ Timber.d("%s up to date", caldavCalendar.name)
+ }
+ val changes: MutableList = ArrayList()
+ for (task in caldavDao.getMoved(caldavCalendar.uuid!!)) {
+ val vtodo = task.vtodo
+ if (!isNullOrEmpty(vtodo)) {
+ changes.add(SyncEntry(vtodo!!, SyncEntry.Actions.DELETE))
+ }
+ }
+ for (task in localChanges.values) {
+ val vtodo = task.vtodo
+ val existingTask = !isNullOrEmpty(vtodo)
+ if (task.isDeleted) {
+ if (existingTask) {
+ changes.add(SyncEntry(vtodo!!, SyncEntry.Actions.DELETE))
+ }
+ } else {
+ changes.add(
+ SyncEntry(
+ String(iCal.toVtodo(task.caldavTask, task.task)),
+ if (existingTask) SyncEntry.Actions.CHANGE else SyncEntry.Actions.ADD))
+ }
+ }
+ remoteCtag = caldavCalendar.ctag
+ val crypto = client.getCrypto(userInfo, journal)
+ val updates: MutableList> = ArrayList()
+ var previous: JournalEntryManager.Entry? = if (isNullOrEmpty(remoteCtag)) null else getFakeWithUid(remoteCtag!!)
+ for (syncEntry in changes) {
+ val entry = JournalEntryManager.Entry()
+ entry.update(crypto, syncEntry.toJson(), previous)
+ updates.add(Pair.create(entry, syncEntry))
+ previous = entry
+ }
+ if (updates.size > 0) {
+ Timber.v("Pushing local changes")
+ client.pushEntries(journal, updates.mapNotNull { it.first }, remoteCtag)
+ Timber.v("Applying local changes")
+ applyEntries(caldavCalendar, updates, HashSet())
+ }
+ Timber.d("UPDATE %s", caldavCalendar)
+ caldavDao.update(caldavCalendar)
+ caldavDao.updateParents(caldavCalendar.uuid!!)
+ localBroadcastManager.broadcastRefresh()
+ }
+
+ private suspend fun applyEntries(
+ caldavCalendar: CaldavCalendar,
+ syncEntries: List>,
+ dirty: MutableSet) {
+ for (entry in syncEntries) {
+ val journalEntry = entry.first
+ val syncEntry = entry.second
+ val action = syncEntry!!.action
+ val vtodo = syncEntry.content
+ Timber.v("%s: %s", action, vtodo)
+ val task = fromVtodo(vtodo) ?: continue
+ val remoteId = task.uid
+ val caldavTask = caldavDao.getTaskByRemoteId(caldavCalendar.uuid!!, remoteId!!)
+ when (action) {
+ SyncEntry.Actions.ADD, SyncEntry.Actions.CHANGE -> if (dirty.contains(remoteId)) {
+ caldavTask!!.vtodo = vtodo
+ caldavDao.update(caldavTask)
+ } else {
+ iCal.fromVtodo(caldavCalendar, caldavTask, task, vtodo, null, null)
+ }
+ SyncEntry.Actions.DELETE -> {
+ dirty.remove(remoteId)
+ if (caldavTask != null) {
+ if (caldavTask.isDeleted()) {
+ caldavDao.delete(caldavTask)
+ } else {
+ taskDeleter.delete(caldavTask.task)
+ }
+ }
+ }
+ }
+ caldavCalendar.ctag = journalEntry!!.uid
+ caldavDao.update(caldavCalendar)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/UpdateCalendarViewModel.kt b/app/src/main/java/org/tasks/etebase/UpdateCalendarViewModel.kt
new file mode 100644
index 000000000..d234cb040
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/UpdateCalendarViewModel.kt
@@ -0,0 +1,13 @@
+package org.tasks.etebase
+
+import androidx.hilt.lifecycle.ViewModelInject
+import org.tasks.data.CaldavAccount
+import org.tasks.data.CaldavCalendar
+import org.tasks.ui.CompletableViewModel
+
+class UpdateCalendarViewModel @ViewModelInject constructor(
+ private val client: EteBaseClient): CompletableViewModel() {
+ suspend fun updateCalendar(account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) {
+ run { client.forAccount(account).updateCollection(calendar, name, color) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etebase/UpdateEteBaseAccountViewModel.kt b/app/src/main/java/org/tasks/etebase/UpdateEteBaseAccountViewModel.kt
new file mode 100644
index 000000000..37497595c
--- /dev/null
+++ b/app/src/main/java/org/tasks/etebase/UpdateEteBaseAccountViewModel.kt
@@ -0,0 +1,22 @@
+package org.tasks.etebase
+
+import androidx.core.util.Pair
+import androidx.hilt.lifecycle.ViewModelInject
+import com.etesync.journalmanager.UserInfoManager.UserInfo
+import org.tasks.Strings.isNullOrEmpty
+import org.tasks.ui.CompletableViewModel
+
+class UpdateEteBaseAccountViewModel @ViewModelInject constructor(
+ private val client: EteBaseClient) : CompletableViewModel>() {
+ suspend fun updateAccount(url: String, user: String, pass: String?, token: String) {
+ run {
+ client.setForeground()
+ if (isNullOrEmpty(pass)) {
+ Pair.create(client.forUrl(url, user, null, token).userInfo(), token)
+ } else {
+ val newToken = client.forUrl(url, user, null, null).getToken(pass)
+ Pair.create(client.forUrl(url, user, null, newToken).userInfo(), newToken)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/etesync/AddEteSyncAccountViewModel.kt b/app/src/main/java/org/tasks/etesync/AddEteSyncAccountViewModel.kt
index 20345c188..9056aaabd 100644
--- a/app/src/main/java/org/tasks/etesync/AddEteSyncAccountViewModel.kt
+++ b/app/src/main/java/org/tasks/etesync/AddEteSyncAccountViewModel.kt
@@ -5,6 +5,7 @@ import androidx.hilt.lifecycle.ViewModelInject
import com.etesync.journalmanager.UserInfoManager.UserInfo
import org.tasks.ui.CompletableViewModel
+@Deprecated("use etebase")
class AddEteSyncAccountViewModel @ViewModelInject constructor(
private val client: EteSyncClient): CompletableViewModel>() {
suspend fun addAccount(url: String, username: String, password: String) {
diff --git a/app/src/main/java/org/tasks/etesync/CreateCalendarViewModel.kt b/app/src/main/java/org/tasks/etesync/CreateCalendarViewModel.kt
index cc13de013..a70ea6fc2 100644
--- a/app/src/main/java/org/tasks/etesync/CreateCalendarViewModel.kt
+++ b/app/src/main/java/org/tasks/etesync/CreateCalendarViewModel.kt
@@ -4,6 +4,7 @@ import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.ui.CompletableViewModel
+@Deprecated("use etebase")
class CreateCalendarViewModel @ViewModelInject constructor(
private val client: EteSyncClient) : CompletableViewModel() {
suspend fun createCalendar(account: CaldavAccount, name: String, color: Int) {
diff --git a/app/src/main/java/org/tasks/etesync/CreateUserInfoViewModel.kt b/app/src/main/java/org/tasks/etesync/CreateUserInfoViewModel.kt
index 12e7ac367..22bfc1bf3 100644
--- a/app/src/main/java/org/tasks/etesync/CreateUserInfoViewModel.kt
+++ b/app/src/main/java/org/tasks/etesync/CreateUserInfoViewModel.kt
@@ -4,6 +4,7 @@ import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.ui.CompletableViewModel
+@Deprecated("use etebase")
class CreateUserInfoViewModel @ViewModelInject constructor(
private val client: EteSyncClient): CompletableViewModel() {
suspend fun createUserInfo(caldavAccount: CaldavAccount, derivedKey: String) {
diff --git a/app/src/main/java/org/tasks/etesync/DeleteCalendarViewModel.kt b/app/src/main/java/org/tasks/etesync/DeleteCalendarViewModel.kt
index fcb497352..4af0817f2 100644
--- a/app/src/main/java/org/tasks/etesync/DeleteCalendarViewModel.kt
+++ b/app/src/main/java/org/tasks/etesync/DeleteCalendarViewModel.kt
@@ -5,6 +5,7 @@ import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.ui.ActionViewModel
+@Deprecated("use etebase")
class DeleteCalendarViewModel @ViewModelInject constructor(
private val client: EteSyncClient) : ActionViewModel() {
suspend fun deleteCalendar(account: CaldavAccount, calendar: CaldavCalendar) {
diff --git a/app/src/main/java/org/tasks/etesync/EncryptionSettingsActivity.kt b/app/src/main/java/org/tasks/etesync/EncryptionSettingsActivity.kt
index 69144c4ef..08f5c54bf 100644
--- a/app/src/main/java/org/tasks/etesync/EncryptionSettingsActivity.kt
+++ b/app/src/main/java/org/tasks/etesync/EncryptionSettingsActivity.kt
@@ -31,6 +31,7 @@ import org.tasks.ui.DisplayableException
import java.net.ConnectException
import javax.inject.Inject
+@Deprecated("use etebase")
@AndroidEntryPoint
class EncryptionSettingsActivity : ThemedInjectingAppCompatActivity(), Toolbar.OnMenuItemClickListener {
@Inject lateinit var encryption: KeyStoreEncryption
diff --git a/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.kt b/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.kt
index 5a8a48320..c66609fb7 100644
--- a/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.kt
+++ b/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.kt
@@ -25,6 +25,7 @@ import org.tasks.data.CaldavAccount
import timber.log.Timber
import javax.inject.Inject
+@Deprecated("use etebase")
@AndroidEntryPoint
class EteSyncAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Toolbar.OnMenuItemClickListener {
@Inject lateinit var eteSyncClient: EteSyncClient
@@ -53,9 +54,6 @@ class EteSyncAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Tool
updateAccountViewModel.removeObserver(this)
}
- override val description: Int
- get() = R.string.etesync_account_description
-
private suspend fun addAccount(userInfoAndToken: Pair) {
caldavAccount = CaldavAccount()
caldavAccount!!.accountType = CaldavAccount.TYPE_ETESYNC
diff --git a/app/src/main/java/org/tasks/etesync/EteSyncCalendarSettingsActivity.kt b/app/src/main/java/org/tasks/etesync/EteSyncCalendarSettingsActivity.kt
index 175e7037a..9f7170e48 100644
--- a/app/src/main/java/org/tasks/etesync/EteSyncCalendarSettingsActivity.kt
+++ b/app/src/main/java/org/tasks/etesync/EteSyncCalendarSettingsActivity.kt
@@ -7,6 +7,7 @@ import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
+@Deprecated("use etebase")
@AndroidEntryPoint
class EteSyncCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
private val createCalendarViewModel: CreateCalendarViewModel by viewModels()
diff --git a/app/src/main/java/org/tasks/etesync/EteSyncClient.kt b/app/src/main/java/org/tasks/etesync/EteSyncClient.kt
index 4a94d3766..22b7e99d7 100644
--- a/app/src/main/java/org/tasks/etesync/EteSyncClient.kt
+++ b/app/src/main/java/org/tasks/etesync/EteSyncClient.kt
@@ -38,6 +38,7 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.net.ssl.SSLContext
+@Deprecated("use etebase")
class EteSyncClient {
private val encryption: KeyStoreEncryption
private val preferences: Preferences
diff --git a/app/src/main/java/org/tasks/etesync/EteSynchronizer.kt b/app/src/main/java/org/tasks/etesync/EteSynchronizer.kt
index 9a1fc01e6..bea6523ef 100644
--- a/app/src/main/java/org/tasks/etesync/EteSynchronizer.kt
+++ b/app/src/main/java/org/tasks/etesync/EteSynchronizer.kt
@@ -33,6 +33,7 @@ import java.util.*
import javax.inject.Inject
import kotlin.collections.HashSet
+@Deprecated("use etebase")
class EteSynchronizer @Inject constructor(
@param:ApplicationContext private val context: Context,
private val caldavDao: CaldavDao,
diff --git a/app/src/main/java/org/tasks/etesync/UpdateCalendarViewModel.kt b/app/src/main/java/org/tasks/etesync/UpdateCalendarViewModel.kt
index 2d9eb8b6d..df85d4fd9 100644
--- a/app/src/main/java/org/tasks/etesync/UpdateCalendarViewModel.kt
+++ b/app/src/main/java/org/tasks/etesync/UpdateCalendarViewModel.kt
@@ -5,6 +5,7 @@ import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.ui.CompletableViewModel
+@Deprecated("use etebase")
class UpdateCalendarViewModel @ViewModelInject constructor(
private val client: EteSyncClient): CompletableViewModel() {
suspend fun updateCalendar(account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) {
diff --git a/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.kt b/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.kt
index 46a93757c..9f4282c1f 100644
--- a/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.kt
+++ b/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.kt
@@ -6,6 +6,7 @@ import com.etesync.journalmanager.UserInfoManager.UserInfo
import org.tasks.Strings.isNullOrEmpty
import org.tasks.ui.CompletableViewModel
+@Deprecated("use etebase")
class UpdateEteSyncAccountViewModel @ViewModelInject constructor(
private val client: EteSyncClient) : CompletableViewModel>() {
suspend fun updateAccount(url: String, user: String, pass: String?, token: String) {
diff --git a/app/src/main/java/org/tasks/jobs/SyncEteBaseWork.kt b/app/src/main/java/org/tasks/jobs/SyncEteBaseWork.kt
new file mode 100644
index 000000000..58ccfd5bf
--- /dev/null
+++ b/app/src/main/java/org/tasks/jobs/SyncEteBaseWork.kt
@@ -0,0 +1,42 @@
+package org.tasks.jobs
+
+import android.content.Context
+import androidx.hilt.Assisted
+import androidx.hilt.work.WorkerInject
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.*
+import org.tasks.LocalBroadcastManager
+import org.tasks.R
+import org.tasks.analytics.Firebase
+import org.tasks.data.CaldavAccount.Companion.TYPE_ETEBASE
+import org.tasks.data.CaldavDao
+import org.tasks.etebase.EteBaseSynchronizer
+import org.tasks.preferences.Preferences
+
+class SyncEteBaseWork @WorkerInject constructor(
+ @Assisted context: Context,
+ @Assisted workerParams: WorkerParameters,
+ firebase: Firebase,
+ localBroadcastManager: LocalBroadcastManager,
+ preferences: Preferences,
+ private val caldavDao: CaldavDao,
+ private val synchronizer: EteBaseSynchronizer
+) : SyncWork(context, workerParams, firebase, localBroadcastManager, preferences) {
+
+ override suspend fun enabled() = caldavDao.getAccounts(TYPE_ETEBASE).isNotEmpty()
+
+ override val syncStatus = R.string.p_sync_ongoing_etebase
+
+ override suspend fun doSync() {
+ jobs().awaitAll()
+ }
+
+ private suspend fun jobs(): List> = coroutineScope {
+ caldavDao.getAccounts(TYPE_ETEBASE)
+ .map {
+ async(Dispatchers.IO) {
+ synchronizer.sync(it)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/jobs/SyncEteSyncWork.kt b/app/src/main/java/org/tasks/jobs/SyncEteSyncWork.kt
index afcb69b87..0f2a0e5da 100644
--- a/app/src/main/java/org/tasks/jobs/SyncEteSyncWork.kt
+++ b/app/src/main/java/org/tasks/jobs/SyncEteSyncWork.kt
@@ -13,6 +13,7 @@ import org.tasks.data.CaldavDao
import org.tasks.etesync.EteSynchronizer
import org.tasks.preferences.Preferences
+@Deprecated("use etebase")
class SyncEteSyncWork @WorkerInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
diff --git a/app/src/main/java/org/tasks/jobs/WorkManager.kt b/app/src/main/java/org/tasks/jobs/WorkManager.kt
index 4495777a5..98263cab3 100644
--- a/app/src/main/java/org/tasks/jobs/WorkManager.kt
+++ b/app/src/main/java/org/tasks/jobs/WorkManager.kt
@@ -20,8 +20,11 @@ interface WorkManager {
fun caldavSync(immediate: Boolean)
+ @Deprecated("use etebase")
fun eteSync(immediate: Boolean)
+ fun eteBaseSync(immediate: Boolean)
+
fun openTaskSync(immediate: Boolean)
fun reverseGeocode(place: Place)
@@ -53,11 +56,13 @@ interface WorkManager {
const val TAG_MIDNIGHT_REFRESH = "tag_midnight_refresh"
const val TAG_SYNC_GOOGLE_TASKS = "tag_sync_google_tasks"
const val TAG_SYNC_CALDAV = "tag_sync_caldav"
- const val TAG_SYNC_ETESYNC = "tag_sync_etesync"
+ @Deprecated("use etebase") const val TAG_SYNC_ETESYNC = "tag_sync_etesync"
+ const val TAG_SYNC_ETEBASE = "tag_sync_etebase"
const val TAG_SYNC_OPENTASK = "tag_sync_opentask"
const val TAG_BACKGROUND_SYNC_GOOGLE_TASKS = "tag_background_sync_google_tasks"
const val TAG_BACKGROUND_SYNC_CALDAV = "tag_background_sync_caldav"
- const val TAG_BACKGROUND_SYNC_ETESYNC = "tag_background_sync_etesync"
+ @Deprecated("use etebase") const val TAG_BACKGROUND_SYNC_ETESYNC = "tag_background_sync_etesync"
+ const val TAG_BACKGROUND_SYNC_ETEBASE = "tag_background_sync_etebase"
const val TAG_BACKGROUND_SYNC_OPENTASKS = "tag_background_sync_opentasks"
const val TAG_REMOTE_CONFIG = "tag_remote_config"
const val TAG_MIGRATE_LOCAL = "tag_migrate_local"
diff --git a/app/src/main/java/org/tasks/jobs/WorkManagerImpl.kt b/app/src/main/java/org/tasks/jobs/WorkManagerImpl.kt
index 849e0a757..2641ed738 100644
--- a/app/src/main/java/org/tasks/jobs/WorkManagerImpl.kt
+++ b/app/src/main/java/org/tasks/jobs/WorkManagerImpl.kt
@@ -14,6 +14,7 @@ import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.data.*
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
+import org.tasks.data.CaldavAccount.Companion.TYPE_ETEBASE
import org.tasks.data.CaldavAccount.Companion.TYPE_ETESYNC
import org.tasks.data.CaldavAccount.Companion.TYPE_OPENTASKS
import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS
@@ -24,6 +25,7 @@ import org.tasks.jobs.SyncWork.Companion.EXTRA_IMMEDIATE
import org.tasks.jobs.WorkManager.Companion.MAX_CLEANUP_LENGTH
import org.tasks.jobs.WorkManager.Companion.REMOTE_CONFIG_INTERVAL_HOURS
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_CALDAV
+import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_ETEBASE
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_ETESYNC
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_GOOGLE_TASKS
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC_OPENTASKS
@@ -33,6 +35,7 @@ import org.tasks.jobs.WorkManager.Companion.TAG_MIGRATE_LOCAL
import org.tasks.jobs.WorkManager.Companion.TAG_REFRESH
import org.tasks.jobs.WorkManager.Companion.TAG_REMOTE_CONFIG
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_CALDAV
+import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_ETEBASE
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_ETESYNC
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_GOOGLE_TASKS
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_OPENTASK
@@ -101,6 +104,9 @@ class WorkManagerImpl constructor(
override fun eteSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_ETESYNC, SyncEteSyncWork::class.java)
+ override fun eteBaseSync(immediate: Boolean) =
+ sync(immediate, TAG_SYNC_ETEBASE, SyncEteBaseWork::class.java)
+
override fun openTaskSync(immediate: Boolean) =
sync(immediate, TAG_SYNC_OPENTASK, SyncOpenTasksWork::class.java, false)
@@ -168,6 +174,13 @@ class WorkManagerImpl constructor(
enabled && caldavDao.getAccounts(TYPE_ETESYNC).isNotEmpty(),
unmetered)
}
+ throttle.run {
+ scheduleBackgroundSync(
+ TAG_BACKGROUND_SYNC_ETEBASE,
+ SyncEteBaseWork::class.java,
+ enabled && caldavDao.getAccounts(TYPE_ETEBASE).isNotEmpty(),
+ unmetered)
+ }
throttle.run {
scheduleBackgroundSync(
TAG_BACKGROUND_SYNC_OPENTASKS,
diff --git a/app/src/main/java/org/tasks/opentasks/OpenTaskAccountSettingsActivity.kt b/app/src/main/java/org/tasks/opentasks/OpenTaskAccountSettingsActivity.kt
index 369ad8ba5..5a480537f 100644
--- a/app/src/main/java/org/tasks/opentasks/OpenTaskAccountSettingsActivity.kt
+++ b/app/src/main/java/org/tasks/opentasks/OpenTaskAccountSettingsActivity.kt
@@ -25,9 +25,6 @@ class OpenTaskAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Too
}
}
- override val description: Int
- get() = 0
-
override val newPassword: String?
get() = ""
diff --git a/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt b/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt
index 31a88041b..6ade99730 100644
--- a/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt
+++ b/app/src/main/java/org/tasks/preferences/fragments/Synchronization.kt
@@ -24,6 +24,7 @@ import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.OpenTaskDao.Companion.ACCOUNT_TYPE_DAVx5
import org.tasks.data.OpenTaskDao.Companion.ACCOUNT_TYPE_ETESYNC
+import org.tasks.etebase.EteBaseAccountSettingsActivity
import org.tasks.etesync.EteSyncAccountSettingsActivity
import org.tasks.injection.InjectingPreferenceFragment
import org.tasks.jobs.WorkManager
@@ -149,7 +150,8 @@ class Synchronization : InjectingPreferenceFragment() {
if (isNullOrEmpty(error)) {
preference.setSummary(when {
account.isCaldavAccount -> R.string.caldav
- account.isEteSyncAccount
+ account.isEteSyncAccount -> R.string.etesync_v1
+ account.isEteBaseAccount
|| (account.isOpenTasks
&& account.uuid?.startsWith(ACCOUNT_TYPE_ETESYNC) == true) ->
R.string.etesync
@@ -165,6 +167,7 @@ class Synchronization : InjectingPreferenceFragment() {
val intent = Intent(context, when {
account.isCaldavAccount -> CaldavAccountSettingsActivity::class.java
account.isEteSyncAccount -> EteSyncAccountSettingsActivity::class.java
+ account.isEteBaseAccount -> EteBaseAccountSettingsActivity::class.java
account.isOpenTasks -> OpenTaskAccountSettingsActivity::class.java
else -> throw IllegalArgumentException("Unexpected account type: $account")
})
diff --git a/app/src/main/java/org/tasks/sync/AddAccountDialog.kt b/app/src/main/java/org/tasks/sync/AddAccountDialog.kt
index d5175881a..716f926f4 100644
--- a/app/src/main/java/org/tasks/sync/AddAccountDialog.kt
+++ b/app/src/main/java/org/tasks/sync/AddAccountDialog.kt
@@ -17,7 +17,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.caldav.CaldavAccountSettingsActivity
import org.tasks.dialogs.DialogBuilder
-import org.tasks.etesync.EteSyncAccountSettingsActivity
+import org.tasks.etebase.EteBaseAccountSettingsActivity
import org.tasks.preferences.fragments.Synchronization.Companion.REQUEST_CALDAV_SETTINGS
import org.tasks.preferences.fragments.Synchronization.Companion.REQUEST_GOOGLE_TASKS
import org.tasks.themes.DrawableUtil
@@ -65,7 +65,7 @@ class AddAccountDialog : DialogFragment() {
Intent(activity, CaldavAccountSettingsActivity::class.java),
REQUEST_CALDAV_SETTINGS)
3 -> activity?.startActivityForResult(
- Intent(activity, EteSyncAccountSettingsActivity::class.java),
+ Intent(activity, EteBaseAccountSettingsActivity::class.java),
REQUEST_CALDAV_SETTINGS)
}
dialog.dismiss()
diff --git a/app/src/main/java/org/tasks/sync/SyncAdapters.kt b/app/src/main/java/org/tasks/sync/SyncAdapters.kt
index 44934697e..533e863db 100644
--- a/app/src/main/java/org/tasks/sync/SyncAdapters.kt
+++ b/app/src/main/java/org/tasks/sync/SyncAdapters.kt
@@ -6,6 +6,7 @@ import kotlinx.coroutines.*
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
+import org.tasks.data.CaldavAccount.Companion.TYPE_ETEBASE
import org.tasks.data.CaldavAccount.Companion.TYPE_ETESYNC
import org.tasks.data.CaldavAccount.Companion.TYPE_OPENTASKS
import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS
@@ -15,6 +16,7 @@ import org.tasks.data.GoogleTaskListDao
import org.tasks.data.OpenTaskDao
import org.tasks.jobs.WorkManager
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_CALDAV
+import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_ETEBASE
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_ETESYNC
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_GOOGLE_TASKS
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC_OPENTASK
@@ -36,7 +38,8 @@ class SyncAdapters @Inject constructor(
private val scope = CoroutineScope(newSingleThreadExecutor().asCoroutineDispatcher() + SupervisorJob())
private val googleTasks = Debouncer(TAG_SYNC_GOOGLE_TASKS) { workManager.googleTaskSync(it) }
private val caldav = Debouncer(TAG_SYNC_CALDAV) { workManager.caldavSync(it) }
- private val eteSync = Debouncer(TAG_SYNC_ETESYNC) { workManager.eteSync(it) }
+ @Deprecated("use etebase") private val eteSync = Debouncer(TAG_SYNC_ETESYNC) { workManager.eteSync(it) }
+ private val eteBaseSync = Debouncer(TAG_SYNC_ETEBASE) { workManager.eteBaseSync(it) }
private val opentasks = Debouncer(TAG_SYNC_OPENTASK) { workManager.openTaskSync(it) }
private val syncStatus = Debouncer("sync_status") {
if (preferences.getBoolean(R.string.p_sync_ongoing_android, false) != it
@@ -62,6 +65,9 @@ class SyncAdapters @Inject constructor(
if (caldavDao.isAccountType(task.id, TYPE_ETESYNC)) {
eteSync.sync(false)
}
+ if (caldavDao.isAccountType(task.id, TYPE_ETEBASE)) {
+ eteBaseSync.sync(false)
+ }
if (caldavDao.isAccountType(task.id, TYPE_OPENTASKS)) {
opentasks.sync(false)
}
@@ -84,6 +90,7 @@ class SyncAdapters @Inject constructor(
val googleTasksEnabled = async { isGoogleTaskSyncEnabled() }
val caldavEnabled = async { isCaldavSyncEnabled() }
val eteSyncEnabled = async { isEteSyncEnabled() }
+ val eteBaseEnabled = async { isEteBaseEnabled() }
val opentasksEnabled = async { isOpenTaskSyncEnabled() }
if (googleTasksEnabled.await()) {
@@ -98,6 +105,10 @@ class SyncAdapters @Inject constructor(
eteSync.sync(immediate)
}
+ if (eteBaseEnabled.await()) {
+ eteBaseSync.sync(immediate)
+ }
+
if (opentasksEnabled.await()) {
opentasks.sync(immediate)
}
@@ -108,8 +119,11 @@ class SyncAdapters @Inject constructor(
private suspend fun isCaldavSyncEnabled() =
caldavDao.getAccounts(TYPE_CALDAV, TYPE_TASKS).isNotEmpty()
+ @Deprecated("use etebase")
private suspend fun isEteSyncEnabled() = caldavDao.getAccounts(TYPE_ETESYNC).isNotEmpty()
+ private suspend fun isEteBaseEnabled() = caldavDao.getAccounts(TYPE_ETEBASE).isNotEmpty()
+
private suspend fun isOpenTaskSyncEnabled() =
caldavDao.getAccounts(TYPE_OPENTASKS).isNotEmpty()
|| openTaskDao.newAccounts().isNotEmpty()
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
index 2fa5c2a1f..e55ba0803 100644
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -12,6 +12,7 @@
Tasks Shortcut
CalDAV
EteSync
+ EteSync v1
DAVx⁵
Tasks.org
https://api.etesync.com
@@ -390,6 +391,7 @@
sync_ongoing_google_tasks
sync_ongoing_caldav
sync_ongoing_etesync
+ sync_ongoing_etebase
sync_ongoing_opentasks
sync_ongoing_android
last_backup