From f07a8f6c2b143de2e11565fedb07d1bdb5435541 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 17 Oct 2023 16:56:50 +0200 Subject: [PATCH] Added chat room participant admin popup to remove a person from a group & give/remove admin rights --- .../chat/fragment/ConversationInfoFragment.kt | 54 +++++++++ .../ui/main/chat/model/ParticipantModel.kt | 21 +++- .../viewmodel/ConversationInfoViewModel.kt | 106 ++++++++++++++++-- .../main/res/layout/account_popup_menu.xml | 3 +- .../main/res/layout/chat_info_fragment.xml | 4 +- .../chat_participant_admin_popup_menu.xml | 91 +++++++++++++++ .../res/layout/chat_participant_list_cell.xml | 1 + .../main/res/layout/start_chat_fragment.xml | 1 - app/src/main/res/values/strings.xml | 3 + 9 files changed, 269 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/layout/chat_participant_admin_popup_menu.xml diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt index 4b5e8aeb2..13cceadad 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt @@ -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) + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt index 876be7714..d91feb56b 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ParticipantModel.kt @@ -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) + } +} diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index 1c6a501ac..0b0cb6ec4 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -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>() - val groupLeftEvent = MutableLiveData>() + val groupLeftEvent: MutableLiveData> by lazy { + MutableLiveData>() + } - val historyDeletedEvent = MutableLiveData>() + val historyDeletedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val showParticipantAdminPopupMenuEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } 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 diff --git a/app/src/main/res/layout/account_popup_menu.xml b/app/src/main/res/layout/account_popup_menu.xml index 1e58b7b03..2e14d623a 100644 --- a/app/src/main/res/layout/account_popup_menu.xml +++ b/app/src/main/res/layout/account_popup_menu.xml @@ -17,8 +17,9 @@ android:background="@drawable/shape_round_popup_menu_background"> diff --git a/app/src/main/res/layout/chat_participant_admin_popup_menu.xml b/app/src/main/res/layout/chat_participant_admin_popup_menu.xml new file mode 100644 index 000000000..4a27eb6bb --- /dev/null +++ b/app/src/main/res/layout/chat_participant_admin_popup_menu.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_participant_list_cell.xml b/app/src/main/res/layout/chat_participant_list_cell.xml index 9b8e37917..f1e5c1358 100644 --- a/app/src/main/res/layout/chat_participant_list_cell.xml +++ b/app/src/main/res/layout/chat_participant_list_cell.xml @@ -81,6 +81,7 @@ Add participants Admin Delete history + Remove from the group + Give admin rights + Remove admin rights No meeting for the moment… New meeting