diff --git a/CHANGES.md b/CHANGES.md index 1315d006e0..d039be252d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Changes in Element 1.0.14 (2020-XX-XX) Features ✨: - Enable url previews for notices (#2562) + - Edit room permissions (#2471) Improvements 🙌: - Add System theme option and set as default (#904, #2387) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt index 4f9c84a47c..6675fb0ff5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt @@ -37,6 +37,6 @@ class SenderNotificationPermissionCondition( fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean { 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) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt index 696b612389..e778f5740d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt @@ -25,28 +25,85 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role */ @JsonClass(generateAdapter = true) data class PowerLevelsContent( + /** + * The level required to ban a user. Defaults to 50 if unspecified. + */ @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, + /** + * The level required to invite a user. Defaults to 50 if unspecified. + */ @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, + /** + * 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") val events: MutableMap = 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 = 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") val users: MutableMap = 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 = 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 = "notifications") val notifications: Map = 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 = 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 powerLevel the new power level, or null to set the default value. */ - fun setUserPowerLevel(userId: String, powerLevel: Int?) { - if (powerLevel == null || powerLevel == usersDefault) { - users.remove(userId) - } else { - users[userId] = powerLevel + fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent { + return copy( + users = users.toMutableMap().apply { + if (powerLevel == null || powerLevel == usersDefault) { + 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" + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt index 47922f6968..4f1253c6df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt @@ -108,19 +108,4 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { val powerLevel = getUserPowerLevelValue(userId) 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 - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index b546584450..804968bac0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -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.Optional 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, private val stateEventDataSource: StateEventDataSource, private val sendStateTask: SendStateTask, - private val fileUploader: FileUploader, - private val addRoomAliasTask: AddRoomAliasTask + private val fileUploader: FileUploader ) : StateService { @AssistedInject.Factory @@ -74,11 +72,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private roomId = roomId, stateKey = stateKey, eventType = eventType, - body = body + body = body.toSafeJson(eventType) ) 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) { sendStateEvent( eventType = EventType.STATE_ROOM_TOPIC, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt new file mode 100644 index 0000000000..a97709e38b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt @@ -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 = emptyMap(), + @Json(name = "users_default") val usersDefault: Int = Role.Default.value, + @Json(name = "users") val users: Map = 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 = emptyMap() +) + +internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict { + return toModel() + ?.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() +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index cc4724e8f3..58b596b05f 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -247,6 +247,7 @@ class UiAllScreensSanityTest { // Room settings clickListItem(R.id.matrixProfileRecyclerView, 3) + navigateToRoomParameters() pressBack() // Notifications @@ -285,6 +286,31 @@ class UiAllScreensSanityTest { 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() { assertDisplayed(R.id.inviteUsersButton) clickOn(R.id.inviteUsersButton) diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 87ab875746..407aa2fc73 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -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.settings.RoomSettingsFragment 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.files.RoomUploadsFilesFragment import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment @@ -364,6 +365,11 @@ interface FragmentModule { @FragmentKey(RoomAliasFragment::class) fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment + @Binds + @IntoMap + @FragmentKey(RoomPermissionsFragment::class) + fun bindRoomPermissionsFragment(fragment: RoomPermissionsFragment): Fragment + @Binds @IntoMap @FragmentKey(RoomMemberProfileFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 1e6e7c9d14..975eda5d2f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -887,13 +887,15 @@ class RoomDetailViewModel @AssistedInject constructor( } 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 - ?.toModel() ?: return + ?.toModel() + ?.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel) + ?.toContent() + ?: return launchSlashCommandFlowSuspendable { - currentPowerLevelsContent.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel) - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent()) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 08a251834e..688f74ba5d 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -324,7 +324,7 @@ class RoomMemberProfileFragment @Inject constructor( } 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)) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 39b5884308..8211a05127 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -162,11 +162,13 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } else if (action.askForValidation && state.isMine) { _viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelDemoteWarning(action.previousValue, action.newValue)) } else { - currentPowerLevelsContent.setUserPowerLevel(state.userId, action.newValue) + val newPowerLevelsContent = currentPowerLevelsContent + .setUserPowerLevel(state.userId, action.newValue) + .toContent() viewModelScope.launch { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) try { - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent()) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent) _viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt index 4316a4bd0d..764271f7ce 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt @@ -19,6 +19,7 @@ package im.vector.app.features.roommemberprofile.powerlevel import android.app.Activity import android.content.DialogInterface import android.view.KeyEvent +import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import im.vector.app.R @@ -29,7 +30,10 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role 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 views = DialogEditPowerLevelBinding.bind(dialogLayout) views.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId -> @@ -45,7 +49,7 @@ object EditPowerLevelDialogs { } AlertDialog.Builder(activity) - .setTitle(R.string.power_level_edit_title) + .setTitle(titleRes) .setView(dialogLayout) .setPositiveButton(R.string.edit) { _, _ -> val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 76649d53b3..7cb713d378 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -27,6 +27,7 @@ import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.addFragment 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.VectorBaseActivity 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.settings.RoomSettingsFragment 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 javax.inject.Inject @@ -102,12 +104,13 @@ class RoomProfileActivity : .observe() .subscribe { sharedAction -> when (sharedAction) { - is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() - is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() - is RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() - is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() - is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() - } + RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() + RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() + RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() + RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() + RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() + RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() + }.exhaustive } .disposeOnDestroy() @@ -144,6 +147,10 @@ class RoomProfileActivity : addFragmentToBackstack(R.id.simpleFragmentContainer, RoomAliasFragment::class.java, roomProfileArgs) } + private fun openRoomPermissions() { + addFragmentToBackstack(R.id.simpleFragmentContainer, RoomPermissionsFragment::class.java, roomProfileArgs) + } + private fun openRoomMembers() { addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 83a610cf1b..2a5775d1af 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -24,6 +24,7 @@ import im.vector.app.core.platform.VectorSharedAction sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() object OpenRoomAliasesSettings : RoomProfileSharedAction() + object OpenRoomPermissionsSettings : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/EditablePermission.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/EditablePermission.kt new file mode 100644 index 0000000000..bb1054b704 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/EditablePermission.kt @@ -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) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt new file mode 100644 index 0000000000..b853dda160 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt @@ -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() +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt new file mode 100644 index 0000000000..92bdac7ae0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt @@ -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() { + + 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 + } + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt new file mode 100644 index 0000000000..61635c9b31 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt @@ -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(), + 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) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewEvents.kt new file mode 100644 index 0000000000..8994398cf3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewEvents.kt @@ -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() +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt new file mode 100644 index 0000000000..183488a5e0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -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(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomPermissionsViewState): RoomPermissionsViewModel + } + + companion object : MvRxViewModelFactory { + + @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) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewState.kt new file mode 100644 index 0000000000..ce38ab87e5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewState.kt @@ -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 = Uninitialized, + val actionPermissions: ActionPermissions = ActionPermissions(), + val showAdvancedPermissions: Boolean = false, + val currentPowerLevelsContent: Async = Uninitialized, + val isLoading: Boolean = false +) : MvRxState { + + constructor(args: RoomProfileArgs) : this(roomId = args.roomId) + + data class ActionPermissions( + val canChangePowerLevels: Boolean = false + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index bf3c1f87f8..1984be078d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -46,6 +46,7 @@ class RoomSettingsController @Inject constructor( fun onTopicChanged(topic: String) fun onHistoryVisibilityClicked() fun onRoomAliasesClicked() + fun onRoomPermissionsClicked() fun onJoinRuleClicked() } @@ -115,6 +116,16 @@ class RoomSettingsController @Inject constructor( 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( id = "historyReadability", title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title), diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 52e273f3d4..1ca539ea7e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -178,6 +178,10 @@ class RoomSettingsFragment @Inject constructor( roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAliasesSettings) } + override fun onRoomPermissionsClicked() { + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomPermissionsSettings) + } + override fun onJoinRuleClicked() = withState(viewModel) { state -> val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules val currentGuestAccess = state.newRoomJoinRules.newGuestAccess ?: state.currentGuestAccess diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 4504febd1a..8612bc43f1 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -610,6 +610,33 @@ 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. Only accept the certificate if the server administrator has published a fingerprint that matches the one above. + + Room permissions + View and update the roles required to change various parts of the room. + + "Permissions" + "Select the roles required to change various parts of the room" + "You don't have permission to update the roles required to change various parts of the room" + + Default role + Send messages + Invite users + Change settings + Kick users + Ban users + Remove messages sent by others + Notify everyone + Modify widgets + Change room avatar + Change main address for the room + Enable room encryption + Change history visibility + Change room name + Change permissions + Send m.room.server_acl events + Upgrade the room + Change topic + Room Details People