Add server selector

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

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

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

@ -18,6 +18,7 @@ import org.tasks.R
import org.tasks.compose.ListSettingsComposables.PrincipalList
import org.tasks.compose.ShareInvite.ShareInviteDialog
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_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
@ -145,13 +146,13 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
companion object {
val CaldavAccount.canRemovePrincipal: Boolean
get() = when (serverType) {
SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV -> true
SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV, SERVER_NEXTCLOUD -> true
else -> false
}
val CaldavAccount.canShare: Boolean
get() = when (serverType) {
SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV -> true
SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV, SERVER_NEXTCLOUD -> true
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.ShareAccess
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_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
@ -52,7 +53,6 @@ import java.io.IOException
import java.io.StringWriter
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.util.*
import kotlin.coroutines.suspendCoroutine
open class CaldavClient(
@ -239,7 +239,7 @@ open class CaldavClient(
href: String,
) {
when (account.serverType) {
SERVER_TASKS, SERVER_SABREDAV -> shareSabredav(href)
SERVER_TASKS, SERVER_SABREDAV, SERVER_NEXTCLOUD -> shareSabredav(href)
SERVER_OWNCLOUD -> shareOwncloud(href)
else -> throw IllegalArgumentException()
}
@ -279,7 +279,7 @@ open class CaldavClient(
href: String,
) {
when (account.serverType) {
SERVER_TASKS, SERVER_SABREDAV -> removeSabrePrincipal(calendar, href)
SERVER_TASKS, SERVER_SABREDAV, SERVER_NEXTCLOUD -> removeSabrePrincipal(calendar, href)
SERVER_OWNCLOUD -> removeOwncloudPrincipal(calendar, href)
else -> throw IllegalArgumentException()
}

@ -146,10 +146,12 @@ class CaldavSynchronizer @Inject constructor(
private suspend fun synchronize(account: CaldavAccount) {
val caldavClient = provider.forAccount(account)
var serverType = SERVER_UNKNOWN
var serverType = account.serverType
val resources = caldavClient.calendars { chain ->
val response = chain.proceed(chain.request())
serverType = getServerType(account, response.headers)
if (serverType == SERVER_UNKNOWN) {
serverType = getServerType(account, response.headers)
}
response
}
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
get() = serverType == SERVER_OPEN_XCHANGE
get() = when (serverType) {
SERVER_OPEN_XCHANGE,
SERVER_MAILBOX_ORG -> true
else -> false
}
override fun describeContents() = 0
@ -224,6 +228,10 @@ class CaldavAccount : Parcelable {
const val SERVER_OWNCLOUD = 1
const val SERVER_SABREDAV = 2
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_PAYMENT_REQUIRED = "HTTP ${HttpURLConnection.HTTP_PAYMENT_REQUIRED}"

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

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

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

@ -92,6 +92,11 @@
</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>
</ScrollView>

@ -282,4 +282,26 @@
<item>@string/bottom</item>
</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>

@ -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="etebase_url">https://api.etebase.com/partner/tasksorg/</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_caldav">https://tasks.org/caldav</string>

Loading…
Cancel
Save