From a1fd35aa67ef098d17a31733477f1acc0ce35300 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 5 Jun 2020 11:13:00 +0200 Subject: [PATCH] Room member: continue to branch admin/moderator actions --- .../RoomMemberProfileAction.kt | 3 + .../RoomMemberProfileController.kt | 59 ++++++++++++++- .../RoomMemberProfileFragment.kt | 68 +++++++++++++++-- .../RoomMemberProfileViewEvents.kt | 3 + .../RoomMemberProfileViewModel.kt | 74 ++++++++++++++++++- .../RoomMemberProfileViewState.kt | 5 +- .../devices/VectorSettingsDevicesFragment.kt | 2 +- .../main/res/layout/dialog_base_edit_text.xml | 2 +- vector/src/main/res/values/strings.xml | 6 ++ 9 files changed, 205 insertions(+), 17 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt index 9f775d3fb9..28c6932d3d 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt @@ -22,6 +22,9 @@ import im.vector.riotx.core.platform.VectorViewModelAction sealed class RoomMemberProfileAction : VectorViewModelAction { object RetryFetchingInfo : RoomMemberProfileAction() object IgnoreUser : RoomMemberProfileAction() + data class BanUser(val reason: String?) : RoomMemberProfileAction() + data class KickUser(val reason: String?) : RoomMemberProfileAction() + object InviteUser : RoomMemberProfileAction() object VerifyUser : RoomMemberProfileAction() object ShareRoomMemberProfile : RoomMemberProfileAction() data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction() diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt index efa2d15f09..6c84543921 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt @@ -19,8 +19,9 @@ package im.vector.riotx.features.roommemberprofile import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.room.powerlevels.Role +import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper +import im.vector.matrix.android.api.session.room.powerlevels.Role import im.vector.riotx.R import im.vector.riotx.core.epoxy.profiles.buildProfileAction import im.vector.riotx.core.epoxy.profiles.buildProfileSection @@ -47,6 +48,10 @@ class RoomMemberProfileController @Inject constructor( fun onJumpToReadReceiptClicked() fun onMentionClicked() fun onSetPowerLevel(userRole: Role) + fun onKickClicked() + fun onBanClicked(isUserBanned: Boolean) + fun onCancelInviteClicked() + fun onInviteClicked() } override fun buildModels(data: RoomMemberProfileViewState?) { @@ -87,11 +92,11 @@ class RoomMemberProfileController @Inject constructor( val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) val userPowerLevel = powerLevelsHelper.getUserRole(state.userId) val myPowerLevel = powerLevelsHelper.getUserRole(session.myUserId) - if ((!state.isMine && myPowerLevel <= userPowerLevel) - || myPowerLevel != Role.Admin) { + if (myPowerLevel < Role.Moderator || (!state.isMine && myPowerLevel <= userPowerLevel)) { return } - buildProfileSection("Admin Actions") + val membership = state.asyncMembership() ?: return + buildProfileSection(stringProvider.getString(R.string.room_profile_section_admin)) buildProfileAction( id = "set_power_level", editable = false, @@ -99,11 +104,46 @@ class RoomMemberProfileController @Inject constructor( dividerColor = dividerColor, action = { callback?.onSetPowerLevel(userPowerLevel) } ) + + if (membership == Membership.JOIN) { + buildProfileAction( + id = "kick", + editable = false, + destructive = true, + title = stringProvider.getString(R.string.room_participants_action_kick), + dividerColor = dividerColor, + action = { callback?.onKickClicked() } + ) + } else if (membership == Membership.INVITE) { + buildProfileAction( + id = "cancel_invite", + title = stringProvider.getString(R.string.room_participants_action_cancel_invite), + dividerColor = dividerColor, + destructive = true, + editable = false, + action = { callback?.onCancelInviteClicked() } + ) + } + val banActionTitle = if (membership == Membership.BAN) { + stringProvider.getString(R.string.room_participants_action_unban) + } else { + stringProvider.getString(R.string.room_participants_action_ban) + } + buildProfileAction( + id = "ban", + editable = false, + destructive = true, + title = banActionTitle, + dividerColor = dividerColor, + action = { callback?.onBanClicked(membership == Membership.BAN) } + ) } private fun buildMoreSection(state: RoomMemberProfileViewState) { // More if (!state.isMine) { + val membership = state.asyncMembership() ?: return + buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileAction( id = "read_receipt", @@ -123,6 +163,17 @@ class RoomMemberProfileController @Inject constructor( divider = ignoreActionTitle != null, action = { callback?.onMentionClicked() } ) + if (membership == Membership.LEAVE || membership == Membership.KNOCK) { + buildProfileAction( + id = "invite", + title = stringProvider.getString(R.string.room_participants_action_invite), + dividerColor = dividerColor, + destructive = false, + editable = false, + divider = ignoreActionTitle != null, + action = { callback?.onInviteClicked() } + ) + } if (ignoreActionTitle != null) { buildProfileAction( id = "ignore", diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt index 94ad93780f..26874b94f1 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.os.Parcelable import android.view.MenuItem import android.view.View +import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.airbnb.mvrx.Fail @@ -96,19 +97,22 @@ class RoomMemberProfileFragment @Inject constructor( matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) viewModel.observeViewEvents { when (it) { - is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) - is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) - is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit - is RoomMemberProfileViewEvents.StartVerification -> handleStartVerification(it) - is RoomMemberProfileViewEvents.ShareRoomMemberProfile -> handleShareRoomMemberProfile(it.permalink) - is RoomMemberProfileViewEvents.OnSetPowerLevelSuccess -> Unit - is RoomMemberProfileViewEvents.ShowPowerLevelValidation -> handleShowPowerLevelAdminWarning(it) + is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) + is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) + is RoomMemberProfileViewEvents.StartVerification -> handleStartVerification(it) + is RoomMemberProfileViewEvents.ShareRoomMemberProfile -> handleShareRoomMemberProfile(it.permalink) + is RoomMemberProfileViewEvents.ShowPowerLevelValidation -> handleShowPowerLevelAdminWarning(it) + is RoomMemberProfileViewEvents.OnKickActionSuccess -> Unit + is RoomMemberProfileViewEvents.OnSetPowerLevelSuccess -> Unit + is RoomMemberProfileViewEvents.OnBanActionSuccess -> Unit + is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit + is RoomMemberProfileViewEvents.OnInviteActionSuccess -> Unit }.exhaustive } } private fun handleShowPowerLevelAdminWarning(event: RoomMemberProfileViewEvents.ShowPowerLevelValidation) { - SetPowerLevelDialogs.showValidation(requireActivity()){ + SetPowerLevelDialogs.showValidation(requireActivity()) { viewModel.handle(RoomMemberProfileAction.SetPowerLevel(event.currentValue, event.newValue, false)) } } @@ -254,4 +258,52 @@ class RoomMemberProfileFragment @Inject constructor( viewModel.handle(RoomMemberProfileAction.SetPowerLevel(userRole.value, newPowerLevel, true)) } } + + override fun onKickClicked() { + val layout = layoutInflater.inflate(R.layout.dialog_base_edit_text, null) + val input = layout.findViewById(R.id.editText) + input.setHint(R.string.reason_hint) + AlertDialog.Builder(requireActivity()) + .setTitle(resources.getQuantityString(R.plurals.room_participants_kick_prompt_msg, 1)) + .setView(layout) + .setPositiveButton(R.string.room_participants_action_kick) { _, _ -> + viewModel.handle(RoomMemberProfileAction.KickUser(input.text.toString())) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + override fun onBanClicked(isUserBanned: Boolean) { + val layout = layoutInflater.inflate(R.layout.dialog_base_edit_text, null) + val input = layout.findViewById(R.id.editText) + input.setHint(R.string.reason_hint) + val (titleRes, positiveButtonRes) = if (isUserBanned) { + input.isVisible = false + Pair(R.string.room_participants_unban_prompt_msg, R.string.room_participants_action_unban) + } else { + Pair(R.string.room_participants_ban_prompt_msg, R.string.room_participants_action_ban) + } + AlertDialog.Builder(requireActivity()) + .setTitle(titleRes) + .setView(layout) + .setPositiveButton(positiveButtonRes) { _, _ -> + viewModel.handle(RoomMemberProfileAction.BanUser(input.text.toString())) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + override fun onCancelInviteClicked() { + AlertDialog.Builder(requireActivity()) + .setTitle(resources.getString(R.string.room_participants_action_cancel_invite_prompt_msg)) + .setPositiveButton(R.string.room_participants_action_cancel_invite) { _, _ -> + viewModel.handle(RoomMemberProfileAction.KickUser(null)) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + override fun onInviteClicked() { + viewModel.handle(RoomMemberProfileAction.InviteUser) + } } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt index dc0e622b7a..9aae5f7979 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -27,6 +27,9 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents { object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() object OnSetPowerLevelSuccess : RoomMemberProfileViewEvents() + object OnInviteActionSuccess : RoomMemberProfileViewEvents() + object OnKickActionSuccess : RoomMemberProfileViewEvents() + object OnBanActionSuccess : RoomMemberProfileViewEvents() data class ShowPowerLevelValidation(val currentValue: Int, val newValue: Int) : RoomMemberProfileViewEvents() data class StartVerification( diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt index 837836660a..d1409c2ddb 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -18,7 +18,9 @@ package im.vector.riotx.features.roommemberprofile import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -35,6 +37,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.profile.ProfileService import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.members.roomMemberQueryParams +import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper @@ -142,6 +145,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v is RoomMemberProfileAction.VerifyUser -> prepareVerification() is RoomMemberProfileAction.ShareRoomMemberProfile -> handleShareRoomMemberProfile() is RoomMemberProfileAction.SetPowerLevel -> handleSetPowerLevel(action) + is RoomMemberProfileAction.BanUser -> handleBanAction(action) + is RoomMemberProfileAction.KickUser -> handleKickAction(action) + RoomMemberProfileAction.InviteUser -> handleInviteAction() } } @@ -182,15 +188,79 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } + private fun handleInviteAction() { + if (room == null) { + return + } + viewModelScope.launch { + try { + _viewEvents.post(RoomMemberProfileViewEvents.Loading()) + awaitCallback { + room.invite(initialState.userId, callback = it) + } + _viewEvents.post(RoomMemberProfileViewEvents.OnInviteActionSuccess) + } catch (failure: Throwable) { + _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) + } + } + } + + private fun handleKickAction(action: RoomMemberProfileAction.KickUser) { + if (room == null) { + return + } + viewModelScope.launch { + try { + _viewEvents.post(RoomMemberProfileViewEvents.Loading()) + awaitCallback { + room.kick(initialState.userId, action.reason, it) + } + _viewEvents.post(RoomMemberProfileViewEvents.OnKickActionSuccess) + } catch (failure: Throwable) { + _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) + } + } + } + + private fun handleBanAction(action: RoomMemberProfileAction.BanUser) = withState { state -> + if (room == null) { + return@withState + } + val membership = state.asyncMembership() ?: return@withState + viewModelScope.launch { + try { + _viewEvents.post(RoomMemberProfileViewEvents.Loading()) + awaitCallback { + if (membership == Membership.BAN) { + room.unban(initialState.userId, action.reason, it) + } else { + room.ban(initialState.userId, action.reason, it) + } + } + _viewEvents.post(RoomMemberProfileViewEvents.OnBanActionSuccess) + } catch (failure: Throwable) { + _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) + } + } + } + private fun observeRoomMemberSummary(room: Room) { val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) } room.rx().liveRoomMembers(queryParams) - .map { it.firstOrNull()?.toMatrixItem().toOptional() } + .map { it.firstOrNull().toOptional() } .unwrap() .execute { - copy(userMatrixItem = it) + when (it) { + is Loading -> copy(userMatrixItem = Loading(), asyncMembership = Loading()) + is Success -> copy( + userMatrixItem = Success(it().toMatrixItem()), + asyncMembership = Success(it().membership) + ) + is Fail -> copy(userMatrixItem = Fail(it.error), asyncMembership = Fail(it.error)) + is Uninitialized -> this + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt index a5c140d0ab..feac734521 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt @@ -21,8 +21,10 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo +import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.util.MatrixItem +import java.lang.reflect.Member data class RoomMemberProfileViewState( val userId: String, @@ -36,7 +38,8 @@ data class RoomMemberProfileViewState( val userMatrixItem: Async = Uninitialized, val userMXCrossSigningInfo: MXCrossSigningInfo? = null, val allDevicesAreTrusted: Boolean = false, - val allDevicesAreCrossSignedTrusted: Boolean = false + val allDevicesAreCrossSignedTrusted: Boolean = false, + val asyncMembership: Async = Uninitialized ) : MvRxState { constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt index fa8ee16931..46ab694248 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -134,7 +134,7 @@ class VectorSettingsDevicesFragment @Inject constructor( val inflater = requireActivity().layoutInflater val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) - val input = layout.findViewById(R.id.edit_text) + val input = layout.findViewById(R.id.editText) input.setText(deviceInfo.displayName) AlertDialog.Builder(requireActivity()) diff --git a/vector/src/main/res/layout/dialog_base_edit_text.xml b/vector/src/main/res/layout/dialog_base_edit_text.xml index 45c55f4451..98dc398042 100644 --- a/vector/src/main/res/layout/dialog_base_edit_text.xml +++ b/vector/src/main/res/layout/dialog_base_edit_text.xml @@ -11,7 +11,7 @@ android:paddingRight="?dialogPreferredPadding"> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 144797cce9..22855af213 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -465,6 +465,7 @@ SESSIONS Invite + Cancel invite Leave this room Remove from this room Ban @@ -481,11 +482,15 @@ Show Session List You will not be able to undo this change as you are promoting the user to have the same power level as yourself.\nAre you sure? + Are you sure you want to cancel the invite for this user? + Are you sure that you want to kick this user from this chat? Are you sure that you want to kick these users from this chat? Are you sure that you want to ban this user from this chat? + Are you sure that you want to unban this user from this chat? + Reason