PowerLevel : clean after Benoit's review

This commit is contained in:
ganfra 2020-06-10 17:19:33 +02:00
parent bf5ad2cf18
commit 60b91d4d50
15 changed files with 93 additions and 40 deletions

View file

@ -43,20 +43,19 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
* @return the power level * @return the power level
*/ */
fun getUserRole(userId: String): Role { fun getUserRole(userId: String): Role {
return getUserPowerLevelValue(userId).let { val value = getUserPowerLevelValue(userId)
Role.fromValue(it, powerLevelsContent.eventsDefault) return Role.fromValue(value, powerLevelsContent.eventsDefault)
}
} }
/** /**
* Tell if an user can send an event of a certain type * Tell if an user can send an event of a certain type
* *
* @param userId the id of the user to check for.
* @param isState true if the event is a state event (ie. state key is not null) * @param isState true if the event is a state event (ie. state key is not null)
* @param eventType the event type to check for * @param eventType the event type to check for
* @param userId the user id
* @return true if the user can send this type of event * @return true if the user can send this type of event
*/ */
fun isAllowedToSend(isState: Boolean, eventType: String?, userId: String): Boolean { fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean {
return if (userId.isNotEmpty()) { return if (userId.isNotEmpty()) {
val powerLevel = getUserPowerLevelValue(userId) val powerLevel = getUserPowerLevelValue(userId)
val minimumPowerLevel = powerLevelsContent.events[eventType] val minimumPowerLevel = powerLevelsContent.events[eventType]
@ -69,22 +68,42 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
} else false } else false
} }
fun canInvite(userId: String): Boolean { /**
* Check if the user have the necessary power level to invite
* @param userId the id of the user to check for.
* @return true if able to invite
*/
fun isUserAbleToInvite(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId) val powerLevel = getUserPowerLevelValue(userId)
return powerLevel >= powerLevelsContent.invite return powerLevel >= powerLevelsContent.invite
} }
fun canBan(userId: String): Boolean { /**
* Check if the user have the necessary power level to ban
* @param userId the id of the user to check for.
* @return true if able to ban
*/
fun isUserAbleToBan(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId) val powerLevel = getUserPowerLevelValue(userId)
return powerLevel >= powerLevelsContent.ban return powerLevel >= powerLevelsContent.ban
} }
fun canKick(userId: String): Boolean { /**
* Check if the user have the necessary power level to kick
* @param userId the id of the user to check for.
* @return true if able to kick
*/
fun isUserAbleToKick(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId) val powerLevel = getUserPowerLevelValue(userId)
return powerLevel >= powerLevelsContent.kick return powerLevel >= powerLevelsContent.kick
} }
fun canRedact(userId: String): Boolean { /**
* Check if the user have the necessary power level to redact
* @param userId the id of the user to check for.
* @return true if able to redact
*/
fun isUserAbleToRedact(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId) val powerLevel = getUserPowerLevelValue(userId)
return powerLevel >= powerLevelsContent.redact return powerLevel >= powerLevelsContent.redact
} }

View file

@ -34,10 +34,11 @@ sealed class Role(open val value: Int, @StringRes val res: Int) : Comparable<Rol
fun fromValue(value: Int, default: Int): Role { fun fromValue(value: Int, default: Int): Role {
return when (value) { return when (value) {
default, Default.value -> Default default,
Moderator.value -> Moderator Default.value -> Default
Admin.value -> Admin Moderator.value -> Moderator
else -> Custom(value) Admin.value -> Admin
else -> Custom(value)
} }
} }
} }

View file

