Room member: continue to branch admin/moderator actions

This commit is contained in:
ganfra 2020-06-05 11:13:00 +02:00
parent ad8ed37ff6
commit a1fd35aa67
9 changed files with 205 additions and 17 deletions

View File

@ -22,6 +22,9 @@ import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomMemberProfileAction : VectorViewModelAction { sealed class RoomMemberProfileAction : VectorViewModelAction {
object RetryFetchingInfo : RoomMemberProfileAction() object RetryFetchingInfo : RoomMemberProfileAction()
object IgnoreUser : 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 VerifyUser : RoomMemberProfileAction()
object ShareRoomMemberProfile : RoomMemberProfileAction() object ShareRoomMemberProfile : RoomMemberProfileAction()
data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction() data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction()

View File

@ -19,8 +19,9 @@ package im.vector.riotx.features.roommemberprofile
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.Session 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.PowerLevelsHelper
import im.vector.matrix.android.api.session.room.powerlevels.Role
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.profiles.buildProfileAction import im.vector.riotx.core.epoxy.profiles.buildProfileAction
import im.vector.riotx.core.epoxy.profiles.buildProfileSection import im.vector.riotx.core.epoxy.profiles.buildProfileSection
@ -47,6 +48,10 @@ class RoomMemberProfileController @Inject constructor(
fun onJumpToReadReceiptClicked() fun onJumpToReadReceiptClicked()
fun onMentionClicked() fun onMentionClicked()
fun onSetPowerLevel(userRole: Role) fun onSetPowerLevel(userRole: Role)
fun onKickClicked()
fun onBanClicked(isUserBanned: Boolean)
fun onCancelInviteClicked()
fun onInviteClicked()
} }
override fun buildModels(data: RoomMemberProfileViewState?) { override fun buildModels(data: RoomMemberProfileViewState?) {
@ -87,11 +92,11 @@ class RoomMemberProfileController @Inject constructor(
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
val userPowerLevel = powerLevelsHelper.getUserRole(state.userId) val userPowerLevel = powerLevelsHelper.getUserRole(state.userId)
val myPowerLevel = powerLevelsHelper.getUserRole(session.myUserId) val myPowerLevel = powerLevelsHelper.getUserRole(session.myUserId)
if ((!state.isMine && myPowerLevel <= userPowerLevel) if (myPowerLevel < Role.Moderator || (!state.isMine && myPowerLevel <= userPowerLevel)) {
|| myPowerLevel != Role.Admin) {
return return
} }
buildProfileSection("Admin Actions") val membership = state.asyncMembership() ?: return
buildProfileSection(stringProvider.getString(R.string.room_profile_section_admin))
buildProfileAction( buildProfileAction(
id = "set_power_level", id = "set_power_level",
editable = false, editable = false,
@ -99,11 +104,46 @@ class RoomMemberProfileController @Inject constructor(
dividerColor = dividerColor, dividerColor = dividerColor,
action = { callback?.onSetPowerLevel(userPowerLevel) } 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) { private fun buildMoreSection(state: RoomMemberProfileViewState) {
// More // More
if (!state.isMine) { if (!state.isMine) {
val membership = state.asyncMembership() ?: return
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction( buildProfileAction(
id = "read_receipt", id = "read_receipt",
@ -123,6 +163,17 @@ class RoomMemberProfileController @Inject constructor(
divider = ignoreActionTitle != null, divider = ignoreActionTitle != null,
action = { callback?.onMentionClicked() } 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) { if (ignoreActionTitle != null) {
buildProfileAction( buildProfileAction(
id = "ignore", id = "ignore",

View File

@ -21,6 +21,7 @@ import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
@ -98,11 +99,14 @@ class RoomMemberProfileFragment @Inject constructor(
when (it) { when (it) {
is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) is RoomMemberProfileViewEvents.Loading -> showLoading(it.message)
is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable)
is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit
is RoomMemberProfileViewEvents.StartVerification -> handleStartVerification(it) is RoomMemberProfileViewEvents.StartVerification -> handleStartVerification(it)
is RoomMemberProfileViewEvents.ShareRoomMemberProfile -> handleShareRoomMemberProfile(it.permalink) is RoomMemberProfileViewEvents.ShareRoomMemberProfile -> handleShareRoomMemberProfile(it.permalink)
is RoomMemberProfileViewEvents.OnSetPowerLevelSuccess -> Unit
is RoomMemberProfileViewEvents.ShowPowerLevelValidation -> handleShowPowerLevelAdminWarning(it) 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 }.exhaustive
} }
} }
@ -254,4 +258,52 @@ class RoomMemberProfileFragment @Inject constructor(
viewModel.handle(RoomMemberProfileAction.SetPowerLevel(userRole.value, newPowerLevel, true)) 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<TextView>(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<TextView>(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)
}
} }

View File

@ -27,6 +27,9 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents {
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
object OnSetPowerLevelSuccess : 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 ShowPowerLevelValidation(val currentValue: Int, val newValue: Int) : RoomMemberProfileViewEvents()
data class StartVerification( data class StartVerification(

View File

@ -18,7 +18,9 @@
package im.vector.riotx.features.roommemberprofile package im.vector.riotx.features.roommemberprofile
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized 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.profile.ProfileService
import im.vector.matrix.android.api.session.room.Room 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.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.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper 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.VerifyUser -> prepareVerification()
is RoomMemberProfileAction.ShareRoomMemberProfile -> handleShareRoomMemberProfile() is RoomMemberProfileAction.ShareRoomMemberProfile -> handleShareRoomMemberProfile()
is RoomMemberProfileAction.SetPowerLevel -> handleSetPowerLevel(action) 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<Unit> {
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<Unit> {
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<Unit> {
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) { private fun observeRoomMemberSummary(room: Room) {
val queryParams = roomMemberQueryParams { val queryParams = roomMemberQueryParams {
this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE)
} }
room.rx().liveRoomMembers(queryParams) room.rx().liveRoomMembers(queryParams)
.map { it.firstOrNull()?.toMatrixItem().toOptional() } .map { it.firstOrNull().toOptional() }
.unwrap() .unwrap()
.execute { .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
}
} }
} }

View File

@ -21,8 +21,10 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo 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.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import java.lang.reflect.Member
data class RoomMemberProfileViewState( data class RoomMemberProfileViewState(
val userId: String, val userId: String,
@ -36,7 +38,8 @@ data class RoomMemberProfileViewState(
val userMatrixItem: Async<MatrixItem> = Uninitialized, val userMatrixItem: Async<MatrixItem> = Uninitialized,
val userMXCrossSigningInfo: MXCrossSigningInfo? = null, val userMXCrossSigningInfo: MXCrossSigningInfo? = null,
val allDevicesAreTrusted: Boolean = false, val allDevicesAreTrusted: Boolean = false,
val allDevicesAreCrossSignedTrusted: Boolean = false val allDevicesAreCrossSignedTrusted: Boolean = false,
val asyncMembership: Async<Membership> = Uninitialized
) : MvRxState { ) : MvRxState {
constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId) constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId)

View File

@ -134,7 +134,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
val inflater = requireActivity().layoutInflater val inflater = requireActivity().layoutInflater
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
val input = layout.findViewById<EditText>(R.id.edit_text) val input = layout.findViewById<EditText>(R.id.editText)
input.setText(deviceInfo.displayName) input.setText(deviceInfo.displayName)
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())

View File

@ -11,7 +11,7 @@
android:paddingRight="?dialogPreferredPadding"> android:paddingRight="?dialogPreferredPadding">
<EditText <EditText
android:id="@+id/edit_text" android:id="@+id/editText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="text"> android:inputType="text">

View File

@ -465,6 +465,7 @@
<string name="room_participants_header_devices">SESSIONS</string> <string name="room_participants_header_devices">SESSIONS</string>
<string name="room_participants_action_invite">Invite</string> <string name="room_participants_action_invite">Invite</string>
<string name="room_participants_action_cancel_invite">Cancel invite</string>
<string name="room_participants_action_leave">Leave this room</string> <string name="room_participants_action_leave">Leave this room</string>
<string name="room_participants_action_remove">Remove from this room</string> <string name="room_participants_action_remove">Remove from this room</string>
<string name="room_participants_action_ban">Ban</string> <string name="room_participants_action_ban">Ban</string>
@ -481,11 +482,15 @@
<string name="room_participants_action_devices_list">Show Session List</string> <string name="room_participants_action_devices_list">Show Session List</string>
<string name="room_participants_power_level_prompt">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?</string> <string name="room_participants_power_level_prompt">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?</string>
<string name="room_participants_action_cancel_invite_prompt_msg">Are you sure you want to cancel the invite for this user?</string>
<plurals name="room_participants_kick_prompt_msg"> <plurals name="room_participants_kick_prompt_msg">
<item quantity="one">Are you sure that you want to kick this user from this chat?</item> <item quantity="one">Are you sure that you want to kick this user from this chat?</item>
<item quantity="other">Are you sure that you want to kick these users from this chat?</item> <item quantity="other">Are you sure that you want to kick these users from this chat?</item>
</plurals> </plurals>
<string name="room_participants_ban_prompt_msg">Are you sure that you want to ban this user from this chat?</string> <string name="room_participants_ban_prompt_msg">Are you sure that you want to ban this user from this chat?</string>
<string name="room_participants_unban_prompt_msg">Are you sure that you want to unban this user from this chat?</string>
<string name="reason_hint">Reason</string> <string name="reason_hint">Reason</string>
<!-- <!--
@ -2068,6 +2073,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="room_profile_section_security">Security</string> <string name="room_profile_section_security">Security</string>
<string name="room_profile_section_security_learn_more">Learn more</string> <string name="room_profile_section_security_learn_more">Learn more</string>
<string name="room_profile_section_more">More</string> <string name="room_profile_section_more">More</string>
<string name="room_profile_section_admin">Admin Actions</string>
<string name="room_profile_section_more_settings">Room settings</string> <string name="room_profile_section_more_settings">Room settings</string>
<string name="room_profile_section_more_notifications">Notifications</string> <string name="room_profile_section_more_notifications">Notifications</string>
<plurals name="room_profile_section_more_member_list"> <plurals name="room_profile_section_more_member_list">