Owners can remove shared list members

Supports Tasks.org, Nextcloud, ownCloud, and sabre/dav
pull/1400/head
Alex Baker 4 years ago
parent e8ad88be21
commit 42f34c1fab

@ -127,7 +127,7 @@ abstract class BaseCaldavCalendarSettingsActivity : BaseListSettingsActivity() {
progressView.visibility = View.GONE
}
private fun requestInProgress(): Boolean {
protected fun requestInProgress(): Boolean {
return progressView.visibility == View.VISIBLE
}

@ -2,8 +2,17 @@ package org.tasks.caldav
import android.os.Bundle
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -15,10 +24,16 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.SERVER_OWNCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.Principal
import org.tasks.data.Principal.Companion.name
import org.tasks.data.PrincipalDao
@ -29,6 +44,8 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
@Inject lateinit var principalDao: PrincipalDao
private val viewModel: CaldavCalendarViewModel by viewModels()
private val createCalendarViewModel: CreateCalendarViewModel by viewModels()
private val deleteCalendarViewModel: DeleteCalendarViewModel by viewModels()
private val updateCalendarViewModel: UpdateCalendarViewModel by viewModels()
@ -38,6 +55,14 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.inFlight.observe(this) { progressView.isVisible = it }
viewModel.error.observe(this) { throwable ->
throwable?.let {
requestFailed(it)
viewModel.error.value = null
}
}
createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed)
deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed)
updateCalendarViewModel.observe(
@ -54,6 +79,29 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
}
}
private val canRemovePrincipals: Boolean
get() = caldavCalendar?.access == ACCESS_OWNER && caldavAccount.canRemovePrincipal
private fun onRemove(principal: Principal) {
if (requestInProgress()) {
return
}
dialogBuilder
.newDialog(R.string.remove_user)
.setMessage(R.string.remove_user_confirmation, principal.name, caldavCalendar?.name)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ -> removePrincipal(principal) }
.show()
}
private fun removePrincipal(principal: Principal) = lifecycleScope.launch {
try {
viewModel.remove(caldavAccount, caldavCalendar!!, principal)
} catch (e: Exception) {
requestFailed(e)
}
}
@Composable
private fun PrincipalList(principals: List<Principal>) {
tasksTheme.TasksTheme {
@ -91,6 +139,19 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onBackground,
)
if (canRemovePrincipals) {
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
IconButton(
modifier = Modifier.then(Modifier.size(24.dp)),
onClick = { onRemove(principal) }) {
Icon(
painter = painterResource(R.drawable.ic_outline_clear_24px),
contentDescription = null,
tint = colorResource(R.color.icon_tint_with_alpha),
)
}
}
}
}
}
@ -112,4 +173,12 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
Principal().apply { displayName = "user2" },
))
}
companion object {
val CaldavAccount.canRemovePrincipal: Boolean
get() = when (serverType) {
SERVER_TASKS, SERVER_OWNCLOUD, SERVER_SABREDAV -> true
else -> false
}
}
}

@ -0,0 +1,37 @@
package org.tasks.caldav
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.Principal
import org.tasks.data.PrincipalDao
import javax.inject.Inject
@HiltViewModel
class CaldavCalendarViewModel @Inject constructor(
private val provider: CaldavClientProvider,
private val principalDao: PrincipalDao
) : ViewModel() {
val error = MutableLiveData<Throwable?>()
val inFlight = MutableLiveData(false)
suspend fun remove(account: CaldavAccount, list: CaldavCalendar, principal: Principal) =
withContext(NonCancellable) {
if (inFlight.value == true) {
return@withContext
}
inFlight.value = true
try {
provider.forAccount(account).removePrincipal(account, list, principal)
principalDao.delete(principal)
} catch (e: Exception) {
error.value = e
} finally {
inFlight.value = false
}
}
}