@ -251,7 +251,7 @@ internal interface RoomAPI {
* @param userIdAndReason the banned user object (userId and reason for ban) * @param userIdAndReason the banned user object (userId and reason for ban)
*/ */
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban")
fun ban(@Path("roomId") roomId: String?, @Body userIdAndReason: UserIdAndReason): Call<Unit> fun ban(@Path("roomId") roomId: String, @Body userIdAndReason: UserIdAndReason): Call<Unit>
/** /**
* unban a user from the given room. * unban a user from the given room.
@ -260,7 +260,7 @@ internal interface RoomAPI {
* @param userIdAndReason the unbanned user object (userId and reason for unban) * @param userIdAndReason the unbanned user object (userId and reason for unban)
*/ */
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban")
fun unban(@Path("roomId") roomId: String?, @Body userIdAndReason: UserIdAndReason): Call<Unit> fun unban(@Path("roomId") roomId: String, @Body userIdAndReason: UserIdAndReason): Call<Unit>
/** /**
* Kick a user from the given room. * Kick a user from the given room.
@ -269,7 +269,7 @@ internal interface RoomAPI {
* @param userIdAndReason the kicked user object (userId and reason for kicking) * @param userIdAndReason the kicked user object (userId and reason for kicking)
*/ */
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick")
fun kick(@Path("roomId") roomId: String?, @Body userIdAndReason: UserIdAndReason): Call<Unit> fun kick(@Path("roomId") roomId: String, @Body userIdAndReason: UserIdAndReason): Call<Unit>
/** /**
* Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room. * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room.

View file

@ -198,6 +198,6 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
stateKey = QueryStringValue.NoCondition stateKey = QueryStringValue.NoCondition
) )
val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: return false val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: return false
return PowerLevelsHelper(powerLevelsContent).isAllowedToSend(true, null, userId) return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, null)
} }
} }

View file

@ -95,8 +95,11 @@
<string name="power_level_custom">Custom (%1$d)</string> <string name="power_level_custom">Custom (%1$d)</string>
<string name="power_level_custom_no_value">Custom</string> <string name="power_level_custom_no_value">Custom</string>
<!-- parameter will be a comma separated list of values of notice_power_level_diff -->
<string name="notice_power_level_changed_by_you">You changed the power level of %1$s.</string> <string name="notice_power_level_changed_by_you">You changed the power level of %1$s.</string>
<!-- parameter will be a comma separated list of values of notice_power_level_diff -->
<string name="notice_power_level_changed">%1$s changed the power level of %2$s.</string> <string name="notice_power_level_changed">%1$s changed the power level of %2$s.</string>
<!-- First parameter will be a userId or display name, the two last ones will be value of power_level_* -->
<string name="notice_power_level_diff">%1$s from %2$s to %3$s</string> <string name="notice_power_level_diff">%1$s from %2$s to %3$s</string>
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string> <string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>

View file

@ -18,11 +18,15 @@ package im.vector.riotx.core.ui.views
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.text.method.LinkMovementMethod
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.error.ResourceLimitErrorFormatter
import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils
import kotlinx.android.synthetic.main.view_notification_area.view.* import kotlinx.android.synthetic.main.view_notification_area.view.*
@ -61,10 +65,11 @@ class NotificationAreaView @JvmOverloads constructor(
cleanUp() cleanUp()
state = newState state = newState
when (newState) { when (newState) {
is State.Default -> renderDefault() is State.Default -> renderDefault()
is State.Hidden -> renderHidden() is State.Hidden -> renderHidden()
is State.NoPermissionToPost -> renderNoPermissionToPost() is State.NoPermissionToPost -> renderNoPermissionToPost()
is State.Tombstone -> renderTombstone(newState) is State.Tombstone -> renderTombstone(newState)
is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState)
} }
} }
@ -93,6 +98,26 @@ class NotificationAreaView @JvmOverloads constructor(
roomNotificationMessage.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary)) roomNotificationMessage.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_secondary))
} }
private fun renderResourceLimitExceededError(state: State.ResourceLimitExceededError) {
visibility = View.VISIBLE
val resourceLimitErrorFormatter = ResourceLimitErrorFormatter(context)
val formatterMode: ResourceLimitErrorFormatter.Mode
val backgroundColor: Int
if (state.isSoft) {
backgroundColor = R.color.soft_resource_limit_exceeded
formatterMode = ResourceLimitErrorFormatter.Mode.Soft
} else {
backgroundColor = R.color.hard_resource_limit_exceeded
formatterMode = ResourceLimitErrorFormatter.Mode.Hard
}
val message = resourceLimitErrorFormatter.format(state.matrixError, formatterMode, clickable = true)
roomNotificationMessage.setTextColor(Color.WHITE)
roomNotificationMessage.text = message
roomNotificationMessage.movementMethod = LinkMovementMethod.getInstance()
roomNotificationMessage.setLinkTextColor(Color.WHITE)
setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
}
private fun renderTombstone(state: State.Tombstone) { private fun renderTombstone(state: State.Tombstone) {
visibility = View.VISIBLE visibility = View.VISIBLE
roomNotificationIcon.setImageResource(R.drawable.error) roomNotificationIcon.setImageResource(R.drawable.error)
@ -129,13 +154,16 @@ class NotificationAreaView @JvmOverloads constructor(
object Default : State() object Default : State()
// User can't post messages to room because his power level doesn't allow it. // User can't post messages to room because his power level doesn't allow it.
object NoPermissionToPost: State() object NoPermissionToPost : State()
// View will be Gone // View will be Gone
object Hidden : State() object Hidden : State()
// The room is dead // The room is dead
data class Tombstone(val tombstoneEvent: Event) : State() data class Tombstone(val tombstoneEvent: Event) : State()
// Resource limit exceeded error will be displayed (only hard for the moment)
data class ResourceLimitExceededError(val isSoft: Boolean, val matrixError: MatrixError) : State()
} }
/** /**

View file

@ -72,8 +72,8 @@ import im.vector.riotx.features.crypto.verification.SupportedVerificationMethods
import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator
import im.vector.riotx.features.home.room.detail.sticker.StickerPickerActionHandler import im.vector.riotx.features.home.room.detail.sticker.StickerPickerActionHandler
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory
import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.home.room.typing.TypingHelper
import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
@ -176,11 +176,12 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun observePowerLevel() { private fun observePowerLevel() {
PowerLevelsObservableFactory(room).createObservable() PowerLevelsObservableFactory(room).createObservable()
.subscribe { .subscribe {
val canSendMessage = PowerLevelsHelper(it).isAllowedToSend(false, EventType.MESSAGE, session.myUserId) val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE)
setState { setState {
copy(canSendMessage = canSendMessage) copy(canSendMessage = canSendMessage)
} }
}.disposeOnClear() }
.disposeOnClear()
} }
private fun observeActiveRoomWidgets() { private fun observeActiveRoomWidgets() {

View file

@ -109,9 +109,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
PowerLevelsObservableFactory(room).createObservable() PowerLevelsObservableFactory(room).createObservable()
.subscribe { .subscribe {
val powerLevelsHelper = PowerLevelsHelper(it) val powerLevelsHelper = PowerLevelsHelper(it)
val canReact = powerLevelsHelper.isAllowedToSend(false, EventType.REACTION, session.myUserId) val canReact = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.REACTION)
val canRedact = powerLevelsHelper.canRedact(session.myUserId) val canRedact = powerLevelsHelper.isUserAbleToRedact(session.myUserId)
val canSendMessage = powerLevelsHelper.isAllowedToSend(false, EventType.MESSAGE, session.myUserId) val canSendMessage = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE)
val permissions = ActionPermissions(canSendMessage = canSendMessage, canRedact = canRedact, canReact = canReact) val permissions = ActionPermissions(canSendMessage = canSendMessage, canRedact = canRedact, canReact = canReact)
setState { setState {
copy(actionPermissions = permissions) copy(actionPermissions = permissions)

View file

@ -22,7 +22,7 @@ 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 BanOrUnbanUser(val reason: String?) : RoomMemberProfileAction()
data class KickUser(val reason: String?) : RoomMemberProfileAction() data class KickUser(val reason: String?) : RoomMemberProfileAction()
object InviteUser : RoomMemberProfileAction() object InviteUser : RoomMemberProfileAction()
object VerifyUser : RoomMemberProfileAction() object VerifyUser : RoomMemberProfileAction()

View file

@ -325,7 +325,7 @@ class RoomMemberProfileFragment @Inject constructor(
reasonHintRes = R.string.room_participants_ban_reason, reasonHintRes = R.string.room_participants_ban_reason,
titleRes = titleRes titleRes = titleRes
) { reason -> ) { reason ->
viewModel.handle(RoomMemberProfileAction.BanUser(reason)) viewModel.handle(RoomMemberProfileAction.BanOrUnbanUser(reason))
} }
} }

View file

@ -144,7 +144,7 @@ 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.BanOrUnbanUser -> handleBanOrUnbanAction(action)
is RoomMemberProfileAction.KickUser -> handleKickAction(action) is RoomMemberProfileAction.KickUser -> handleKickAction(action)
RoomMemberProfileAction.InviteUser -> handleInviteAction() RoomMemberProfileAction.InviteUser -> handleInviteAction()
} }
@ -223,7 +223,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
} }
} }
private fun handleBanAction(action: RoomMemberProfileAction.BanUser) = withState { state -> private fun handleBanOrUnbanAction(action: RoomMemberProfileAction.BanOrUnbanUser) = withState { state ->
if (room == null) { if (room == null) {
return@withState return@withState
} }
@ -286,10 +286,10 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
powerLevelsContentLive.subscribe { powerLevelsContentLive.subscribe {
val powerLevelsHelper = PowerLevelsHelper(it) val powerLevelsHelper = PowerLevelsHelper(it)
val permissions = ActionPermissions( val permissions = ActionPermissions(
canKick = powerLevelsHelper.canKick(session.myUserId), canKick = powerLevelsHelper.isUserAbleToKick(session.myUserId),
canBan = powerLevelsHelper.canBan(session.myUserId), canBan = powerLevelsHelper.isUserAbleToBan(session.myUserId),
canInvite = powerLevelsHelper.canInvite(session.myUserId), canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId),
canEditPowerLevel = powerLevelsHelper.isAllowedToSend(true, EventType.STATE_ROOM_POWER_LEVELS, session.myUserId) canEditPowerLevel = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_POWER_LEVELS)
) )
setState { copy(powerLevelsContent = it, actionPermissions = permissions) } setState { copy(powerLevelsContent = it, actionPermissions = permissions) }
}.disposeOnClear() }.disposeOnClear()

View file

@ -73,7 +73,7 @@ object EditPowerLevelDialogs {
} }
fun showValidation(activity: Activity, onValidate: () -> Unit) { fun showValidation(activity: Activity, onValidate: () -> Unit) {
// ask to the user to confirmation thu upgrade. // Ask to the user the confirmation to upgrade.
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setMessage(R.string.room_participants_power_level_prompt) .setMessage(R.string.room_participants_power_level_prompt)
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
@ -84,6 +84,7 @@ object EditPowerLevelDialogs {
} }
fun showDemoteWarning(activity: Activity, onValidate: () -> Unit) { fun showDemoteWarning(activity: Activity, onValidate: () -> Unit) {
// Ask to the user the confirmation to downgrade his own role.
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setTitle(R.string.room_participants_power_level_demote_warning_title) .setTitle(R.string.room_participants_power_level_demote_warning_title)
.setMessage(R.string.room_participants_power_level_demote_warning_prompt) .setMessage(R.string.room_participants_power_level_demote_warning_prompt)

View file

@ -124,7 +124,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
PowerLevelsObservableFactory(room).createObservable() PowerLevelsObservableFactory(room).createObservable()
.subscribe { .subscribe {
val permissions = ActionPermissions( val permissions = ActionPermissions(
canInvite = PowerLevelsHelper(it).canInvite(session.myUserId) canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId)
) )
setState { setState {
copy(actionsPermissions = permissions) copy(actionsPermissions = permissions)

View file

@ -154,7 +154,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
val canSend = if (powerLevelsContent == null) { val canSend = if (powerLevelsContent == null) {
false false
} else { } else {
PowerLevelsHelper(powerLevelsContent).isAllowedToSend(isState, eventType, session.myUserId) PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(session.myUserId, isState, eventType)
} }
if (canSend) { if (canSend) {
Timber.d("## canSendEvent() returns true") Timber.d("## canSendEvent() returns true")

View file

@ -112,7 +112,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
.mapOptional { it.content.toModel<PowerLevelsContent>() } .mapOptional { it.content.toModel<PowerLevelsContent>() }
.unwrap() .unwrap()
.map { .map {
PowerLevelsHelper(it).isAllowedToSend(true, null, session.myUserId) PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, null)
}.subscribe { }.subscribe {
setState { copy(canManageWidgets = it) } setState { copy(canManageWidgets = it) }
}.disposeOnClear() }.disposeOnClear()