mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 11:59:12 +03:00
Merge pull request #2627 from vector-im/feature/bma/edit_power_level
Edit power level
This commit is contained in:
commit
5431584b3c
24 changed files with 874 additions and 44 deletions
|
@ -3,6 +3,7 @@ Changes in Element 1.0.14 (2020-XX-XX)
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
- Enable url previews for notices (#2562)
|
- Enable url previews for notices (#2562)
|
||||||
|
- Edit room permissions (#2471)
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Add System theme option and set as default (#904, #2387)
|
- Add System theme option and set as default (#904, #2387)
|
||||||
|
|
|
@ -37,6 +37,6 @@ class SenderNotificationPermissionCondition(
|
||||||
|
|
||||||
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
||||||
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
||||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevels.notificationLevel(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,28 +25,85 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class PowerLevelsContent(
|
data class PowerLevelsContent(
|
||||||
|
/**
|
||||||
|
* The level required to ban a user. Defaults to 50 if unspecified.
|
||||||
|
*/
|
||||||
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
||||||
|
/**
|
||||||
|
* The level required to kick a user. Defaults to 50 if unspecified.
|
||||||
|
*/
|
||||||
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
||||||
|
/**
|
||||||
|
* The level required to invite a user. Defaults to 50 if unspecified.
|
||||||
|
*/
|
||||||
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
||||||
|
/**
|
||||||
|
* The level required to redact an event. Defaults to 50 if unspecified.
|
||||||
|
*/
|
||||||
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
||||||
|
/**
|
||||||
|
* The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
|
||||||
|
*/
|
||||||
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
||||||
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
|
/**
|
||||||
|
* The level required to send specific event types. This is a mapping from event type to power level required.
|
||||||
|
*/
|
||||||
|
@Json(name = "events") val events: Map<String, Int> = emptyMap(),
|
||||||
|
/**
|
||||||
|
* The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
|
||||||
|
*/
|
||||||
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
||||||
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
|
/**
|
||||||
|
* The power levels for specific users. This is a mapping from user_id to power level for that user.
|
||||||
|
*/
|
||||||
|
@Json(name = "users") val users: Map<String, Int> = emptyMap(),
|
||||||
|
/**
|
||||||
|
* The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
|
||||||
|
*/
|
||||||
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
||||||
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
|
/**
|
||||||
|
* The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
|
||||||
|
*/
|
||||||
|
@Json(name = "notifications") val notifications: Map<String, Any> = emptyMap()
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Alter this content with a new power level for the specified user
|
* Return a copy of this content with a new power level for the specified user
|
||||||
*
|
*
|
||||||
* @param userId the userId to alter the power level of
|
* @param userId the userId to alter the power level of
|
||||||
* @param powerLevel the new power level, or null to set the default value.
|
* @param powerLevel the new power level, or null to set the default value.
|
||||||
*/
|
*/
|
||||||
fun setUserPowerLevel(userId: String, powerLevel: Int?) {
|
fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
|
||||||
if (powerLevel == null || powerLevel == usersDefault) {
|
return copy(
|
||||||
users.remove(userId)
|
users = users.toMutableMap().apply {
|
||||||
} else {
|
if (powerLevel == null || powerLevel == usersDefault) {
|
||||||
users[userId] = powerLevel
|
remove(userId)
|
||||||
|
} else {
|
||||||
|
put(userId, powerLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notification level for a dedicated key.
|
||||||
|
*
|
||||||
|
* @param key the notification key
|
||||||
|
* @return the level, default to Moderator if the key is not found
|
||||||
|
*/
|
||||||
|
fun notificationLevel(key: String): Int {
|
||||||
|
return when (val value = notifications[key]) {
|
||||||
|
// the first implementation was a string value
|
||||||
|
is String -> value.toInt()
|
||||||
|
is Double -> value.toInt()
|
||||||
|
is Int -> value
|
||||||
|
else -> Role.Moderator.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Key to use for content.notifications and get the level required to trigger an @room notification. Defaults to 50 if unspecified.
|
||||||
|
*/
|
||||||
|
const val NOTIFICATIONS_ROOM_KEY = "room"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,19 +108,4 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
val powerLevel = getUserPowerLevelValue(userId)
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
return powerLevel >= powerLevelsContent.redact
|
return powerLevel >= powerLevelsContent.redact
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the notification level for a dedicated key.
|
|
||||||
*
|
|
||||||
* @param key the notification key
|
|
||||||
* @return the level
|
|
||||||
*/
|
|
||||||
fun notificationLevel(key: String): Int {
|
|
||||||
return when (val value = powerLevelsContent.notifications[key]) {
|
|
||||||
// the first implementation was a string value
|
|
||||||
is String -> value.toInt()
|
|
||||||
is Int -> value
|
|
||||||
else -> Role.Moderator.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,13 +35,11 @@ import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.api.util.MimeTypes
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.session.content.FileUploader
|
import org.matrix.android.sdk.internal.session.content.FileUploader
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
|
|
||||||
|
|
||||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val stateEventDataSource: StateEventDataSource,
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
private val sendStateTask: SendStateTask,
|
private val sendStateTask: SendStateTask,
|
||||||
private val fileUploader: FileUploader,
|
private val fileUploader: FileUploader
|
||||||
private val addRoomAliasTask: AddRoomAliasTask
|
|
||||||
) : StateService {
|
) : StateService {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
|
@ -74,11 +72,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
stateKey = stateKey,
|
stateKey = stateKey,
|
||||||
eventType = eventType,
|
eventType = eventType,
|
||||||
body = body
|
body = body.toSafeJson(eventType)
|
||||||
)
|
)
|
||||||
sendStateTask.execute(params)
|
sendStateTask.execute(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
|
||||||
|
// Safe treatment for PowerLevelContent
|
||||||
|
return when (eventType) {
|
||||||
|
EventType.STATE_ROOM_POWER_LEVELS -> toSafePowerLevelsContentDict()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun updateTopic(topic: String) {
|
override suspend fun updateTopic(topic: String) {
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_TOPIC,
|
eventType = EventType.STATE_ROOM_TOPIC,
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.state
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SerializablePowerLevelsContent(
|
||||||
|
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
||||||
|
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
||||||
|
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
||||||
|
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
||||||
|
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
||||||
|
@Json(name = "events") val events: Map<String, Int> = emptyMap(),
|
||||||
|
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
||||||
|
@Json(name = "users") val users: Map<String, Int> = emptyMap(),
|
||||||
|
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
||||||
|
// `Int` is the diff here (instead of `Any`)
|
||||||
|
@Json(name = "notifications") val notifications: Map<String, Int> = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
|
||||||
|
return toModel<PowerLevelsContent>()
|
||||||
|
?.let { content ->
|
||||||
|
SerializablePowerLevelsContent(
|
||||||
|
ban = content.ban,
|
||||||
|
kick = content.kick,
|
||||||
|
invite = content.invite,
|
||||||
|
redact = content.redact,
|
||||||
|
eventsDefault = content.eventsDefault,
|
||||||
|
events = content.events,
|
||||||
|
usersDefault = content.usersDefault,
|
||||||
|
users = content.users,
|
||||||
|
stateDefault = content.stateDefault,
|
||||||
|
notifications = content.notifications.mapValues { content.notificationLevel(it.key) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
?.toContent()
|
||||||
|
?: emptyMap()
|
||||||
|
}
|
|
@ -247,6 +247,7 @@ class UiAllScreensSanityTest {
|
||||||
|
|
||||||
// Room settings
|
// Room settings
|
||||||
clickListItem(R.id.matrixProfileRecyclerView, 3)
|
clickListItem(R.id.matrixProfileRecyclerView, 3)
|
||||||
|
navigateToRoomParameters()
|
||||||
pressBack()
|
pressBack()
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
|
@ -285,6 +286,31 @@ class UiAllScreensSanityTest {
|
||||||
pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToRoomParameters() {
|
||||||
|
// Room addresses
|
||||||
|
clickListItem(R.id.roomSettingsRecyclerView, 4)
|
||||||
|
onView(isRoot()).perform(waitForView(withText(R.string.room_alias_published_alias_title)))
|
||||||
|
pressBack()
|
||||||
|
|
||||||
|
// Room permissions
|
||||||
|
clickListItem(R.id.roomSettingsRecyclerView, 6)
|
||||||
|
onView(isRoot()).perform(waitForView(withText(R.string.room_permissions_title)))
|
||||||
|
clickOn(R.string.room_permissions_change_room_avatar)
|
||||||
|
clickDialogNegativeButton()
|
||||||
|
// Toggle
|
||||||
|
clickOn(R.string.show_advanced)
|
||||||
|
clickOn(R.string.hide_advanced)
|
||||||
|
pressBack()
|
||||||
|
|
||||||
|
// Room history readability
|
||||||
|
clickListItem(R.id.roomSettingsRecyclerView, 8)
|
||||||
|
pressBack()
|
||||||
|
|
||||||
|
// Room access
|
||||||
|
clickListItem(R.id.roomSettingsRecyclerView, 10)
|
||||||
|
pressBack()
|
||||||
|
}
|
||||||
|
|
||||||
private fun navigateToInvite() {
|
private fun navigateToInvite() {
|
||||||
assertDisplayed(R.id.inviteUsersButton)
|
assertDisplayed(R.id.inviteUsersButton)
|
||||||
clickOn(R.id.inviteUsersButton)
|
clickOn(R.id.inviteUsersButton)
|
||||||
|
|
|
@ -84,6 +84,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
|
||||||
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
||||||
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
||||||
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
|
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
|
||||||
|
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
|
||||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
||||||
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
|
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
|
||||||
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
|
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
|
||||||
|
@ -364,6 +365,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(RoomAliasFragment::class)
|
@FragmentKey(RoomAliasFragment::class)
|
||||||
fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment
|
fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomPermissionsFragment::class)
|
||||||
|
fun bindRoomPermissionsFragment(fragment: RoomPermissionsFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(RoomMemberProfileFragment::class)
|
@FragmentKey(RoomMemberProfileFragment::class)
|
||||||
|
|
|
@ -887,13 +887,15 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
private fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
||||||
val currentPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
|
val newPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
|
||||||
?.content
|
?.content
|
||||||
?.toModel<PowerLevelsContent>() ?: return
|
?.toModel<PowerLevelsContent>()
|
||||||
|
?.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel)
|
||||||
|
?.toContent()
|
||||||
|
?: return
|
||||||
|
|
||||||
launchSlashCommandFlowSuspendable {
|
launchSlashCommandFlowSuspendable {
|
||||||
currentPowerLevelsContent.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel)
|
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent)
|
||||||
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -324,7 +324,7 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEditPowerLevel(currentRole: Role) {
|
override fun onEditPowerLevel(currentRole: Role) {
|
||||||
EditPowerLevelDialogs.showChoice(requireActivity(), currentRole) { newPowerLevel ->
|
EditPowerLevelDialogs.showChoice(requireActivity(), R.string.power_level_edit_title, currentRole) { newPowerLevel ->
|
||||||
viewModel.handle(RoomMemberProfileAction.SetPowerLevel(currentRole.value, newPowerLevel, true))
|
viewModel.handle(RoomMemberProfileAction.SetPowerLevel(currentRole.value, newPowerLevel, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,11 +162,13 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
} else if (action.askForValidation && state.isMine) {
|
} else if (action.askForValidation && state.isMine) {
|
||||||
_viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelDemoteWarning(action.previousValue, action.newValue))
|
_viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelDemoteWarning(action.previousValue, action.newValue))
|
||||||
} else {
|
} else {
|
||||||
currentPowerLevelsContent.setUserPowerLevel(state.userId, action.newValue)
|
val newPowerLevelsContent = currentPowerLevelsContent
|
||||||
|
.setUserPowerLevel(state.userId, action.newValue)
|
||||||
|
.toContent()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_viewEvents.post(RoomMemberProfileViewEvents.Loading())
|
_viewEvents.post(RoomMemberProfileViewEvents.Loading())
|
||||||
try {
|
try {
|
||||||
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent())
|
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent)
|
||||||
_viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess)
|
_viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(RoomMemberProfileViewEvents.Failure(failure))
|
_viewEvents.post(RoomMemberProfileViewEvents.Failure(failure))
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.roommemberprofile.powerlevel
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
@ -29,7 +30,10 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
|
|
||||||
object EditPowerLevelDialogs {
|
object EditPowerLevelDialogs {
|
||||||
|
|
||||||
fun showChoice(activity: Activity, currentRole: Role, listener: (Int) -> Unit) {
|
fun showChoice(activity: Activity,
|
||||||
|
@StringRes titleRes: Int,
|
||||||
|
currentRole: Role,
|
||||||
|
listener: (Int) -> Unit) {
|
||||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null)
|
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null)
|
||||||
val views = DialogEditPowerLevelBinding.bind(dialogLayout)
|
val views = DialogEditPowerLevelBinding.bind(dialogLayout)
|
||||||
views.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId ->
|
views.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||||
|
@ -45,7 +49,7 @@ object EditPowerLevelDialogs {
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder(activity)
|
AlertDialog.Builder(activity)
|
||||||
.setTitle(R.string.power_level_edit_title)
|
.setTitle(titleRes)
|
||||||
.setView(dialogLayout)
|
.setView(dialogLayout)
|
||||||
.setPositiveButton(R.string.edit) { _, _ ->
|
.setPositiveButton(R.string.edit) { _, _ ->
|
||||||
val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) {
|
val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.di.ScreenComponent
|
import im.vector.app.core.di.ScreenComponent
|
||||||
import im.vector.app.core.extensions.addFragment
|
import im.vector.app.core.extensions.addFragment
|
||||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
import im.vector.app.core.extensions.addFragmentToBackstack
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.ToolbarConfigurable
|
import im.vector.app.core.platform.ToolbarConfigurable
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.databinding.ActivitySimpleBinding
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
|
@ -38,6 +39,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
|
||||||
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
||||||
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
||||||
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
|
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
|
||||||
|
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
|
||||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -102,12 +104,13 @@ class RoomProfileActivity :
|
||||||
.observe()
|
.observe()
|
||||||
.subscribe { sharedAction ->
|
.subscribe { sharedAction ->
|
||||||
when (sharedAction) {
|
when (sharedAction) {
|
||||||
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
|
RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
|
||||||
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
|
RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
|
||||||
is RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias()
|
RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias()
|
||||||
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
|
RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions()
|
||||||
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
|
RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
|
||||||
}
|
RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
|
|
||||||
|
@ -144,6 +147,10 @@ class RoomProfileActivity :
|
||||||
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomAliasFragment::class.java, roomProfileArgs)
|
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomAliasFragment::class.java, roomProfileArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openRoomPermissions() {
|
||||||
|
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomPermissionsFragment::class.java, roomProfileArgs)
|
||||||
|
}
|
||||||
|
|
||||||
private fun openRoomMembers() {
|
private fun openRoomMembers() {
|
||||||
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs)
|
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import im.vector.app.core.platform.VectorSharedAction
|
||||||
sealed class RoomProfileSharedAction : VectorSharedAction {
|
sealed class RoomProfileSharedAction : VectorSharedAction {
|
||||||
object OpenRoomSettings : RoomProfileSharedAction()
|
object OpenRoomSettings : RoomProfileSharedAction()
|
||||||
object OpenRoomAliasesSettings : RoomProfileSharedAction()
|
object OpenRoomAliasesSettings : RoomProfileSharedAction()
|
||||||
|
object OpenRoomPermissionsSettings : RoomProfileSharedAction()
|
||||||
object OpenRoomUploads : RoomProfileSharedAction()
|
object OpenRoomUploads : RoomProfileSharedAction()
|
||||||
object OpenRoomMembers : RoomProfileSharedAction()
|
object OpenRoomMembers : RoomProfileSharedAction()
|
||||||
object OpenBannedRoomMembers : RoomProfileSharedAction()
|
object OpenBannedRoomMembers : RoomProfileSharedAction()
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* 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.permissions
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import im.vector.app.R
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change on each permission has an effect on the power level event. Try to sort the effect by category.
|
||||||
|
*/
|
||||||
|
sealed class EditablePermission(@StringRes val labelResId: Int) {
|
||||||
|
// Updates `content.events.[eventType]`
|
||||||
|
open class EventTypeEditablePermission(val eventType: String, @StringRes labelResId: Int) : EditablePermission(labelResId)
|
||||||
|
|
||||||
|
class ModifyWidgets : EventTypeEditablePermission(
|
||||||
|
// Note: Element Web still use legacy value
|
||||||
|
EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||||
|
R.string.room_permissions_modify_widgets
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChangeRoomAvatar : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_AVATAR,
|
||||||
|
R.string.room_permissions_change_room_avatar
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChangeMainAddressForTheRoom : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
|
R.string.room_permissions_change_main_address_for_the_room
|
||||||
|
)
|
||||||
|
|
||||||
|
class EnableRoomEncryption : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_ENCRYPTION,
|
||||||
|
R.string.room_permissions_enable_room_encryption
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChangeHistoryVisibility : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
|
R.string.room_permissions_change_history_visibility
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChangeRoomName : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_NAME,
|
||||||
|
R.string.room_permissions_change_room_name
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChangePermissions : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_POWER_LEVELS,
|
||||||
|
R.string.room_permissions_change_permissions
|
||||||
|
)
|
||||||
|
|
||||||
|
class SendRoomServerAclEvents : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_SERVER_ACL,
|
||||||
|
R.string.room_permissions_send_m_room_server_acl_events
|
||||||
|
)
|
||||||
|
|
||||||
|
class UpgradeTheRoom : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_TOMBSTONE,
|
||||||
|
R.string.room_permissions_upgrade_the_room
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChangeTopic : EventTypeEditablePermission(
|
||||||
|
EventType.STATE_ROOM_TOPIC,
|
||||||
|
R.string.room_permissions_change_topic
|
||||||
|
)
|
||||||
|
|
||||||
|
// Updates `content.users_default`
|
||||||
|
class DefaultRole : EditablePermission(R.string.room_permissions_default_role)
|
||||||
|
|
||||||
|
// Updates `content.events_default`
|
||||||
|
class SendMessages : EditablePermission(R.string.room_permissions_send_messages)
|
||||||
|
|
||||||
|
// Updates `content.invites`
|
||||||
|
class InviteUsers : EditablePermission(R.string.room_permissions_invite_users)
|
||||||
|
|
||||||
|
// Updates `content.state_default`
|
||||||
|
class ChangeSettings : EditablePermission(R.string.room_permissions_change_settings)
|
||||||
|
|
||||||
|
// Updates `content.kick`
|
||||||
|
class KickUsers : EditablePermission(R.string.room_permissions_kick_users)
|
||||||
|
|
||||||
|
// Updates `content.ban`
|
||||||
|
class BanUsers : EditablePermission(R.string.room_permissions_ban_users)
|
||||||
|
|
||||||
|
// Updates `content.redact`
|
||||||
|
class RemoveMessagesSentByOthers : EditablePermission(R.string.room_permissions_remove_messages_sent_by_others)
|
||||||
|
|
||||||
|
// Updates `content.notification.room`
|
||||||
|
class NotifyEveryone : EditablePermission(R.string.room_permissions_notify_everyone)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.permissions
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class RoomPermissionsAction : VectorViewModelAction {
|
||||||
|
object ToggleShowAllPermissions : RoomPermissionsAction()
|
||||||
|
|
||||||
|
data class UpdatePermission(val editablePermission: EditablePermission, val powerLevel: Int) : RoomPermissionsAction()
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.permissions
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
|
import im.vector.app.core.epoxy.profiles.buildProfileAction
|
||||||
|
import im.vector.app.core.epoxy.profiles.buildProfileSection
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.discovery.settingsInfoItem
|
||||||
|
import im.vector.app.features.form.formAdvancedToggleItem
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomPermissionsController @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
colorProvider: ColorProvider
|
||||||
|
) : TypedEpoxyController<RoomPermissionsViewState>() {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onEditPermission(editablePermission: EditablePermission, currentRole: Role)
|
||||||
|
fun toggleShowAllPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||||
|
|
||||||
|
// Order is the order applied in the UI
|
||||||
|
// Element Web order is not really nice, try to put the settings which are more likely to be updated first
|
||||||
|
// And a second section, hidden by default
|
||||||
|
private val usefulEditablePermissions = listOf(
|
||||||
|
EditablePermission.ChangeRoomAvatar(),
|
||||||
|
EditablePermission.ChangeRoomName(),
|
||||||
|
EditablePermission.ChangeTopic()
|
||||||
|
)
|
||||||
|
|
||||||
|
private val advancedEditablePermissions = listOf(
|
||||||
|
EditablePermission.ChangeMainAddressForTheRoom(),
|
||||||
|
|
||||||
|
EditablePermission.DefaultRole(),
|
||||||
|
EditablePermission.InviteUsers(),
|
||||||
|
EditablePermission.KickUsers(),
|
||||||
|
EditablePermission.BanUsers(),
|
||||||
|
|
||||||
|
EditablePermission.SendMessages(),
|
||||||
|
|
||||||
|
EditablePermission.RemoveMessagesSentByOthers(),
|
||||||
|
EditablePermission.NotifyEveryone(),
|
||||||
|
|
||||||
|
EditablePermission.ChangeSettings(),
|
||||||
|
EditablePermission.ModifyWidgets(),
|
||||||
|
EditablePermission.ChangeHistoryVisibility(),
|
||||||
|
EditablePermission.ChangePermissions(),
|
||||||
|
EditablePermission.SendRoomServerAclEvents(),
|
||||||
|
EditablePermission.EnableRoomEncryption(),
|
||||||
|
EditablePermission.UpgradeTheRoom()
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
setData(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomPermissionsViewState?) {
|
||||||
|
buildProfileSection(
|
||||||
|
stringProvider.getString(R.string.room_permissions_title)
|
||||||
|
)
|
||||||
|
|
||||||
|
when (val content = data?.currentPowerLevelsContent) {
|
||||||
|
is Success -> buildPermissions(data, content())
|
||||||
|
else -> {
|
||||||
|
loadingItem {
|
||||||
|
id("loading")
|
||||||
|
loadingText(stringProvider.getString(R.string.loading))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildPermissions(data: RoomPermissionsViewState, content: PowerLevelsContent) {
|
||||||
|
val editable = data.actionPermissions.canChangePowerLevels
|
||||||
|
settingsInfoItem {
|
||||||
|
id("notice")
|
||||||
|
helperText(stringProvider.getString(if (editable) R.string.room_permissions_notice else R.string.room_permissions_notice_read_only))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Useful permissions
|
||||||
|
usefulEditablePermissions.forEach { buildPermission(it, content, editable) }
|
||||||
|
|
||||||
|
// Toggle
|
||||||
|
formAdvancedToggleItem {
|
||||||
|
id("showAdvanced")
|
||||||
|
title(stringProvider.getString(if (data.showAdvancedPermissions) R.string.hide_advanced else R.string.show_advanced))
|
||||||
|
expanded(!data.showAdvancedPermissions)
|
||||||
|
listener { callback?.toggleShowAllPermissions() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advanced permissions
|
||||||
|
if (data.showAdvancedPermissions) {
|
||||||
|
advancedEditablePermissions.forEach { buildPermission(it, content, editable) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildPermission(editablePermission: EditablePermission, content: PowerLevelsContent, editable: Boolean) {
|
||||||
|
val currentRole = getCurrentRole(editablePermission, content)
|
||||||
|
buildProfileAction(
|
||||||
|
id = editablePermission.labelResId.toString(),
|
||||||
|
title = stringProvider.getString(editablePermission.labelResId),
|
||||||
|
subtitle = getSubtitle(currentRole),
|
||||||
|
dividerColor = dividerColor,
|
||||||
|
divider = true,
|
||||||
|
editable = editable,
|
||||||
|
action = {
|
||||||
|
callback
|
||||||
|
?.takeIf { editable }
|
||||||
|
?.onEditPermission(editablePermission, currentRole)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSubtitle(currentRole: Role): String {
|
||||||
|
return when (currentRole) {
|
||||||
|
Role.Admin,
|
||||||
|
Role.Moderator,
|
||||||
|
Role.Default -> stringProvider.getString(currentRole.res)
|
||||||
|
is Role.Custom -> stringProvider.getString(currentRole.res, currentRole.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrentRole(editablePermission: EditablePermission, content: PowerLevelsContent): Role {
|
||||||
|
val value = when (editablePermission) {
|
||||||
|
is EditablePermission.EventTypeEditablePermission -> content.events[editablePermission.eventType] ?: content.stateDefault
|
||||||
|
is EditablePermission.DefaultRole -> content.usersDefault
|
||||||
|
is EditablePermission.SendMessages -> content.eventsDefault
|
||||||
|
is EditablePermission.InviteUsers -> content.invite
|
||||||
|
is EditablePermission.ChangeSettings -> content.stateDefault
|
||||||
|
is EditablePermission.KickUsers -> content.kick
|
||||||
|
is EditablePermission.BanUsers -> content.ban
|
||||||
|
is EditablePermission.RemoveMessagesSentByOthers -> content.redact
|
||||||
|
is EditablePermission.NotifyEveryone -> content.notificationLevel(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Role.fromValue(
|
||||||
|
value,
|
||||||
|
when (editablePermission) {
|
||||||
|
is EditablePermission.EventTypeEditablePermission -> content.stateDefault
|
||||||
|
is EditablePermission.DefaultRole -> Role.Default.value
|
||||||
|
is EditablePermission.SendMessages -> Role.Default.value
|
||||||
|
is EditablePermission.InviteUsers -> Role.Moderator.value
|
||||||
|
is EditablePermission.ChangeSettings -> Role.Moderator.value
|
||||||
|
is EditablePermission.KickUsers -> Role.Moderator.value
|
||||||
|
is EditablePermission.BanUsers -> Role.Moderator.value
|
||||||
|
is EditablePermission.RemoveMessagesSentByOthers -> Role.Moderator.value
|
||||||
|
is EditablePermission.NotifyEveryone -> Role.Moderator.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.permissions
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
import im.vector.app.core.extensions.configureWith
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.utils.toast
|
||||||
|
import im.vector.app.databinding.FragmentRoomSettingGenericBinding
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs
|
||||||
|
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomPermissionsFragment @Inject constructor(
|
||||||
|
val viewModelFactory: RoomPermissionsViewModel.Factory,
|
||||||
|
private val controller: RoomPermissionsController,
|
||||||
|
private val avatarRenderer: AvatarRenderer
|
||||||
|
) :
|
||||||
|
VectorBaseFragment<FragmentRoomSettingGenericBinding>(),
|
||||||
|
RoomPermissionsController.Callback {
|
||||||
|
|
||||||
|
private val viewModel: RoomPermissionsViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
private val roomProfileArgs: RoomProfileArgs by args()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomSettingGenericBinding {
|
||||||
|
return FragmentRoomSettingGenericBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
controller.callback = this
|
||||||
|
setupToolbar(views.roomSettingsToolbar)
|
||||||
|
views.roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true)
|
||||||
|
views.waitingView.waitingStatusText.setText(R.string.please_wait)
|
||||||
|
views.waitingView.waitingStatusText.isVisible = true
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is RoomPermissionsViewEvents.Failure -> showFailure(it.throwable)
|
||||||
|
RoomPermissionsViewEvents.Success -> showSuccess()
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSuccess() {
|
||||||
|
activity?.toast(R.string.room_settings_save_success)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
controller.callback = null
|
||||||
|
views.roomSettingsRecyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
views.waitingView.root.isVisible = state.isLoading
|
||||||
|
controller.setData(state)
|
||||||
|
renderRoomSummary(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderRoomSummary(state: RoomPermissionsViewState) {
|
||||||
|
state.roomSummary()?.let {
|
||||||
|
views.roomSettingsToolbarTitleView.text = it.displayName
|
||||||
|
avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEditPermission(editablePermission: EditablePermission, currentRole: Role) {
|
||||||
|
EditPowerLevelDialogs.showChoice(requireActivity(), editablePermission.labelResId, currentRole) { newPowerLevel ->
|
||||||
|
viewModel.handle(RoomPermissionsAction.UpdatePermission(editablePermission, newPowerLevel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggleShowAllPermissions() {
|
||||||
|
viewModel.handle(RoomPermissionsAction.ToggleShowAllPermissions)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.permissions
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transient events for room settings screen
|
||||||
|
*/
|
||||||
|
sealed class RoomPermissionsViewEvents : VectorViewEvents {
|
||||||
|
data class Failure(val throwable: Throwable) : RoomPermissionsViewEvents()
|
||||||
|
object Success : RoomPermissionsViewEvents()
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.permissions
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
|
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.rx.rx
|
||||||
|
import org.matrix.android.sdk.rx.unwrap
|
||||||
|
|
||||||
|
class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialState: RoomPermissionsViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<RoomPermissionsViewState, RoomPermissionsAction, RoomPermissionsViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: RoomPermissionsViewState): RoomPermissionsViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<RoomPermissionsViewModel, RoomPermissionsViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: RoomPermissionsViewState): RoomPermissionsViewModel? {
|
||||||
|
val fragment: RoomPermissionsFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.viewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeRoomSummary()
|
||||||
|
observePowerLevel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRoomSummary() {
|
||||||
|
room.rx().liveRoomSummary()
|
||||||
|
.unwrap()
|
||||||
|
.execute { async ->
|
||||||
|
copy(
|
||||||
|
roomSummary = async
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observePowerLevel() {
|
||||||
|
PowerLevelsObservableFactory(room)
|
||||||
|
.createObservable()
|
||||||
|
.subscribe { powerLevelContent ->
|
||||||
|
val powerLevelsHelper = PowerLevelsHelper(powerLevelContent)
|
||||||
|
val permissions = RoomPermissionsViewState.ActionPermissions(
|
||||||
|
canChangePowerLevels = powerLevelsHelper.isUserAllowedToSend(
|
||||||
|
userId = session.myUserId,
|
||||||
|
isState = true,
|
||||||
|
eventType = EventType.STATE_ROOM_POWER_LEVELS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
actionPermissions = permissions,
|
||||||
|
currentPowerLevelsContent = Success(powerLevelContent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: RoomPermissionsAction) {
|
||||||
|
when (action) {
|
||||||
|
is RoomPermissionsAction.UpdatePermission -> updatePermission(action)
|
||||||
|
RoomPermissionsAction.ToggleShowAllPermissions -> toggleShowAllPermissions()
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleShowAllPermissions() {
|
||||||
|
setState {
|
||||||
|
copy(showAdvancedPermissions = !showAdvancedPermissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePermission(action: RoomPermissionsAction.UpdatePermission) {
|
||||||
|
withState { state ->
|
||||||
|
val currentPowerLevel = state.currentPowerLevelsContent.invoke() ?: return@withState
|
||||||
|
postLoading(true)
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val newPowerLevelsContent = when (action.editablePermission) {
|
||||||
|
is EditablePermission.EventTypeEditablePermission -> currentPowerLevel.copy(
|
||||||
|
events = currentPowerLevel.events.toMutableMap().apply {
|
||||||
|
put(action.editablePermission.eventType, action.powerLevel)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is EditablePermission.DefaultRole -> currentPowerLevel.copy(usersDefault = action.powerLevel)
|
||||||
|
is EditablePermission.SendMessages -> currentPowerLevel.copy(eventsDefault = action.powerLevel)
|
||||||
|
is EditablePermission.InviteUsers -> currentPowerLevel.copy(invite = action.powerLevel)
|
||||||
|
is EditablePermission.ChangeSettings -> currentPowerLevel.copy(stateDefault = action.powerLevel)
|
||||||
|
is EditablePermission.KickUsers -> currentPowerLevel.copy(kick = action.powerLevel)
|
||||||
|
is EditablePermission.BanUsers -> currentPowerLevel.copy(ban = action.powerLevel)
|
||||||
|
is EditablePermission.RemoveMessagesSentByOthers -> currentPowerLevel.copy(redact = action.powerLevel)
|
||||||
|
is EditablePermission.NotifyEveryone -> currentPowerLevel.copy(
|
||||||
|
notifications = currentPowerLevel.notifications.toMutableMap().apply {
|
||||||
|
put(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY, action.powerLevel)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent.toContent())
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
postLoading(false)
|
||||||
|
_viewEvents.post(RoomPermissionsViewEvents.Failure(failure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postLoading(isLoading: Boolean) {
|
||||||
|
setState {
|
||||||
|
copy(isLoading = isLoading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.permissions
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
data class RoomPermissionsViewState(
|
||||||
|
val roomId: String,
|
||||||
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
|
val actionPermissions: ActionPermissions = ActionPermissions(),
|
||||||
|
val showAdvancedPermissions: Boolean = false,
|
||||||
|
val currentPowerLevelsContent: Async<PowerLevelsContent> = Uninitialized,
|
||||||
|
val isLoading: Boolean = false
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
|
||||||
|
data class ActionPermissions(
|
||||||
|
val canChangePowerLevels: Boolean = false
|
||||||
|
)
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ class RoomSettingsController @Inject constructor(
|
||||||
fun onTopicChanged(topic: String)
|
fun onTopicChanged(topic: String)
|
||||||
fun onHistoryVisibilityClicked()
|
fun onHistoryVisibilityClicked()
|
||||||
fun onRoomAliasesClicked()
|
fun onRoomAliasesClicked()
|
||||||
|
fun onRoomPermissionsClicked()
|
||||||
fun onJoinRuleClicked()
|
fun onJoinRuleClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +116,16 @@ class RoomSettingsController @Inject constructor(
|
||||||
action = { callback?.onRoomAliasesClicked() }
|
action = { callback?.onRoomAliasesClicked() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
buildProfileAction(
|
||||||
|
id = "permissions",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_permissions_title),
|
||||||
|
subtitle = stringProvider.getString(R.string.room_settings_permissions_subtitle),
|
||||||
|
dividerColor = dividerColor,
|
||||||
|
divider = true,
|
||||||
|
editable = true,
|
||||||
|
action = { callback?.onRoomPermissionsClicked() }
|
||||||
|
)
|
||||||
|
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "historyReadability",
|
id = "historyReadability",
|
||||||
title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title),
|
title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title),
|
||||||
|
|
|
@ -178,6 +178,10 @@ class RoomSettingsFragment @Inject constructor(
|
||||||
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAliasesSettings)
|
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAliasesSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRoomPermissionsClicked() {
|
||||||
|
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomPermissionsSettings)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onJoinRuleClicked() = withState(viewModel) { state ->
|
override fun onJoinRuleClicked() = withState(viewModel) { state ->
|
||||||
val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules
|
val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules
|
||||||
val currentGuestAccess = state.newRoomJoinRules.newGuestAccess ?: state.currentGuestAccess
|
val currentGuestAccess = state.newRoomJoinRules.newGuestAccess ?: state.currentGuestAccess
|
||||||
|
|
|
@ -610,6 +610,33 @@
|
||||||
<string name="ssl_expected_existing_expl">The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.</string>
|
<string name="ssl_expected_existing_expl">The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.</string>
|
||||||
<string name="ssl_only_accept">Only accept the certificate if the server administrator has published a fingerprint that matches the one above.</string>
|
<string name="ssl_only_accept">Only accept the certificate if the server administrator has published a fingerprint that matches the one above.</string>
|
||||||
|
|
||||||
|
<!-- Room Permissions -->
|
||||||
|
<string name="room_settings_permissions_title">Room permissions</string>
|
||||||
|
<string name="room_settings_permissions_subtitle">View and update the roles required to change various parts of the room.</string>
|
||||||
|
|
||||||
|
<string name="room_permissions_title">"Permissions"</string>
|
||||||
|
<string name="room_permissions_notice">"Select the roles required to change various parts of the room"</string>
|
||||||
|
<string name="room_permissions_notice_read_only">"You don't have permission to update the roles required to change various parts of the room"</string>
|
||||||
|
|
||||||
|
<string name="room_permissions_default_role">Default role</string>
|
||||||
|
<string name="room_permissions_send_messages">Send messages</string>
|
||||||
|
<string name="room_permissions_invite_users">Invite users</string>
|
||||||
|
<string name="room_permissions_change_settings">Change settings</string>
|
||||||
|
<string name="room_permissions_kick_users">Kick users</string>
|
||||||
|
<string name="room_permissions_ban_users">Ban users</string>
|
||||||
|
<string name="room_permissions_remove_messages_sent_by_others">Remove messages sent by others</string>
|
||||||
|
<string name="room_permissions_notify_everyone">Notify everyone</string>
|
||||||
|
<string name="room_permissions_modify_widgets">Modify widgets</string>
|
||||||
|
<string name="room_permissions_change_room_avatar">Change room avatar</string>
|
||||||
|
<string name="room_permissions_change_main_address_for_the_room">Change main address for the room</string>
|
||||||
|
<string name="room_permissions_enable_room_encryption">Enable room encryption</string>
|
||||||
|
<string name="room_permissions_change_history_visibility">Change history visibility</string>
|
||||||
|
<string name="room_permissions_change_room_name">Change room name</string>
|
||||||
|
<string name="room_permissions_change_permissions">Change permissions</string>
|
||||||
|
<string name="room_permissions_send_m_room_server_acl_events">Send m.room.server_acl events</string>
|
||||||
|
<string name="room_permissions_upgrade_the_room">Upgrade the room</string>
|
||||||
|
<string name="room_permissions_change_topic">Change topic</string>
|
||||||
|
|
||||||
<!-- Room Details -->
|
<!-- Room Details -->
|
||||||
<string name="room_details_title">Room Details</string>
|
<string name="room_details_title">Room Details</string>
|
||||||
<string name="room_details_people">People</string>
|
<string name="room_details_people">People</string>
|
||||||
|
|
Loading…
Reference in a new issue