Restricted room mgmt when supported

This commit is contained in:
Valere 2021-07-09 16:15:51 +02:00
parent 3da20aea29
commit 2f16a7fff3
13 changed files with 240 additions and 16 deletions

View file

@ -101,4 +101,8 @@ interface Room :
* Use this room as a Space, if the type is correct.
*/
fun asSpace(): Space?
suspend fun setJoinRulePublic()
suspend fun setJoinRuleInviteOnly()
suspend fun setJoinRuleRestricted(allowList: List<String>)
}

View file

@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.Optional
@ -53,7 +54,7 @@ interface StateService {
/**
* Update the join rule and/or the guest access
*/
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?)
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>? = null)
/**
* Update the avatar of the room

View file

@ -17,17 +17,24 @@
package org.matrix.android.sdk.internal.session.permalinks
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
import java.net.URLEncoder
import javax.inject.Inject
import javax.inject.Provider
internal class ViaParameterFinder @Inject constructor(
@UserId private val userId: String,
private val roomGetterProvider: Provider<RoomGetter>
private val roomGetterProvider: Provider<RoomGetter>,
private val stateEventDataSource: StateEventDataSource
) {
fun computeViaParams(roomId: String, max: Int): List<String> {
@ -70,4 +77,28 @@ internal class ViaParameterFinder @Inject constructor(
.orEmpty()
.toSet()
}
fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
?.filter { userCanInvite(userId, roomId) }
.orEmpty()
.toSet()
return userThatCanInvite.map { it.getDomain() }
.groupBy { it }
.mapValues { it.value.size }
.toMutableMap()
.let { map -> map.keys.sortedByDescending { map[it] } }
.take(max)
}
fun userCanInvite(userId: String, roomId: String): Boolean {
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
?.content?.toModel<PowerLevelsContent>()
?.let { PowerLevelsHelper(it) }
return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false
}
}

View file

@ -24,6 +24,8 @@ import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataServic
import org.matrix.android.sdk.api.session.room.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.members.MembershipService
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
@ -164,4 +166,20 @@ internal class DefaultRoom(override val roomId: String,
if (roomSummary()?.roomType != RoomType.SPACE) return null
return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
}
override suspend fun setJoinRulePublic() {
stateService.updateJoinRule(RoomJoinRules.PUBLIC, null)
}
override suspend fun setJoinRuleInviteOnly() {
stateService.updateJoinRule(RoomJoinRules.INVITE, null)
}
override suspend fun setJoinRuleRestricted(allowList: List<String>) {
// we need to compute correct via parameters and check if PL are correct
val allowEntries = allowList.map { spaceId ->
RoomJoinRulesAllowEntry(spaceId, viaParameterFinder.computeViaParamsForRestricted(spaceId, 3))
}
stateService.updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries)
}
}

View file

@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room.state
import android.net.Uri
import androidx.lifecycle.LiveData
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@ -29,12 +29,13 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.content.FileUploader
import java.lang.UnsupportedOperationException
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
private val stateEventDataSource: StateEventDataSource,
@ -126,12 +127,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
)
}
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>?) {
if (joinRules != null) {
if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported")
val body = if (joinRules == RoomJoinRules.RESTRICTED) {
RoomJoinRulesContent(
_joinRules = RoomJoinRules.RESTRICTED.value,
allowList = allowList
).toContent()
} else {
mapOf("join_rule" to joinRules)
}
sendStateEvent(
eventType = EventType.STATE_ROOM_JOIN_RULES,
body = mapOf("join_rule" to joinRules),
body = body,
stateKey = null
)
}

View file

@ -18,11 +18,20 @@ package im.vector.app.features.roomprofile.settings.joinrule
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.viewModel
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
@ -39,6 +48,11 @@ class RoomJoinRuleActivity : VectorBaseActivity<ActivitySimpleBinding>(),
@Inject
lateinit var allowListViewModelFactory: RoomJoinRuleChooseRestrictedViewModel.Factory
@Inject
lateinit var errorFormatter: ErrorFormatter
val viewModel: RoomJoinRuleChooseRestrictedViewModel by viewModel()
override fun create(initialState: RoomJoinRuleChooseRestrictedState) = allowListViewModelFactory.create(initialState)
override fun injectWith(injector: ScreenComponent) {
@ -56,6 +70,27 @@ class RoomJoinRuleActivity : VectorBaseActivity<ActivitySimpleBinding>(),
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.selectSubscribe(this, RoomJoinRuleChooseRestrictedState::updatingStatus) {
when (it) {
Uninitialized -> {
// nop
}
is Loading -> {
views.simpleActivityWaitingView.isVisible = true
}
is Success -> {
finish()
}
is Fail -> {
views.simpleActivityWaitingView.isVisible = false
toast(errorFormatter.toHumanReadable(it.error))
}
}
}
}
companion object {
fun newIntent(context: Context, roomId: String): Intent {

View file

@ -26,6 +26,7 @@ import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import timber.log.Timber
import javax.inject.Inject
class RoomJoinRuleAdvancedController @Inject constructor(
@ -76,12 +77,13 @@ class RoomJoinRuleAdvancedController @Inject constructor(
if (choices.firstOrNull { it.rule == RoomJoinRules.RESTRICTED } != null) {
val restrictedRule = choices.first { it.rule == RoomJoinRules.RESTRICTED }
Timber.w("##@@ ${state.updatedAllowList}")
spaceJoinRuleItem {
id("restricted")
avatarRenderer(host.avatarRenderer)
needUpgrade(restrictedRule.needUpgrade)
selected(state.currentRoomJoinRules == RoomJoinRules.RESTRICTED)
restrictedList(state.updatedAllowList.orEmpty())
restrictedList(state.updatedAllowList)
listener { host.interactionListener?.didSelectRule(RoomJoinRules.RESTRICTED) }
}
}

View file

@ -21,8 +21,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.commitTransaction
@ -48,10 +50,20 @@ class RoomJoinRuleFragment @Inject constructor(
FragmentJoinRulesRecyclerBinding.inflate(inflater, container, false)
override fun onBackPressed(toolbarButton: Boolean): Boolean {
// TODO
val hasUnsavedChanges = withState(viewModel) { it.hasUnsavedChanges }
if (!hasUnsavedChanges) {
val isLoading = withState(viewModel) { it.updatingStatus is Loading }
if (!hasUnsavedChanges || isLoading) {
requireActivity().finish()
} else {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.dialog_title_warning)
.setMessage(R.string.warning_unsaved_change)
.setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ ->
requireActivity().finish()
}
.setNegativeButton(R.string.cancel, null)
.show()
return true
}
return true
}
@ -65,6 +77,9 @@ class RoomJoinRuleFragment @Inject constructor(
views.positiveButton.text = getString(R.string.warning_unsaved_change_discard)
views.positiveButton.isVisible = true
views.positiveButton.text = getString(R.string.save)
views.positiveButton.debouncedClicks {
viewModel.handle(RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules)
}
} else {
views.cancelButton.isVisible = false
views.positiveButton.isVisible = true
@ -86,6 +101,9 @@ class RoomJoinRuleFragment @Inject constructor(
}
override fun didSelectRule(rules: RoomJoinRules) {
val isLoading = withState(viewModel) { it.updatingStatus is Loading }
if (isLoading) return
val oldRule = withState(viewModel) { it.currentRoomJoinRules }
viewModel.handle(RoomJoinRuleChooseRestrictedActions.SelectJoinRules(rules))
if (rules == RoomJoinRules.RESTRICTED && oldRule == RoomJoinRules.RESTRICTED) {

View file

@ -74,6 +74,7 @@ abstract class SpaceJoinRuleItem : VectorEpoxyModel<SpaceJoinRuleItem.Holder>()
holder.listTitle.isVisible = true
restrictedList.forEachIndexed { index, matrixItem ->
if (index < items.size) {
items[index].isVisible = true
avatarRenderer.render(matrixItem, items[index])
} else if (index == items.size) {
holder.spaceMore.isVisible = true

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.roomprofile.settings.joinrule.advanced
import im.vector.app.core.platform.ViewModelTask
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject
sealed class ChangeJoinRuleTaskResult {
object Success : ChangeJoinRuleTaskResult()
data class Failure(val error: Throwable) : ChangeJoinRuleTaskResult()
}
data class ChangeJoinRuleParams(
val roomId: String,
val newJoinRule: RoomJoinRules,
val newAllowIfRestricted: List<String>? = null
)
class ChangeJoinRuleViewModelTask @Inject constructor(
private val session: Session
) : ViewModelTask<ChangeJoinRuleParams, ChangeJoinRuleTaskResult> {
override suspend fun execute(params: ChangeJoinRuleParams): ChangeJoinRuleTaskResult {
val room = session.getRoom(params.roomId) ?: return ChangeJoinRuleTaskResult.Failure(IllegalArgumentException("Unknown room"))
try {
when (params.newJoinRule) {
RoomJoinRules.PUBLIC,
RoomJoinRules.INVITE -> {
room.updateJoinRule(params.newJoinRule, null)
}
RoomJoinRules.RESTRICTED -> updateRestrictedJoinRule(params.roomId, params.newAllowIfRestricted.orEmpty())
RoomJoinRules.KNOCK,
RoomJoinRules.PRIVATE -> {
return ChangeJoinRuleTaskResult.Failure(UnsupportedOperationException())
}
}
} catch (failure: Throwable) {
return ChangeJoinRuleTaskResult.Failure(failure)
}
return ChangeJoinRuleTaskResult.Success
}
fun updateRestrictedJoinRule(roomId: String, allowList: List<String>) {
// let's compute correct via list
allowList.map {
session.getRoomSummary(it)
}
}
}

View file

@ -24,4 +24,5 @@ sealed class RoomJoinRuleChooseRestrictedActions : VectorViewModelAction {
data class FilterWith(val filter: String) : RoomJoinRuleChooseRestrictedActions()
data class ToggleSelection(val matrixItem: MatrixItem) : RoomJoinRuleChooseRestrictedActions()
data class SelectJoinRules(val rules: RoomJoinRules) : RoomJoinRuleChooseRestrictedActions()
object DoUpdateJoinRules : RoomJoinRuleChooseRestrictedActions()
}

View file

@ -39,7 +39,8 @@ data class RoomJoinRuleChooseRestrictedState(
val unknownRestricted: List<MatrixItem> = emptyList(),
val filter: String = "",
val filteredResults: Async<List<MatrixItem>> = Uninitialized,
val hasUnsavedChanges: Boolean = false
val hasUnsavedChanges: Boolean = false,
val updatingStatus: Async<Unit> = Uninitialized
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}

View file

@ -18,6 +18,7 @@ package im.vector.app.features.roomprofile.settings.joinrule.advanced
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
@ -61,10 +62,6 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
?.toModel<RoomJoinRulesContent>()
val initialAllowList = joinRulesContent?.allowList
// val others = session.spaceService().getSpaceSummaries(spaceSummaryQueryParams {
// memberships = listOf(Membership.JOIN)
// }).map { it.toMatrixItem() }
val knownParentSpacesAllowed = mutableListOf<MatrixItem>()
val unknownAllowedOrRooms = mutableListOf<MatrixItem>()
initialAllowList.orEmpty().forEach { entry ->
@ -130,7 +127,6 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
updatedAllowList = initialAllowList.orEmpty().map {
session.getRoomSummary(it.spaceID)?.toMatrixItem() ?: MatrixItem.RoomItem(it.spaceID, null, null)
},
// selectedRoomList = initialAllowList.orEmpty().map { it.spaceID },
possibleSpaceCandidate = possibleSpaceCandidate,
unknownRestricted = unknownAllowedOrRooms
)
@ -170,11 +166,50 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
is RoomJoinRuleChooseRestrictedActions.FilterWith -> handleFilter(action)
is RoomJoinRuleChooseRestrictedActions.ToggleSelection -> handleToggleSelection(action)
is RoomJoinRuleChooseRestrictedActions.SelectJoinRules -> handleSelectRule(action)
RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules -> handleSubmit()
}.exhaustive
checkForChanges()
}
fun handleSubmit() = withState { state ->
setState { copy(updatingStatus = Loading()) }
viewModelScope.launch {
try {
when (state.currentRoomJoinRules) {
RoomJoinRules.PUBLIC -> room.setJoinRulePublic()
RoomJoinRules.INVITE -> room.setJoinRuleInviteOnly()
RoomJoinRules.RESTRICTED -> room.setJoinRuleRestricted(state.updatedAllowList.map { it.id })
RoomJoinRules.KNOCK,
RoomJoinRules.PRIVATE,
null -> {
throw UnsupportedOperationException()
}
}
setState { copy(updatingStatus = Success(Unit)) }
} catch (failure: Throwable) {
setState { copy(updatingStatus = Fail(failure)) }
}
}
}
fun handleSelectRule(action: RoomJoinRuleChooseRestrictedActions.SelectJoinRules) = withState { state ->
if (action.rules == RoomJoinRules.RESTRICTED && state.currentRoomJoinRules != RoomJoinRules.RESTRICTED) {
// switching to restricted
// if allow list is empty, then default to current space parents
if (state.updatedAllowList.isEmpty()) {
val candidates = session.getRoomSummary(initialState.roomId)
?.flattenParentIds
?.filter {
session.getRoomSummary(it)?.spaceChildren?.firstOrNull { it.childRoomId == initialState.roomId } != null
}?.mapNotNull {
session.getRoomSummary(it)?.toMatrixItem()
}.orEmpty()
setState {
copy(updatedAllowList = candidates)
}
}
}
setState {
copy(
currentRoomJoinRules = action.rules