Started admin view of conference participants list

This commit is contained in:
Sylvain Berfini 2024-04-04 16:46:31 +02:00
parent 561216320f
commit f70ab87952
5 changed files with 201 additions and 56 deletions

View file

@ -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)

View file

@ -67,6 +67,8 @@ class ConferenceModel {
val isMeParticipantSendingVideo = MutableLiveData<Boolean>()
val isMeAdmin = MutableLiveData<Boolean>()
val showLayoutMenuEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
@ -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<ConferenceParticipantModel>()
@ -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<ConferenceParticipantModel>): ArrayList<ConferenceParticipantModel> {
val sortedList = arrayListOf<ConferenceParticipantModel>()
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<ConferenceParticipantDeviceModel>()
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<ConferenceParticipantModel>()
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,

View file

@ -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<Boolean>()
val isMeAdmin = MutableLiveData<Boolean>()
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)
}
}
}

View file

@ -56,19 +56,56 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/admin"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/conversation_info_participant_is_admin_label"
android:textSize="12sp"
android:textColor="?attr/color_main2_400"
android:maxLines="1"
android:ellipsize="end"
android:gravity="center"
android:visibility="@{model.isAdmin ? View.VISIBLE : View.GONE, default=gone}"
android:visibility="@{model.isAdmin &amp;&amp; !model.isMeAdmin ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@id/name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/admin_toggle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.google.android.material.materialswitch.MaterialSwitch
style="@style/material_switch_style"
android:id="@+id/admin_toggle"
android:onClick="@{() -> model.toggleAdminStatus()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/conversation_info_participant_is_admin_label"
android:textSize="12sp"
android:textColor="?attr/color_main2_400"
android:maxLines="1"
android:ellipsize="end"
android:gravity="center"
android:enabled="@{!model.isMyself}"
android:checked="@{model.isAdmin}"
android:visibility="@{model.isMeAdmin ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintStart_toEndOf="@id/admin"
app:layout_constraintEnd_toStartOf="@id/remove_participant"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<ImageView
android:id="@+id/remove_participant"
android:onClick="@{() -> model.removeParticipant()}"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="10dp"
android:src="@drawable/x"
android:visibility="@{model.isMeAdmin ? (model.isMyself ? View.INVISIBLE : View.VISIBLE) : View.GONE, default=gone}"
app:layout_constraintStart_toEndOf="@id/admin_toggle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?attr/color_danger_500" />
<View
android:id="@+id/separator"
android:layout_width="0dp"

View file

@ -7,6 +7,9 @@
<variable
name="backClickListener"
type="View.OnClickListener" />
<variable
name="addParticipantsClickListener"
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" />
@ -53,6 +56,21 @@
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_participants"
android:onClick="@{addParticipantsClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/plus_circle"
android:visibility="@{viewModel.conferenceModel.isMeAdmin ? View.VISIBLE : View.GONE, default=gone}"
app:tint="?attr/color_on_main"
app:backgroundTint="?attr/color_main1_500"
app:shapeAppearanceOverlay="@style/rounded"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>