Added calls list while in conference + started conference participants list

This commit is contained in:
Sylvain Berfini 2023-10-27 16:18:37 +02:00
parent 48baed897c
commit 56ec0f8911
15 changed files with 559 additions and 40 deletions

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.ui.call.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.linphone.R
import org.linphone.databinding.CallConferenceParticipantListCellBinding
import org.linphone.ui.call.model.ConferenceParticipantModel
class ConferenceParticipantsListAdapter(private val viewLifecycleOwner: LifecycleOwner) :
ListAdapter<ConferenceParticipantModel, RecyclerView.ViewHolder>(ParticipantDiffCallback()) {
var selectedAdapterPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: CallConferenceParticipantListCellBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.call_conference_participant_list_cell,
parent,
false
)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as ViewHolder).bind(getItem(position))
}
fun resetSelection() {
notifyItemChanged(selectedAdapterPosition)
selectedAdapterPosition = -1
}
inner class ViewHolder(
val binding: CallConferenceParticipantListCellBinding
) : RecyclerView.ViewHolder(binding.root) {
@UiThread
fun bind(participantModel: ConferenceParticipantModel) {
with(binding) {
model = participantModel
lifecycleOwner = viewLifecycleOwner
binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition
executePendingBindings()
}
}
}
private class ParticipantDiffCallback : DiffUtil.ItemCallback<ConferenceParticipantModel>() {
override fun areItemsTheSame(
oldItem: ConferenceParticipantModel,
newItem: ConferenceParticipantModel
): Boolean {
return oldItem.sipUri == newItem.sipUri
}
override fun areContentsTheSame(
oldItem: ConferenceParticipantModel,
newItem: ConferenceParticipantModel
): Boolean {
return false
}
}
}

View file

