Add server selector

pull/1811/head
Alex Baker 4 years ago
parent 0cfb27df22
commit e8f231b288

@ -12,10 +12,16 @@ import android.view.inputmethod.InputMethodManager
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.exception.HttpException
import com.google.android.material.composethemeadapter.MdcTheme
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
@ -26,7 +32,9 @@ import org.tasks.Strings.isNullOrEmpty
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.billing.PurchaseActivity import org.tasks.billing.PurchaseActivity
import org.tasks.compose.ServerSelector
import org.tasks.data.CaldavAccount import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.databinding.ActivityCaldavAccountSettingsBinding import org.tasks.databinding.ActivityCaldavAccountSettingsBinding
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
@ -52,17 +60,24 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
protected var caldavAccount: CaldavAccount? = null protected var caldavAccount: CaldavAccount? = null
protected lateinit var binding: ActivityCaldavAccountSettingsBinding protected lateinit var binding: ActivityCaldavAccountSettingsBinding
protected lateinit var serverType: MutableState<Int>
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityCaldavAccountSettingsBinding.inflate(layoutInflater) binding = ActivityCaldavAccountSettingsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
caldavAccount = if (savedInstanceState == null) intent.getParcelableExtra(EXTRA_CALDAV_DATA) else savedInstanceState.getParcelable(EXTRA_CALDAV_DATA) caldavAccount = if (savedInstanceState == null) intent.getParcelableExtra(EXTRA_CALDAV_DATA) else savedInstanceState.getParcelable(EXTRA_CALDAV_DATA)
serverType = mutableStateOf(
savedInstanceState?.getInt(EXTRA_SERVER_TYPE, SERVER_UNKNOWN)
?: caldavAccount?.serverType
?: SERVER_UNKNOWN
)
if (caldavAccount == null || caldavAccount!!.id == Task.NO_ID) { if (caldavAccount == null || caldavAccount!!.id == Task.NO_ID) {
binding.nameLayout.visibility = View.GONE binding.nameLayout.visibility = View.GONE
binding.description.visibility = View.VISIBLE binding.description.visibility = View.VISIBLE
binding.description.setText(description) binding.description.setText(description)
Linkify.safeLinkify(binding.description, android.text.util.Linkify.WEB_URLS) Linkify.safeLinkify(binding.description, android.text.util.Linkify.WEB_URLS)
serverType.value = SERVER_UNKNOWN
} else { } else {
binding.nameLayout.visibility = View.VISIBLE binding.nameLayout.visibility = View.VISIBLE
binding.description.visibility = View.GONE binding.description.visibility = View.GONE
@ -80,6 +95,7 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
if (!isNullOrEmpty(it.password)) { if (!isNullOrEmpty(it.password)) {
binding.password.setText(PASSWORD_MASK) binding.password.setText(PASSWORD_MASK)
} }
serverType.value = it.serverType
} }
} }
val toolbar = binding.toolbar.toolbar val toolbar = binding.toolbar.toolbar
@ -116,6 +132,15 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
onTextChanged = { _, _, _, _ -> binding.passwordLayout.error = null } onTextChanged = { _, _, _, _ -> binding.passwordLayout.error = null }
) )
binding.password.setOnFocusChangeListener { _, hasFocus -> onPasswordFocused(hasFocus) } binding.password.setOnFocusChangeListener { _, hasFocus -> onPasswordFocused(hasFocus) }
binding.serverSelector.setContent {
MdcTheme {
var selected by rememberSaveable { serverType }
ServerSelector(selected) {
serverType.value = it
selected = it
}
}
}
} }
@get:StringRes @get:StringRes
@ -126,6 +151,7 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putParcelable(EXTRA_CALDAV_DATA, caldavAccount) outState.putParcelable(EXTRA_CALDAV_DATA, caldavAccount)
outState.putInt(EXTRA_SERVER_TYPE, serverType.value)
} }
private fun showProgressIndicator() { private fun showProgressIndicator() {
@ -283,8 +309,12 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
(!isNullOrEmpty(binding.name.text.toString().trim { it <= ' ' }) (!isNullOrEmpty(binding.name.text.toString().trim { it <= ' ' })
|| !isNullOrEmpty(newPassword) || !isNullOrEmpty(newPassword)
|| !isNullOrEmpty(binding.url.text.toString().trim { it <= ' ' }) || !isNullOrEmpty(binding.url.text.toString().trim { it <= ' ' })
|| !isNullOrEmpty(newUsername)) || !isNullOrEmpty(newUsername)
} else needsValidation() || newName != caldavAccount!!.name || serverType.value != SERVER_UNKNOWN
)
} else needsValidation() ||
newName != caldavAccount!!.name ||
serverType.value != caldavAccount!!.serverType
} }
protected open fun needsValidation(): Boolean = protected open fun needsValidation(): Boolean =
@ -347,6 +377,7 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
companion object { companion object {
const val EXTRA_CALDAV_DATA = "caldavData" // $NON-NLS-1$ const val EXTRA_CALDAV_DATA = "caldavData" // $NON-NLS-1$
const val EXTRA_SERVER_TYPE = "serverType"
const val PASSWORD_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" const val PASSWORD_MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
} }
} }