@ -1,7 +1,9 @@
package org.tasks.caldav
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.dav4jvm.DavCollection
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.DavResource.Companion.MIME_XML
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.Response
import at.bitfire.dav4jvm.Response.HrefRelation
@ -10,21 +12,38 @@ import at.bitfire.dav4jvm.XmlUtils.NS_CALDAV
import at.bitfire.dav4jvm.XmlUtils.NS_WEBDAV
import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.property.*
import at.bitfire.dav4jvm.property.CalendarColor
import at.bitfire.dav4jvm.property.CalendarHomeSet
import at.bitfire.dav4jvm.property.CurrentUserPrincipal
import at.bitfire.dav4jvm.property.CurrentUserPrivilegeSet
import at.bitfire.dav4jvm.property.DisplayName
import at.bitfire.dav4jvm.property.GetCTag
import at.bitfire.dav4jvm.property.ResourceType
import at.bitfire.dav4jvm.property.ResourceType.Companion.CALENDAR
import at.bitfire.dav4jvm.property.SupportedCalendarComponentSet
import at.bitfire.dav4jvm.property.SyncToken
import com.todoroo.astrid.helper.UUIDHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.caldav.property.Invite
import org.tasks.caldav.property.OCInvite
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_OWNCLOUD
import org.tasks.data.CaldavAccount.Companion.SERVER_SABREDAV
import org.tasks.data.CaldavAccount.Companion.SERVER_TASKS
import org.tasks.data.CaldavCalendar
import org.tasks.data.Principal
import org.tasks.ui.DisplayableException
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory
@ -216,7 +235,52 @@ open class CaldavClient(
return this
}
suspend fun removePrincipal(
account: CaldavAccount,
calendar: CaldavCalendar,
principal: Principal,
) {
when (account.serverType) {
SERVER_TASKS, SERVER_SABREDAV -> removeSabrePrincipal(calendar, principal)
SERVER_OWNCLOUD -> removeOwncloudPrincipal(calendar, principal)
else -> throw IllegalArgumentException()
}
}
private suspend fun removeOwncloudPrincipal(calendar: CaldavCalendar, principal: Principal) =
withContext(Dispatchers.IO) {
DavCollection(httpClient, calendar.url!!.toHttpUrl())
.post(
"""
<x4:share xmlns:x4="$NS_OWNCLOUD">
<x4:remove>
<x0:href xmlns:x0="$NS_WEBDAV">${principal.principal}</x0:href>
</x4:remove>
</x4:share>
""".trimIndent().toRequestBody(MIME_XML)
) {}
}
private suspend fun removeSabrePrincipal(calendar: CaldavCalendar, principal: Principal) =
withContext(Dispatchers.IO) {
DavCollection(httpClient, calendar.url!!.toHttpUrl())
.post(
"""
<D:share-resource xmlns:D="$NS_WEBDAV">
<D:sharee>
<D:href>${principal.principal}</D:href>
<D:share-access>
<D:no-access />
</D:share-access>
</D:sharee>
</D:share-resource>
""".trimIndent().toRequestBody(MEDIATYPE_SHARING)
) {}
}
companion object {
private val MEDIATYPE_SHARING = "application/davsharing+xml".toMediaType()
private val calendarProperties = arrayOf(
ResourceType.NAME,
DisplayName.NAME,

@ -43,7 +43,7 @@ class CaldavCalendar : Parcelable {
var order = NO_ORDER
@ColumnInfo(name = "cdl_access")
var access = 0
var access = ACCESS_OWNER
constructor()

@ -15,6 +15,9 @@ WHERE principal_list = :list
AND principal NOT IN (:principals)""")
fun deleteRemoved(list: Long, principals: List<String>)
@Delete
fun delete(principal: Principal)
@Delete
fun delete(principals: List<Principal>)

@ -689,4 +689,6 @@ File %1$s contained %2$s.\n\n
<string name="account_not_included">Not included with \'Name your price\' subscriptions</string>
<string name="map_theme_use_app_theme">Use app theme</string>
<string name="list_members">List members</string>
<string name="remove_user">Remove user?</string>
<string name="remove_user_confirmation">%1$s will no longer have access to %2$s</string>
</resources>

Loading…
Cancel
Save