@ -28,6 +28,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
@ -113,6 +114,18 @@ class ActiveConferenceCallFragment : GenericCallFragment() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {} override fun onSlide(bottomSheet: View, slideOffset: Float) {}
}) })
binding.setCallsListClickListener {
Log.i("$TAG Going to calls list fragment")
val action = ActiveConferenceCallFragmentDirections.actionActiveConferenceCallFragmentToCallsListFragment()
findNavController().navigate(action)
}
binding.setParticipantsListClickListener {
Log.i("$TAG Going to conference participants list fragment")
val action = ActiveConferenceCallFragmentDirections.actionActiveConferenceCallFragmentToConferenceParticipantsListFragment()
findNavController().navigate(action)
}
binding.setShareConferenceClickListener { binding.setShareConferenceClickListener {
val sipUri = callViewModel.conferenceModel.sipUri.value.orEmpty() val sipUri = callViewModel.conferenceModel.sipUri.value.orEmpty()
if (sipUri.isNotEmpty()) { if (sipUri.isNotEmpty()) {

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.ui.call.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import org.linphone.core.tools.Log
import org.linphone.databinding.CallConferenceParticipantsListFragmentBinding
import org.linphone.ui.call.adapter.ConferenceParticipantsListAdapter
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
class ConferenceParticipantsListFragment : GenericCallFragment() {
companion object {
private const val TAG = "[Conference Participants List Fragment]"
}
private lateinit var binding: CallConferenceParticipantsListFragmentBinding
private lateinit var viewModel: CurrentCallViewModel
private lateinit var adapter: ConferenceParticipantsListAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = CallConferenceParticipantsListFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = requireActivity().run {
ViewModelProvider(this)[CurrentCallViewModel::class.java]
}
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
adapter = ConferenceParticipantsListAdapter(viewLifecycleOwner)
binding.participantsList.setHasFixedSize(true)
binding.participantsList.adapter = adapter
binding.participantsList.layoutManager = LinearLayoutManager(requireContext())
binding.setBackClickListener {
findNavController().popBackStack()
}
viewModel.conferenceModel.participants.observe(viewLifecycleOwner) {
Log.i("$TAG participants list updated with [${it.size}] items")
adapter.submitList(it)
}
}
}

View file

@ -56,10 +56,17 @@ class CallModel @WorkerThread constructor(val call: Call) {
init { init {
call.addListener(callListener) call.addListener(callListener)
displayName.postValue(friend?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress)) val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(
contact.postValue( call.remoteAddress
coreContext.contactsManager.getContactAvatarModelForAddress(call.remoteAddress)
) )
val conferenceInfo = coreContext.core.findConferenceInformationFromUri(call.remoteAddress)
if (conferenceInfo != null) {
displayName.postValue(conferenceInfo.subject)
avatarModel.showConferenceIcon.postValue(true)
} else {
displayName.postValue(friend?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress))
}
contact.postValue(avatarModel)
state.postValue(LinphoneUtils.callStateToString(call.state)) state.postValue(LinphoneUtils.callStateToString(call.state))
isPaused.postValue(LinphoneUtils.isCallPaused(call.state)) isPaused.postValue(LinphoneUtils.isCallPaused(call.state))

View file

@ -21,12 +21,14 @@ package org.linphone.ui.call.model
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.linphone.R
import org.linphone.core.Call import org.linphone.core.Call
import org.linphone.core.Conference import org.linphone.core.Conference
import org.linphone.core.ConferenceListenerStub import org.linphone.core.ConferenceListenerStub
import org.linphone.core.Participant import org.linphone.core.Participant
import org.linphone.core.ParticipantDevice import org.linphone.core.ParticipantDevice
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
class ConferenceModel { class ConferenceModel {
companion object { companion object {
@ -37,13 +39,48 @@ class ConferenceModel {
val sipUri = MutableLiveData<String>() val sipUri = MutableLiveData<String>()
val participants = MutableLiveData<ArrayList<ConferenceParticipantModel>>()
val participantDevices = MutableLiveData<ArrayList<ConferenceParticipantDeviceModel>>() val participantDevices = MutableLiveData<ArrayList<ConferenceParticipantDeviceModel>>()
val participantsLabel = MutableLiveData<String>()
private lateinit var conference: Conference private lateinit var conference: Conference
val isCurrentCallInConference = MutableLiveData<Boolean>() val isCurrentCallInConference = MutableLiveData<Boolean>()
private val conferenceListener = object : ConferenceListenerStub() { private val conferenceListener = object : ConferenceListenerStub() {
@WorkerThread
override fun onParticipantAdded(conference: Conference, participant: Participant) {
Log.i(
"$TAG Participant added: ${participant.address.asStringUriOnly()}"
)
addParticipant(participant)
}
@WorkerThread
override fun onParticipantRemoved(conference: Conference, participant: Participant) {
Log.i(
"$TAG Participant removed: ${participant.address.asStringUriOnly()}"
)
removeParticipant(participant)
}
@WorkerThread
override fun onParticipantAdminStatusChanged(
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)
}
@WorkerThread @WorkerThread
override fun onParticipantDeviceAdded( override fun onParticipantDeviceAdded(
conference: Conference, conference: Conference,
@ -52,14 +89,7 @@ class ConferenceModel {
Log.i( Log.i(
"$TAG Participant device added: ${participantDevice.address.asStringUriOnly()}" "$TAG Participant device added: ${participantDevice.address.asStringUriOnly()}"
) )
addParticipantDevice(participantDevice)
val list = arrayListOf<ConferenceParticipantDeviceModel>()
list.addAll(participantDevices.value.orEmpty())
val newModel = ConferenceParticipantDeviceModel(participantDevice)
list.add(newModel)
participantDevices.postValue(sortParticipantDevicesList(list))
} }
@WorkerThread @WorkerThread
@ -70,19 +100,7 @@ class ConferenceModel {
Log.i( Log.i(
"$TAG Participant device removed: ${participantDevice.address.asStringUriOnly()}" "$TAG Participant device removed: ${participantDevice.address.asStringUriOnly()}"
) )
removeParticipantDevice(participantDevice)
val list = arrayListOf<ConferenceParticipantDeviceModel>()
list.addAll(participantDevices.value.orEmpty())
val toRemove = list.find {
participantDevice.address.weakEqual(it.device.address)
}
if (toRemove != null) {
toRemove.destroy()
list.remove(toRemove)
}
participantDevices.postValue(list)
} }
@WorkerThread @WorkerThread
@ -100,7 +118,7 @@ class ConferenceModel {
override fun onStateChanged(conference: Conference, state: Conference.State) { override fun onStateChanged(conference: Conference, state: Conference.State) {
Log.i("$TAG State changed [$state]") Log.i("$TAG State changed [$state]")
if (conference.state == Conference.State.Created) { if (conference.state == Conference.State.Created) {
computeParticipantsDevices() computeParticipants()
} }
} }
} }
@ -132,46 +150,63 @@ class ConferenceModel {
subject.postValue(conference.subject) subject.postValue(conference.subject)
if (conference.state == Conference.State.Created) { if (conference.state == Conference.State.Created) {
computeParticipantsDevices() computeParticipants()
} }
} }
@WorkerThread @WorkerThread
private fun computeParticipantsDevices() { private fun computeParticipants() {
participants.value.orEmpty().forEach(ConferenceParticipantModel::destroy)
participantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceModel::destroy) participantDevices.value.orEmpty().forEach(ConferenceParticipantDeviceModel::destroy)
val list = arrayListOf<ConferenceParticipantDeviceModel>()
val participants = conference.participantList val participantsList = arrayListOf<ConferenceParticipantModel>()
Log.i("$TAG [${participants.size}] participant in conference") val devicesList = arrayListOf<ConferenceParticipantDeviceModel>()
for (participant in participants) { val conferenceParticipants = conference.participantList
Log.i("$TAG [${conferenceParticipants.size}] participant in conference")
val meParticipant = conference.me
val meParticipantModel = ConferenceParticipantModel(meParticipant)
participantsList.add(meParticipantModel)
for (participant in conferenceParticipants) {
val devices = participant.devices val devices = participant.devices
val role = participant.role val role = participant.role
Log.i( Log.i(
"$TAG Participant [${participant.address.asStringUriOnly()}] has [${devices.size}] devices and role [${role.name}]" "$TAG Participant [${participant.address.asStringUriOnly()}] has [${devices.size}] devices and role [${role.name}]"
) )
val participantModel = ConferenceParticipantModel(participant)
participantsList.add(participantModel)
if (role == Participant.Role.Listener) { if (role == Participant.Role.Listener) {
continue continue
} }
for (device in participant.devices) { for (device in participant.devices) {
val model = ConferenceParticipantDeviceModel(device) val model = ConferenceParticipantDeviceModel(device)
list.add(model) devicesList.add(model)
} }
} }
Log.i( Log.i(
"$TAG [${list.size}] participant devices will be displayed (not counting ourselves)" "$TAG [${devicesList.size}] participant devices for [${participantsList.size}] participants will be displayed (not counting ourselves)"
) )
val ourDevices = conference.me.devices val ourDevices = conference.me.devices
Log.i("$TAG We have [${ourDevices.size}] devices") Log.i("$TAG We have [${ourDevices.size}] devices")
for (device in ourDevices) { for (device in ourDevices) {
val model = ConferenceParticipantDeviceModel(device, true) val model = ConferenceParticipantDeviceModel(device, true)
list.add(model) devicesList.add(model)
} }
participantDevices.postValue(sortParticipantDevicesList(list)) participantDevices.postValue(sortParticipantDevicesList(devicesList))
participants.postValue(participantsList)
participantsLabel.postValue(
AppUtils.getFormattedString(
R.string.conference_participants_list_title,
participantsList.size
)
)
} }
private fun sortParticipantDevicesList(devices: List<ConferenceParticipantDeviceModel>): ArrayList<ConferenceParticipantDeviceModel> { private fun sortParticipantDevicesList(devices: List<ConferenceParticipantDeviceModel>): ArrayList<ConferenceParticipantDeviceModel> {
@ -195,4 +230,64 @@ class ConferenceModel {
return sortedList return sortedList
} }
@WorkerThread
private fun addParticipant(participant: Participant) {
val list = arrayListOf<ConferenceParticipantModel>()
list.addAll(participants.value.orEmpty())
val newModel = ConferenceParticipantModel(participant)
list.add(newModel)
participants.postValue(list)
participantsLabel.postValue(
AppUtils.getFormattedString(R.string.conference_participants_list_title, list.size)
)
}
@WorkerThread
private fun addParticipantDevice(participantDevice: ParticipantDevice) {
val list = arrayListOf<ConferenceParticipantDeviceModel>()
list.addAll(participantDevices.value.orEmpty())
val newModel = ConferenceParticipantDeviceModel(participantDevice)
list.add(newModel)
participantDevices.postValue(sortParticipantDevicesList(list))
}
@WorkerThread
private fun removeParticipant(participant: Participant) {
val list = arrayListOf<ConferenceParticipantModel>()
list.addAll(participants.value.orEmpty())
val toRemove = list.find {
participant.address.weakEqual(it.participant.address)
}
if (toRemove != null) {
toRemove.destroy()
list.remove(toRemove)
}
participants.postValue(list)
participantsLabel.postValue(
AppUtils.getFormattedString(R.string.conference_participants_list_title, list.size)
)
}
@WorkerThread
private fun removeParticipantDevice(participantDevice: ParticipantDevice) {
val list = arrayListOf<ConferenceParticipantDeviceModel>()
list.addAll(participantDevices.value.orEmpty())
val toRemove = list.find {
participantDevice.address.weakEqual(it.device.address)
}
if (toRemove != null) {
toRemove.destroy()
list.remove(toRemove)
}
participantDevices.postValue(list)
}
} }

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.ui.call.model
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Participant
class ConferenceParticipantModel @WorkerThread constructor(val participant: Participant) {
val sipUri = participant.address.asStringUriOnly()
val avatarModel = coreContext.contactsManager.getContactAvatarModelForAddress(
participant.address
)
val isAdmin = MutableLiveData<Boolean>()
init {
isAdmin.postValue(participant.isAdmin)
}
@WorkerThread
fun destroy() {
}
}

View file

@ -12,6 +12,9 @@
<variable <variable
name="callsListClickListener" name="callsListClickListener"
type="View.OnClickListener" /> type="View.OnClickListener" />
<variable
name="participantsListClickListener"
type="View.OnClickListener" />
<variable <variable
name="viewModel" name="viewModel"
type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" /> type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" />
@ -208,6 +211,7 @@
layout="@layout/call_extra_conference_actions" layout="@layout/call_extra_conference_actions"
bind:viewModel="@{viewModel}" bind:viewModel="@{viewModel}"
bind:callsViewModel="@{callsViewModel}" bind:callsViewModel="@{callsViewModel}"
bind:participantsListClickListener="@{participantsListClickListener}"
bind:callsListClickListener="@{callsListClickListener}"/> bind:callsListClickListener="@{callsListClickListener}"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,94 @@
<?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="org.linphone.core.ChatRoom.SecurityLevel" />
<variable
name="onClickListener"
type="View.OnClickListener" />
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable
name="model"
type="org.linphone.ui.call.model.ConferenceParticipantModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:onClick="@{onClickListener}"
android:onLongClick="@{onLongClickListener}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/primary_cell_background">
<com.google.android.material.imageview.ShapeableImageView
style="@style/avatar_imageview"
android:id="@+id/avatar"
android:layout_width="@dimen/avatar_list_cell_size"
android:layout_height="@dimen/avatar_list_cell_size"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
coilAvatar="@{model.avatarModel}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/trust_badge"
android:layout_width="@dimen/avatar_presence_badge_size"
android:layout_height="@dimen/avatar_presence_badge_size"
android:src="@{model.avatarModel.trust == SecurityLevel.Safe ? @drawable/trusted : @drawable/not_trusted, default=@drawable/trusted}"
android:visibility="@{model.avatarModel.trust == SecurityLevel.Safe || model.avatarModel.trust == SecurityLevel.Unsafe ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@{model.avatarModel.name, default=`John Doe`}"
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintEnd_toStartOf="@id/admin"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/admin"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/conversation_info_participant_is_admin_label"
android:textSize="12sp"
android:textColor="@color/gray_main2_400"
android:gravity="center"
app:layout_constraintStart_toEndOf="@id/name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<View
android:id="@+id/separator"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginEnd="10dp"
android:background="@color/gray_main2_200"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,57 @@
<?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" />
<variable
name="backClickListener"
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:padding="15dp"
android:adjustViewBounds="true"
android:onClick="@{backClickListener}"
android:src="@drawable/caret_left"
app:tint="@color/orange_main_500"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/main_page_title_style"
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@{viewModel.conferenceModel.participantsLabel, default=@string/conference_participants_list_title}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/participants_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -8,6 +8,9 @@
<variable <variable
name="callsListClickListener" name="callsListClickListener"
type="View.OnClickListener" /> type="View.OnClickListener" />
<variable
name="participantsListClickListener"
type="View.OnClickListener" />
<variable <variable
name="viewModel" name="viewModel"
type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" /> type="org.linphone.ui.call.viewmodel.CurrentCallViewModel" />
@ -50,6 +53,7 @@
<ImageView <ImageView
android:id="@+id/participants" android:id="@+id/participants"
android:onClick="@{participantsListClickListener}"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="@dimen/call_button_size" android:layout_height="@dimen/call_button_size"
android:layout_marginTop="@dimen/call_extra_button_top_margin" android:layout_marginTop="@dimen/call_extra_button_top_margin"
@ -167,6 +171,7 @@
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
style="@style/in_call_extra_action_label_style" style="@style/in_call_extra_action_label_style"
android:id="@+id/participants_label" android:id="@+id/participants_label"
android:onClick="@{participantsListClickListener}"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/conference_action_show_participants" android:text="@string/conference_action_show_participants"

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:bind="http://schemas.android.com/tools">
<data> <data>
<import type="android.view.View" /> <import type="android.view.View" />
@ -47,7 +46,7 @@
android:id="@+id/calls_list" android:id="@+id/calls_list"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="20dp" android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintTop_toBottomOf="@id/title"

View file

@ -72,6 +72,7 @@
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
style="@style/avatar_imageview" style="@style/avatar_imageview"
android:id="@+id/avatar" android:id="@+id/avatar"
android:onClick="@{goToInfoClickListener}"
android:layout_width="@dimen/avatar_list_cell_size" android:layout_width="@dimen/avatar_list_cell_size"
android:layout_height="@dimen/avatar_list_cell_size" android:layout_height="@dimen/avatar_list_cell_size"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
@ -106,6 +107,7 @@
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style" style="@style/default_text_style"
android:id="@+id/title" android:id="@+id/title"
android:onClick="@{goToInfoClickListener}"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height" android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"

View file

@ -490,7 +490,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:layout_marginStart="8dp" android:layout_marginStart="10dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:visibility="@{viewModel.participants.size() > 0 ? View.VISIBLE : View.GONE, default=gone}" android:visibility="@{viewModel.participants.size() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/participants" app:layout_constraintTop_toBottomOf="@id/participants"
@ -499,6 +499,19 @@
entries="@{viewModel.participants}" entries="@{viewModel.participants}"
layout="@{@layout/meeting_schedule_participant_list_cell}"/> layout="@{@layout/meeting_schedule_participant_list_cell}"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/add_more_participants"
android:onClick="@{pickParticipantsClickListener}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:text="@string/meeting_schedule_add_more_participants_title"
android:visibility="@{viewModel.participants.size() > 0 ? View.VISIBLE : View.GONE, default=gone}"
app:layout_constraintTop_toBottomOf="@id/participants_list"
app:layout_constraintStart_toEndOf="@id/participants_list_icon" />
<View <View
android:id="@+id/separator_5" android:id="@+id/separator_5"
android:layout_width="0dp" android:layout_width="0dp"
@ -506,7 +519,7 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/participants_list" app:layout_constraintTop_toBottomOf="@id/add_more_participants"
android:background="@color/gray_main2_200" /> android:background="@color/gray_main2_200" />
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.materialswitch.MaterialSwitch

View file

@ -133,6 +133,18 @@
app:popUpTo="@id/activeConferenceCallFragment" app:popUpTo="@id/activeConferenceCallFragment"
app:popUpToInclusive="true" app:popUpToInclusive="true"
app:launchSingleTop="true" /> app:launchSingleTop="true" />
<action
android:id="@+id/action_activeConferenceCallFragment_to_conferenceParticipantsListFragment"
app:destination="@id/conferenceParticipantsListFragment"
app:enterAnim="@anim/slide_in"
app:popExitAnim="@anim/slide_out"
app:launchSingleTop="true" />
<action
android:id="@+id/action_activeConferenceCallFragment_to_callsListFragment"
app:destination="@id/callsListFragment"
app:enterAnim="@anim/slide_in"
app:popExitAnim="@anim/slide_out"
app:launchSingleTop="true" />
</fragment> </fragment>
<action android:id="@+id/action_global_activeConferenceCallFragment" <action android:id="@+id/action_global_activeConferenceCallFragment"
@ -141,4 +153,10 @@
app:popUpToInclusive="true" app:popUpToInclusive="true"
app:launchSingleTop="true"/> app:launchSingleTop="true"/>
<fragment
android:id="@+id/conferenceParticipantsListFragment"
android:name="org.linphone.ui.call.fragment.ConferenceParticipantsListFragment"
android:label="ConferenceParticipantsListFragment"
tools:layout="@layout/call_conference_participants_list_fragment"/>
</navigation> </navigation>

View file

@ -390,6 +390,7 @@
<string name="meeting_schedule_one_time_label">One time</string> <string name="meeting_schedule_one_time_label">One time</string>
<string name="meeting_schedule_description_hint">Add description</string> <string name="meeting_schedule_description_hint">Add description</string>
<string name="meeting_schedule_add_participants_title">Add participants</string> <string name="meeting_schedule_add_participants_title">Add participants</string>
<string name="meeting_schedule_add_more_participants_title">Click to add more participants</string>
<string name="meeting_schedule_add_speaker_title">Add speaker</string> <string name="meeting_schedule_add_speaker_title">Add speaker</string>
<string name="meeting_schedule_send_invitations_title">Send invitation to participants</string> <string name="meeting_schedule_send_invitations_title">Send invitation to participants</string>
<string name="meeting_info_join_title">Join the meeting now</string> <string name="meeting_info_join_title">Join the meeting now</string>
@ -433,6 +434,7 @@
<string name="conference_call_empty">Waiting for other participants…</string> <string name="conference_call_empty">Waiting for other participants…</string>
<string name="conference_action_screen_sharing">Screen share</string> <string name="conference_action_screen_sharing">Screen share</string>
<string name="conference_action_show_participants">Participants</string> <string name="conference_action_show_participants">Participants</string>
<string name="conference_participants_list_title">%s participants</string>
<string name="connection_error_for_non_default_account">Account connection error</string> <string name="connection_error_for_non_default_account">Account connection error</string>