@ -60,6 +60,7 @@ class CaldavAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Toolb
caldavAccount!!.url = principal caldavAccount!!.url = principal
caldavAccount!!.username = newUsername caldavAccount!!.username = newUsername
caldavAccount!!.error = "" caldavAccount!!.error = ""
caldavAccount!!.serverType = serverType.value
if (passwordChanged()) { if (passwordChanged()) {
caldavAccount!!.password = encryption.encrypt(newPassword!!) caldavAccount!!.password = encryption.encrypt(newPassword!!)
} }

@ -18,6 +18,7 @@ import org.tasks.R
import org.tasks.compose.ListSettingsComposables.PrincipalList import org.tasks.compose.ListSettingsComposables.PrincipalList
import org.tasks.compose.ShareInvite.ShareInviteDialog import org.tasks.compose.ShareInvite.ShareInviteDialog
import org.tasks.data.CaldavAccount import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.SERVER_NEXTCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
@ -145,13 +146,13 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
companion object { companion object {
val CaldavAccount.canRemovePrincipal: Boolean val CaldavAccount.canRemovePrincipal: Boolean
get() = when (serverType) { get() = when (serverType) {
SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV -> true SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV, SERVER_NEXTCLOUD -> true
else -> false else -> false
} }
val CaldavAccount.canShare: Boolean val CaldavAccount.canShare: Boolean
get() = when (serverType) { get() = when (serverType) {
SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV -> true SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV, SERVER_NEXTCLOUD -> true
else -> false else -> false
} }
} }

