Added chat room participant admin popup to remove a person from a group & give/remove admin rights

This commit is contained in:
Sylvain Berfini 2023-10-17 16:56:50 +02:00
parent 1c7b97d8db
commit f07a8f6c2b
9 changed files with 269 additions and 15 deletions

View file

@ -20,16 +20,22 @@
package org.linphone.ui.main.chat.fragment
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupWindow
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.linphone.R
import org.linphone.core.tools.Log
import org.linphone.databinding.ChatInfoFragmentBinding
import org.linphone.databinding.ChatParticipantAdminPopupMenuBinding
import org.linphone.ui.main.chat.model.ParticipantModel
import org.linphone.ui.main.chat.viewmodel.ConversationInfoViewModel
import org.linphone.ui.main.fragment.GenericFragment
@ -111,8 +117,56 @@ class ConversationInfoFragment : GenericFragment() {
}
}
viewModel.showParticipantAdminPopupMenuEvent.observe(viewLifecycleOwner) {
it.consume { pair ->
showParticipantAdminPopupMenu(pair.first, pair.second)
}
}
binding.setBackClickListener {
goBack()
}
}
private fun showParticipantAdminPopupMenu(view: View, participantModel: ParticipantModel) {
val popupView: ChatParticipantAdminPopupMenuBinding = DataBindingUtil.inflate(
LayoutInflater.from(requireContext()),
R.layout.chat_participant_admin_popup_menu,
null,
false
)
val popupWindow = PopupWindow(
popupView.root,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true
)
val address = participantModel.sipUri
val isAdmin = participantModel.isParticipantAdmin
popupView.isParticipantAdmin = isAdmin
popupView.setRemoveParticipantClickListener {
Log.w("$TAG Trying to remove participant [$address]")
viewModel.removeParticipant(participantModel)
popupWindow.dismiss()
}
popupView.setSetAdminClickListener {
Log.w("$TAG Trying to give admin rights to participant [$address]")
viewModel.giveAdminRightsTo(participantModel)
popupWindow.dismiss()
}
popupView.setUnsetAdminClickListener {
Log.w("$TAG Trying to remove admin rights from participant [$address]")
viewModel.removeAdminRightsFrom(participantModel)
popupWindow.dismiss()
}
// Elevation is for showing a shadow around the popup
popupWindow.elevation = 20f
popupWindow.showAsDropDown(view, 0, 0, Gravity.BOTTOM)
}
}

View file

@ -19,8 +19,25 @@
*/
package org.linphone.ui.main.chat.model
import android.view.View
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import org.linphone.core.Address
import org.linphone.core.Friend
import org.linphone.ui.main.contacts.model.ContactAvatarModel
class ParticipantModel(friend: Friend, val isMyselfAdmin: Boolean, val isParticipantAdmin: Boolean) :
ContactAvatarModel(friend)
class ParticipantModel @WorkerThread constructor(
friend: Friend,
val address: Address,
val isMyselfAdmin: Boolean,
val isParticipantAdmin: Boolean,
private val onMenuClicked: ((view: View, model: ParticipantModel) -> Unit)? = null
) : ContactAvatarModel(friend) {
val sipUri = address.asStringUriOnly()
@UiThread
fun openMenu(view: View) {
onMenuClicked?.invoke(view, this)
}
}

View file

