mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Started admin view of conference participants list
This commit is contained in:
parent
561216320f
commit
f70ab87952
5 changed files with 201 additions and 56 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 && !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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Add table
Reference in a new issue