diff --git a/app/src/main/java/org/linphone/ui/call/fragment/ConferenceParticipantsListFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/ConferenceParticipantsListFragment.kt index 6050dab97..3ac825494 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/ConferenceParticipantsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/ConferenceParticipantsListFragment.kt @@ -78,6 +78,10 @@ class ConferenceParticipantsListFragment : GenericCallFragment() { findNavController().popBackStack() } + binding.setAddParticipantsClickListener { + // TODO FIXME: display add participants fragment + } + viewModel.conferenceModel.participants.observe(viewLifecycleOwner) { Log.i("$TAG participants list updated with [${it.size}] items") adapter.submitList(it) diff --git a/app/src/main/java/org/linphone/ui/call/model/ConferenceModel.kt b/app/src/main/java/org/linphone/ui/call/model/ConferenceModel.kt index 8449e641c..682c87542 100644 --- a/app/src/main/java/org/linphone/ui/call/model/ConferenceModel.kt +++ b/app/src/main/java/org/linphone/ui/call/model/ConferenceModel.kt @@ -67,6 +67,8 @@ class ConferenceModel { val isMeParticipantSendingVideo = MutableLiveData() + val isMeAdmin = MutableLiveData() + val showLayoutMenuEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -130,14 +132,8 @@ class ConferenceModel { conference: Conference, participant: Participant ) { - val newAdminStatus = participant.isAdmin - Log.i( - "$TAG Participant [${participant.address.asStringUriOnly()}] is [${if (newAdminStatus) "now admin" else "no longer admin"}]" - ) - val participantModel = participants.value.orEmpty().find { - it.participant.address.weakEqual(participant.address) - } - participantModel?.isAdmin?.postValue(newAdminStatus) + // Only recompute participants list + computeParticipants(true) } @WorkerThread @@ -336,7 +332,7 @@ class ConferenceModel { } @WorkerThread - private fun computeParticipants() { + private fun computeParticipants(skipDevices: Boolean = false) { participantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceModel::destroy) val participantsList = arrayListOf() @@ -345,6 +341,13 @@ class ConferenceModel { val conferenceParticipants = conference.participantList Log.i("$TAG [${conferenceParticipants.size}] participant in conference") + val meParticipant = conference.me + val admin = meParticipant.isAdmin + isMeAdmin.postValue(admin) + if (admin) { + Log.i("$TAG We are admin of that conference!") + } + var activeSpeakerParticipantDeviceFound = false for (participant in conferenceParticipants) { val devices = participant.devices @@ -353,22 +356,34 @@ class ConferenceModel { Log.i( "$TAG Participant [${participant.address.asStringUriOnly()}] has [${devices.size}] devices and role [${role.name}]" ) - val participantModel = ConferenceParticipantModel(participant) + val participantModel = ConferenceParticipantModel( + participant, + admin, + false, + { participant -> // Remove from conference + conference.removeParticipant(participant) + }, + { participant, setAdmin -> // Change admin status + conference.setParticipantAdminStatus(participant, setAdmin) + } + ) participantsList.add(participantModel) if (role == Participant.Role.Listener) { continue } - for (device in participant.devices) { - val model = ConferenceParticipantDeviceModel(device) - devicesList.add(model) + if (!skipDevices) { + for (device in devices) { + val model = ConferenceParticipantDeviceModel(device) + devicesList.add(model) - if (device == conference.activeSpeakerParticipantDevice) { - Log.i("$TAG Using participant is [${model.name}] as current active speaker") - model.isActiveSpeaker.postValue(true) - activeSpeaker.postValue(model) - activeSpeakerParticipantDeviceFound = true + if (device == conference.activeSpeakerParticipantDevice) { + Log.i("$TAG Using participant is [${model.name}] as current active speaker") + model.isActiveSpeaker.postValue(true) + activeSpeaker.postValue(model) + activeSpeakerParticipantDeviceFound = true + } } } } @@ -376,27 +391,28 @@ class ConferenceModel { "$TAG [${devicesList.size}] participant devices for [${participantsList.size}] participants will be displayed (not counting ourselves)" ) - val meParticipant = conference.me - val meParticipantModel = ConferenceParticipantModel(meParticipant) + val meParticipantModel = ConferenceParticipantModel(meParticipant, admin, true, null, null) participantsList.add(meParticipantModel) - val ourDevices = conference.me.devices - Log.i("$TAG We have [${ourDevices.size}] devices") - for (device in ourDevices) { - val model = ConferenceParticipantDeviceModel(device, true) - devicesList.add(model) + if (!skipDevices) { + val ourDevices = conference.me.devices + Log.i("$TAG We have [${ourDevices.size}] devices") + for (device in ourDevices) { + val model = ConferenceParticipantDeviceModel(device, true) + devicesList.add(model) - if (device == conference.activeSpeakerParticipantDevice) { - Log.i("$TAG Using our device [${model.name}] as current active speaker") - model.isActiveSpeaker.postValue(true) - activeSpeaker.postValue(model) - activeSpeakerParticipantDeviceFound = true + if (device == conference.activeSpeakerParticipantDevice) { + Log.i("$TAG Using our device [${model.name}] as current active speaker") + model.isActiveSpeaker.postValue(true) + activeSpeaker.postValue(model) + activeSpeakerParticipantDeviceFound = true + } + + val direction = device.getStreamCapability(StreamType.Video) + isMeParticipantSendingVideo.postValue( + direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly + ) } - - val direction = device.getStreamCapability(StreamType.Video) - isMeParticipantSendingVideo.postValue( - direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly - ) } if (!activeSpeakerParticipantDeviceFound && devicesList.isNotEmpty()) { @@ -408,17 +424,41 @@ class ConferenceModel { activeSpeaker.postValue(first) } - checkIfTooManyParticipantDevicesForGridLayout(devicesList) - - participantDevices.postValue(sortParticipantDevicesList(devicesList)) - participants.postValue(participantsList) - participantsLabel.postValue( - AppUtils.getStringWithPlural( - R.plurals.conference_participants_list_title, - participantsList.size, - "${participantsList.size}" + participants.postValue(sortParticipantList(participantsList)) + if (!skipDevices) { + checkIfTooManyParticipantDevicesForGridLayout(devicesList) + participantDevices.postValue(sortParticipantDevicesList(devicesList)) + participantsLabel.postValue( + AppUtils.getStringWithPlural( + R.plurals.conference_participants_list_title, + participantsList.size, + "${participantsList.size}" + ) ) - ) + } + } + + @WorkerThread + private fun sortParticipantList(devices: List): ArrayList { + val sortedList = arrayListOf() + sortedList.addAll(devices) + + val meModel = sortedList.find { + it.isMyself + } + if (meModel != null) { + val index = sortedList.indexOf(meModel) + val expectedIndex = 0 + if (index != expectedIndex) { + Log.i( + "$TAG Me participant model is at index $index, moving it to index $expectedIndex" + ) + sortedList.removeAt(index) + sortedList.add(expectedIndex, meModel) + } + } + + return sortedList } @WorkerThread @@ -426,11 +466,11 @@ class ConferenceModel { val sortedList = arrayListOf() sortedList.addAll(devices) - val meDeviceData = sortedList.find { + val meDeviceModel = sortedList.find { it.isMe } - if (meDeviceData != null) { - val index = sortedList.indexOf(meDeviceData) + if (meDeviceModel != null) { + val index = sortedList.indexOf(meDeviceModel) val expectedIndex = if (conferenceLayout.value == ACTIVE_SPEAKER_LAYOUT) { Log.i( "$TAG Current conference layout is [Active Speaker], expecting our device to be at the beginning of the list" @@ -444,10 +484,10 @@ class ConferenceModel { } if (index != expectedIndex) { Log.i( - "$TAG Me device data is at index $index, moving it to index $expectedIndex" + "$TAG Me device model is at index $index, moving it to index $expectedIndex" ) sortedList.removeAt(index) - sortedList.add(expectedIndex, meDeviceData) + sortedList.add(expectedIndex, meDeviceModel) } } @@ -459,10 +499,20 @@ class ConferenceModel { val list = arrayListOf() list.addAll(participants.value.orEmpty()) - val newModel = ConferenceParticipantModel(participant) + val newModel = ConferenceParticipantModel( + participant, + isMeAdmin.value == true, + false, + { participant -> // Remove from conference + conference.removeParticipant(participant) + }, + { participant, setAdmin -> // Change admin status + conference.setParticipantAdminStatus(participant, setAdmin) + } + ) list.add(newModel) - participants.postValue(list) + participants.postValue(sortParticipantList(list)) participantsLabel.postValue( AppUtils.getStringWithPlural( R.plurals.conference_participants_list_title, diff --git a/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantModel.kt b/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantModel.kt index fd637c622..119e9e47f 100644 --- a/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantModel.kt +++ b/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantModel.kt @@ -19,12 +19,24 @@ */ package org.linphone.ui.call.model +import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.Participant +import org.linphone.core.tools.Log + +class ConferenceParticipantModel @WorkerThread constructor( + val participant: Participant, + val isMyselfAdmin: Boolean, + val isMyself: Boolean, + private val removeFromConference: ((participant: Participant) -> Unit)?, + private val changeAdminStatus: ((participant: Participant, setAdmin: Boolean) -> Unit)? +) { + companion object { + private const val TAG = "[Conference Participant Model]" + } -class ConferenceParticipantModel @WorkerThread constructor(val participant: Participant) { val sipUri = participant.address.asStringUriOnly() val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress( @@ -33,7 +45,31 @@ class ConferenceParticipantModel @WorkerThread constructor(val participant: Part val isAdmin = MutableLiveData() + val isMeAdmin = MutableLiveData() + init { isAdmin.postValue(participant.isAdmin) + isMeAdmin.postValue(isMyselfAdmin) + } + + @UiThread + fun removeParticipant() { + Log.w("$TAG Removing participant from conference") + coreContext.postOnCoreThread { + removeFromConference?.invoke(participant) + } + } + + @UiThread + fun toggleAdminStatus() { + val newStatus = isAdmin.value == false + Log.w( + "$TAG Changing participant admin status to ${if (newStatus) "admin" else "not admin"}" + ) + isAdmin.postValue(newStatus) + + coreContext.postOnCoreThread { + changeAdminStatus?.invoke(participant, newStatus) + } } } diff --git a/app/src/main/res/layout/call_conference_participant_list_cell.xml b/app/src/main/res/layout/call_conference_participant_list_cell.xml index 9207d1f99..d53d9a32e 100644 --- a/app/src/main/res/layout/call_conference_participant_list_cell.xml +++ b/app/src/main/res/layout/call_conference_participant_list_cell.xml @@ -56,19 +56,56 @@ + + + + + @@ -53,6 +56,21 @@ app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintBottom_toBottomOf="parent"/> + + \ No newline at end of file