@ -39,6 +39,7 @@ import org.tasks.caldav.property.OCOwnerPrincipal
import org.tasks.caldav.property.PropertyUtils.NS_OWNCLOUD import org.tasks.caldav.property.PropertyUtils.NS_OWNCLOUD
import org.tasks.caldav.property.ShareAccess import org.tasks.caldav.property.ShareAccess
import org.tasks.data.CaldavAccount import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.SERVER_NEXTCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
@ -52,7 +53,6 @@ import java.io.IOException
import java.io.StringWriter import java.io.StringWriter
import java.security.KeyManagementException import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.*
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
open class CaldavClient( open class CaldavClient(
@ -239,7 +239,7 @@ open class CaldavClient(
href: String, href: String,
) { ) {
when (account.serverType) { when (account.serverType) {
SERVER_TASKS, SERVER_SABREDAV -> shareSabredav(href) SERVER_TASKS, SERVER_SABREDAV, SERVER_NEXTCLOUD -> shareSabredav(href)
SERVER_OWNCLOUD -> shareOwncloud(href) SERVER_OWNCLOUD -> shareOwncloud(href)
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
@ -279,7 +279,7 @@ open class CaldavClient(
href: String, href: String,
) { ) {
when (account.serverType) { when (account.serverType) {
SERVER_TASKS, SERVER_SABREDAV -> removeSabrePrincipal(calendar, href) SERVER_TASKS, SERVER_SABREDAV, SERVER_NEXTCLOUD -> removeSabrePrincipal(calendar, href)
SERVER_OWNCLOUD -> removeOwncloudPrincipal(calendar, href) SERVER_OWNCLOUD -> removeOwncloudPrincipal(calendar, href)
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }

@ -146,10 +146,12 @@ class CaldavSynchronizer @Inject constructor(
private suspend fun synchronize(account: CaldavAccount) { private suspend fun synchronize(account: CaldavAccount) {
val caldavClient = provider.forAccount(account) val caldavClient = provider.forAccount(account)
var serverType = SERVER_UNKNOWN var serverType = account.serverType
val resources = caldavClient.calendars { chain -> val resources = caldavClient.calendars { chain ->
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
if (serverType == SERVER_UNKNOWN) {
serverType = getServerType(account, response.headers) serverType = getServerType(account, response.headers)
}
response response
} }
if (serverType != account.serverType) { if (serverType != account.serverType) {

@ -0,0 +1,61 @@
package org.tasks.compose
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.android.material.composethemeadapter.MdcTheme
import org.tasks.R
@Composable
fun ServerSelector(selected: Int, onSelected: (Int) -> Unit) {
var expanded by rememberSaveable { mutableStateOf(false) }
Column(modifier = Modifier
.padding(16.dp)
.clickable { expanded = !expanded }) {
Text(
text = stringResource(id = R.string.caldav_server_type),
style = MaterialTheme.typography.caption.copy(
color = colorResource(id = R.color.text_secondary)
),
)
Spinner(
options = LocalContext.current.resources.getStringArray(R.array.caldav_servers)
.toList(),
values = LocalContext.current.resources.getIntArray(R.array.caldav_server_values)
.toList(),
selected = selected,
expanded = expanded,
onSelected = {
expanded = false
onSelected(it)
},
setExpanded = { expanded = it },
modifier = Modifier
.padding(vertical = 6.dp)
.fillMaxWidth()
)
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun ServerSelectorPreview() =
MdcTheme {
ServerSelector(1) {}
}

@ -0,0 +1,40 @@
package org.tasks.compose
import androidx.compose.foundation.layout.Row
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun <T> Spinner(
options: List<String>,
values: List<T>,
selected: T,
expanded: Boolean,
modifier: Modifier = Modifier,
onSelected: (T) -> Unit,
setExpanded: (Boolean) -> Unit,
) {
val selectedIndex = values.indexOf(selected)
val selectedOption = options[selectedIndex]
Row(modifier = modifier) {
Text(text = selectedOption)
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = "")
DropdownMenu(expanded = expanded, onDismissRequest = {
setExpanded(false)
}) {
options.forEach {
DropdownMenuItem(onClick = {
onSelected(values[options.indexOf(it)])
}) {
Text(text = it)
}
}
}
}
}

@ -116,7 +116,11 @@ class CaldavAccount : Parcelable {
} }
val isSuppressRepeatingTasks: Boolean val isSuppressRepeatingTasks: Boolean
get() = serverType == SERVER_OPEN_XCHANGE get() = when (serverType) {
SERVER_OPEN_XCHANGE,
SERVER_MAILBOX_ORG -> true
else -> false
}
override fun describeContents() = 0 override fun describeContents() = 0
@ -224,6 +228,10 @@ class CaldavAccount : Parcelable {
const val SERVER_OWNCLOUD = 1 const val SERVER_OWNCLOUD = 1
const val SERVER_SABREDAV = 2 const val SERVER_SABREDAV = 2
const val SERVER_OPEN_XCHANGE = 3 const val SERVER_OPEN_XCHANGE = 3
const val SERVER_NEXTCLOUD = 4
const val SERVER_SYNOLOGY_CALENDAR = 5
const val SERVER_MAILBOX_ORG = 6
const val SERVER_OTHER = 99
const val ERROR_UNAUTHORIZED = "HTTP ${HttpURLConnection.HTTP_UNAUTHORIZED}" const val ERROR_UNAUTHORIZED = "HTTP ${HttpURLConnection.HTTP_UNAUTHORIZED}"
const val ERROR_PAYMENT_REQUIRED = "HTTP ${HttpURLConnection.HTTP_PAYMENT_REQUIRED}" const val ERROR_PAYMENT_REQUIRED = "HTTP ${HttpURLConnection.HTTP_PAYMENT_REQUIRED}"

@ -24,6 +24,7 @@ class EtebaseAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Tool
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding.serverSelector.visibility = View.GONE
binding.showAdvanced.visibility = View.VISIBLE binding.showAdvanced.visibility = View.VISIBLE
binding.showAdvanced.setOnCheckedChangeListener { _, _ -> binding.showAdvanced.setOnCheckedChangeListener { _, _ ->
updateUrlVisibility() updateUrlVisibility()

@ -15,6 +15,7 @@ class EteSyncAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Tool
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding.showAdvanced.visibility = View.GONE binding.showAdvanced.visibility = View.GONE
binding.serverSelector.visibility = View.GONE
binding.description.visibility = View.VISIBLE binding.description.visibility = View.VISIBLE
binding.description.setTextColor(ContextCompat.getColor(this, R.color.overdue)) binding.description.setTextColor(ContextCompat.getColor(this, R.color.overdue))
binding.description.setText(description) binding.description.setText(description)

@ -32,6 +32,7 @@ class OpenTaskAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Too
if (passwordChanged()) { if (passwordChanged()) {
caldavAccount!!.password = encryption.encrypt(newPassword) caldavAccount!!.password = encryption.encrypt(newPassword)
} }
caldavAccount!!.serverType = serverType.value
caldavDao.update(caldavAccount!!) caldavDao.update(caldavAccount!!)
setResult(Activity.RESULT_OK) setResult(Activity.RESULT_OK)
finish() finish()

@ -92,6 +92,11 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/server_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

@ -282,4 +282,26 @@
<item>@string/bottom</item> <item>@string/bottom</item>
</string-array> </string-array>
<string-array name="caldav_servers">
<item>@string/caldav_server_mailbox_org</item>
<item>@string/caldav_server_nextcloud</item>
<item>@string/caldav_server_openxchange</item>
<item>@string/caldav_server_owncloud</item>
<item>@string/caldav_server_sabredav</item>
<item>@string/caldav_server_synology_calendar</item>
<item>@string/caldav_server_other</item>
<item>@string/caldav_server_unknown</item>
</string-array>
<integer-array name="caldav_server_values">
<item>6</item>
<item>4</item>
<item>3</item>
<item>1</item>
<item>2</item>
<item>5</item>
<item>99</item>
<item>-1</item>
</integer-array>
</resources> </resources>

@ -18,6 +18,12 @@
<string name="etesync_deprecated">Native EteSync v1 support has been removed. Please switch to the EteSync client to continue synchronizing your EteSync v1 account, or migrate to EteSync v2</string> <string name="etesync_deprecated">Native EteSync v1 support has been removed. Please switch to the EteSync client to continue synchronizing your EteSync v1 account, or migrate to EteSync v2</string>
<string name="etebase_url">https://api.etebase.com/partner/tasksorg/</string> <string name="etebase_url">https://api.etebase.com/partner/tasksorg/</string>
<string name="help_url_sync">https://tasks.org/sync</string> <string name="help_url_sync">https://tasks.org/sync</string>
<string name="caldav_server_owncloud">ownCloud</string>
<string name="caldav_server_nextcloud">Nextcloud</string>
<string name="caldav_server_sabredav">sabre/dav</string>
<string name="caldav_server_openxchange">Open-Xchange</string>
<string name="caldav_server_synology_calendar">Synology Calendar</string>
<string name="caldav_server_mailbox_org">Mailbox.org</string>
<string name="url_davx5">https://tasks.org/davx5</string> <string name="url_davx5">https://tasks.org/davx5</string>
<string name="url_caldav">https://tasks.org/caldav</string> <string name="url_caldav">https://tasks.org/caldav</string>

Loading…
Cancel
Save