Merge pull request #3904 from nextcloud/issue-3898-ban

🚫 Allow Banning Users and Guests
This commit is contained in:
Julius Linus 2024-08-06 11:16:37 -05:00 committed by GitHub
commit 9866062704
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 796 additions and 17 deletions

View file

@ -25,6 +25,8 @@ import com.nextcloud.talk.models.json.notifications.NotificationOverall;
import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall;
import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
import com.nextcloud.talk.models.json.participants.TalkBan;
import com.nextcloud.talk.models.json.participants.TalkBanOverall;
import com.nextcloud.talk.models.json.push.PushRegistrationOverall;
import com.nextcloud.talk.models.json.reactions.ReactionsOverall;
import com.nextcloud.talk.models.json.reminder.ReminderOverall;
@ -333,7 +335,7 @@ public interface NcApi {
*/
@DELETE
Observable<Void> unregisterDeviceForNotificationsWithProxy(@Url String url,
@QueryMap Map<String,String> fields);
@QueryMap Map<String, String> fields);
@FormUrlEncoded
@PUT
@ -704,9 +706,25 @@ public interface NcApi {
@POST
Observable<GenericOverall> acceptInvitation(@Header("Authorization") String authorization,
@Url String url);
@Url String url);
@DELETE
Observable<GenericOverall> rejectInvitation(@Header("Authorization") String authorization,
@Url String url);
@Url String url);
@GET
Observable<TalkBanOverall> listBans(@Header("Authorization") String authorization,
@Url String url);
@FormUrlEncoded
@POST
Observable<TalkBan> banActor(@Header("Authorization") String authorization,
@Url String url,
@Field("actorType") String actorType,
@Field("actorId") String actorId,
@Field("internalNote") String internalNote);
@DELETE
Observable<GenericOverall> unbanActor(@Header("Authorization") String authorization,
@Url String url);
}

View file

@ -13,6 +13,7 @@ import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.reminder.Reminder
import io.reactivex.Observable
import retrofit2.Response
@ -29,6 +30,7 @@ interface ChatRepository {
timeStamp: Int,
chatApiVersion: Int
): Observable<Reminder>
fun getReminder(user: User, roomToken: String, messageId: String, apiVersion: Int): Observable<Reminder>
fun deleteReminder(user: User, roomToken: String, messageId: String, apiVersion: Int): Observable<GenericOverall>
fun shareToNotes(
@ -37,6 +39,7 @@ interface ChatRepository {
message: String,
displayName: String
): Observable<GenericOverall> // last two fields are false
fun checkForNoteToSelf(credentials: String, url: String, includeStatus: Boolean): Observable<RoomsOverall>
fun shareLocationToNotes(
credentials: String,
@ -45,6 +48,7 @@ interface ChatRepository {
objectId: String,
metadata: String
): Observable<GenericOverall>
fun leaveRoom(credentials: String, url: String): Observable<GenericOverall>
fun sendChatMessage(
credentials: String,
@ -54,9 +58,20 @@ interface ChatRepository {
replyTo: Int,
sendWithoutNotification: Boolean
): Observable<GenericOverall>
fun pullChatMessages(credentials: String, url: String, fieldMap: HashMap<String, Int>): Observable<Response<*>>
fun deleteChatMessage(credentials: String, url: String): Observable<ChatOverallSingleMessage>
fun createRoom(credentials: String, url: String, map: Map<String, String>): Observable<RoomOverall>
fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int): Observable<GenericOverall>
fun editChatMessage(credentials: String, url: String, text: String): Observable<ChatOverallSingleMessage>
fun listBans(credentials: String, url: String): Observable<List<TalkBan>>
fun banActor(
credentials: String,
url: String,
actorType: String,
actorId: String,
internalNote: String
): Observable<TalkBan>
fun unbanActor(credentials: String, url: String): Observable<GenericOverall>
}

View file

@ -15,6 +15,7 @@ import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observable
@ -179,4 +180,22 @@ class NetworkChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
override fun editChatMessage(credentials: String, url: String, text: String): Observable<ChatOverallSingleMessage> {
return ncApi.editChatMessage(credentials, url, text).map { it }
}
override fun listBans(credentials: String, url: String): Observable<List<TalkBan>> {
return ncApi.listBans(credentials, url).map { it.ocs?.data }
}
override fun banActor(
credentials: String,
url: String,
actorType: String,
actorId: String,
internalNote: String
): Observable<TalkBan> {
return ncApi.banActor(credentials, url, actorType, actorId, internalNote)
}
override fun unbanActor(credentials: String, url: String): Observable<GenericOverall> {
return ncApi.unbanActor(credentials, url)
}
}

