Added banned user screen
This commit is contained in:
parent
de32cdb703
commit
c82e910c38
@ -8,6 +8,7 @@ Improvements 🙌:
|
|||||||
- Sending events is now retried only 3 times, so we avoid blocking the sending queue too long.
|
- Sending events is now retried only 3 times, so we avoid blocking the sending queue too long.
|
||||||
- Display warning when fail to send events in room list
|
- Display warning when fail to send events in room list
|
||||||
- Improve UI of edit role action in member profile
|
- Improve UI of edit role action in member profile
|
||||||
|
- Moderation | New screen to display list of banned users in room settings, with unban action
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Fix theme issue on Room directory screen (#1613)
|
- Fix theme issue on Room directory screen (#1613)
|
||||||
|
@ -77,6 +77,7 @@ import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
|
|||||||
import im.vector.riotx.features.roommemberprofile.devices.DeviceListFragment
|
import im.vector.riotx.features.roommemberprofile.devices.DeviceListFragment
|
||||||
import im.vector.riotx.features.roommemberprofile.devices.DeviceTrustInfoActionFragment
|
import im.vector.riotx.features.roommemberprofile.devices.DeviceTrustInfoActionFragment
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
||||||
|
import im.vector.riotx.features.roomprofile.banned.RoomBannedMemberListFragment
|
||||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||||
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
||||||
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
|
||||||
@ -534,4 +535,9 @@ interface FragmentModule {
|
|||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(ContactsBookFragment::class)
|
@FragmentKey(ContactsBookFragment::class)
|
||||||
fun bindPhoneBookFragment(fragment: ContactsBookFragment): Fragment
|
fun bindPhoneBookFragment(fragment: ContactsBookFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(RoomBannedMemberListFragment::class)
|
||||||
|
fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.core.epoxy.profiles
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.features.crypto.util.toImageRes
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item_progress)
|
||||||
|
abstract class ProfileMatrixItemProgress : VectorEpoxyModel<ProfileMatrixItemProgress.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
|
@EpoxyAttribute var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||||
|
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
val bestName = matrixItem.getBestName()
|
||||||
|
val matrixId = matrixItem.id
|
||||||
|
.takeIf { it != bestName }
|
||||||
|
// Special case for ThreePid fake matrix item
|
||||||
|
.takeIf { it != "@" }
|
||||||
|
holder.titleView.text = bestName
|
||||||
|
holder.subtitleView.setTextOrHide(matrixId)
|
||||||
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
|
holder.avatarDecorationImageView.setImageResource(userEncryptionTrustLevel.toImageRes())
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val titleView by bind<TextView>(R.id.matrixItemTitle)
|
||||||
|
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
|
||||||
|
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
|
||||||
|
val avatarDecorationImageView by bind<ImageView>(R.id.matrixItemAvatarDecoration)
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ import im.vector.riotx.core.platform.VectorBaseActivity
|
|||||||
import im.vector.riotx.features.room.RequireActiveMembershipViewEvents
|
import im.vector.riotx.features.room.RequireActiveMembershipViewEvents
|
||||||
import im.vector.riotx.features.room.RequireActiveMembershipViewModel
|
import im.vector.riotx.features.room.RequireActiveMembershipViewModel
|
||||||
import im.vector.riotx.features.room.RequireActiveMembershipViewState
|
import im.vector.riotx.features.room.RequireActiveMembershipViewState
|
||||||
|
import im.vector.riotx.features.roomprofile.banned.RoomBannedMemberListFragment
|
||||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||||
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
||||||
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
|
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
|
||||||
@ -81,9 +82,10 @@ class RoomProfileActivity :
|
|||||||
.observe()
|
.observe()
|
||||||
.subscribe { sharedAction ->
|
.subscribe { sharedAction ->
|
||||||
when (sharedAction) {
|
when (sharedAction) {
|
||||||
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
|
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
|
||||||
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
|
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
|
||||||
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
|
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
|
||||||
|
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
@ -114,6 +116,10 @@ class RoomProfileActivity :
|
|||||||
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs)
|
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openBannedRoomMembers() {
|
||||||
|
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomBannedMemberListFragment::class.java, roomProfileArgs)
|
||||||
|
}
|
||||||
|
|
||||||
override fun configure(toolbar: Toolbar) {
|
override fun configure(toolbar: Toolbar) {
|
||||||
configureToolbar(toolbar)
|
configureToolbar(toolbar)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ class RoomProfileController @Inject constructor(
|
|||||||
interface Callback {
|
interface Callback {
|
||||||
fun onLearnMoreClicked()
|
fun onLearnMoreClicked()
|
||||||
fun onMemberListClicked()
|
fun onMemberListClicked()
|
||||||
|
fun onBannedMemberListClicked()
|
||||||
fun onNotificationsClicked()
|
fun onNotificationsClicked()
|
||||||
fun onUploadsClicked()
|
fun onUploadsClicked()
|
||||||
fun onSettingsClicked()
|
fun onSettingsClicked()
|
||||||
@ -92,6 +93,16 @@ class RoomProfileController @Inject constructor(
|
|||||||
accessory = R.drawable.ic_shield_warning.takeIf { hasWarning } ?: 0,
|
accessory = R.drawable.ic_shield_warning.takeIf { hasWarning } ?: 0,
|
||||||
action = { callback?.onMemberListClicked() }
|
action = { callback?.onMemberListClicked() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (data.bannedMembership.invoke()?.isNotEmpty() == true) {
|
||||||
|
buildProfileAction(
|
||||||
|
id = "banned_list",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_banned_users_title),
|
||||||
|
dividerColor = dividerColor,
|
||||||
|
icon = R.drawable.ic_settings_root_labs,
|
||||||
|
action = { callback?.onBannedMemberListClicked() }
|
||||||
|
)
|
||||||
|
}
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "uploads",
|
id = "uploads",
|
||||||
title = stringProvider.getString(R.string.room_profile_section_more_uploads),
|
title = stringProvider.getString(R.string.room_profile_section_more_uploads),
|
||||||
|
@ -206,6 +206,10 @@ class RoomProfileFragment @Inject constructor(
|
|||||||
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomMembers)
|
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomMembers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBannedMemberListClicked() {
|
||||||
|
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenBannedRoomMembers)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSettingsClicked() {
|
override fun onSettingsClicked() {
|
||||||
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomSettings)
|
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomSettings)
|
||||||
}
|
}
|
||||||
|
@ -25,4 +25,5 @@ sealed class RoomProfileSharedAction : VectorSharedAction {
|
|||||||
object OpenRoomSettings : RoomProfileSharedAction()
|
object OpenRoomSettings : RoomProfileSharedAction()
|
||||||
object OpenRoomUploads : RoomProfileSharedAction()
|
object OpenRoomUploads : RoomProfileSharedAction()
|
||||||
object OpenRoomMembers : RoomProfileSharedAction()
|
object OpenRoomMembers : RoomProfileSharedAction()
|
||||||
|
object OpenBannedRoomMembers : RoomProfileSharedAction()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.roomprofile
|
|||||||
|
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
@ -26,6 +27,8 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.EventType
|
||||||
|
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.powerlevels.PowerLevelsHelper
|
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
@ -61,7 +64,8 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx().liveRoomSummary()
|
val rxRoom = room.rx()
|
||||||
|
rxRoom.liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute {
|
.execute {
|
||||||
copy(roomSummary = it)
|
copy(roomSummary = it)
|
||||||
@ -77,6 +81,16 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disposeOnClear()
|
.disposeOnClear()
|
||||||
|
|
||||||
|
rxRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) })
|
||||||
|
.subscribe {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
bannedMembership = Success(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: RoomProfileAction) = when (action) {
|
override fun handle(action: RoomProfileAction) = when (action) {
|
||||||
|
@ -20,11 +20,13 @@ package im.vector.riotx.features.roomprofile
|
|||||||
import com.airbnb.mvrx.Async
|
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.room.model.RoomMemberSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
data class RoomProfileViewState(
|
data class RoomProfileViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
|
val bannedMembership: Async<List<RoomMemberSummary>> = Uninitialized,
|
||||||
val canChangeAvatar: Boolean = false
|
val canChangeAvatar: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.banned
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.dividerItem
|
||||||
|
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
||||||
|
import im.vector.riotx.core.epoxy.profiles.profileMatrixItem
|
||||||
|
import im.vector.riotx.core.epoxy.profiles.profileMatrixItemProgress
|
||||||
|
import im.vector.riotx.core.extensions.join
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.ui.list.genericFooterItem
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomBannedMemberListController @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
colorProvider: ColorProvider
|
||||||
|
) : TypedEpoxyController<RoomBannedMemberListViewState>() {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onUnbanClicked(roomMember: RoomMemberSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
setData(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels(data: RoomBannedMemberListViewState?) {
|
||||||
|
val bannedList = data?.bannedMemberSummaries?.invoke() ?: return
|
||||||
|
|
||||||
|
buildProfileSection(
|
||||||
|
stringProvider.getString(R.string.room_settings_banned_users_title)
|
||||||
|
)
|
||||||
|
|
||||||
|
bannedList.join(
|
||||||
|
each = { _, roomMember ->
|
||||||
|
if (data.onGoingModerationAction.contains(roomMember.userId)) {
|
||||||
|
profileMatrixItemProgress {
|
||||||
|
id(roomMember.userId)
|
||||||
|
matrixItem(roomMember.toMatrixItem())
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
profileMatrixItem {
|
||||||
|
id(roomMember.userId)
|
||||||
|
matrixItem(roomMember.toMatrixItem())
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
|
clickListener { _ ->
|
||||||
|
callback?.onUnbanClicked(roomMember)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
between = { _, roomMemberBefore ->
|
||||||
|
dividerItem {
|
||||||
|
id("divider_${roomMemberBefore.userId}")
|
||||||
|
color(dividerColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
genericFooterItem {
|
||||||
|
id("footer")
|
||||||
|
text(stringProvider.getQuantityString(R.plurals.room_settings_banned_users_count, bannedList.size, bannedList.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.banned
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.riotx.core.utils.toast
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
||||||
|
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomBannedMemberListFragment @Inject constructor(
|
||||||
|
val viewModelFactory: RoomBannedListMemberViewModel.Factory,
|
||||||
|
private val roomMemberListController: RoomBannedMemberListController,
|
||||||
|
private val avatarRenderer: AvatarRenderer
|
||||||
|
) : VectorBaseFragment(), RoomBannedMemberListController.Callback {
|
||||||
|
|
||||||
|
private val viewModel: RoomBannedListMemberViewModel by fragmentViewModel()
|
||||||
|
private val roomProfileArgs: RoomProfileArgs by args()
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
|
||||||
|
|
||||||
|
override fun onUnbanClicked(roomMember: RoomMemberSummary) {
|
||||||
|
viewModel.handle(RoomBannedListMemberAction.QueryInfo(roomMember))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
roomMemberListController.callback = this
|
||||||
|
setupToolbar(roomSettingsToolbar)
|
||||||
|
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is RoomBannedViewEvents.ShowBannedInfo -> {
|
||||||
|
val canBan = withState(viewModel) { state -> state.canUserBan }
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(getString(R.string.member_banned_by, it.bannedByUserId))
|
||||||
|
.setMessage(getString(R.string.reason_colon, it.banReason))
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.apply {
|
||||||
|
if (canBan) {
|
||||||
|
setNegativeButton(R.string.room_participants_action_unban) { _, _ ->
|
||||||
|
viewModel.handle(RoomBannedListMemberAction.UnBanUser(it.roomMemberSummary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
is RoomBannedViewEvents.ToastError -> {
|
||||||
|
requireActivity().toast(it.info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { viewState ->
|
||||||
|
roomMemberListController.setData(viewState)
|
||||||
|
renderRoomSummary(viewState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderRoomSummary(state: RoomBannedMemberListViewState) {
|
||||||
|
state.roomSummary()?.let {
|
||||||
|
roomSettingsToolbarTitleView.text = it.displayName
|
||||||
|
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* 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.roomprofile.banned
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
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.toModel
|
||||||
|
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.RoomMemberContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
|
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.internal.util.awaitCallback
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.matrix.rx.unwrap
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory
|
||||||
|
import im.vector.riotx.features.roomprofile.RoomProfileArgs
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
data class RoomBannedMemberListViewState(
|
||||||
|
val roomId: String,
|
||||||
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
|
val bannedMemberSummaries: Async<List<RoomMemberSummary>> = Uninitialized,
|
||||||
|
val onGoingModerationAction: List<String> = emptyList(),
|
||||||
|
val canUserBan: Boolean = false
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class RoomBannedListMemberAction : VectorViewModelAction {
|
||||||
|
data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
|
||||||
|
data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class RoomBannedViewEvents : VectorViewEvents {
|
||||||
|
data class ShowBannedInfo(val bannedByUserId: String, val banReason: String, val roomMemberSummary: RoomMemberSummary) : RoomBannedViewEvents()
|
||||||
|
data class ToastError(val info: String) : RoomBannedViewEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<RoomBannedMemberListViewState, RoomBannedListMemberAction, RoomBannedViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: RoomBannedMemberListViewState): RoomBannedListMemberViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
|
|
||||||
|
init {
|
||||||
|
val rxRoom = room.rx()
|
||||||
|
|
||||||
|
room.rx().liveRoomSummary()
|
||||||
|
.unwrap()
|
||||||
|
.execute { async ->
|
||||||
|
copy(roomSummary = async)
|
||||||
|
}
|
||||||
|
|
||||||
|
rxRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) })
|
||||||
|
.execute {
|
||||||
|
copy(
|
||||||
|
bannedMemberSummaries = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable()
|
||||||
|
|
||||||
|
powerLevelsContentLive.subscribe {
|
||||||
|
val powerLevelsHelper = PowerLevelsHelper(it)
|
||||||
|
setState { copy(canUserBan = powerLevelsHelper.isUserAbleToBan(session.myUserId)) }
|
||||||
|
}.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<RoomBannedListMemberViewModel, RoomBannedMemberListViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedListMemberViewModel? {
|
||||||
|
val fragment: RoomBannedMemberListFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.viewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: RoomBannedListMemberAction) {
|
||||||
|
when (action) {
|
||||||
|
is RoomBannedListMemberAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary)
|
||||||
|
is RoomBannedListMemberAction.UnBanUser -> unBanUser(action.roomMemberSummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onQueryBanInfo(roomMemberSummary: RoomMemberSummary) {
|
||||||
|
val bannedEvent = room.getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(roomMemberSummary.userId))
|
||||||
|
val content = bannedEvent?.getClearContent().toModel<RoomMemberContent>()
|
||||||
|
if (content?.membership != Membership.BAN) {
|
||||||
|
// may be report error?
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val reason = content.reason
|
||||||
|
val bannedBy = bannedEvent?.senderId ?: return
|
||||||
|
|
||||||
|
_viewEvents.post(RoomBannedViewEvents.ShowBannedInfo(bannedBy, reason ?: "", roomMemberSummary))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unBanUser(roomMemberSummary: RoomMemberSummary) {
|
||||||
|
setState {
|
||||||
|
copy(onGoingModerationAction = this.onGoingModerationAction + roomMemberSummary.userId)
|
||||||
|
}
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
awaitCallback<Unit> {
|
||||||
|
room.unban(roomMemberSummary.userId, null, it)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
_viewEvents.post(RoomBannedViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban)))
|
||||||
|
} finally {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
onGoingModerationAction = onGoingModerationAction - roomMemberSummary.userId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:minHeight="64dp"
|
||||||
|
android:paddingLeft="@dimen/layout_horizontal_margin"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingRight="@dimen/layout_horizontal_margin"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/matrixItemAvatar"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/matrixItemAvatarDecoration"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
app:layout_constraintCircle="@+id/matrixItemAvatar"
|
||||||
|
app:layout_constraintCircleAngle="135"
|
||||||
|
app:layout_constraintCircleRadius="16dp"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@drawable/ic_shield_trusted" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/matrixItemTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/matrixItemProgress"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_goneMarginStart="0dp"
|
||||||
|
tools:text="@sample/matrix.json/data/displayName" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/matrixItemSubtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/matrixItemProgress"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/matrixItemTitle"
|
||||||
|
app:layout_goneMarginStart="0dp"
|
||||||
|
tools:text="@sample/matrix.json/data/mxid" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/matrixItemProgress"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -992,6 +992,10 @@
|
|||||||
|
|
||||||
<!-- Room settings: banned users -->
|
<!-- Room settings: banned users -->
|
||||||
<string name="room_settings_banned_users_title">Banned users</string>
|
<string name="room_settings_banned_users_title">Banned users</string>
|
||||||
|
<plurals name="room_settings_banned_users_count">
|
||||||
|
<item quantity="one">1 banned user</item>
|
||||||
|
<item quantity="other">%d banned users</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<!-- advanced -->
|
<!-- advanced -->
|
||||||
<string name="room_settings_category_advanced_title">Advanced</string>
|
<string name="room_settings_category_advanced_title">Advanced</string>
|
||||||
@ -2561,4 +2565,7 @@ Not all features in Riot are implemented in Element yet. Main missing (and comin
|
|||||||
|
|
||||||
<string name="three_pid_revoke_invite_dialog_title">Revoke invite</string>
|
<string name="three_pid_revoke_invite_dialog_title">Revoke invite</string>
|
||||||
<string name="three_pid_revoke_invite_dialog_content">Revoke invite to %1$s?</string>
|
<string name="three_pid_revoke_invite_dialog_content">Revoke invite to %1$s?</string>
|
||||||
|
|
||||||
|
<string name="member_banned_by">Banned by %1$s</string>
|
||||||
|
<string name="failed_to_unban">Failed to UnBan user</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user