@ -19,6 +19,7 @@
*/
package org.linphone.ui.main.chat.viewmodel
import android.view.View
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
@ -59,9 +60,17 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
val chatRoomFoundEvent = MutableLiveData<Event<Boolean>>()
val groupLeftEvent = MutableLiveData<Event<Boolean>>()
val groupLeftEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val historyDeletedEvent = MutableLiveData<Event<Boolean>>()
val historyDeletedEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val showParticipantAdminPopupMenuEvent: MutableLiveData<Event<Pair<View, ParticipantModel>>> by lazy {
MutableLiveData<Event<Pair<View, ParticipantModel>>>()
}
private lateinit var chatRoom: ChatRoom
@ -70,21 +79,34 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
private val chatRoomListener = object : ChatRoomListenerStub() {
@WorkerThread
override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) {
Log.i("$TAG A participant has been added to the group [${chatRoom.subject}]")
// TODO: show toast
computeParticipantsList()
}
@WorkerThread
override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) {
Log.i("$TAG A participant has been removed from the group [${chatRoom.subject}]")
// TODO: show toast
computeParticipantsList()
}
@WorkerThread
override fun onParticipantAdminStatusChanged(chatRoom: ChatRoom, eventLog: EventLog) {
Log.i(
"$TAG A participant has been given/removed administration rights for group [${chatRoom.subject}]"
)
// TODO: show toast
// TODO FIXME: list doesn't have the changes...
computeParticipantsList()
}
@WorkerThread
override fun onSubjectChanged(chatRoom: ChatRoom, eventLog: EventLog) {
Log.i(
"$TAG Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] has a new subject [${chatRoom.subject}]"
)
// TODO: show toast
subject.postValue(chatRoom.subject)
}
}
@ -177,6 +199,69 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
expandParticipants.value = expandParticipants.value == false
}
@UiThread
fun removeParticipant(participantModel: ParticipantModel) {
coreContext.postOnCoreThread {
val address = participantModel.address
Log.i(
"$TAG Removing participant [$address] from the conversation [${LinphoneUtils.getChatRoomId(
chatRoom
)}]"
)
val participant = chatRoom.participants.find {
it.address.weakEqual(address)
}
if (participant != null) {
chatRoom.removeParticipant(participant)
Log.i("$TAG Participant removed")
} else {
Log.e("$TAG Couldn't find participant matching address [$address]!")
}
}
}
@UiThread
fun giveAdminRightsTo(participantModel: ParticipantModel) {
coreContext.postOnCoreThread {
val address = participantModel.address
Log.i(
"$TAG Granting admin rights to participant [$address] from the conversation [${LinphoneUtils.getChatRoomId(
chatRoom
)}]"
)
val participant = chatRoom.participants.find {
it.address.weakEqual(address)
}
if (participant != null) {
chatRoom.setParticipantAdminStatus(participant, true)
Log.i("$TAG Participant will become admin soon")
} else {
Log.e("$TAG Couldn't find participant matching address [$address]!")
}
}
}
@UiThread
fun removeAdminRightsFrom(participantModel: ParticipantModel) {
coreContext.postOnCoreThread {
val address = participantModel.address
Log.i(
"$TAG Removing admin rights from participant [$address] from the conversation [${LinphoneUtils.getChatRoomId(
chatRoom
)}]"
)
val participant = chatRoom.participants.find {
it.address.weakEqual(address)
}
if (participant != null) {
chatRoom.setParticipantAdminStatus(participant, false)
Log.i("$TAG Participant will be removed as admin soon")
} else {
Log.e("$TAG Couldn't find participant matching address [$address]!")
}
}
}
@WorkerThread
private fun configureChatRoom() {
isMuted.postValue(chatRoom.muted)
@ -235,12 +320,9 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
}
@WorkerThread
private fun getParticipantModelForAddress(address: Address?, isAdmin: Boolean): ParticipantModel {
private fun getParticipantModelForAddress(address: Address, isAdmin: Boolean): ParticipantModel {
Log.i("$TAG Looking for participant model with address [${address?.asStringUriOnly()}]")
if (address == null) {
val fakeFriend = coreContext.core.createFriend()
return ParticipantModel(fakeFriend, isMyselfAdmin.value == true, false)
}
val selfAdmin = chatRoom.me?.isAdmin == true
val clone = address.clone()
clone.clean()
@ -251,11 +333,17 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() {
val friend = coreContext.contactsManager.findContactByAddress(clone)
val avatar = if (friend != null) {
ParticipantModel(friend, isMyselfAdmin.value == true, isAdmin)
ParticipantModel(friend, clone, selfAdmin, isAdmin) { view, model ->
// openMenu
showParticipantAdminPopupMenuEvent.postValue(Event(Pair(view, model)))
}
} else {
val fakeFriend = coreContext.core.createFriend()
fakeFriend.address = clone
ParticipantModel(fakeFriend, isMyselfAdmin.value == true, isAdmin)
ParticipantModel(fakeFriend, clone, selfAdmin, isAdmin) { view, model ->
// openMenu
showParticipantAdminPopupMenuEvent.postValue(Event(Pair(view, model)))
}
}
avatarsMap[key] = avatar

View file

@ -17,8 +17,9 @@
android:background="@drawable/shape_round_popup_menu_background">
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{manageProfileClickListener}"
style="@style/default_text_style"
android:id="@+id/manage_profile"
android:onClick="@{manageProfileClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"

View file

@ -224,7 +224,7 @@
android:ellipsize="end"
android:drawableStart="@drawable/plus_circle"
android:drawablePadding="8dp"
android:visibility="@{!viewModel.expandParticipants || !viewModel.isMyselfAdmin || viewModel.isGroup || viewModel.isReadOnly ? View.GONE : View.VISIBLE}"
android:visibility="@{!viewModel.expandParticipants || !viewModel.isMyselfAdmin || !viewModel.isGroup || viewModel.isReadOnly ? View.GONE : View.VISIBLE}"
app:drawableTint="@color/orange_main_500"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -234,7 +234,7 @@
android:id="@+id/participants_anchor"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:visibility="@{!viewModel.expandParticipants || !viewModel.isMyselfAdmin || viewModel.isGroup || viewModel.isReadOnly ? View.GONE : View.VISIBLE}"
android:visibility="@{!viewModel.expandParticipants || !viewModel.isMyselfAdmin || !viewModel.isGroup || viewModel.isReadOnly ? View.GONE : View.VISIBLE}"
app:layout_constraintTop_toBottomOf="@id/add_participants"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

View file

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<import type="android.graphics.Typeface" />
<variable
name="removeParticipantClickListener"
type="View.OnClickListener" />
<variable
name="setAdminClickListener"
type="View.OnClickListener" />
<variable
name="unsetAdminClickListener"
type="View.OnClickListener" />
<variable
name="isParticipantAdmin"
type="Boolean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_round_popup_menu_background">
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/remove_participant"
android:onClick="@{removeParticipantClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:text="@string/conversation_info_admin_menu_remove_participant"
android:textSize="14sp"
android:textColor="@color/red_danger_500"
android:drawableStart="@drawable/trash_simple"
android:drawablePadding="5dp"
app:drawableTint="@color/red_danger_500"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/set_admin"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/set_admin"
android:onClick="@{setAdminClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:visibility="@{isParticipantAdmin ? View.GONE : View.VISIBLE}"
android:text="@string/conversation_info_admin_menu_set_participant_admin"
android:textSize="14sp"
android:textColor="@color/gray_main2_500"
android:drawableStart="@drawable/trash_simple"
android:drawablePadding="5dp"
app:drawableTint="@color/gray_main2_700"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_participant"
app:layout_constraintBottom_toTopOf="@id/unset_admin"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/unset_admin"
android:onClick="@{unsetAdminClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:visibility="@{isParticipantAdmin ? View.VISIBLE : View.GONE, default=gone}"
android:text="@string/conversation_info_admin_menu_unset_participant_admin"
android:textSize="14sp"
android:textColor="@color/gray_main2_500"
android:drawableStart="@drawable/trash_simple"
android:drawablePadding="5dp"
app:drawableTint="@color/gray_main2_700"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/set_admin"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -81,6 +81,7 @@
<ImageView
android:id="@+id/participant_menu"
android:onClick="@{() -> model.openMenu(participantMenu)}"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:src="@drawable/dots_three_vertical"

View file

@ -93,7 +93,6 @@
android:id="@+id/required_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="25dp"
android:layout_marginTop="16dp"
android:text="@string/required"
android:textSize="12sp"

View file

@ -354,6 +354,9 @@
<string name="conversation_info_add_participants_label">Add participants</string>
<string name="conversation_info_participant_is_admin_label">Admin</string>
<string name="conversation_info_delete_history_action">Delete history</string>
<string name="conversation_info_admin_menu_remove_participant">Remove from the group</string>
<string name="conversation_info_admin_menu_set_participant_admin">Give admin rights</string>
<string name="conversation_info_admin_menu_unset_participant_admin">Remove admin rights</string>
<string name="meetings_list_empty">No meeting for the moment…</string>
<string name="meeting_schedule_title">New meeting</string>