View file

@ -22,6 +22,7 @@ import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.ViewModelProvider
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
@ -47,6 +48,7 @@ import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
import com.nextcloud.talk.databinding.DialogBanActorBinding
import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.extensions.loadConversationAvatar
import com.nextcloud.talk.extensions.loadNoteToSelfAvatar
@ -60,6 +62,7 @@ import com.nextcloud.talk.models.domain.LobbyState
import com.nextcloud.talk.models.domain.NotificationLevel
import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelConverter
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.Participant.ActorType.CIRCLES
@ -68,6 +71,7 @@ import com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS
import com.nextcloud.talk.models.json.participants.ParticipantsOverall
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
import com.nextcloud.talk.ui.dialog.DialogBanListFragment
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.ConversationUtils
@ -181,6 +185,7 @@ class ConversationInfoActivity :
binding.leaveConversationAction.setOnClickListener { leaveConversation() }
binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog() }
binding.addParticipantsAction.setOnClickListener { addParticipants() }
binding.listBansButton.setOnClickListener { listBans() }
viewModel.getRoom(conversationUser, conversationToken)
@ -233,6 +238,20 @@ class ConversationInfoActivity :
else -> {}
}
}
viewModel.getBanActorState.observe(this) { state ->
when (state) {
is ConversationInfoViewModel.BanActorSuccessState -> {
getListOfParticipants() // Refresh the list of participants
}
ConversationInfoViewModel.BanActorErrorState -> {
Snackbar.make(binding.root, "Error banning actor", Snackbar.LENGTH_SHORT).show()
}
else -> {}
}
}
}
private fun setupActionBar() {
@ -569,6 +588,17 @@ class ConversationInfoActivity :
})
}
private fun listBans() {
val fragmentManager = supportFragmentManager
val newFragment = DialogBanListFragment(conversationToken)
val transaction = fragmentManager.beginTransaction()
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
transaction
.add(android.R.id.content, newFragment)
.addToBackStack(null)
.commit()
}
private fun addParticipants() {
val bundle = Bundle()
val existingParticipantsId = arrayListOf<String>()
@ -734,6 +764,15 @@ class ConversationInfoActivity :
binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
}
binding.listBansButton.visibility =
if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities) &&
ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation!!.type
) {
VISIBLE
} else {
GONE
}
if (conversation!!.notificationCalls === null) {
binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
} else {
@ -1068,6 +1107,10 @@ class ConversationInfoActivity :
}
}
private fun banActor(actorType: String, actorId: String, internalNote: String) {
viewModel.banActor(conversationUser, conversationToken, actorType, actorId, internalNote)
}
private fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) {
if (apiVersion >= ApiUtils.API_V4) {
ncApi.removeAttendeeFromConversation(
@ -1264,6 +1307,15 @@ class ConversationInfoActivity :
)
)
if (CapabilitiesUtil.isBanningAvailable(conversationUser.capabilities?.spreedCapability!!)) {
items.add(
BasicListItemWithImage(
R.drawable.baseline_block_24,
"Ban Participant"
)
)
}
if (participant.type == Participant.ParticipantType.MODERATOR ||
participant.type == Participant.ParticipantType.GUEST_MODERATOR
) {
@ -1296,18 +1348,24 @@ class ConversationInfoActivity :
actionToTrigger++
}
if (actionToTrigger == 0) {
// Pin, nothing to do
} else if (actionToTrigger == 1) {
// Promote/demote
if (apiVersion >= ApiUtils.API_V4) {
toggleModeratorStatus(apiVersion, participant)
} else {
toggleModeratorStatusLegacy(apiVersion, participant)
when (actionToTrigger) {
DEMOTE_OR_PROMOTE -> {
if (apiVersion >= ApiUtils.API_V4) {
toggleModeratorStatus(apiVersion, participant)
} else {
toggleModeratorStatusLegacy(apiVersion, participant)
}
}
} else if (actionToTrigger == 2) {
// Remove from conversation
removeAttendeeFromConversation(apiVersion, participant)
REMOVE_FROM_CONVERSATION -> {
removeAttendeeFromConversation(apiVersion, participant)
}
BAN_FROM_CONVERSATION -> {
handleBan(participant)
}
else -> {}
}
}
}
@ -1315,6 +1373,36 @@ class ConversationInfoActivity :
return true
}
private fun MaterialDialog.handleBan(participant: Participant) {
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
val binding = DialogBanActorBinding.inflate(layoutInflater)
val actorTypeConverter = EnumActorTypeConverter()
val dialog = MaterialAlertDialogBuilder(context)
.setView(binding.root)
.create()
binding.avatarImage.loadUserAvatar(
conversationUser,
participant.actorId!!,
true,
false
)
binding.displayNameText.text = participant.actorId
binding.buttonBan.setOnClickListener {
banActor(
actorTypeConverter.convertToString(participant.actorType!!),
participant.actorId!!,
binding.banActorEdit.text.toString()
)
removeAttendeeFromConversation(apiVersion, participant)
dialog.dismiss()
}
binding.buttonClose.setOnClickListener { dialog.dismiss() }
viewThemeUtils.material.colorTextInputLayout(binding.banActorEditLayout)
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.buttonBan)
viewThemeUtils.material.colorMaterialButtonText(binding.buttonClose)
dialog.show()
}
private fun setUpNotificationSettings(module: DatabaseStorageModule) {
binding.notificationSettingsView.notificationSettingsImportantConversation.setOnClickListener {
val isChecked = binding.notificationSettingsView.importantConversationSwitch.isChecked
@ -1353,6 +1441,9 @@ class ConversationInfoActivity :
private const val LOW_EMPHASIS_OPACITY: Float = 0.38f
private const val RECORDING_CONSENT_NOT_REQUIRED_FOR_CONVERSATION: Int = 0
private const val RECORDING_CONSENT_REQUIRED_FOR_CONVERSATION: Int = 1
private const val DEMOTE_OR_PROMOTE = 1
private const val REMOVE_FROM_CONVERSATION = 2
private const val BAN_FROM_CONVERSATION = 3
}
/**

View file

@ -16,6 +16,9 @@ import com.nextcloud.talk.chat.data.ChatRepository
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -31,8 +34,9 @@ class ConversationInfoViewModel @Inject constructor(
PAUSED,
RESUMED
}
lateinit var currentLifeCycleFlag: LifeCycleFlag
public val disposableSet = mutableSetOf<Disposable>()
val disposableSet = mutableSetOf<Disposable>()
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
@ -49,6 +53,27 @@ class ConversationInfoViewModel @Inject constructor(
sealed interface ViewState
class ListBansSuccessState(val talkBans: List<TalkBan>) : ViewState
object ListBansErrorState : ViewState
private val _getTalkBanState: MutableLiveData<ViewState> = MutableLiveData()
val getTalkBanState: LiveData<ViewState>
get() = _getTalkBanState
class BanActorSuccessState(val talkBan: TalkBan) : ViewState
object BanActorErrorState : ViewState
private val _getBanActorState: MutableLiveData<ViewState> = MutableLiveData()
val getBanActorState: LiveData<ViewState>
get() = _getBanActorState
object UnBanActorSuccessState : ViewState
object UnBanActorErrorState : ViewState
private val _getUnBanActorState: MutableLiveData<ViewState> = MutableLiveData()
val getUnBanActorState: LiveData<ViewState>
get() = _getUnBanActorState
object GetRoomStartState : ViewState
object GetRoomErrorState : ViewState
open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
@ -103,6 +128,78 @@ class ConversationInfoViewModel @Inject constructor(
}
}
fun listBans(user: User, token: String) {
val url = ApiUtils.getUrlForBans(user.baseUrl!!, token)
chatRepository.listBans(user.getCredentials(), url)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<List<TalkBan>> {
override fun onSubscribe(p0: Disposable) {
// unused atm
}
override fun onError(e: Throwable) {
_getTalkBanState.value = ListBansErrorState
}
override fun onComplete() {
// unused atm
}
override fun onNext(talkBans: List<TalkBan>) {
_getTalkBanState.value = ListBansSuccessState(talkBans)
}
})
}
fun banActor(user: User, token: String, actorType: String, actorId: String, internalNote: String) {
val url = ApiUtils.getUrlForBans(user.baseUrl!!, token)
chatRepository.banActor(user.getCredentials(), url, actorType, actorId, internalNote)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<TalkBan> {
override fun onSubscribe(p0: Disposable) {
// unused atm
}
override fun onError(e: Throwable) {
_getBanActorState.value = BanActorErrorState
}
override fun onComplete() {
// unused atm
}
override fun onNext(talkBan: TalkBan) {
_getBanActorState.value = BanActorSuccessState(talkBan)
}
})
}
fun unbanActor(user: User, token: String, banId: Int) {
val url = ApiUtils.getUrlForUnban(user.baseUrl!!, token, banId)
chatRepository.unbanActor(user.getCredentials(), url)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(p0: Disposable) {
// unused atm
}
override fun onError(p0: Throwable) {
_getUnBanActorState.value = UnBanActorErrorState
}
override fun onComplete() {
// unused atm
}
override fun onNext(p0: GenericOverall) {
_getUnBanActorState.value = UnBanActorSuccessState
}
})
}
inner class GetRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm

View file

@ -0,0 +1,40 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.participants
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class TalkBan(
@JsonField(name = ["id"])
var id: String?,
@JsonField(name = ["moderatorActorType"])
var moderatorActorType: String?,
@JsonField(name = ["moderatorActorId"])
var moderatorActorId: String?,
@JsonField(name = ["moderatorDisplayName"])
var moderatorDisplayName: String?,
@JsonField(name = ["bannedActorType"])
var bannedActorType: String?,
@JsonField(name = ["bannedActorId"])
var bannedActorId: String?,
@JsonField(name = ["bannedDisplayName"])
var bannedDisplayName: String?,
@JsonField(name = ["bannedTime"])
var bannedTime: Int?,
@JsonField(name = ["internalNote"])
var internalNote: String?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() :
this(null, null, null, null, null, null, null, null, null)
}

View file

@ -0,0 +1,26 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.participants
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import com.nextcloud.talk.models.json.generic.GenericMeta
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class TalkBanOCS(
@JsonField(name = ["meta"])
var meta: GenericMeta?,
@JsonField(name = ["data"])
var data: List<TalkBan>? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View file

@ -0,0 +1,23 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.participants
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class TalkBanOverall(
@JsonField(name = ["ocs"])
var ocs: TalkBanOCS? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}

View file

@ -0,0 +1,146 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.ui.dialog
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.BanItemListBinding
import com.nextcloud.talk.databinding.FragmentDialogBanListBinding
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class DialogBanListFragment(val roomToken: String) : DialogFragment() {
lateinit var binding: FragmentDialogBanListBinding
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
lateinit var viewModel: ConversationInfoViewModel
private lateinit var conversationUser: User
private val adapter = object : BaseAdapter() {
private var bans: List<TalkBan> = mutableListOf()
fun setItems(items: List<TalkBan>) {
bans = items
}
override fun getCount(): Int {
return bans.size
}
override fun getItem(position: Int): Any {
return bans[position]
}
override fun getItemId(position: Int): Long {
return bans[position].bannedTime!!.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val binding = BanItemListBinding.inflate(LayoutInflater.from(context))
binding.banActorName.text = bans[position].bannedDisplayName
val time = bans[position].bannedTime!!.toLong() * ONE_SEC
binding.banTime.text = DateUtils.formatDateTime(
requireContext(),
time,
(DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME)
)
binding.banReason.text = bans[position].internalNote
binding.unbanBtn.setOnClickListener {
unBanActor(bans[position].id!!.toInt())
}
return binding.root
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
binding = FragmentDialogBanListBinding.inflate(LayoutInflater.from(context))
viewModel =
ViewModelProvider(this, viewModelFactory)[ConversationInfoViewModel::class.java]
conversationUser = currentUserProvider.currentUser.blockingGet()
themeView()
initObservers()
initListeners()
getBanList()
return binding.root
}
private fun initObservers() {
viewModel.getTalkBanState.observe(viewLifecycleOwner) { state ->
when (state) {
is ConversationInfoViewModel.ListBansSuccessState -> {
adapter.setItems(state.talkBans)
binding.banListView.adapter = adapter
}
is ConversationInfoViewModel.ListBansErrorState -> {}
else -> {}
}
}
viewModel.getUnBanActorState.observe(viewLifecycleOwner) { state ->
when (state) {
is ConversationInfoViewModel.UnBanActorSuccessState -> {
getBanList()
}
is ConversationInfoViewModel.UnBanActorErrorState -> {
Snackbar.make(binding.root, getString(R.string.error_unbanning), Snackbar.LENGTH_SHORT).show()
}
else -> {}
}
}
}
private fun themeView() {
viewThemeUtils.platform.colorViewBackground(binding.root)
}
private fun initListeners() {
binding.closeBtn.setOnClickListener { dismiss() }
}
private fun getBanList() {
viewModel.listBans(conversationUser, roomToken)
}
private fun unBanActor(banId: Int) {
viewModel.unbanActor(conversationUser, roomToken, banId)
}
companion object {
@JvmStatic
fun newInstance(roomToken: String) = DialogBanListFragment(roomToken)
const val ONE_SEC = 1000L
}
}

View file

@ -572,4 +572,12 @@ object ApiUtils {
fun getUrlForRoomCapabilities(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRooms(version, baseUrl) + "/" + token + "/capabilities"
}
fun getUrlForBans(baseUrl: String, token: String): String {
return "$baseUrl/ocs/v1.php$SPREED_API_VERSION/ban/$token"
}
fun getUrlForUnban(baseUrl: String, token: String, banId: Int): String {
return "${getUrlForBans(baseUrl, token)}/$banId"
}
}

View file

@ -53,7 +53,8 @@ enum class SpreedFeatures(val value: String) {
CHAT_PERMISSION("chat-permission"),
CONVERSATION_PERMISSION("conversation-permissions"),
FEDERATION_V1("federation-v1"),
DELETE_MESSAGES_UNLIMITED("delete-messages-unlimited")
DELETE_MESSAGES_UNLIMITED("delete-messages-unlimited"),
BAN_V1("ban-v1")
}
@Suppress("TooManyFunctions")
@ -213,6 +214,10 @@ object CapabilitiesUtil {
return RECORDING_CONSENT_NOT_REQUIRED
}
fun isBanningAvailable(spreedCapabilities: SpreedCapability): Boolean {
return hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.BAN_V1)
}
// endregion
//region SpreedCapabilities that can't be used with federation as the settings for them are global

View file

@ -0,0 +1,19 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2024 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="@color/design_default_color_error"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z" />
</vector>

View file

@ -354,6 +354,35 @@
tools:listitem="@layout/rv_item_conversation_info_participant" />
</LinearLayout>
<LinearLayout
android:id="@+id/list_bans_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/standard_margin"
android:paddingTop="@dimen/standard_half_margin"
android:paddingEnd="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="24dp"
android:layout_height="40dp"
android:layout_marginEnd="@dimen/standard_margin"
android:contentDescription="@null"
android:src="@drawable/baseline_block_24"
app:tint="@color/grey_600" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/show_banned_participants"
android:textSize="@dimen/headline_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/danger_zone_options"
android:layout_width="match_parent"

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ban_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/ban_actor_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textAlignment="viewStart"
android:textAppearance="@style/ListItem"
android:textSize="@dimen/md_title_textsize"
tools:text="User 2" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/ban_time"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textSize="@dimen/sm_text_size"
tools:text="11th August, 2023" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/ban_reason"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:text="Was being mean"
android:padding="@dimen/standard_half_padding" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/unban_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_half_margin"
android:layout_gravity="center"
android:text="@string/unban"
android:textColor="@color/hwSecurityRed"
style="@style/Widget.Material3.Button.TextButton"
/>
</LinearLayout>

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2024 Julius Linus <julius.linus@nextcloud.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@color/white"
tools:visibility="visible">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_margin"
android:text="@string/ban_actor"
android:textSize="@dimen/md_title_textsize" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<RelativeLayout
android:id="@+id/ban_actor_profile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_margin"
android:layout_marginTop="@dimen/standard_quarter_margin"
android:layout_marginEnd="@dimen/standard_margin"
android:animateLayoutChanges="true"
tools:visibility="visible">
<ImageView
android:id="@+id/avatar_image"
android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/standard_margin"
android:contentDescription="@string/avatar"
tools:src="@drawable/account_circle_48dp" />
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/display_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/avatar_image"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size"
tools:text="Jane Doe" />
</RelativeLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ban_actor_edit_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_margin"
android:hint="@string/internal_note">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/ban_actor_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textShortMessage"
android:lines="1"
android:padding="@dimen/standard_half_padding" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_half_margin"
android:gravity="end"
android:orientation="horizontal"
android:paddingStart="@dimen/dialog_padding"
android:paddingEnd="@dimen/dialog_padding"
android:paddingBottom="@dimen/dialog_padding_top_bottom">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_close"
style="@style/Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_size_clickable_area"
android:text="@string/close" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_ban"
style="@style/Button.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_size_clickable_area"
android:text="@string/ban" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.dialog.DialogBanListFragment"
tools:background="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginHorizontal="@dimen/standard_margin">
<ImageView
android:id="@+id/close_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/close"
android:src="@drawable/ic_close_search"/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/ban_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/standard_margin"
android:text="@string/bans_list"
android:textSize="@dimen/md_title_textsize" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ListView
android:id="@+id/ban_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/standard_padding">
</ListView>
</LinearLayout>

View file

@ -83,6 +83,7 @@
<dimen name="notification_icon_width">24dp</dimen>
<dimen name="notification_icon_height">24dp</dimen>
<dimen name="notification_icon_layout_right_end_margin">21dp</dimen>
<dimen name="sm_text_size">12sp</dimen>
</resources>

View file

@ -336,7 +336,6 @@ How to translate with transifex:
<string name="nc_add_emojis">Add emojis</string>
<string name="nc_push_to_talk">Push-to-talk</string>
<string name="nc_push_to_talk_desc">With microphone disabled, click&amp;hold to use Push-to-talk</string>
<string name="nc_configure_cert_auth">Select authentication certificate</string>
@ -793,4 +792,12 @@ How to translate with transifex:
<string name="message_last_edited_by">Edited by %1$s</string>
<string name="share_link_to_conversation">Join conversation %1$s at %2$s</string>
<string name="nc_conversation_settings">Conversation settings</string>
<string name="show_banned_participants">Show Banned Participants</string>
<string name="bans_list">Bans List</string>
<string name="unban">Unban</string>
<string name="internal_note">Internal Note</string>
<string name="ban_actor">Ban Actor</string>
<string name="ban">Ban</string>
<string name="show_ban_reason">Show ban reason</string>
<string name="error_unbanning">Error occured when unbanning actor</string>
</resources>