Power level: start to handle updating

This commit is contained in:
ganfra 2020-06-04 13:09:20 +02:00
parent 3dc2cd4d7a
commit 9075371145
9 changed files with 300 additions and 37 deletions

View File

@ -22,4 +22,6 @@ object PowerLevelsConstants {
const val DEFAULT_ROOM_ADMIN_LEVEL = 100
const val DEFAULT_ROOM_MODERATOR_LEVEL = 50
const val DEFAULT_ROOM_USER_LEVEL = 0
val DEFAULT_DEFINED_LEVELS = listOf(DEFAULT_ROOM_ADMIN_LEVEL, DEFAULT_ROOM_MODERATOR_LEVEL, DEFAULT_ROOM_USER_LEVEL)
}

View File

@ -24,4 +24,5 @@ sealed class RoomMemberProfileAction : VectorViewModelAction {
object IgnoreUser : RoomMemberProfileAction()
object VerifyUser : RoomMemberProfileAction()
object ShareRoomMemberProfile : RoomMemberProfileAction()
data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction()
}

View File

@ -18,6 +18,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.PowerLevelsConstants
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
@ -28,7 +31,8 @@ import javax.inject.Inject
class RoomMemberProfileController @Inject constructor(
private val stringProvider: StringProvider,
colorProvider: ColorProvider
colorProvider: ColorProvider,
private val session: Session
) : TypedEpoxyController<RoomMemberProfileViewState>() {
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
@ -42,6 +46,7 @@ class RoomMemberProfileController @Inject constructor(
fun onShowDeviceListNoCrossSigning()
fun onJumpToReadReceiptClicked()
fun onMentionClicked()
fun onSetPowerLevel(userPowerLevel: Int)
}
override fun buildModels(data: RoomMemberProfileViewState?) {
@ -71,6 +76,68 @@ class RoomMemberProfileController @Inject constructor(
}
private fun buildRoomMemberActions(state: RoomMemberProfileViewState) {
buildSecuritySection(state)
buildMoreSection(state)
buildAdminSection(state)
}
private fun buildAdminSection(state: RoomMemberProfileViewState) {
val powerLevelsContent = state.powerLevelsContent() ?: return
val powerLevelsStr = state.userPowerLevelString() ?: return
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
val userPowerLevel = powerLevelsHelper.getUserPowerLevel(state.userId)
val myPowerLevel = powerLevelsHelper.getUserPowerLevel(session.myUserId)
if ((!state.isMine && myPowerLevel <= userPowerLevel)
|| myPowerLevel != PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL) {
return
}
buildProfileSection("Admin Actions")
buildProfileAction(
id = "set_power_level",
editable = false,
title = powerLevelsStr,
dividerColor = dividerColor,
action = { callback?.onSetPowerLevel(userPowerLevel) }
)
}
private fun buildMoreSection(state: RoomMemberProfileViewState) {
// More
if (!state.isMine) {
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction(
id = "read_receipt",
editable = false,
title = stringProvider.getString(R.string.room_member_jump_to_read_receipt),
dividerColor = dividerColor,
action = { callback?.onJumpToReadReceiptClicked() }
)
val ignoreActionTitle = state.buildIgnoreActionTitle()
buildProfileAction(
id = "mention",
title = stringProvider.getString(R.string.room_participants_action_mention),
dividerColor = dividerColor,
editable = false,
divider = ignoreActionTitle != null,
action = { callback?.onMentionClicked() }
)
if (ignoreActionTitle != null) {
buildProfileAction(
id = "ignore",
title = ignoreActionTitle,
dividerColor = dividerColor,
destructive = true,
editable = false,
divider = false,
action = { callback?.onIgnoreClicked() }
)
}
}
}
private fun buildSecuritySection(state: RoomMemberProfileViewState) {
// Security
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
@ -148,40 +215,6 @@ class RoomMemberProfileController @Inject constructor(
centered(false)
}
}
// More
if (!state.isMine) {
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction(
id = "read_receipt",
editable = false,
title = stringProvider.getString(R.string.room_member_jump_to_read_receipt),
dividerColor = dividerColor,
action = { callback?.onJumpToReadReceiptClicked() }
)
val ignoreActionTitle = state.buildIgnoreActionTitle()
buildProfileAction(
id = "mention",
title = stringProvider.getString(R.string.room_participants_action_mention),
dividerColor = dividerColor,
editable = false,
divider = ignoreActionTitle != null,
action = { callback?.onMentionClicked() }
)
if (ignoreActionTitle != null) {
buildProfileAction(
id = "ignore",
title = ignoreActionTitle,
dividerColor = dividerColor,
destructive = true,
editable = false,
divider = false,
action = { callback?.onIgnoreClicked() }
)
}
}
}
private fun RoomMemberProfileViewState.buildIgnoreActionTitle(): String? {

View File

@ -43,6 +43,7 @@ import im.vector.riotx.core.utils.startSharePlainTextIntent
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.roommemberprofile.devices.DeviceListBottomSheet
import im.vector.riotx.features.roommemberprofile.powerlevel.SetPowerLevelDialogs
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_matrix_profile.*
import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.*
@ -94,15 +95,23 @@ class RoomMemberProfileFragment @Inject constructor(
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
viewModel.observeViewEvents {
when (it) {
is RoomMemberProfileViewEvents.Loading -> showLoading(it.message)
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)
}.exhaustive
}
}
private fun handleShowPowerLevelAdminWarning(event: RoomMemberProfileViewEvents.ShowPowerLevelValidation) {
SetPowerLevelDialogs.showValidation(requireActivity()){
viewModel.handle(RoomMemberProfileAction.SetPowerLevel(event.currentValue, event.newValue, false))
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.roomMemberProfileShareAction -> {
@ -238,4 +247,10 @@ class RoomMemberProfileFragment @Inject constructor(
private fun onAvatarClicked(view: View, userMatrixItem: MatrixItem) {
navigator.openBigImageViewer(requireActivity(), view, userMatrixItem)
}
override fun onSetPowerLevel(userPowerLevel: Int) {
SetPowerLevelDialogs.showChoice(requireActivity(), userPowerLevel) { newPowerLevel ->
viewModel.handle(RoomMemberProfileAction.SetPowerLevel(userPowerLevel, newPowerLevel, true))
}
}
}

View File

@ -26,6 +26,8 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents()
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
object OnSetPowerLevelSuccess : RoomMemberProfileViewEvents()
data class ShowPowerLevelValidation(val currentValue: Int, val newValue: Int) : RoomMemberProfileViewEvents()
data class StartVerification(
val userId: String,

View File

@ -30,6 +30,7 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory
import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent
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
@ -41,6 +42,7 @@ import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.matrix.rx.mapOptional
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
@ -140,6 +142,31 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction()
is RoomMemberProfileAction.VerifyUser -> prepareVerification()
is RoomMemberProfileAction.ShareRoomMemberProfile -> handleShareRoomMemberProfile()
is RoomMemberProfileAction.SetPowerLevel -> handleSetPowerLevel(action)
}
}
private fun handleSetPowerLevel(action: RoomMemberProfileAction.SetPowerLevel) = withState { state ->
if (room == null || action.previousValue == action.newValue) {
return@withState
}
val currentPowerLevelsContent = state.powerLevelsContent() ?: return@withState
val myPowerLevel = PowerLevelsHelper(currentPowerLevelsContent).getUserPowerLevel(session.myUserId)
if (action.askForValidation && action.newValue >= myPowerLevel) {
_viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelValidation(action.previousValue, action.newValue))
} else {
currentPowerLevelsContent.users[state.userId] = action.newValue
viewModelScope.launch {
_viewEvents.post(RoomMemberProfileViewEvents.Loading())
try {
awaitCallback<Unit> {
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent(), it)
}
_viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess)
} catch (failure: Throwable) {
_viewEvents.post(RoomMemberProfileViewEvents.Failure(failure))
}
}
}
}
@ -208,7 +235,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
} else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL) {
stringProvider.getString(R.string.room_member_power_level_moderator_in, roomName)
} else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL) {
""
stringProvider.getString(R.string.room_member_power_level_user_in, roomName)
} else {
stringProvider.getString(R.string.room_member_power_level_custom_in, userPowerLevel, roomName)
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2020 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.riotx.features.roommemberprofile.powerlevel
import android.app.Activity
import android.content.DialogInterface
import android.view.KeyEvent
import android.widget.SeekBar
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsConstants
import im.vector.riotx.R
import im.vector.riotx.core.extensions.hideKeyboard
import kotlinx.android.synthetic.main.dialog_set_power_level.view.*
object SetPowerLevelDialogs {
fun showChoice(activity: Activity, currentValue: Int, listener: (Int) -> Unit) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_set_power_level, null)
dialogLayout.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId ->
dialogLayout.powerLevelCustomLayout.isVisible = checkedId == R.id.powerLevelCustomRadio
}
dialogLayout.powerLevelCustomSlider.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
dialogLayout.powerLevelCustomValue.text = progress.toString()
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
//NOOP
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
//NOOP
}
})
dialogLayout.powerLevelCustomSlider.progress = currentValue
when (currentValue) {
PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL -> dialogLayout.powerLevelAdminRadio.isChecked = true
PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL -> dialogLayout.powerLevelModeratorRadio.isChecked = true
PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL -> dialogLayout.powerLevelDefaultRadio.isChecked = true
else -> dialogLayout.powerLevelCustomRadio.isChecked = true
}
AlertDialog.Builder(activity)
.setTitle("Change power level")
.setView(dialogLayout)
.setPositiveButton(R.string.action_change)
{ _, _ ->
val newValue = when (dialogLayout.powerLevelRadioGroup.checkedRadioButtonId) {
R.id.powerLevelAdminRadio -> PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL
R.id.powerLevelModeratorRadio -> PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL
R.id.powerLevelDefaultRadio -> PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL
else -> dialogLayout.powerLevelCustomSlider.progress
}
listener(newValue)
}
.setNegativeButton(R.string.cancel, null)
.setOnKeyListener(DialogInterface.OnKeyListener
{ dialog, keyCode, event ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
dialog.cancel()
return@OnKeyListener true
}
false
})
.setOnDismissListener {
dialogLayout.hideKeyboard()
}
.create()
.show()
}
fun showValidation(activity: Activity, onValidate: () -> Unit) {
// ask to the user to confirmation thu upgrade.
AlertDialog.Builder(activity)
.setMessage(R.string.room_participants_power_level_prompt)
.setPositiveButton(R.string.yes) { _, _ ->
onValidate()
}
.setNegativeButton(R.string.no, null)
.show()
}
}

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?dialogPreferredPadding"
android:paddingLeft="?dialogPreferredPadding"
android:paddingTop="12dp"
android:paddingEnd="?dialogPreferredPadding"
android:paddingRight="?dialogPreferredPadding">
<RadioGroup
android:id="@+id/powerLevelRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/powerLevelAdminRadio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Admin"
android:textColor="?riotx_text_primary" />
<RadioButton
android:id="@+id/powerLevelModeratorRadio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Moderator"
android:textColor="?riotx_text_primary" />
<RadioButton
android:id="@+id/powerLevelDefaultRadio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Default"
android:textColor="?riotx_text_primary" />
<RadioButton
android:id="@+id/powerLevelCustomRadio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Custom"
android:textColor="?riotx_text_primary" />
</RadioGroup>
<RelativeLayout
android:id="@+id/powerLevelCustomLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/powerLevelCustomTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:text="Custom power level"
android:layout_marginStart="16dp"
android:textColor="?riotx_text_primary"
android:textStyle="bold" />
<TextView
android:id="@+id/powerLevelCustomValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginEnd="16dp"
android:text="20"
android:textColor="?riotx_text_secondary" />
<SeekBar
android:id="@+id/powerLevelCustomSlider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_below="@id/powerLevelCustomTitle"
android:max="100"
android:progress="1" />
</RelativeLayout>
</LinearLayout>

View File

@ -2086,6 +2086,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="room_member_power_level_admin_in">Admin in %1$s</string>
<string name="room_member_power_level_moderator_in">Moderator in %1$s</string>
<string name="room_member_power_level_user_in">Default in %1$s</string>
<string name="room_member_power_level_custom_in">Custom (%1$d) in %2$s</string>
<string name="room_member_jump_to_read_receipt">Jump to read receipt</string>